svelte-multiselect 11.5.2 → 11.6.1
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/CmdPalette.svelte.d.ts +2 -2
- package/dist/MultiSelect.svelte +231 -102
- package/dist/MultiSelect.svelte.d.ts +2 -2
- package/dist/Nav.svelte +124 -61
- package/dist/Nav.svelte.d.ts +2 -1
- package/dist/attachments.d.ts +3 -0
- package/dist/attachments.js +145 -37
- package/dist/heading-anchors.js +10 -16
- package/dist/live-examples/highlighter.js +35 -0
- package/dist/live-examples/index.d.ts +7 -0
- package/dist/live-examples/index.js +23 -0
- package/dist/live-examples/mdsvex-transform.d.ts +32 -0
- package/dist/live-examples/mdsvex-transform.js +185 -0
- package/dist/live-examples/vite-plugin.d.ts +6 -0
- package/dist/live-examples/vite-plugin.js +170 -0
- package/dist/types.d.ts +16 -1
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +15 -0
- package/package.json +37 -14
|
@@ -2,7 +2,7 @@ import type { MultiSelectProps } from './types';
|
|
|
2
2
|
declare function $$render<Option extends import('./types').Option>(): {
|
|
3
3
|
props: MultiSelectProps<Option>;
|
|
4
4
|
exports: {};
|
|
5
|
-
bindings: "
|
|
5
|
+
bindings: "value" | "selected" | "invalid" | "open" | "activeIndex" | "activeOption" | "form_input" | "input" | "matchingOptions" | "maxSelect" | "options" | "outerDiv" | "searchText" | "collapsedGroups" | "collapseAllGroups" | "expandAllGroups" | "undo" | "redo" | "canUndo" | "canRedo";
|
|
6
6
|
slots: {};
|
|
7
7
|
events: {};
|
|
8
8
|
};
|
|
@@ -10,7 +10,7 @@ declare class __sveltets_Render<Option extends import('./types').Option> {
|
|
|
10
10
|
props(): ReturnType<typeof $$render<Option>>['props'];
|
|
11
11
|
events(): ReturnType<typeof $$render<Option>>['events'];
|
|
12
12
|
slots(): ReturnType<typeof $$render<Option>>['slots'];
|
|
13
|
-
bindings(): "
|
|
13
|
+
bindings(): "value" | "selected" | "invalid" | "open" | "activeIndex" | "activeOption" | "form_input" | "input" | "matchingOptions" | "maxSelect" | "options" | "outerDiv" | "searchText" | "collapsedGroups" | "collapseAllGroups" | "expandAllGroups" | "undo" | "redo" | "canUndo" | "canRedo";
|
|
14
14
|
exports(): {};
|
|
15
15
|
}
|
|
16
16
|
interface $$IsomorphicComponent {
|
package/dist/Nav.svelte
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
<script lang="ts">import { click_outside, tooltip } from './attachments';
|
|
2
2
|
import Icon from './Icon.svelte';
|
|
3
|
-
|
|
3
|
+
import { get_uuid } from './utils';
|
|
4
|
+
let { routes = [], children, item, link, menu_props, link_props, page, labels, tooltips, tooltip_options, breakpoint = 767, dropdown_cooldown = 150, onnavigate, onopen, onclose, ...rest } = $props();
|
|
4
5
|
let is_open = $state(false);
|
|
5
6
|
let hovered_dropdown = $state(null);
|
|
7
|
+
let pinned_dropdown = $state(null);
|
|
6
8
|
let focused_item_index = $state(-1);
|
|
7
9
|
let is_touch_device = $state(false);
|
|
8
10
|
let is_mobile = $state(false);
|
|
9
|
-
|
|
11
|
+
let hide_timeout = null;
|
|
12
|
+
const panel_id = `nav-menu-${get_uuid()}`;
|
|
10
13
|
// Track previous is_open state for callbacks
|
|
11
14
|
let prev_is_open = $state(false);
|
|
12
15
|
// Detect touch device and handle responsive breakpoint
|
|
@@ -32,22 +35,49 @@ $effect(() => {
|
|
|
32
35
|
}
|
|
33
36
|
prev_is_open = is_open;
|
|
34
37
|
});
|
|
38
|
+
$effect(() => () => {
|
|
39
|
+
if (hide_timeout)
|
|
40
|
+
clearTimeout(hide_timeout);
|
|
41
|
+
}); // cleanup on destroy
|
|
35
42
|
function close_menus() {
|
|
43
|
+
if (hide_timeout)
|
|
44
|
+
clearTimeout(hide_timeout);
|
|
36
45
|
is_open = false;
|
|
37
46
|
hovered_dropdown = null;
|
|
47
|
+
pinned_dropdown = null;
|
|
38
48
|
focused_item_index = -1;
|
|
39
49
|
}
|
|
40
50
|
function toggle_dropdown(href, focus_first = false) {
|
|
41
|
-
const is_opening =
|
|
42
|
-
|
|
51
|
+
const is_opening = pinned_dropdown !== href;
|
|
52
|
+
pinned_dropdown = is_opening ? href : null;
|
|
53
|
+
hovered_dropdown = is_opening ? href : null;
|
|
43
54
|
focused_item_index = is_opening && focus_first ? 0 : -1;
|
|
44
|
-
// Focus management for keyboard users
|
|
45
55
|
if (is_opening && focus_first) {
|
|
46
56
|
setTimeout(() => {
|
|
47
|
-
document
|
|
57
|
+
document
|
|
58
|
+
.querySelector(`.dropdown[data-href="${CSS.escape(href)}"] [role="menuitem"]`)
|
|
59
|
+
?.focus();
|
|
48
60
|
}, 0);
|
|
49
61
|
}
|
|
50
62
|
}
|
|
63
|
+
function open_dropdown(href, from_mouse = false) {
|
|
64
|
+
if (from_mouse && is_touch_device)
|
|
65
|
+
return;
|
|
66
|
+
if (hide_timeout)
|
|
67
|
+
clearTimeout(hide_timeout);
|
|
68
|
+
if (pinned_dropdown && pinned_dropdown !== href)
|
|
69
|
+
pinned_dropdown = null;
|
|
70
|
+
hovered_dropdown = href;
|
|
71
|
+
}
|
|
72
|
+
function schedule_hide(href, is_pinned) {
|
|
73
|
+
if (is_touch_device || is_pinned)
|
|
74
|
+
return;
|
|
75
|
+
clearTimeout(hide_timeout);
|
|
76
|
+
hide_timeout = setTimeout(() => {
|
|
77
|
+
if (hovered_dropdown === href)
|
|
78
|
+
hovered_dropdown = null;
|
|
79
|
+
}, dropdown_cooldown);
|
|
80
|
+
}
|
|
51
81
|
function onkeydown(event) {
|
|
52
82
|
if (event.key === `Escape`)
|
|
53
83
|
close_menus();
|
|
@@ -59,15 +89,18 @@ function handle_dropdown_keydown(event, href, sub_routes) {
|
|
|
59
89
|
toggle_dropdown(href, true);
|
|
60
90
|
return;
|
|
61
91
|
}
|
|
92
|
+
// Check if dropdown is open (either via hover or pinned)
|
|
93
|
+
const is_open = hovered_dropdown === href || pinned_dropdown === href;
|
|
62
94
|
// Arrow key navigation within open dropdown
|
|
63
|
-
if (
|
|
95
|
+
if (is_open && (key === `ArrowDown` || key === `ArrowUp`)) {
|
|
64
96
|
event.preventDefault();
|
|
65
97
|
const direction = key === `ArrowDown` ? 1 : -1;
|
|
66
98
|
focused_item_index = Math.max(0, Math.min(sub_routes.length - 1, focused_item_index + direction));
|
|
67
|
-
document
|
|
99
|
+
document
|
|
100
|
+
.querySelectorAll(`.dropdown[data-href="${CSS.escape(href)}"] [role="menuitem"]`)?.[focused_item_index]?.focus();
|
|
68
101
|
}
|
|
69
102
|
// Open dropdown with ArrowDown when closed
|
|
70
|
-
if (
|
|
103
|
+
if (!is_open && key === `ArrowDown`) {
|
|
71
104
|
event.preventDefault();
|
|
72
105
|
toggle_dropdown(href, true);
|
|
73
106
|
}
|
|
@@ -76,7 +109,9 @@ function handle_dropdown_item_keydown(event, href) {
|
|
|
76
109
|
if (event.key === `Escape`) {
|
|
77
110
|
event.preventDefault();
|
|
78
111
|
close_menus();
|
|
79
|
-
document
|
|
112
|
+
document
|
|
113
|
+
.querySelector(`.dropdown[data-href="${href}"] [data-dropdown-toggle]`)
|
|
114
|
+
?.focus();
|
|
80
115
|
}
|
|
81
116
|
}
|
|
82
117
|
function is_current(path) {
|
|
@@ -195,7 +230,7 @@ function get_external_attrs(route) {
|
|
|
195
230
|
<button
|
|
196
231
|
class="burger"
|
|
197
232
|
type="button"
|
|
198
|
-
onclick={() => is_open = !is_open}
|
|
233
|
+
onclick={() => (is_open = !is_open)}
|
|
199
234
|
aria-label="Toggle navigation menu"
|
|
200
235
|
aria-expanded={is_open}
|
|
201
236
|
aria-controls={panel_id}
|
|
@@ -222,7 +257,7 @@ function get_external_attrs(route) {
|
|
|
222
257
|
? route
|
|
223
258
|
: Array.isArray(route)
|
|
224
259
|
? route[0]
|
|
225
|
-
: route.href ?? `sep-${route_idx}`
|
|
260
|
+
: (route.href ?? `sep-${route_idx}`)
|
|
226
261
|
}`)
|
|
227
262
|
}
|
|
228
263
|
{@const parsed_route = parse_route(route)}
|
|
@@ -240,7 +275,11 @@ function get_external_attrs(route) {
|
|
|
240
275
|
<!-- Dropdown menu item -->
|
|
241
276
|
{@const child_is_active = is_child_current(sub_routes)}
|
|
242
277
|
{@const parent_page_exists = sub_routes.includes(parsed_route.href)}
|
|
243
|
-
{@const filtered_sub_routes = sub_routes.filter(
|
|
278
|
+
{@const filtered_sub_routes = sub_routes.filter(
|
|
279
|
+
(r) => r !== parsed_route.href,
|
|
280
|
+
)}
|
|
281
|
+
{@const is_pinned = pinned_dropdown === parsed_route.href}
|
|
282
|
+
{@const dropdown_open = hovered_dropdown === parsed_route.href || is_pinned}
|
|
244
283
|
<div
|
|
245
284
|
class="dropdown"
|
|
246
285
|
class:active={child_is_active}
|
|
@@ -248,13 +287,13 @@ function get_external_attrs(route) {
|
|
|
248
287
|
data-href={parsed_route.href}
|
|
249
288
|
role="group"
|
|
250
289
|
aria-current={child_is_active ? `true` : undefined}
|
|
251
|
-
onmouseenter={() =>
|
|
252
|
-
onmouseleave={() =>
|
|
253
|
-
onfocusin={() => (
|
|
290
|
+
onmouseenter={() => open_dropdown(parsed_route.href, true)}
|
|
291
|
+
onmouseleave={() => schedule_hide(parsed_route.href, is_pinned)}
|
|
292
|
+
onfocusin={() => open_dropdown(parsed_route.href)}
|
|
254
293
|
onfocusout={(event) => {
|
|
255
294
|
const next = event.relatedTarget as Node | null
|
|
256
295
|
if (!next || !(event.currentTarget as HTMLElement).contains(next)) {
|
|
257
|
-
hovered_dropdown = null
|
|
296
|
+
if (!is_pinned) hovered_dropdown = null
|
|
258
297
|
}
|
|
259
298
|
}}
|
|
260
299
|
>
|
|
@@ -287,9 +326,11 @@ function get_external_attrs(route) {
|
|
|
287
326
|
{/if}
|
|
288
327
|
<button
|
|
289
328
|
type="button"
|
|
329
|
+
class="dropdown-toggle"
|
|
330
|
+
class:open={dropdown_open}
|
|
290
331
|
data-dropdown-toggle
|
|
291
332
|
aria-label="Toggle {formatted.label} submenu"
|
|
292
|
-
aria-expanded={
|
|
333
|
+
aria-expanded={dropdown_open}
|
|
293
334
|
aria-haspopup="true"
|
|
294
335
|
onclick={() => toggle_dropdown(parsed_route.href, false)}
|
|
295
336
|
onkeydown={(event) =>
|
|
@@ -299,25 +340,15 @@ function get_external_attrs(route) {
|
|
|
299
340
|
filtered_sub_routes,
|
|
300
341
|
)}
|
|
301
342
|
>
|
|
302
|
-
<Icon
|
|
303
|
-
icon="ChevronExpand"
|
|
304
|
-
style="width: 0.8em; height: 0.8em"
|
|
305
|
-
/>
|
|
343
|
+
<Icon icon="ChevronDown" style="width: 0.7em; height: 0.7em" />
|
|
306
344
|
</button>
|
|
307
345
|
</div>
|
|
308
346
|
<div
|
|
309
|
-
class:visible={
|
|
347
|
+
class:visible={dropdown_open}
|
|
310
348
|
role="menu"
|
|
311
349
|
tabindex="-1"
|
|
312
|
-
onmouseenter={() =>
|
|
313
|
-
onmouseleave={() =>
|
|
314
|
-
onfocusin={() => (hovered_dropdown = parsed_route.href)}
|
|
315
|
-
onfocusout={(event) => {
|
|
316
|
-
const next = event.relatedTarget as Node | null
|
|
317
|
-
if (!next || !(event.currentTarget as HTMLElement).contains(next)) {
|
|
318
|
-
hovered_dropdown = null
|
|
319
|
-
}
|
|
320
|
-
}}
|
|
350
|
+
onmouseenter={() => open_dropdown(parsed_route.href, true)}
|
|
351
|
+
onmouseleave={() => schedule_hide(parsed_route.href, is_pinned)}
|
|
321
352
|
>
|
|
322
353
|
{#each filtered_sub_routes as child_href (child_href)}
|
|
323
354
|
{@const child_formatted = format_label(child_href, true)}
|
|
@@ -382,9 +413,12 @@ function get_external_attrs(route) {
|
|
|
382
413
|
nav {
|
|
383
414
|
position: relative;
|
|
384
415
|
margin: -0.75em auto 1.25em;
|
|
385
|
-
--nav-border-radius:
|
|
416
|
+
--nav-border-radius: 3pt;
|
|
386
417
|
--nav-surface-bg: light-dark(#fafafa, #1a1a1a);
|
|
387
|
-
--nav-surface-border: light-dark(
|
|
418
|
+
--nav-surface-border: light-dark(
|
|
419
|
+
rgba(128, 128, 128, 0.25),
|
|
420
|
+
rgba(200, 200, 200, 0.2)
|
|
421
|
+
);
|
|
388
422
|
--nav-surface-shadow: light-dark(
|
|
389
423
|
0 2px 8px rgba(0, 0, 0, 0.15),
|
|
390
424
|
0 4px 12px rgba(0, 0, 0, 0.5)
|
|
@@ -398,17 +432,21 @@ function get_external_attrs(route) {
|
|
|
398
432
|
flex-wrap: wrap;
|
|
399
433
|
padding: 0.5em;
|
|
400
434
|
}
|
|
401
|
-
.menu > span
|
|
435
|
+
.menu > span {
|
|
436
|
+
display: flex;
|
|
437
|
+
align-items: center;
|
|
438
|
+
border-radius: var(--nav-border-radius);
|
|
439
|
+
background-color: var(--nav-link-bg);
|
|
440
|
+
transition: background-color 0.2s;
|
|
441
|
+
}
|
|
442
|
+
.menu > span:hover {
|
|
443
|
+
background-color: var(--nav-link-bg-hover, rgba(0, 0, 0, 0.1));
|
|
444
|
+
}
|
|
402
445
|
.menu > span > a {
|
|
403
446
|
line-height: 1.3;
|
|
404
|
-
padding: 1pt
|
|
405
|
-
border-radius: var(--nav-border-radius);
|
|
447
|
+
padding: var(--nav-item-padding, 1pt 4pt);
|
|
406
448
|
text-decoration: none;
|
|
407
449
|
color: inherit;
|
|
408
|
-
transition: background-color 0.2s;
|
|
409
|
-
}
|
|
410
|
-
.menu > span > a:hover {
|
|
411
|
-
background-color: var(--nav-link-bg-hover);
|
|
412
450
|
}
|
|
413
451
|
.menu > span > a[aria-current='page'] {
|
|
414
452
|
color: var(--nav-link-active-color);
|
|
@@ -450,24 +488,23 @@ function get_external_attrs(route) {
|
|
|
450
488
|
content: '';
|
|
451
489
|
position: absolute;
|
|
452
490
|
top: 100%;
|
|
453
|
-
left:
|
|
454
|
-
right:
|
|
455
|
-
height: var(--nav-dropdown-margin,
|
|
491
|
+
left: -5pt;
|
|
492
|
+
right: -5pt;
|
|
493
|
+
height: calc(var(--nav-dropdown-margin, 2pt) + 5pt);
|
|
456
494
|
}
|
|
457
495
|
.dropdown > div:first-child {
|
|
458
496
|
display: flex;
|
|
459
497
|
align-items: center;
|
|
460
|
-
gap: 0;
|
|
461
498
|
border-radius: var(--nav-border-radius);
|
|
499
|
+
background-color: var(--nav-link-bg);
|
|
462
500
|
transition: background-color 0.2s;
|
|
463
501
|
}
|
|
464
502
|
.dropdown > div:first-child:hover {
|
|
465
|
-
background-color: var(--nav-link-bg-hover);
|
|
503
|
+
background-color: var(--nav-link-bg-hover, rgba(0, 0, 0, 0.1));
|
|
466
504
|
}
|
|
467
|
-
.dropdown > div:first-child > a,
|
|
468
|
-
.dropdown > div:first-child > span {
|
|
505
|
+
.dropdown > div:first-child > a, .dropdown > div:first-child > span {
|
|
469
506
|
line-height: 1.3;
|
|
470
|
-
padding: 1pt
|
|
507
|
+
padding: var(--nav-item-padding, 1pt 4pt);
|
|
471
508
|
text-decoration: none;
|
|
472
509
|
color: inherit;
|
|
473
510
|
border-radius: var(--nav-border-radius) 0 0 var(--nav-border-radius);
|
|
@@ -476,7 +513,7 @@ function get_external_attrs(route) {
|
|
|
476
513
|
color: var(--nav-link-active-color);
|
|
477
514
|
}
|
|
478
515
|
.dropdown > div:first-child > button {
|
|
479
|
-
padding:
|
|
516
|
+
padding: 2pt 4pt;
|
|
480
517
|
border: none;
|
|
481
518
|
background: transparent;
|
|
482
519
|
color: inherit;
|
|
@@ -486,40 +523,52 @@ function get_external_attrs(route) {
|
|
|
486
523
|
justify-content: center;
|
|
487
524
|
border-radius: 0 var(--nav-border-radius) var(--nav-border-radius) 0;
|
|
488
525
|
outline-offset: -1px;
|
|
526
|
+
opacity: 0.6;
|
|
527
|
+
transition: opacity 0.15s, transform 0.2s ease;
|
|
528
|
+
}
|
|
529
|
+
.dropdown > div:first-child > button:hover {
|
|
530
|
+
opacity: 1;
|
|
531
|
+
}
|
|
532
|
+
.dropdown > div:first-child > button.open {
|
|
533
|
+
opacity: 1;
|
|
534
|
+
transform: rotate(180deg);
|
|
489
535
|
}
|
|
490
536
|
.dropdown > div:first-child > button:focus-visible {
|
|
491
537
|
outline: 2px solid currentColor;
|
|
492
538
|
outline-offset: -2px;
|
|
539
|
+
opacity: 1;
|
|
493
540
|
}
|
|
494
541
|
.dropdown > div:last-child {
|
|
495
542
|
position: absolute;
|
|
496
543
|
top: 100%;
|
|
497
|
-
left: 0;
|
|
498
|
-
|
|
499
|
-
|
|
544
|
+
left: var(--nav-dropdown-left, 0);
|
|
545
|
+
right: var(--nav-dropdown-right, auto);
|
|
546
|
+
margin: var(--nav-dropdown-margin, 2pt) 0 0 0;
|
|
547
|
+
min-width: var(--nav-dropdown-min-width, 100%); /* at least as wide as parent */
|
|
548
|
+
max-width: var(--nav-dropdown-max-width, none);
|
|
549
|
+
width: var(--nav-dropdown-width, max-content); /* grow wider if content needs it */
|
|
500
550
|
background-color: var(--nav-dropdown-bg, var(--nav-surface-bg));
|
|
501
551
|
border: 1px solid var(--nav-dropdown-border-color, var(--nav-surface-border));
|
|
502
552
|
border-radius: var(--nav-border-radius, 6pt);
|
|
503
553
|
box-shadow: var(--nav-dropdown-shadow, var(--nav-surface-shadow));
|
|
504
|
-
padding: var(--nav-dropdown-padding,
|
|
554
|
+
padding: var(--nav-dropdown-padding, 3pt 0);
|
|
505
555
|
display: none;
|
|
506
556
|
flex-direction: column;
|
|
507
|
-
gap: var(--nav-dropdown-gap, 5pt);
|
|
508
557
|
z-index: var(--nav-dropdown-z-index, 100);
|
|
509
558
|
}
|
|
510
559
|
.dropdown > div:last-child.visible {
|
|
511
560
|
display: flex;
|
|
512
561
|
}
|
|
513
562
|
.dropdown > div:last-child a {
|
|
514
|
-
padding: var(--nav-dropdown-link-padding,
|
|
515
|
-
border-radius: var(--nav-border-radius);
|
|
563
|
+
padding: var(--nav-dropdown-link-padding, 2pt 6pt);
|
|
516
564
|
text-decoration: none;
|
|
517
565
|
color: inherit;
|
|
518
566
|
white-space: nowrap;
|
|
519
|
-
|
|
567
|
+
font-size: 0.92em;
|
|
568
|
+
transition: background-color 0.15s;
|
|
520
569
|
}
|
|
521
570
|
.dropdown > div:last-child a:hover {
|
|
522
|
-
background-color: var(--nav-link-bg-hover);
|
|
571
|
+
background-color: var(--nav-link-bg-hover, rgba(0, 0, 0, 0.1));
|
|
523
572
|
}
|
|
524
573
|
.dropdown > div:last-child a[aria-current='page'] {
|
|
525
574
|
color: var(--nav-link-active-color);
|
|
@@ -610,15 +659,29 @@ function get_external_attrs(route) {
|
|
|
610
659
|
nav.mobile .dropdown > div:first-child > button {
|
|
611
660
|
padding: 4pt 8pt;
|
|
612
661
|
border-radius: var(--nav-border-radius);
|
|
662
|
+
opacity: 0.6;
|
|
663
|
+
}
|
|
664
|
+
nav.mobile .dropdown > div:first-child > button.open {
|
|
665
|
+
opacity: 1;
|
|
613
666
|
}
|
|
614
667
|
nav.mobile .dropdown > div:last-child {
|
|
615
668
|
position: static;
|
|
616
669
|
border: none;
|
|
617
670
|
box-shadow: none;
|
|
618
|
-
margin-top:
|
|
619
|
-
padding: 0
|
|
671
|
+
margin-top: 2pt;
|
|
672
|
+
padding: 0;
|
|
620
673
|
background-color: transparent;
|
|
621
674
|
}
|
|
675
|
+
nav.mobile .dropdown > div:last-child a {
|
|
676
|
+
padding: 4pt 8pt 4pt 6pt;
|
|
677
|
+
margin-left: 8pt;
|
|
678
|
+
border-left: 2px solid transparent;
|
|
679
|
+
font-size: 0.9em;
|
|
680
|
+
}
|
|
681
|
+
nav.mobile .dropdown > div:last-child a:hover,
|
|
682
|
+
nav.mobile .dropdown > div:last-child a[aria-current='page'] {
|
|
683
|
+
border-left-color: var(--nav-link-active-color, currentColor);
|
|
684
|
+
}
|
|
622
685
|
/* Mobile right-aligned items stack normally */
|
|
623
686
|
nav.mobile .menu > .align-right,
|
|
624
687
|
nav.mobile .menu > .dropdown.align-right {
|
package/dist/Nav.svelte.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Page } from '@sveltejs/kit';
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
-
import {
|
|
4
|
+
import type { TooltipOptions } from './attachments';
|
|
5
5
|
import type { NavRoute, NavRouteObject } from './types';
|
|
6
6
|
interface ItemSnippetParams {
|
|
7
7
|
route: NavRouteObject;
|
|
@@ -30,6 +30,7 @@ type $$ComponentProps = {
|
|
|
30
30
|
tooltips?: Record<string, string | Omit<TooltipOptions, `disabled`>>;
|
|
31
31
|
tooltip_options?: Omit<TooltipOptions, `content`>;
|
|
32
32
|
breakpoint?: number;
|
|
33
|
+
dropdown_cooldown?: number;
|
|
33
34
|
onnavigate?: (data: {
|
|
34
35
|
href: string;
|
|
35
36
|
event: MouseEvent;
|
package/dist/attachments.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type Attachment } from 'svelte/attachments';
|
|
2
|
+
import { get_uuid } from './utils';
|
|
3
|
+
export { get_uuid };
|
|
2
4
|
declare global {
|
|
3
5
|
interface CSS {
|
|
4
6
|
highlights: HighlightRegistry;
|
|
@@ -62,6 +64,7 @@ export interface TooltipOptions {
|
|
|
62
64
|
show_arrow?: boolean;
|
|
63
65
|
offset?: number;
|
|
64
66
|
allow_html?: boolean;
|
|
67
|
+
sanitize_html?: (html: string) => string;
|
|
65
68
|
}
|
|
66
69
|
export declare const tooltip: (options?: TooltipOptions) => Attachment;
|
|
67
70
|
export type ClickOutsideConfig<T extends HTMLElement> = {
|
package/dist/attachments.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import {} from 'svelte/attachments';
|
|
2
|
+
import { get_uuid } from './utils';
|
|
3
|
+
// Re-export get_uuid for backwards compatibility
|
|
4
|
+
export { get_uuid };
|
|
2
5
|
// Svelte 5 attachment factory to make an element draggable
|
|
3
6
|
// @param options - Configuration options for dragging behavior
|
|
4
7
|
// @returns Attachment function that sets up dragging on an element
|
|
@@ -419,16 +422,141 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
419
422
|
content = new_content;
|
|
420
423
|
// Only update tooltip if this element owns it
|
|
421
424
|
if (current_tooltip?._owner === element) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
425
|
+
const content_el = current_tooltip.querySelector(`.tooltip-content`);
|
|
426
|
+
if (content_el) {
|
|
427
|
+
if (options.allow_html !== false) {
|
|
428
|
+
let html = content.replace(/\r/g, `<br/>`);
|
|
429
|
+
if (options.sanitize_html)
|
|
430
|
+
html = options.sanitize_html(html);
|
|
431
|
+
content_el.innerHTML = html;
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
content_el.textContent = content;
|
|
435
|
+
}
|
|
436
|
+
// Re-run sizing/positioning after content change
|
|
437
|
+
resize_and_position_tooltip(current_tooltip, element);
|
|
427
438
|
}
|
|
428
439
|
}
|
|
429
440
|
}
|
|
430
441
|
});
|
|
431
442
|
observer.observe(element, { attributes: true, attributeFilter: tooltip_attrs });
|
|
443
|
+
// Shrink tooltip to fit content, then position for correct centering
|
|
444
|
+
function resize_and_position_tooltip(tooltip_el, trigger) {
|
|
445
|
+
// Reset width to allow natural sizing before measuring
|
|
446
|
+
tooltip_el.style.width = ``;
|
|
447
|
+
requestAnimationFrame(() => {
|
|
448
|
+
if (!document.body.contains(tooltip_el))
|
|
449
|
+
return;
|
|
450
|
+
const computed = getComputedStyle(tooltip_el);
|
|
451
|
+
const padding_h = parseFloat(computed.paddingLeft) +
|
|
452
|
+
parseFloat(computed.paddingRight);
|
|
453
|
+
const border_h = parseFloat(computed.borderLeftWidth) +
|
|
454
|
+
parseFloat(computed.borderRightWidth);
|
|
455
|
+
// Handle max-width: none as unbounded, fallback to 280 only for invalid values
|
|
456
|
+
const max_width_raw = computed.maxWidth;
|
|
457
|
+
const max_width_parsed = parseFloat(max_width_raw);
|
|
458
|
+
const max_width = max_width_raw === `none`
|
|
459
|
+
? Infinity
|
|
460
|
+
: Number.isFinite(max_width_parsed)
|
|
461
|
+
? max_width_parsed
|
|
462
|
+
: 280;
|
|
463
|
+
// With border-box, width includes padding+border; with content-box, subtract them
|
|
464
|
+
const box_adjust = computed.boxSizing === `border-box` ? 0 : padding_h + border_h;
|
|
465
|
+
const style = tooltip_el.style;
|
|
466
|
+
const placement = tooltip_el.getAttribute(`data-placement`) || `bottom`;
|
|
467
|
+
// Save styles, measure single-line width with wrapping disabled
|
|
468
|
+
const saved = {
|
|
469
|
+
maxWidth: style.maxWidth,
|
|
470
|
+
wordWrap: style.wordWrap,
|
|
471
|
+
textWrap: style.textWrap,
|
|
472
|
+
whiteSpace: style.whiteSpace,
|
|
473
|
+
};
|
|
474
|
+
Object.assign(style, {
|
|
475
|
+
maxWidth: `none`,
|
|
476
|
+
wordWrap: `normal`,
|
|
477
|
+
textWrap: `nowrap`,
|
|
478
|
+
whiteSpace: `nowrap`,
|
|
479
|
+
width: `auto`,
|
|
480
|
+
});
|
|
481
|
+
const single_line_width = tooltip_el.offsetWidth;
|
|
482
|
+
Object.assign(style, saved);
|
|
483
|
+
// Convert offsetWidth to the value needed for style.width
|
|
484
|
+
const content_width = single_line_width - box_adjust;
|
|
485
|
+
if (content_width <= max_width) {
|
|
486
|
+
// Single-line: set exact width and prevent wrapping
|
|
487
|
+
style.width = `${Math.max(0, content_width)}px`;
|
|
488
|
+
style.textWrap = `nowrap`;
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
// Multi-line: binary search for minimum width that doesn't add line breaks
|
|
492
|
+
style.width = ``;
|
|
493
|
+
const baseline_height = tooltip_el.offsetHeight;
|
|
494
|
+
const initial_width = tooltip_el.offsetWidth;
|
|
495
|
+
// Get min-content (longest word) as lower bound
|
|
496
|
+
Object.assign(style, {
|
|
497
|
+
maxWidth: `none`,
|
|
498
|
+
wordWrap: `normal`,
|
|
499
|
+
width: `min-content`,
|
|
500
|
+
});
|
|
501
|
+
const min_width = tooltip_el.offsetWidth;
|
|
502
|
+
Object.assign(style, {
|
|
503
|
+
maxWidth: saved.maxWidth,
|
|
504
|
+
wordWrap: saved.wordWrap,
|
|
505
|
+
width: `${initial_width - box_adjust}px`,
|
|
506
|
+
});
|
|
507
|
+
// If longest word exceeds wrapped width, use min_width (can't shrink further)
|
|
508
|
+
if (min_width >= initial_width) {
|
|
509
|
+
style.width = `${min_width - box_adjust}px`;
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
// Binary search for minimum width that maintains baseline height
|
|
513
|
+
// Work in offsetWidth units, convert to style.width only when setting
|
|
514
|
+
let low = min_width, high = initial_width, best = initial_width;
|
|
515
|
+
while (high - low > 1) {
|
|
516
|
+
const mid = Math.floor((low + high) / 2);
|
|
517
|
+
style.width = `${mid - box_adjust}px`;
|
|
518
|
+
if (tooltip_el.offsetHeight > baseline_height)
|
|
519
|
+
low = mid;
|
|
520
|
+
else {
|
|
521
|
+
best = mid;
|
|
522
|
+
high = mid;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
style.width = `${best - box_adjust}px`;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
// Position tooltip after width adjustment so centering uses final dimensions
|
|
529
|
+
const rect = trigger.getBoundingClientRect();
|
|
530
|
+
const tooltip_rect = tooltip_el.getBoundingClientRect();
|
|
531
|
+
const margin = options.offset ?? 12;
|
|
532
|
+
let top = 0, left = 0;
|
|
533
|
+
if (placement === `top`) {
|
|
534
|
+
top = rect.top - tooltip_rect.height - margin;
|
|
535
|
+
left = rect.left + rect.width / 2 - tooltip_rect.width / 2;
|
|
536
|
+
}
|
|
537
|
+
else if (placement === `left`) {
|
|
538
|
+
top = rect.top + rect.height / 2 - tooltip_rect.height / 2;
|
|
539
|
+
left = rect.left - tooltip_rect.width - margin;
|
|
540
|
+
}
|
|
541
|
+
else if (placement === `right`) {
|
|
542
|
+
top = rect.top + rect.height / 2 - tooltip_rect.height / 2;
|
|
543
|
+
left = rect.right + margin;
|
|
544
|
+
}
|
|
545
|
+
else { // bottom
|
|
546
|
+
top = rect.bottom + margin;
|
|
547
|
+
left = rect.left + rect.width / 2 - tooltip_rect.width / 2;
|
|
548
|
+
}
|
|
549
|
+
// Keep in viewport
|
|
550
|
+
const viewport_width = globalThis.innerWidth;
|
|
551
|
+
const viewport_height = globalThis.innerHeight;
|
|
552
|
+
left = Math.max(8, Math.min(left, viewport_width - tooltip_rect.width - 8));
|
|
553
|
+
top = Math.max(8, Math.min(top, viewport_height - tooltip_rect.height - 8));
|
|
554
|
+
style.left = `${left + globalThis.scrollX}px`;
|
|
555
|
+
style.top = `${top + globalThis.scrollY}px`;
|
|
556
|
+
style.opacity =
|
|
557
|
+
getComputedStyle(trigger).getPropertyValue(`--tooltip-opacity`).trim() || `1`;
|
|
558
|
+
});
|
|
559
|
+
}
|
|
432
560
|
function show_tooltip() {
|
|
433
561
|
// Skip tooltip on touch input when 'touch-devices' option is set
|
|
434
562
|
if (options.disabled === `touch-devices` && last_pointer_type === `touch`)
|
|
@@ -440,13 +568,13 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
440
568
|
const placement = options.placement || `bottom`;
|
|
441
569
|
tooltip_el.setAttribute(`data-placement`, placement);
|
|
442
570
|
// Accessibility: link tooltip to trigger element
|
|
443
|
-
const tooltip_id = `tooltip-${
|
|
571
|
+
const tooltip_id = `tooltip-${get_uuid()}`;
|
|
444
572
|
tooltip_el.id = tooltip_id;
|
|
445
573
|
tooltip_el.setAttribute(`role`, `tooltip`);
|
|
446
574
|
element.setAttribute(`aria-describedby`, tooltip_id);
|
|
447
575
|
// Apply base styles
|
|
448
576
|
tooltip_el.style.cssText = `
|
|
449
|
-
position: absolute; z-index: 9999; opacity: 0;
|
|
577
|
+
position: absolute; z-index: 9999; opacity: 0; display: inline-block;
|
|
450
578
|
background: var(--tooltip-bg, #333); color: var(--text-color, white); border: var(--tooltip-border, none);
|
|
451
579
|
padding: var(--tooltip-padding, 6px 10px); border-radius: var(--tooltip-radius, 6px); font-size: var(--tooltip-font-size, 13px); line-height: 1.4;
|
|
452
580
|
max-width: var(--tooltip-max-width, 280px); word-wrap: break-word; text-wrap: balance; pointer-events: none;
|
|
@@ -462,13 +590,20 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
462
590
|
tooltip_el.style.setProperty(property, value);
|
|
463
591
|
});
|
|
464
592
|
}
|
|
593
|
+
// Wrap content in a span for reactive content updates
|
|
594
|
+
const content_span = document.createElement(`span`);
|
|
595
|
+
content_span.className = `tooltip-content`;
|
|
465
596
|
// Security: allow_html defaults to true; set to false for plain text rendering
|
|
466
597
|
if (options.allow_html !== false) {
|
|
467
|
-
|
|
598
|
+
let html = content?.replace(/\r/g, `<br/>`) ?? ``;
|
|
599
|
+
if (options.sanitize_html)
|
|
600
|
+
html = options.sanitize_html(html);
|
|
601
|
+
content_span.innerHTML = html;
|
|
468
602
|
}
|
|
469
603
|
else {
|
|
470
|
-
|
|
604
|
+
content_span.textContent = content ?? ``;
|
|
471
605
|
}
|
|
606
|
+
tooltip_el.appendChild(content_span);
|
|
472
607
|
// Mirror CSS custom properties from the trigger node onto the tooltip element
|
|
473
608
|
const trigger_styles = getComputedStyle(element);
|
|
474
609
|
[
|
|
@@ -575,34 +710,7 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
575
710
|
append_border_arrow();
|
|
576
711
|
tooltip_el.appendChild(arrow);
|
|
577
712
|
}
|
|
578
|
-
|
|
579
|
-
const rect = element.getBoundingClientRect();
|
|
580
|
-
const tooltip_rect = tooltip_el.getBoundingClientRect();
|
|
581
|
-
const margin = options.offset ?? 12;
|
|
582
|
-
let top = 0, left = 0;
|
|
583
|
-
if (placement === `top`) {
|
|
584
|
-
top = rect.top - tooltip_rect.height - margin;
|
|
585
|
-
left = rect.left + rect.width / 2 - tooltip_rect.width / 2;
|
|
586
|
-
}
|
|
587
|
-
else if (placement === `left`) {
|
|
588
|
-
top = rect.top + rect.height / 2 - tooltip_rect.height / 2;
|
|
589
|
-
left = rect.left - tooltip_rect.width - margin;
|
|
590
|
-
}
|
|
591
|
-
else if (placement === `right`) {
|
|
592
|
-
top = rect.top + rect.height / 2 - tooltip_rect.height / 2;
|
|
593
|
-
left = rect.right + margin;
|
|
594
|
-
}
|
|
595
|
-
else { // bottom
|
|
596
|
-
top = rect.bottom + margin;
|
|
597
|
-
left = rect.left + rect.width / 2 - tooltip_rect.width / 2;
|
|
598
|
-
}
|
|
599
|
-
// Keep in viewport
|
|
600
|
-
left = Math.max(8, Math.min(left, globalThis.innerWidth - tooltip_rect.width - 8));
|
|
601
|
-
top = Math.max(8, Math.min(top, globalThis.innerHeight - tooltip_rect.height - 8));
|
|
602
|
-
tooltip_el.style.left = `${left + globalThis.scrollX}px`;
|
|
603
|
-
tooltip_el.style.top = `${top + globalThis.scrollY}px`;
|
|
604
|
-
const custom_opacity = trigger_styles.getPropertyValue(`--tooltip-opacity`).trim();
|
|
605
|
-
tooltip_el.style.opacity = custom_opacity || `1`;
|
|
713
|
+
resize_and_position_tooltip(tooltip_el, element);
|
|
606
714
|
current_tooltip = Object.assign(tooltip_el, { _owner: element });
|
|
607
715
|
}, options.delay || 100);
|
|
608
716
|
}
|