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/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
- // Detect touch device
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 !== `undefined`) {
15
- is_touch_device = `ontouchstart` in globalThis || navigator.maxTouchPoints > 0;
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
- const dropdown = document.querySelector(`.dropdown[data-href="${href}"]`);
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
- const new_index = Math.max(0, Math.min(sub_routes.length - 1, focused_item_index + direction));
52
- focused_item_index = new_index;
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
- // Return focus to dropdown toggle button
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
- const label = text.replace(/^\//, ``).replaceAll(`-`, ` `);
92
- return { label, style: `text-transform: capitalize` };
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, label: route };
97
- const [first, second] = route;
98
- return Array.isArray(second)
99
- ? { href: first, label: first, children: second }
100
- : { href: first, label: second };
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 route (JSON.stringify(route))}
133
- {@const { href, label, children: sub_routes } = parse_route(route)}
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
- {#if sub_routes}
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((route) => route !== href)}
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
- data-href={href}
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 parent_page_exists}
159
- {@const { label, style } = parent}
160
- <a {href} aria-current={is_current(href)} onclick={close_menus} {style}>
161
- {@html label}
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 style={parent.style}>{@html parent.label}</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 {parent.label} submenu"
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) => handle_dropdown_keydown(event, href, filtered_sub_routes)}
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 child = format_label(child_href, true)}
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: child.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={close_menus}
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={`${child.style}; ${link_props?.style ?? ``}`}
335
+ style={`${child_formatted.style}; ${link_props?.style ?? ``}`}
336
+ {@attach child_tooltip}
208
337
  >
209
- {@html child.label}
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
- {@const regular = format_label(label)}
218
- {#if link}
219
- {@render link({ href, label })}
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
- <a
222
- {href}
223
- aria-current={is_current(href)}
224
- onclick={close_menus}
225
- {...link_props}
226
- style={`${regular.style}; ${link_props?.style ?? ``}`}
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 > a {
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-color);
544
+ background-color: var(--text);
371
545
  border-radius: 8px;
372
546
  transition: all 0.2s linear;
373
- transform-origin: 1px;
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
- @media (max-width: 767px) {
386
- .burger {
387
- display: flex;
388
- }
389
- .menu {
390
- position: fixed;
391
- top: 3rem;
392
- left: 1rem;
393
- background-color: var(--nav-surface-bg);
394
- border: 1px solid var(--nav-surface-border);
395
- box-shadow: var(--nav-surface-shadow);
396
- opacity: 0;
397
- visibility: hidden;
398
- transition: all 0.3s ease;
399
- z-index: var(--nav-mobile-z-index, 2);
400
- flex-direction: column;
401
- align-items: stretch;
402
- justify-content: start;
403
- gap: 0.2em;
404
- max-width: 90vw;
405
- border-radius: 6px;
406
- }
407
- .menu.open {
408
- opacity: 1;
409
- visibility: visible;
410
- }
411
- .menu > a,
412
- .dropdown {
413
- padding: 2pt 8pt;
414
- }
415
-
416
- /* Mobile dropdown styles - show as expandable section */
417
- .dropdown {
418
- flex-direction: column;
419
- align-items: stretch;
420
- }
421
- .dropdown > div:first-child {
422
- display: flex;
423
- align-items: center;
424
- justify-content: space-between;
425
- }
426
- .dropdown > div:first-child > a,
427
- .dropdown > div:first-child > span {
428
- flex: 1;
429
- border-radius: var(--nav-border-radius);
430
- }
431
- .dropdown > div:first-child > button {
432
- padding: 4pt 8pt;
433
- border-radius: var(--nav-border-radius);
434
- }
435
- .dropdown > div:last-child {
436
- position: static;
437
- border: none;
438
- box-shadow: none;
439
- margin-top: 0.25em;
440
- padding: 0 0 0 1em;
441
- background-color: transparent;
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>