svelte-multiselect 11.5.1 → 11.5.2
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/CodeExample.svelte +1 -1
- package/dist/MultiSelect.svelte +117 -67
- package/dist/Nav.svelte +317 -134
- package/dist/Nav.svelte.d.ts +38 -37
- package/dist/Wiggle.svelte +9 -2
- package/dist/Wiggle.svelte.d.ts +7 -4
- package/dist/attachments.d.ts +5 -1
- package/dist/attachments.js +161 -108
- package/dist/heading-anchors.d.ts +14 -0
- package/dist/heading-anchors.js +120 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +30 -0
- package/package.json +18 -17
- package/readme.md +25 -1
package/dist/Nav.svelte
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
|
-
<script
|
|
2
|
-
lang="ts"
|
|
3
|
-
generics="Route extends string | [string, string] | [string, string[]]"
|
|
4
|
-
>import { click_outside } from './attachments';
|
|
1
|
+
<script lang="ts">import { click_outside, tooltip } from './attachments';
|
|
5
2
|
import Icon from './Icon.svelte';
|
|
6
|
-
let { routes = [], children, link, menu_props, link_props, page, labels, ...rest } = $props();
|
|
3
|
+
let { routes = [], children, item, link, menu_props, link_props, page, labels, tooltips, tooltip_options, breakpoint = 767, onnavigate, onopen, onclose, ...rest } = $props();
|
|
7
4
|
let is_open = $state(false);
|
|
8
5
|
let hovered_dropdown = $state(null);
|
|
9
6
|
let focused_item_index = $state(-1);
|
|
10
7
|
let is_touch_device = $state(false);
|
|
8
|
+
let is_mobile = $state(false);
|
|
11
9
|
const panel_id = `nav-menu-${crypto.randomUUID()}`;
|
|
12
|
-
//
|
|
10
|
+
// Track previous is_open state for callbacks
|
|
11
|
+
let prev_is_open = $state(false);
|
|
12
|
+
// Detect touch device and handle responsive breakpoint
|
|
13
13
|
$effect(() => {
|
|
14
|
-
if (typeof globalThis
|
|
15
|
-
|
|
14
|
+
if (typeof globalThis === `undefined`)
|
|
15
|
+
return;
|
|
16
|
+
is_touch_device = `ontouchstart` in globalThis || navigator.maxTouchPoints > 0;
|
|
17
|
+
// Handle responsive breakpoint via JS since CSS variables don't work in media queries
|
|
18
|
+
const check_mobile = () => {
|
|
19
|
+
is_mobile = globalThis.innerWidth <= breakpoint;
|
|
20
|
+
};
|
|
21
|
+
check_mobile();
|
|
22
|
+
globalThis.addEventListener(`resize`, check_mobile);
|
|
23
|
+
return () => globalThis.removeEventListener(`resize`, check_mobile);
|
|
24
|
+
});
|
|
25
|
+
// Call onopen/onclose callbacks when menu state changes
|
|
26
|
+
$effect(() => {
|
|
27
|
+
if (is_open && !prev_is_open) {
|
|
28
|
+
onopen?.();
|
|
29
|
+
}
|
|
30
|
+
else if (!is_open && prev_is_open) {
|
|
31
|
+
onclose?.();
|
|
16
32
|
}
|
|
33
|
+
prev_is_open = is_open;
|
|
17
34
|
});
|
|
18
35
|
function close_menus() {
|
|
19
36
|
is_open = false;
|
|
@@ -27,9 +44,7 @@ function toggle_dropdown(href, focus_first = false) {
|
|
|
27
44
|
// Focus management for keyboard users
|
|
28
45
|
if (is_opening && focus_first) {
|
|
29
46
|
setTimeout(() => {
|
|
30
|
-
|
|
31
|
-
const first_link = dropdown?.querySelector(`[role="menuitem"]`);
|
|
32
|
-
first_link?.focus();
|
|
47
|
+
document.querySelector(`.dropdown[data-href="${href}"] [role="menuitem"]`)?.focus();
|
|
33
48
|
}, 0);
|
|
34
49
|
}
|
|
35
50
|
}
|
|
@@ -48,11 +63,8 @@ function handle_dropdown_keydown(event, href, sub_routes) {
|
|
|
48
63
|
if (hovered_dropdown === href && (key === `ArrowDown` || key === `ArrowUp`)) {
|
|
49
64
|
event.preventDefault();
|
|
50
65
|
const direction = key === `ArrowDown` ? 1 : -1;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const dropdown = document.querySelector(`.dropdown[data-href="${href}"]`);
|
|
54
|
-
const links = dropdown?.querySelectorAll(`[role="menuitem"]`);
|
|
55
|
-
links?.[new_index]?.focus();
|
|
66
|
+
focused_item_index = Math.max(0, Math.min(sub_routes.length - 1, focused_item_index + direction));
|
|
67
|
+
document.querySelectorAll(`.dropdown[data-href="${href}"] [role="menuitem"]`)?.[focused_item_index]?.focus();
|
|
56
68
|
}
|
|
57
69
|
// Open dropdown with ArrowDown when closed
|
|
58
70
|
if (hovered_dropdown !== href && key === `ArrowDown`) {
|
|
@@ -64,14 +76,12 @@ function handle_dropdown_item_keydown(event, href) {
|
|
|
64
76
|
if (event.key === `Escape`) {
|
|
65
77
|
event.preventDefault();
|
|
66
78
|
close_menus();
|
|
67
|
-
|
|
68
|
-
document
|
|
69
|
-
.querySelector(`.dropdown[data-href="${href}"]`)
|
|
70
|
-
?.querySelector(`[data-dropdown-toggle]`)
|
|
71
|
-
?.focus();
|
|
79
|
+
document.querySelector(`.dropdown[data-href="${href}"] [data-dropdown-toggle]`)?.focus();
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
function is_current(path) {
|
|
83
|
+
if (!path)
|
|
84
|
+
return undefined;
|
|
75
85
|
if (path === `/`)
|
|
76
86
|
return page?.url.pathname === `/` ? `page` : undefined;
|
|
77
87
|
// Match exact path or path followed by / to avoid partial matches
|
|
@@ -83,28 +93,103 @@ function is_current(path) {
|
|
|
83
93
|
}
|
|
84
94
|
const is_child_current = (sub_routes) => sub_routes.some((child_path) => is_current(child_path) === `page`);
|
|
85
95
|
function format_label(text, remove_parent = false) {
|
|
96
|
+
if (!text)
|
|
97
|
+
return { label: ``, style: `` };
|
|
86
98
|
const custom_label = labels?.[text];
|
|
87
99
|
if (custom_label)
|
|
88
100
|
return { label: custom_label, style: `` };
|
|
89
101
|
if (remove_parent)
|
|
90
102
|
text = text.split(`/`).filter(Boolean).pop() ?? text;
|
|
91
|
-
|
|
92
|
-
|
|
103
|
+
let label = text.replace(/^\//, ``).replaceAll(`-`, ` `);
|
|
104
|
+
// Handle root path '/' which becomes empty after stripping
|
|
105
|
+
if (!label && text === `/`)
|
|
106
|
+
label = `Home`;
|
|
107
|
+
return { label, style: label ? `text-transform: capitalize` : `` };
|
|
93
108
|
}
|
|
109
|
+
// Normalize all route formats to NavRouteObject
|
|
94
110
|
function parse_route(route) {
|
|
95
111
|
if (typeof route === `string`)
|
|
96
|
-
return { href: route
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
112
|
+
return { href: route };
|
|
113
|
+
if (Array.isArray(route)) {
|
|
114
|
+
const [href, second] = route;
|
|
115
|
+
return Array.isArray(second)
|
|
116
|
+
? { href, children: second }
|
|
117
|
+
: { href, label: second };
|
|
118
|
+
}
|
|
119
|
+
return route;
|
|
120
|
+
}
|
|
121
|
+
function get_tooltip(route) {
|
|
122
|
+
// Priority: disabled message > route.tooltip > tooltips[href]
|
|
123
|
+
if (typeof route.disabled === `string`) {
|
|
124
|
+
return tooltip({ ...tooltip_options, content: route.disabled });
|
|
125
|
+
}
|
|
126
|
+
const content = route.tooltip ?? tooltips?.[route.href];
|
|
127
|
+
if (!content)
|
|
128
|
+
return undefined;
|
|
129
|
+
// Support both string (content only) and object (full options) formats
|
|
130
|
+
const opts = typeof content === `string` ? { content } : content;
|
|
131
|
+
return tooltip({ ...tooltip_options, ...opts });
|
|
132
|
+
}
|
|
133
|
+
// Handle link click with onnavigate callback
|
|
134
|
+
function handle_link_click(event, route) {
|
|
135
|
+
if (route.disabled) {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (onnavigate) {
|
|
140
|
+
const result = onnavigate({ href: route.href, event, route });
|
|
141
|
+
if (result === false) {
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
close_menus();
|
|
147
|
+
}
|
|
148
|
+
// Get external link attributes
|
|
149
|
+
function get_external_attrs(route) {
|
|
150
|
+
if (!route.external)
|
|
151
|
+
return {};
|
|
152
|
+
return { target: `_blank`, rel: `noopener noreferrer` };
|
|
101
153
|
}
|
|
102
154
|
</script>
|
|
103
155
|
|
|
104
156
|
<svelte:window {onkeydown} />
|
|
105
157
|
|
|
158
|
+
<!-- Default item rendering snippet for escape hatch -->
|
|
159
|
+
{#snippet default_item_render(
|
|
160
|
+
parsed_route: NavRouteObject,
|
|
161
|
+
formatted: { label: string; style: string },
|
|
162
|
+
item_tooltip: ReturnType<typeof tooltip> | undefined,
|
|
163
|
+
)}
|
|
164
|
+
{@const is_disabled = Boolean(parsed_route.disabled)}
|
|
165
|
+
{#if is_disabled}
|
|
166
|
+
<span
|
|
167
|
+
class="disabled {parsed_route.class ?? ``}"
|
|
168
|
+
style={`${formatted.style}; ${parsed_route.style ?? ``}`}
|
|
169
|
+
aria-disabled="true"
|
|
170
|
+
{@attach item_tooltip}
|
|
171
|
+
>{@html formatted.label}</span>
|
|
172
|
+
{:else if link}
|
|
173
|
+
{@render link({ href: parsed_route.href, label: formatted.label })}
|
|
174
|
+
{:else}
|
|
175
|
+
<a
|
|
176
|
+
href={parsed_route.href}
|
|
177
|
+
aria-current={is_current(parsed_route.href)}
|
|
178
|
+
onclick={(event) => handle_link_click(event, parsed_route)}
|
|
179
|
+
class={parsed_route.class}
|
|
180
|
+
{...link_props}
|
|
181
|
+
{...get_external_attrs(parsed_route)}
|
|
182
|
+
style={`${formatted.style}; ${link_props?.style ?? ``}; ${parsed_route.style ?? ``}`}
|
|
183
|
+
{@attach item_tooltip}
|
|
184
|
+
>
|
|
185
|
+
{@html formatted.label}
|
|
186
|
+
</a>
|
|
187
|
+
{/if}
|
|
188
|
+
{/snippet}
|
|
189
|
+
|
|
106
190
|
<nav
|
|
107
191
|
{...rest}
|
|
192
|
+
class:mobile={is_mobile}
|
|
108
193
|
{@attach click_outside({ callback: close_menus })}
|
|
109
194
|
>
|
|
110
195
|
<button
|
|
@@ -129,24 +214,43 @@ function parse_route(route) {
|
|
|
129
214
|
{onkeydown}
|
|
130
215
|
{...menu_props}
|
|
131
216
|
>
|
|
132
|
-
{#each routes as
|
|
133
|
-
|
|
217
|
+
{#each routes as
|
|
218
|
+
route,
|
|
219
|
+
route_idx
|
|
220
|
+
(`${route_idx}-${
|
|
221
|
+
typeof route === `string`
|
|
222
|
+
? route
|
|
223
|
+
: Array.isArray(route)
|
|
224
|
+
? route[0]
|
|
225
|
+
: route.href ?? `sep-${route_idx}`
|
|
226
|
+
}`)
|
|
227
|
+
}
|
|
228
|
+
{@const parsed_route = parse_route(route)}
|
|
229
|
+
{@const formatted = format_label(parsed_route.label ?? parsed_route.href)}
|
|
230
|
+
{@const sub_routes = parsed_route.children}
|
|
231
|
+
{@const is_active = is_current(parsed_route.href) === `page`}
|
|
232
|
+
{@const is_dropdown = Boolean(sub_routes)}
|
|
233
|
+
{@const is_right = parsed_route.align === `right`}
|
|
234
|
+
{@const item_tooltip = get_tooltip(parsed_route)}
|
|
134
235
|
|
|
135
|
-
|
|
236
|
+
<!-- Separator-only item -->
|
|
237
|
+
{#if parsed_route.separator && !parsed_route.href}
|
|
238
|
+
<div class="separator" role="separator"></div>
|
|
239
|
+
{:else if sub_routes}
|
|
136
240
|
<!-- Dropdown menu item -->
|
|
137
|
-
{@const parent = format_label(label)}
|
|
138
241
|
{@const child_is_active = is_child_current(sub_routes)}
|
|
139
|
-
{@const parent_page_exists = sub_routes.includes(href)}
|
|
140
|
-
{@const filtered_sub_routes = sub_routes.filter((
|
|
242
|
+
{@const parent_page_exists = sub_routes.includes(parsed_route.href)}
|
|
243
|
+
{@const filtered_sub_routes = sub_routes.filter((r) => r !== parsed_route.href)}
|
|
141
244
|
<div
|
|
142
245
|
class="dropdown"
|
|
143
246
|
class:active={child_is_active}
|
|
144
|
-
|
|
247
|
+
class:align-right={is_right}
|
|
248
|
+
data-href={parsed_route.href}
|
|
145
249
|
role="group"
|
|
146
250
|
aria-current={child_is_active ? `true` : undefined}
|
|
147
|
-
onmouseenter={() => !is_touch_device && (hovered_dropdown = href)}
|
|
251
|
+
onmouseenter={() => !is_touch_device && (hovered_dropdown = parsed_route.href)}
|
|
148
252
|
onmouseleave={() => !is_touch_device && (hovered_dropdown = null)}
|
|
149
|
-
onfocusin={() => (hovered_dropdown = href)}
|
|
253
|
+
onfocusin={() => (hovered_dropdown = parsed_route.href)}
|
|
150
254
|
onfocusout={(event) => {
|
|
151
255
|
const next = event.relatedTarget as Node | null
|
|
152
256
|
if (!next || !(event.currentTarget as HTMLElement).contains(next)) {
|
|
@@ -155,22 +259,45 @@ function parse_route(route) {
|
|
|
155
259
|
}}
|
|
156
260
|
>
|
|
157
261
|
<div>
|
|
158
|
-
{#if
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
{
|
|
262
|
+
{#if parsed_route.disabled}
|
|
263
|
+
<span
|
|
264
|
+
class="disabled {parsed_route.class ?? ``}"
|
|
265
|
+
style={`${formatted.style}; ${parsed_route.style ?? ``}`}
|
|
266
|
+
aria-disabled="true"
|
|
267
|
+
{@attach item_tooltip}
|
|
268
|
+
>{@html formatted.label}</span>
|
|
269
|
+
{:else if parent_page_exists}
|
|
270
|
+
<a
|
|
271
|
+
href={parsed_route.href}
|
|
272
|
+
aria-current={is_current(parsed_route.href)}
|
|
273
|
+
onclick={(event) => handle_link_click(event, parsed_route)}
|
|
274
|
+
class={parsed_route.class}
|
|
275
|
+
style={`${formatted.style}; ${parsed_route.style ?? ``}`}
|
|
276
|
+
{...get_external_attrs(parsed_route)}
|
|
277
|
+
{@attach item_tooltip}
|
|
278
|
+
>
|
|
279
|
+
{@html formatted.label}
|
|
162
280
|
</a>
|
|
163
281
|
{:else}
|
|
164
|
-
<span
|
|
282
|
+
<span
|
|
283
|
+
class={parsed_route.class}
|
|
284
|
+
style={`${formatted.style}; ${parsed_route.style ?? ``}`}
|
|
285
|
+
{@attach item_tooltip}
|
|
286
|
+
>{@html formatted.label}</span>
|
|
165
287
|
{/if}
|
|
166
288
|
<button
|
|
167
289
|
type="button"
|
|
168
290
|
data-dropdown-toggle
|
|
169
|
-
aria-label="Toggle {
|
|
170
|
-
aria-expanded={hovered_dropdown === href}
|
|
291
|
+
aria-label="Toggle {formatted.label} submenu"
|
|
292
|
+
aria-expanded={hovered_dropdown === parsed_route.href}
|
|
171
293
|
aria-haspopup="true"
|
|
172
|
-
onclick={() => toggle_dropdown(href, false)}
|
|
173
|
-
onkeydown={(event) =>
|
|
294
|
+
onclick={() => toggle_dropdown(parsed_route.href, false)}
|
|
295
|
+
onkeydown={(event) =>
|
|
296
|
+
handle_dropdown_keydown(
|
|
297
|
+
event,
|
|
298
|
+
parsed_route.href,
|
|
299
|
+
filtered_sub_routes,
|
|
300
|
+
)}
|
|
174
301
|
>
|
|
175
302
|
<Icon
|
|
176
303
|
icon="ChevronExpand"
|
|
@@ -179,12 +306,12 @@ function parse_route(route) {
|
|
|
179
306
|
</button>
|
|
180
307
|
</div>
|
|
181
308
|
<div
|
|
182
|
-
class:visible={hovered_dropdown === href}
|
|
309
|
+
class:visible={hovered_dropdown === parsed_route.href}
|
|
183
310
|
role="menu"
|
|
184
311
|
tabindex="-1"
|
|
185
|
-
onmouseenter={() => !is_touch_device && (hovered_dropdown = href)}
|
|
312
|
+
onmouseenter={() => !is_touch_device && (hovered_dropdown = parsed_route.href)}
|
|
186
313
|
onmouseleave={() => !is_touch_device && (hovered_dropdown = null)}
|
|
187
|
-
onfocusin={() => (hovered_dropdown = href)}
|
|
314
|
+
onfocusin={() => (hovered_dropdown = parsed_route.href)}
|
|
188
315
|
onfocusout={(event) => {
|
|
189
316
|
const next = event.relatedTarget as Node | null
|
|
190
317
|
if (!next || !(event.currentTarget as HTMLElement).contains(next)) {
|
|
@@ -193,40 +320,56 @@ function parse_route(route) {
|
|
|
193
320
|
}}
|
|
194
321
|
>
|
|
195
322
|
{#each filtered_sub_routes as child_href (child_href)}
|
|
196
|
-
{@const
|
|
323
|
+
{@const child_formatted = format_label(child_href, true)}
|
|
324
|
+
{@const child_tooltip = get_tooltip({ href: child_href })}
|
|
197
325
|
{#if link}
|
|
198
|
-
{@render link({ href: child_href, label:
|
|
326
|
+
{@render link({ href: child_href, label: child_formatted.label })}
|
|
199
327
|
{:else}
|
|
200
328
|
<a
|
|
201
329
|
href={child_href}
|
|
202
330
|
role="menuitem"
|
|
203
331
|
aria-current={is_current(child_href)}
|
|
204
|
-
onclick={
|
|
205
|
-
onkeydown={(event) => handle_dropdown_item_keydown(event, href)}
|
|
332
|
+
onclick={(event) => handle_link_click(event, { href: child_href })}
|
|
333
|
+
onkeydown={(event) => handle_dropdown_item_keydown(event, parsed_route.href)}
|
|
206
334
|
{...link_props}
|
|
207
|
-
style={`${
|
|
335
|
+
style={`${child_formatted.style}; ${link_props?.style ?? ``}`}
|
|
336
|
+
{@attach child_tooltip}
|
|
208
337
|
>
|
|
209
|
-
{@html
|
|
338
|
+
{@html child_formatted.label}
|
|
210
339
|
</a>
|
|
211
340
|
{/if}
|
|
212
341
|
{/each}
|
|
213
342
|
</div>
|
|
214
343
|
</div>
|
|
344
|
+
<!-- Separator after dropdown if specified -->
|
|
345
|
+
{#if parsed_route.separator}
|
|
346
|
+
<div class="separator" role="separator"></div>
|
|
347
|
+
{/if}
|
|
215
348
|
{:else}
|
|
216
349
|
<!-- Regular link item -->
|
|
217
|
-
{
|
|
218
|
-
|
|
219
|
-
{
|
|
350
|
+
{#if item}
|
|
351
|
+
<!-- User-provided item snippet with render_default escape hatch -->
|
|
352
|
+
{#snippet render_default_snippet()}
|
|
353
|
+
{@render default_item_render(parsed_route, formatted, item_tooltip)}
|
|
354
|
+
{/snippet}
|
|
355
|
+
<span class:align-right={is_right}>
|
|
356
|
+
{@render item({
|
|
357
|
+
route: parsed_route,
|
|
358
|
+
href: parsed_route.href,
|
|
359
|
+
label: formatted.label,
|
|
360
|
+
is_active,
|
|
361
|
+
is_dropdown,
|
|
362
|
+
render_default: render_default_snippet,
|
|
363
|
+
})}
|
|
364
|
+
</span>
|
|
220
365
|
{:else}
|
|
221
|
-
<
|
|
222
|
-
{
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
>
|
|
228
|
-
{@html regular.label}
|
|
229
|
-
</a>
|
|
366
|
+
<span class:align-right={is_right}>
|
|
367
|
+
{@render default_item_render(parsed_route, formatted, item_tooltip)}
|
|
368
|
+
</span>
|
|
369
|
+
{/if}
|
|
370
|
+
<!-- Separator after item if specified -->
|
|
371
|
+
{#if parsed_route.separator}
|
|
372
|
+
<div class="separator" role="separator"></div>
|
|
230
373
|
{/if}
|
|
231
374
|
{/if}
|
|
232
375
|
{/each}
|
|
@@ -255,7 +398,8 @@ function parse_route(route) {
|
|
|
255
398
|
flex-wrap: wrap;
|
|
256
399
|
padding: 0.5em;
|
|
257
400
|
}
|
|
258
|
-
.menu >
|
|
401
|
+
.menu > span,
|
|
402
|
+
.menu > span > a {
|
|
259
403
|
line-height: 1.3;
|
|
260
404
|
padding: 1pt 5pt;
|
|
261
405
|
border-radius: var(--nav-border-radius);
|
|
@@ -263,13 +407,37 @@ function parse_route(route) {
|
|
|
263
407
|
color: inherit;
|
|
264
408
|
transition: background-color 0.2s;
|
|
265
409
|
}
|
|
266
|
-
.menu > a:hover {
|
|
410
|
+
.menu > span > a:hover {
|
|
267
411
|
background-color: var(--nav-link-bg-hover);
|
|
268
412
|
}
|
|
269
|
-
.menu > a[aria-current='page'] {
|
|
413
|
+
.menu > span > a[aria-current='page'] {
|
|
270
414
|
color: var(--nav-link-active-color);
|
|
271
415
|
}
|
|
272
|
-
|
|
416
|
+
/* Disabled items */
|
|
417
|
+
.menu .disabled {
|
|
418
|
+
opacity: var(--nav-disabled-opacity, 0.5);
|
|
419
|
+
cursor: not-allowed;
|
|
420
|
+
pointer-events: none;
|
|
421
|
+
}
|
|
422
|
+
/* Right-aligned items - only first one gets margin-left: auto */
|
|
423
|
+
.menu > .align-right,
|
|
424
|
+
.menu > .dropdown.align-right {
|
|
425
|
+
margin-left: auto;
|
|
426
|
+
}
|
|
427
|
+
.menu > .align-right + .align-right,
|
|
428
|
+
.menu > .align-right + .dropdown.align-right,
|
|
429
|
+
.menu > .dropdown.align-right + .align-right,
|
|
430
|
+
.menu > .dropdown.align-right + .dropdown.align-right {
|
|
431
|
+
margin-left: 0;
|
|
432
|
+
}
|
|
433
|
+
/* Separator */
|
|
434
|
+
.menu > .separator {
|
|
435
|
+
width: 1px;
|
|
436
|
+
height: 1.2em;
|
|
437
|
+
background-color: var(--nav-separator-color, currentColor);
|
|
438
|
+
opacity: 0.3;
|
|
439
|
+
margin: var(--nav-separator-margin, 0 0.25em);
|
|
440
|
+
}
|
|
273
441
|
/* Dropdown styles */
|
|
274
442
|
.dropdown {
|
|
275
443
|
position: relative;
|
|
@@ -317,6 +485,11 @@ function parse_route(route) {
|
|
|
317
485
|
align-items: center;
|
|
318
486
|
justify-content: center;
|
|
319
487
|
border-radius: 0 var(--nav-border-radius) var(--nav-border-radius) 0;
|
|
488
|
+
outline-offset: -1px;
|
|
489
|
+
}
|
|
490
|
+
.dropdown > div:first-child > button:focus-visible {
|
|
491
|
+
outline: 2px solid currentColor;
|
|
492
|
+
outline-offset: -2px;
|
|
320
493
|
}
|
|
321
494
|
.dropdown > div:last-child {
|
|
322
495
|
position: absolute;
|
|
@@ -366,79 +539,89 @@ function parse_route(route) {
|
|
|
366
539
|
z-index: var(--nav-toggle-btn-z-index, 10);
|
|
367
540
|
}
|
|
368
541
|
.burger span {
|
|
542
|
+
width: 100%;
|
|
369
543
|
height: 0.18rem;
|
|
370
|
-
background-color: var(--text
|
|
544
|
+
background-color: var(--text);
|
|
371
545
|
border-radius: 8px;
|
|
372
546
|
transition: all 0.2s linear;
|
|
373
|
-
transform-origin:
|
|
547
|
+
transform-origin: center;
|
|
374
548
|
}
|
|
375
549
|
.burger[aria-expanded='true'] span:first-child {
|
|
376
|
-
transform: rotate(45deg);
|
|
550
|
+
transform: translateY(0.4rem) rotate(45deg);
|
|
377
551
|
}
|
|
378
552
|
.burger[aria-expanded='true'] span:nth-child(2) {
|
|
379
553
|
opacity: 0;
|
|
380
554
|
}
|
|
381
555
|
.burger[aria-expanded='true'] span:nth-child(3) {
|
|
382
|
-
transform: rotate(-45deg);
|
|
556
|
+
transform: translateY(-0.4rem) rotate(-45deg);
|
|
383
557
|
}
|
|
384
|
-
/* Mobile styles */
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
558
|
+
/* Mobile styles - using .mobile class set via JS based on breakpoint prop */
|
|
559
|
+
nav.mobile .burger {
|
|
560
|
+
display: flex;
|
|
561
|
+
}
|
|
562
|
+
nav.mobile .menu {
|
|
563
|
+
position: fixed;
|
|
564
|
+
top: 3rem;
|
|
565
|
+
left: 1rem;
|
|
566
|
+
background-color: var(--nav-surface-bg);
|
|
567
|
+
border: 1px solid var(--nav-surface-border);
|
|
568
|
+
box-shadow: var(--nav-surface-shadow);
|
|
569
|
+
opacity: 0;
|
|
570
|
+
visibility: hidden;
|
|
571
|
+
transition: all 0.3s ease;
|
|
572
|
+
z-index: var(--nav-mobile-z-index, 2);
|
|
573
|
+
flex-direction: column;
|
|
574
|
+
align-items: stretch;
|
|
575
|
+
justify-content: start;
|
|
576
|
+
gap: 0.2em;
|
|
577
|
+
max-width: 90vw;
|
|
578
|
+
border-radius: 6px;
|
|
579
|
+
}
|
|
580
|
+
nav.mobile .menu.open {
|
|
581
|
+
opacity: 1;
|
|
582
|
+
visibility: visible;
|
|
583
|
+
}
|
|
584
|
+
nav.mobile .menu > span,
|
|
585
|
+
nav.mobile .menu > span > a,
|
|
586
|
+
nav.mobile .dropdown {
|
|
587
|
+
padding: 2pt 8pt;
|
|
588
|
+
}
|
|
589
|
+
/* Mobile separator */
|
|
590
|
+
nav.mobile .menu > .separator {
|
|
591
|
+
width: 100%;
|
|
592
|
+
height: 1px;
|
|
593
|
+
margin: var(--nav-separator-margin, 0.25em 0);
|
|
594
|
+
}
|
|
595
|
+
/* Mobile dropdown styles - show as expandable section */
|
|
596
|
+
nav.mobile .dropdown {
|
|
597
|
+
flex-direction: column;
|
|
598
|
+
align-items: stretch;
|
|
599
|
+
}
|
|
600
|
+
nav.mobile .dropdown > div:first-child {
|
|
601
|
+
display: flex;
|
|
602
|
+
align-items: center;
|
|
603
|
+
justify-content: space-between;
|
|
604
|
+
}
|
|
605
|
+
nav.mobile .dropdown > div:first-child > a,
|
|
606
|
+
nav.mobile .dropdown > div:first-child > span {
|
|
607
|
+
flex: 1;
|
|
608
|
+
border-radius: var(--nav-border-radius);
|
|
609
|
+
}
|
|
610
|
+
nav.mobile .dropdown > div:first-child > button {
|
|
611
|
+
padding: 4pt 8pt;
|
|
612
|
+
border-radius: var(--nav-border-radius);
|
|
613
|
+
}
|
|
614
|
+
nav.mobile .dropdown > div:last-child {
|
|
615
|
+
position: static;
|
|
616
|
+
border: none;
|
|
617
|
+
box-shadow: none;
|
|
618
|
+
margin-top: 0.25em;
|
|
619
|
+
padding: 0 0 0 1em;
|
|
620
|
+
background-color: transparent;
|
|
621
|
+
}
|
|
622
|
+
/* Mobile right-aligned items stack normally */
|
|
623
|
+
nav.mobile .menu > .align-right,
|
|
624
|
+
nav.mobile .menu > .dropdown.align-right {
|
|
625
|
+
margin-left: 0;
|
|
443
626
|
}
|
|
444
627
|
</style>
|