@fuzdev/fuz_ui 0.211.0 → 0.213.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Alert.svelte CHANGED
@@ -22,7 +22,8 @@
22
22
  div_attrs,
23
23
  children,
24
24
  ...rest
25
- }: // generic element attrs, the common denominator of the rendered roots - // branch-specific attributes go in `button_attrs`/`div_attrs`
25
+ }: // generic element attrs, the common denominator of the rendered roots;
26
+ // branch-specific attributes go in `button_attrs` and `div_attrs`
26
27
  HTMLAttributes<HTMLElement> & {
27
28
  status?: AlertStatus;
28
29
  color?: string;
@@ -32,7 +33,11 @@
32
33
  /** Renders the alert as a `<button>` when provided. */
33
34
  onclick?: (() => void) | undefined;
34
35
  disabled?: boolean;
35
- icon?: string | Snippet<[icon: string]> | null | undefined; // TODO experimenting with this, gets complex in the impl
36
+ /**
37
+ * A decorative icon, hidden from assistive tech: a string, or a snippet given the
38
+ * status's icon. Defaults to the status's icon, and `null` or an empty string is none.
39
+ */
40
+ icon?: string | Snippet<[icon: string]> | null | undefined;
36
41
  /** Button attributes, applied only when `onclick` renders the alert as a `<button>`. */
37
42
  button_attrs?: SvelteHTMLElements['button'];
38
43
  /** Div attributes, applied only when the alert renders as a `<div>` (no `onclick`). */
@@ -44,9 +49,7 @@
44
49
  // TODO change this to use the hue and put transparency on the borders, or add a borderColor option
45
50
  const { color: status_color, icon: status_icon } = $derived(options);
46
51
  const final_color = $derived(color ?? status_color);
47
- const final_icon = $derived(
48
- typeof icon === 'string' ? icon : (status_icon ?? alert_status_options.inform.icon!)
49
- );
52
+ const final_icon = $derived(typeof icon === 'string' ? icon : status_icon);
50
53
  </script>
51
54
 
52
55
  {#if onclick}
@@ -59,7 +62,7 @@
59
62
  {onclick}
60
63
  disabled={disabled ?? button_attrs?.disabled}
61
64
  >
62
- {@render content()}
65
+ {@render alert_content()}
63
66
  </button>
64
67
  {:else}
65
68
  <div
@@ -69,21 +72,21 @@
69
72
  class="alert panel {rest.class} {div_attrs?.class}"
70
73
  style:--text_color={final_color}
71
74
  >
72
- {@render content()}
75
+ {@render alert_content()}
73
76
  </div>
74
77
  {/if}
75
78
 
76
- {#snippet content()}
77
- {#if icon !== null}
78
- <div class="icon">
79
- {#if !icon || typeof icon === 'string'}
80
- {final_icon}
81
- {:else}
79
+ {#snippet alert_content()}
80
+ <!-- the status's icon by default, or one given, and none for `null` or an empty string -->
81
+ {#if icon === undefined || icon}
82
+ <div class="icon" aria-hidden="true">
83
+ {#if typeof icon === 'function'}
82
84
  {@render icon(final_icon)}
85
+ {:else}
86
+ {final_icon}
83
87
  {/if}
84
88
  </div>
85
89
  {/if}
86
- <!-- TODO conflict in the names of content as the snippet and this inner div -->
87
90
  <div class="content">
88
91
  {@render children()}
89
92
  </div>
@@ -1,13 +1,17 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLAttributes, SvelteHTMLElements } from 'svelte/elements';
3
3
  import { type AlertStatus } from './alert.js';
4
- type $$ComponentProps = // generic element attrs, the common denominator of the rendered roots - // branch-specific attributes go in `button_attrs`/`div_attrs`
4
+ type $$ComponentProps = // generic element attrs, the common denominator of the rendered roots;
5
5
  HTMLAttributes<HTMLElement> & {
6
6
  status?: AlertStatus;
7
7
  color?: string;
8
8
  /** Renders the alert as a `<button>` when provided. */
9
9
  onclick?: (() => void) | undefined;
10
10
  disabled?: boolean;
11
+ /**
12
+ * A decorative icon, hidden from assistive tech: a string, or a snippet given the
13
+ * status's icon. Defaults to the status's icon, and `null` or an empty string is none.
14
+ */
11
15
  icon?: string | Snippet<[icon: string]> | null | undefined;
12
16
  /** Button attributes, applied only when `onclick` renders the alert as a `<button>`. */
13
17
  button_attrs?: SvelteHTMLElements['button'];
@@ -1 +1 @@
1
- {"version":3,"file":"Alert.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Alert.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1E,OAAO,EAAwB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAEnE,KAAK,gBAAgB,GACpB,AADwB,uIAAuI;AAC/J,cAAc,CAAC,WAAW,CAAC,GAAG;IAC7B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IAIf,uDAAuD;IACvD,OAAO,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;IAC3D,wFAAwF;IACxF,YAAY,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAC5C,uFAAuF;IACvF,SAAS,CAAC,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACtC,QAAQ,EAAE,OAAO,CAAC;CAClB,CAAC;AAkEJ,QAAA,MAAM,KAAK,sDAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"Alert.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Alert.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1E,OAAO,EAAwB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAEnE,KAAK,gBAAgB,GAEpB,AAFwB,uEAAuE;AAE/F,cAAc,CAAC,WAAW,CAAC,GAAG;IAC7B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IAIf,uDAAuD;IACvD,OAAO,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;IAC3D,wFAAwF;IACxF,YAAY,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAC5C,uFAAuF;IACvF,SAAS,CAAC,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACtC,QAAQ,EAAE,OAAO,CAAC;CAClB,CAAC;AAgEJ,QAAA,MAAM,KAAK,sDAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
@@ -103,12 +103,15 @@
103
103
  align-items: center;
104
104
  font-size: var(--font_size, var(--font_size_md));
105
105
  }
106
+ /* at least one line of text tall, so a root link holding only an icon is as tall as a
107
+ * breadcrumb with path pieces, and the layout around it doesn't shift between pages */
106
108
  a {
107
109
  position: relative;
108
110
  z-index: 1;
109
111
  padding: 0 calc(var(--font_size, var(--font_size_md)) * 0.5);
110
112
  display: flex;
111
113
  align-items: center;
114
+ min-height: 1lh;
112
115
  }
113
116
  .separator {
114
117
  position: relative;
package/dist/Card.svelte CHANGED
@@ -4,30 +4,47 @@
4
4
  import type { Snippet } from 'svelte';
5
5
  import type { HTMLAttributes, SvelteHTMLElements } from 'svelte/elements';
6
6
 
7
+ import { href_is_current_page } from './page_helpers.js';
8
+
7
9
  // TODO think through Alert+Card APIs together, one can be a button and the other a link atm
8
10
 
9
11
  const {
10
12
  href,
11
13
  tag,
12
- align = 'left',
14
+ align = 'start',
13
15
  icon,
14
16
  a_attrs,
15
17
  children,
16
18
  ...rest
17
- }: // generic element attrs, the common denominator of the rendered roots - // branch-specific attributes go in `a_attrs`
19
+ }: // generic element attrs, the common denominator of the rendered roots;
20
+ // branch-specific attributes go in `a_attrs`
18
21
  HTMLAttributes<HTMLElement> & {
19
- /** Renders the card as an `<a>` when provided. */
22
+ /**
23
+ * Renders the card as an `<a>`, marked `selected` while it leads to the current
24
+ * page, ignoring a trailing slash, the query, and the hash.
25
+ */
20
26
  href?: string | undefined;
21
- tag?: string | undefined;
22
- align?: 'left' | 'right' | 'above' | 'below';
23
- icon?: string | Snippet;
27
+ /** The element to render, defaulting to `a` with an `href` and `div` without. */
28
+ tag?: keyof HTMLElementTagNameMap | undefined;
29
+ /**
30
+ * The card's layout: where the icon sits, at the inline start or end beside the
31
+ * content or above or below it, and how the content aligns, toward the end for
32
+ * `end` and centered for `top` and `bottom`.
33
+ * @default 'start'
34
+ */
35
+ align?: 'start' | 'end' | 'top' | 'bottom';
36
+ /**
37
+ * A decorative icon, hidden from assistive tech: a string like an emoji or a glyph,
38
+ * or a snippet for anything else. None when absent, null, or empty.
39
+ */
40
+ icon?: string | Snippet | null;
24
41
  /** Anchor attributes, applied only when `href` renders the card as an `<a>`. */
25
42
  a_attrs?: SvelteHTMLElements['a'];
26
43
  children: Snippet;
27
44
  } = $props();
28
45
 
29
46
  const link = $derived(!!href);
30
- const selected = $derived(link && page.url.pathname === href);
47
+ const selected = $derived(!!href && href_is_current_page(href, page.url));
31
48
  // the tag renders through `svelte:element`, so declare the styled one for fuz_css extraction
32
49
  // @fuz-elements a
33
50
  const final_tag = $derived(tag ?? (link ? 'a' : 'div'));
@@ -43,41 +60,38 @@
43
60
  }
44
61
  });
45
62
  }
46
-
47
- const left = $derived(align === 'left');
48
- const right = $derived(align === 'right');
49
- const above = $derived(align === 'above');
50
- const below = $derived(align === 'below');
51
-
52
- const final_icon: string | Snippet = $derived(icon ?? (link ? '🔗' : '🪧'));
53
63
  </script>
54
64
 
65
+ <!-- a button that isn't told otherwise doesn't submit a form around it -->
55
66
  <svelte:element
56
67
  this={final_tag}
68
+ type={final_tag === 'button' ? 'button' : undefined}
57
69
  {...rest}
58
70
  {...inferred_attrs}
59
- class={['card', rest.class, inferred_attrs?.class, { link, selected, left, right, above, below }]}
71
+ class={['card', align, rest.class, inferred_attrs?.class, { link, selected }]}
60
72
  >
61
- {#if align === 'left' || align === 'above'}
73
+ {#if align === 'start' || align === 'top'}
62
74
  {@render icon_snippet()}
63
75
  {/if}
64
76
  <div class="content">
65
77
  {@render children()}
66
78
  </div>
67
- {#if align === 'right' || align === 'below'}
79
+ {#if align === 'end' || align === 'bottom'}
68
80
  {@render icon_snippet()}
69
81
  {/if}
70
82
  </svelte:element>
71
83
 
72
- <!-- TODO name? -->
84
+ <!-- an empty string is no icon, so no empty element takes the icon's margin -->
73
85
  {#snippet icon_snippet()}
74
- <div class="icon">
75
- {#if typeof final_icon === 'string'}
76
- {final_icon}
77
- {:else}
78
- {@render final_icon()}
79
- {/if}
80
- </div>
86
+ {#if icon}
87
+ <div class="icon" aria-hidden="true">
88
+ {#if typeof icon === 'string'}
89
+ {icon}
90
+ {:else}
91
+ {@render icon()}
92
+ {/if}
93
+ </div>
94
+ {/if}
81
95
  {/snippet}
82
96
 
83
97
  <style>
@@ -92,13 +106,13 @@
92
106
  background-color: var(--fg_10);
93
107
  border-radius: var(--border_radius, var(--border_radius_md));
94
108
  text-decoration: none;
95
- text-align: left;
109
+ text-align: start;
96
110
  }
97
- .right {
111
+ .end {
98
112
  justify-content: flex-end;
99
113
  }
100
- .above,
101
- .below {
114
+ .top,
115
+ .bottom {
102
116
  flex-direction: column;
103
117
  text-align: center;
104
118
  }
@@ -128,12 +142,15 @@
128
142
  .link:hover .content {
129
143
  text-decoration: underline;
130
144
  }
131
- .left .content {
132
- padding-right: var(--space_sm);
145
+ .end .content {
146
+ text-align: end;
147
+ }
148
+ /* a little room on the side away from the icon */
149
+ .start:has(> .icon) .content {
150
+ padding-inline-end: var(--space_sm);
133
151
  }
134
- .right .content {
135
- text-align: right;
136
- padding-left: var(--space_sm);
152
+ .end:has(> .icon) .content {
153
+ padding-inline-start: var(--space_sm);
137
154
  }
138
155
  .icon {
139
156
  font-size: var(--icon_size, var(--icon_size_md));
@@ -141,17 +158,16 @@
141
158
  display: flex;
142
159
  justify-content: center;
143
160
  }
144
- /* TODO @many remove all :global usage after https://github.com/sveltejs/svelte/issues/10143 */
145
- .left :global(.icon) {
146
- margin-right: var(--icon_margin);
161
+ .start .icon {
162
+ margin-inline-end: var(--icon_margin);
147
163
  }
148
- .right :global(.icon) {
149
- margin-left: var(--icon_margin);
164
+ .end .icon {
165
+ margin-inline-start: var(--icon_margin);
150
166
  }
151
- .above :global(.icon) {
167
+ .top .icon {
152
168
  margin-bottom: var(--icon_margin);
153
169
  }
154
- .below :global(.icon) {
170
+ .bottom .icon {
155
171
  margin-top: var(--icon_margin);
156
172
  }
157
173
  @media (max-width: 460px) {
@@ -159,19 +175,12 @@
159
175
  font-size: var(--font_size_xl);
160
176
  }
161
177
  }
178
+ /* the icon's size and margin come from the variables, whichever side it is on */
162
179
  @media (max-width: 380px) {
163
180
  .card {
164
181
  --icon_size: var(--icon_size_md);
165
182
  --icon_margin: var(--space_sm);
166
183
  font-size: var(--font_size_lg);
167
184
  }
168
- .icon {
169
- font-size: var(--icon_size_md);
170
- margin-right: var(--space_sm);
171
- }
172
- .right :global(.icon) {
173
- margin-right: 0;
174
- margin-left: var(--space_sm);
175
- }
176
185
  }
177
186
  </style>
@@ -1,12 +1,26 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLAttributes, SvelteHTMLElements } from 'svelte/elements';
3
- type $$ComponentProps = // generic element attrs, the common denominator of the rendered roots - // branch-specific attributes go in `a_attrs`
3
+ type $$ComponentProps = // generic element attrs, the common denominator of the rendered roots;
4
4
  HTMLAttributes<HTMLElement> & {
5
- /** Renders the card as an `<a>` when provided. */
5
+ /**
6
+ * Renders the card as an `<a>`, marked `selected` while it leads to the current
7
+ * page, ignoring a trailing slash, the query, and the hash.
8
+ */
6
9
  href?: string | undefined;
7
- tag?: string | undefined;
8
- align?: 'left' | 'right' | 'above' | 'below';
9
- icon?: string | Snippet;
10
+ /** The element to render, defaulting to `a` with an `href` and `div` without. */
11
+ tag?: keyof HTMLElementTagNameMap | undefined;
12
+ /**
13
+ * The card's layout: where the icon sits, at the inline start or end beside the
14
+ * content or above or below it, and how the content aligns, toward the end for
15
+ * `end` and centered for `top` and `bottom`.
16
+ * @default 'start'
17
+ */
18
+ align?: 'start' | 'end' | 'top' | 'bottom';
19
+ /**
20
+ * A decorative icon, hidden from assistive tech: a string like an emoji or a glyph,
21
+ * or a snippet for anything else. None when absent, null, or empty.
22
+ */
23
+ icon?: string | Snippet | null;
10
24
  /** Anchor attributes, applied only when `href` renders the card as an `<a>`. */
11
25
  a_attrs?: SvelteHTMLElements['a'];
12
26
  children: Snippet;
@@ -1 +1 @@
1
- {"version":3,"file":"Card.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Card.svelte"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEzE,KAAK,gBAAgB,GACpB,AADwB,sHAAsH;AAC9I,cAAc,CAAC,WAAW,CAAC,GAAG;IAC7B,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAC7C,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,gFAAgF;IAChF,OAAO,CAAC,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAClC,QAAQ,EAAE,OAAO,CAAC;CAClB,CAAC;AAwEJ,QAAA,MAAM,IAAI,sDAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"Card.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Card.svelte"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAIzE,KAAK,gBAAgB,GAEpB,AAFwB,uEAAuE;AAE/F,cAAc,CAAC,WAAW,CAAC,GAAG;IAC7B;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,iFAAiF;IACjF,GAAG,CAAC,EAAE,MAAM,qBAAqB,GAAG,SAAS,CAAC;IAC9C;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC3C;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IAC/B,gFAAgF;IAChF,OAAO,CAAC,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAClC,QAAQ,EAAE,OAAO,CAAC;CAClB,CAAC;AAsEJ,QAAA,MAAM,IAAI,sDAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { contextmenu_context } from './contextmenu_state.svelte.js';
8
8
  import { icon_external_link, icon_link } from './icons.js';
9
+ import { href_is_current_page } from './page_helpers.js';
9
10
  import type { SvgData } from './svg.js';
10
11
  import Svg from './Svg.svelte';
11
12
  import ContextmenuIcon from './ContextmenuIcon.svelte';
@@ -53,7 +54,7 @@
53
54
  const external = $derived(!(text[0] === '.' || (text[0] === '/' && text[1] !== '/')));
54
55
  const rel = $derived(external ? external_rel : undefined);
55
56
 
56
- const current_page = $derived(page.url.pathname === href);
57
+ const current_page = $derived(href_is_current_page(href, page.url));
57
58
  </script>
58
59
 
59
60
  <li role="none">
@@ -1 +1 @@
1
- {"version":3,"file":"ContextmenuLinkEntry.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ContextmenuLinkEntry.svelte"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAKtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAIvC,KAAK,gBAAgB,GAAI;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AA2FH,QAAA,MAAM,oBAAoB,sDAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"ContextmenuLinkEntry.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/ContextmenuLinkEntry.svelte"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAMtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAIvC,KAAK,gBAAgB,GAAI;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AA4FH,QAAA,MAAM,oBAAoB,sDAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
@@ -28,7 +28,10 @@
28
28
  const url = $derived(host + path);
29
29
 
30
30
  onMount(() => {
31
- if (auto) void goto(url, { replaceState: true }); // eslint-disable-line svelte/no-navigation-without-resolve
31
+ if (!auto) return;
32
+ // `goto` rejects URLs that aren't routes of this app, like static files, other apps,
33
+ // and other hosts, which navigate the browser itself
34
+ goto(url, { replace: true }).catch(() => location.replace(url));
32
35
  });
33
36
  </script>
34
37
 
@@ -1 +1 @@
1
- {"version":3,"file":"Redirect.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Redirect.svelte"],"names":[],"mappings":"AAMA,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAE9C,KAAK,gBAAgB,GAAI;IACxB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;CAClC,CAAC;AAoCH,QAAA,MAAM,QAAQ,sDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"Redirect.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Redirect.svelte"],"names":[],"mappings":"AAMA,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAE9C,KAAK,gBAAgB,GAAI;IACxB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;CAClC,CAAC;AAuCH,QAAA,MAAM,QAAQ,sDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
package/dist/alert.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type AlertStatus = 'inform' | 'help' | 'error';
2
2
  export interface AlertStatusOptions {
3
3
  color: string;
4
- icon: string | null;
4
+ icon: string;
5
5
  }
6
6
  export declare const alert_status_options: Record<AlertStatus, AlertStatusOptions>;
7
7
  //# sourceMappingURL=alert.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"alert.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/alert.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,WAAW,EAAE,kBAAkB,CAIxE,CAAC"}
1
+ {"version":3,"file":"alert.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/alert.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,WAAW,EAAE,kBAAkB,CAIxE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"autofocus.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/autofocus.svelte.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,YAAY,KAAG,UAAU,CAAC,WAAW,GAAG,UAAU,CAI5D,CAAC"}
1
+ {"version":3,"file":"autofocus.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/autofocus.svelte.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,YAAY,KAAG,UAAU,CAAC,WAAW,GAAG,UAAU,CAG5D,CAAC"}
@@ -10,6 +10,5 @@
10
10
  * @param options - focus options forwarded to `element.focus()`; defaults to `{focusVisible: true}` to show focus indicators on programmatic focus
11
11
  */
12
12
  export const autofocus = (options) => (el) => {
13
- // focusVisible missing from TS lib.dom.d.ts as of 5.9
14
13
  el.focus({ focusVisible: true, ...options });
15
14
  };
package/dist/csp.d.ts CHANGED
@@ -58,7 +58,7 @@ export interface CreateCspDirectivesOptions {
58
58
  };
59
59
  }
60
60
  /**
61
- * Builds a CSP directives map for use with SvelteKit's `kit.csp.directives` option.
61
+ * Builds a CSP directives map for use with SvelteKit's `csp.directives` option (passed to `sveltekit()`).
62
62
  *
63
63
  * Restrictive by default; opt into specific permissions via `extend` (append) or
64
64
  * `overrides` (replace). Designed to read as an audit log: every user-added source
package/dist/csp.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Builds a CSP directives map for use with SvelteKit's `kit.csp.directives` option.
2
+ * Builds a CSP directives map for use with SvelteKit's `csp.directives` option (passed to `sveltekit()`).
3
3
  *
4
4
  * Restrictive by default; opt into specific permissions via `extend` (append) or
5
5
  * `overrides` (replace). Designed to read as an audit log: every user-added source
@@ -15,7 +15,7 @@ export declare const docs_links_context: {
15
15
  */
16
16
  export declare const docs_slugify: (str: string) => string;
17
17
  export declare const DOCS_PATH_DEFAULT = "/docs";
18
- export declare const DOCS_PATH: string;
18
+ export declare const DOCS_PATH: "/" | "/about" | "/docs" | "/docs/Alert" | "/docs/Breadcrumb" | "/docs/Breadcrumb/a" | "/docs/Breadcrumb/a/b" | "/docs/Breadcrumb/a/b/c" | "/docs/Breadcrumb/a/b/c/d" | "/docs/Card" | "/docs/Contextmenu" | "/docs/Details" | "/docs/Dialog" | "/docs/Docs" | "/docs/HueInput" | "/docs/LibraryDetail" | "/docs/LibrarySummary" | "/docs/Library" | "/docs/PendingAnimation" | "/docs/PendingButton" | "/docs/Redirect" | "/docs/Sparkline" | "/docs/Svg" | "/docs/Teleport" | "/docs/ThemeRoot" | "/docs/api" | "/docs/csp" | "/docs/icons" | "/docs/intersect" | "/docs/introduction" | "/docs/logos" | "/docs/theming" | "/docs/vite_plugin_pkg_json" | "/help" | `/${string}/` | `/${`docs/api/${string}` & {}}` | `/${string}/help` | `/${string}/about` | `/${string}/docs` | `/${string}/docs/Alert` | `/${string}/docs/Breadcrumb` | `/${string}/docs/Breadcrumb/a` | `/${string}/docs/Breadcrumb/a/b` | `/${string}/docs/Breadcrumb/a/b/c` | `/${string}/docs/Breadcrumb/a/b/c/d` | `/${string}/docs/Card` | `/${string}/docs/Contextmenu` | `/${string}/docs/Details` | `/${string}/docs/Dialog` | `/${string}/docs/Docs` | `/${string}/docs/HueInput` | `/${string}/docs/LibraryDetail` | `/${string}/docs/LibrarySummary` | `/${string}/docs/Library` | `/${string}/docs/PendingAnimation` | `/${string}/docs/PendingButton` | `/${string}/docs/Redirect` | `/${string}/docs/Sparkline` | `/${string}/docs/Svg` | `/${string}/docs/Teleport` | `/${string}/docs/ThemeRoot` | `/${string}/docs/api` | `/${string}/${`docs/api/${string}` & {}}` | `/${string}/docs/csp` | `/${string}/docs/icons` | `/${string}/docs/intersect` | `/${string}/docs/introduction` | `/${string}/docs/logos` | `/${string}/docs/theming` | `/${string}/docs/vite_plugin_pkg_json`;
19
19
  export declare const DOCS_API_PATH: string;
20
20
  export declare const to_docs_path_info: (path_slug: string, pathname: string, root_path?: string) => {
21
21
  path: string;
@@ -1 +1 @@
1
- {"version":3,"file":"docs_helpers.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/docs_helpers.svelte.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAKzD,eAAO,MAAM,kBAAkB;;;;CAA8B,CAAC;AAE9D;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,MAY1C,CAAC;AAEF,eAAO,MAAM,iBAAiB,UAAU,CAAC;AACzC,eAAO,MAAM,SAAS,QAAmB,CAAC;AAC1C,eAAO,MAAM,aAAa,QAAqB,CAAC;AAEhD,eAAO,MAAM,iBAAiB,GAC7B,WAAW,MAAM,EACjB,UAAU,MAAM,EAChB,kBAA6B,KAC3B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;CAK7E,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,WAAW,GAAG,SAAS,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B;AAED,qBAAa,SAAS;;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,YAAY,CAAC,CAAmB;IAgBlE,UAAU,iBA4BP;IAEH,QAAQ,CAAC,kBAAkB,EAAE,SAAS,CAAC,MAAM,CAAC,CAAmB;gBAErD,SAAS,SAAoB;IAIzC,GAAG,CACF,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,WAAW,EACjB,KAAK,SAAI,EACT,SAAS,CAAC,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,MAAM,GAClB,MAAM;IAkBT,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIxB;;;OAGG;IACH,mBAAmB,IAAI,MAAM;CAG7B"}
1
+ {"version":3,"file":"docs_helpers.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/docs_helpers.svelte.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAKzD,eAAO,MAAM,kBAAkB;;;;CAA8B,CAAC;AAE9D;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,MAY1C,CAAC;AAEF,eAAO,MAAM,iBAAiB,UAAU,CAAC;AACzC,eAAO,MAAM,SAAS,6qDAAmB,CAAC;AAC1C,eAAO,MAAM,aAAa,QAAqB,CAAC;AAEhD,eAAO,MAAM,iBAAiB,GAC7B,WAAW,MAAM,EACjB,UAAU,MAAM,EAChB,kBAA6B,KAC3B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;CAK7E,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,WAAW,GAAG,SAAS,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B;AAED,qBAAa,SAAS;;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,YAAY,CAAC,CAAmB;IAgBlE,UAAU,iBA4BP;IAEH,QAAQ,CAAC,kBAAkB,EAAE,SAAS,CAAC,MAAM,CAAC,CAAmB;gBAErD,SAAS,SAAoB;IAIzC,GAAG,CACF,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,WAAW,EACjB,KAAK,SAAI,EACT,SAAS,CAAC,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,MAAM,GAClB,MAAM;IAkBT,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIxB;;;OAGG;IACH,mBAAmB,IAAI,MAAM;CAG7B"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Helpers for relating links to the current page.
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Whether `href` leads to the page at `url`: the same origin and pathname, with a
8
+ * trailing slash, the query, and the hash ignored. A relative `href` resolves
9
+ * against `url`, as a browser resolves it.
10
+ *
11
+ * @param href - a link's `href`, absolute or relative
12
+ * @param url - the current page's url, e.g. SvelteKit's readonly `page.url`
13
+ * @returns `false` for an `href` that isn't a valid url
14
+ */
15
+ export declare const href_is_current_page: (href: string, url: Pick<URL, "href" | "origin" | "pathname">) => boolean;
16
+ //# sourceMappingURL=page_helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/page_helpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,MAAM,EACZ,KAAK,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC,KAC5C,OAOF,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Helpers for relating links to the current page.
3
+ *
4
+ * @module
5
+ */
6
+ /** A pathname without its trailing slash, except the root's. */
7
+ const strip_trailing_slash = (pathname) => pathname.length > 1 && pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
8
+ /**
9
+ * Whether `href` leads to the page at `url`: the same origin and pathname, with a
10
+ * trailing slash, the query, and the hash ignored. A relative `href` resolves
11
+ * against `url`, as a browser resolves it.
12
+ *
13
+ * @param href - a link's `href`, absolute or relative
14
+ * @param url - the current page's url, e.g. SvelteKit's readonly `page.url`
15
+ * @returns `false` for an `href` that isn't a valid url
16
+ */
17
+ export const href_is_current_page = (href, url) => {
18
+ if (!URL.canParse(href, url.href))
19
+ return false;
20
+ const target = new URL(href, url.href);
21
+ return (target.origin === url.origin &&
22
+ strip_trailing_slash(target.pathname) === strip_trailing_slash(url.pathname));
23
+ };
@@ -21,7 +21,7 @@
21
21
  *
22
22
  * ```ts
23
23
  * // vite.config.ts
24
- * import {vite_plugin_pkg_json} from './vite_plugin_pkg_json.js';
24
+ * import {vite_plugin_pkg_json} from '@fuzdev/fuz_ui/vite_plugin_pkg_json.ts';
25
25
  * export default defineConfig({plugins: [vite_plugin_pkg_json(), sveltekit()]});
26
26
  * ```
27
27
  *
@@ -21,7 +21,7 @@
21
21
  *
22
22
  * ```ts
23
23
  * // vite.config.ts
24
- * import {vite_plugin_pkg_json} from './vite_plugin_pkg_json.js';
24
+ * import {vite_plugin_pkg_json} from '@fuzdev/fuz_ui/vite_plugin_pkg_json.ts';
25
25
  * export default defineConfig({plugins: [vite_plugin_pkg_json(), sveltekit()]});
26
26
  * ```
27
27
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_ui",
3
- "version": "0.211.0",
3
+ "version": "0.213.0",
4
4
  "description": "Svelte UI library",
5
5
  "tagline": "friendly user zystem",
6
6
  "glyph": "🧶",
@@ -29,6 +29,10 @@
29
29
  "deploy": "gro deploy"
30
30
  },
31
31
  "type": "module",
32
+ "imports": {
33
+ "#lib/*": "./src/lib/*",
34
+ "#routes/*": "./src/routes/*"
35
+ },
32
36
  "engines": {
33
37
  "node": ">=24.14"
34
38
  },
@@ -36,9 +40,9 @@
36
40
  "@fuzdev/fuz_code": ">=0.46.0",
37
41
  "@fuzdev/fuz_css": ">=0.65.0",
38
42
  "@fuzdev/fuz_util": ">=0.72.0",
39
- "@fuzdev/gro": ">=0.204.0",
43
+ "@fuzdev/gro": ">=0.214.0",
40
44
  "@fuzdev/mdz": ">=0.2.0",
41
- "@sveltejs/kit": "^2.47.3",
45
+ "@sveltejs/kit": "^3",
42
46
  "esm-env": "^1",
43
47
  "svelte": "^5",
44
48
  "zod": "^4.1.12"
@@ -64,28 +68,28 @@
64
68
  "@changesets/changelog-git": "^0.2.1",
65
69
  "@fuzdev/blake3-wasm": "^0.2.0",
66
70
  "@fuzdev/fuz_code": "^0.50.0",
67
- "@fuzdev/fuz_css": "^0.66.0",
71
+ "@fuzdev/fuz_css": "^0.66.1",
68
72
  "@fuzdev/fuz_util": "^0.72.0",
69
- "@fuzdev/gro": "^0.213.0",
70
- "@fuzdev/mdz": "^0.3.0",
71
- "@ryanatkn/eslint-config": "^0.12.2",
72
- "@sveltejs/adapter-static": "^3.0.10",
73
- "@sveltejs/kit": "^2.69.1",
74
- "@sveltejs/package": "^2.5.7",
75
- "@sveltejs/vite-plugin-svelte": "^6.2.4",
73
+ "@fuzdev/gro": "^0.214.0",
74
+ "@fuzdev/mdz": "^0.4.0",
75
+ "@ryanatkn/eslint-config": "^0.12.3",
76
+ "@sveltejs/adapter-static": "^4.0.0",
77
+ "@sveltejs/kit": "^3.0.1",
78
+ "@sveltejs/package": "^3.0.0",
79
+ "@sveltejs/vite-plugin-svelte": "^7.3.1",
76
80
  "@types/node": "^24.10.1",
77
81
  "@webref/css": "^8.2.0",
78
82
  "eslint": "^9.39.4",
79
83
  "eslint-plugin-svelte": "^3.19.0",
80
84
  "esm-env": "^1.2.2",
81
85
  "jsdom": "^29.1.1",
82
- "svelte": "^5.56.4",
83
- "svelte-check": "^4.7.2",
84
- "svelte-docinfo": "^0.8.0",
86
+ "svelte": "^5.57.2",
87
+ "svelte-check": "^4.7.6",
88
+ "svelte-docinfo": "^0.8.1",
85
89
  "tslib": "^2.8.1",
86
- "typescript": "^5.9.3",
87
- "typescript-eslint": "^8.60.0",
88
- "vite": "^7.3.6",
90
+ "typescript": "^6.0.3",
91
+ "typescript-eslint": "^8.71.1",
92
+ "vite": "^8.3.4",
89
93
  "vitest": "^5.0.0",
90
94
  "zod": "^4.3.6"
91
95
  },
package/src/lib/alert.ts CHANGED
@@ -4,7 +4,7 @@ export type AlertStatus = 'inform' | 'help' | 'error';
4
4
 
5
5
  export interface AlertStatusOptions {
6
6
  color: string;
7
- icon: string | null;
7
+ icon: string;
8
8
  }
9
9
 
10
10
  export const alert_status_options: Record<AlertStatus, AlertStatusOptions> = {
@@ -15,6 +15,5 @@ import type { Attachment } from 'svelte/attachments';
15
15
  export const autofocus =
16
16
  (options?: FocusOptions): Attachment<HTMLElement | SVGElement> =>
17
17
  (el) => {
18
- // focusVisible missing from TS lib.dom.d.ts as of 5.9
19
- el.focus({ focusVisible: true, ...options } as FocusOptions);
18
+ el.focus({ focusVisible: true, ...options });
20
19
  };
package/src/lib/csp.ts CHANGED
@@ -65,7 +65,7 @@ export interface CreateCspDirectivesOptions {
65
65
  }
66
66
 
67
67
  /**
68
- * Builds a CSP directives map for use with SvelteKit's `kit.csp.directives` option.
68
+ * Builds a CSP directives map for use with SvelteKit's `csp.directives` option (passed to `sveltekit()`).
69
69
  *
70
70
  * Restrictive by default; opt into specific permissions via `extend` (append) or
71
71
  * `overrides` (replace). Designed to read as an audit log: every user-added source
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Helpers for relating links to the current page.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ /** A pathname without its trailing slash, except the root's. */
8
+ const strip_trailing_slash = (pathname: string): string =>
9
+ pathname.length > 1 && pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
10
+
11
+ /**
12
+ * Whether `href` leads to the page at `url`: the same origin and pathname, with a
13
+ * trailing slash, the query, and the hash ignored. A relative `href` resolves
14
+ * against `url`, as a browser resolves it.
15
+ *
16
+ * @param href - a link's `href`, absolute or relative
17
+ * @param url - the current page's url, e.g. SvelteKit's readonly `page.url`
18
+ * @returns `false` for an `href` that isn't a valid url
19
+ */
20
+ export const href_is_current_page = (
21
+ href: string,
22
+ url: Pick<URL, 'href' | 'origin' | 'pathname'>
23
+ ): boolean => {
24
+ if (!URL.canParse(href, url.href)) return false;
25
+ const target = new URL(href, url.href);
26
+ return (
27
+ target.origin === url.origin &&
28
+ strip_trailing_slash(target.pathname) === strip_trailing_slash(url.pathname)
29
+ );
30
+ };