svelte-multiselect 11.5.2 → 11.6.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/CmdPalette.svelte.d.ts +2 -2
- package/dist/MultiSelect.svelte +207 -94
- package/dist/MultiSelect.svelte.d.ts +2 -2
- package/dist/Nav.svelte +108 -55
- package/dist/Nav.svelte.d.ts +1 -1
- package/dist/attachments.d.ts +2 -0
- package/dist/attachments.js +4 -1
- package/dist/heading-anchors.js +10 -16
- package/dist/live-examples/highlighter.js +62 -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 +184 -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 +15 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +11 -0
- package/package.json +37 -14
package/dist/Nav.svelte
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
<script lang="ts">import { click_outside, tooltip } from './attachments';
|
|
2
2
|
import Icon from './Icon.svelte';
|
|
3
|
+
import { get_uuid } from './utils';
|
|
3
4
|
let { routes = [], children, item, link, menu_props, link_props, page, labels, tooltips, tooltip_options, breakpoint = 767, 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
|
-
const panel_id = `nav-menu-${
|
|
11
|
+
const panel_id = `nav-menu-${get_uuid()}`;
|
|
10
12
|
// Track previous is_open state for callbacks
|
|
11
13
|
let prev_is_open = $state(false);
|
|
12
14
|
// Detect touch device and handle responsive breakpoint
|
|
@@ -35,19 +37,37 @@ $effect(() => {
|
|
|
35
37
|
function close_menus() {
|
|
36
38
|
is_open = false;
|
|
37
39
|
hovered_dropdown = null;
|
|
40
|
+
pinned_dropdown = null;
|
|
38
41
|
focused_item_index = -1;
|
|
39
42
|
}
|
|
40
43
|
function toggle_dropdown(href, focus_first = false) {
|
|
41
|
-
const is_opening =
|
|
42
|
-
|
|
44
|
+
const is_opening = pinned_dropdown !== href;
|
|
45
|
+
pinned_dropdown = is_opening ? href : null;
|
|
46
|
+
hovered_dropdown = is_opening ? href : null;
|
|
43
47
|
focused_item_index = is_opening && focus_first ? 0 : -1;
|
|
44
48
|
// Focus management for keyboard users
|
|
45
49
|
if (is_opening && focus_first) {
|
|
46
50
|
setTimeout(() => {
|
|
47
|
-
document
|
|
51
|
+
document
|
|
52
|
+
.querySelector(`.dropdown[data-href="${CSS.escape(href)}"] [role="menuitem"]`)
|
|
53
|
+
?.focus();
|
|
48
54
|
}, 0);
|
|
49
55
|
}
|
|
50
56
|
}
|
|
57
|
+
function handle_dropdown_mouseenter(href) {
|
|
58
|
+
if (is_touch_device)
|
|
59
|
+
return;
|
|
60
|
+
const is_this_pinned = pinned_dropdown === href;
|
|
61
|
+
if (pinned_dropdown && !is_this_pinned)
|
|
62
|
+
pinned_dropdown = null;
|
|
63
|
+
hovered_dropdown = href;
|
|
64
|
+
}
|
|
65
|
+
function handle_dropdown_focusin(href) {
|
|
66
|
+
const is_this_pinned = pinned_dropdown === href;
|
|
67
|
+
if (pinned_dropdown && !is_this_pinned)
|
|
68
|
+
pinned_dropdown = null;
|
|
69
|
+
hovered_dropdown = href;
|
|
70
|
+
}
|
|
51
71
|
function onkeydown(event) {
|
|
52
72
|
if (event.key === `Escape`)
|
|
53
73
|
close_menus();
|
|
@@ -59,15 +79,18 @@ function handle_dropdown_keydown(event, href, sub_routes) {
|
|
|
59
79
|
toggle_dropdown(href, true);
|
|
60
80
|
return;
|
|
61
81
|
}
|
|
82
|
+
// Check if dropdown is open (either via hover or pinned)
|
|
83
|
+
const is_open = hovered_dropdown === href || pinned_dropdown === href;
|
|
62
84
|
// Arrow key navigation within open dropdown
|
|
63
|
-
if (
|
|
85
|
+
if (is_open && (key === `ArrowDown` || key === `ArrowUp`)) {
|
|
64
86
|
event.preventDefault();
|
|
65
87
|
const direction = key === `ArrowDown` ? 1 : -1;
|
|
66
88
|
focused_item_index = Math.max(0, Math.min(sub_routes.length - 1, focused_item_index + direction));
|
|
67
|
-
document
|
|
89
|
+
document
|
|
90
|
+
.querySelectorAll(`.dropdown[data-href="${CSS.escape(href)}"] [role="menuitem"]`)?.[focused_item_index]?.focus();
|
|
68
91
|
}
|
|
69
92
|
// Open dropdown with ArrowDown when closed
|
|
70
|
-
if (
|
|
93
|
+
if (!is_open && key === `ArrowDown`) {
|
|
71
94
|
event.preventDefault();
|
|
72
95
|
toggle_dropdown(href, true);
|
|
73
96
|
}
|
|
@@ -76,7 +99,9 @@ function handle_dropdown_item_keydown(event, href) {
|
|
|
76
99
|
if (event.key === `Escape`) {
|
|
77
100
|
event.preventDefault();
|
|
78
101
|
close_menus();
|
|
79
|
-
document
|
|
102
|
+
document
|
|
103
|
+
.querySelector(`.dropdown[data-href="${href}"] [data-dropdown-toggle]`)
|
|
104
|
+
?.focus();
|
|
80
105
|
}
|
|
81
106
|
}
|
|
82
107
|
function is_current(path) {
|
|
@@ -195,7 +220,7 @@ function get_external_attrs(route) {
|
|
|
195
220
|
<button
|
|
196
221
|
class="burger"
|
|
197
222
|
type="button"
|
|
198
|
-
onclick={() => is_open = !is_open}
|
|
223
|
+
onclick={() => (is_open = !is_open)}
|
|
199
224
|
aria-label="Toggle navigation menu"
|
|
200
225
|
aria-expanded={is_open}
|
|
201
226
|
aria-controls={panel_id}
|
|
@@ -222,7 +247,7 @@ function get_external_attrs(route) {
|
|
|
222
247
|
? route
|
|
223
248
|
: Array.isArray(route)
|
|
224
249
|
? route[0]
|
|
225
|
-
: route.href ?? `sep-${route_idx}`
|
|
250
|
+
: (route.href ?? `sep-${route_idx}`)
|
|
226
251
|
}`)
|
|
227
252
|
}
|
|
228
253
|
{@const parsed_route = parse_route(route)}
|
|
@@ -240,7 +265,11 @@ function get_external_attrs(route) {
|
|
|
240
265
|
<!-- Dropdown menu item -->
|
|
241
266
|
{@const child_is_active = is_child_current(sub_routes)}
|
|
242
267
|
{@const parent_page_exists = sub_routes.includes(parsed_route.href)}
|
|
243
|
-
{@const filtered_sub_routes = sub_routes.filter(
|
|
268
|
+
{@const filtered_sub_routes = sub_routes.filter(
|
|
269
|
+
(r) => r !== parsed_route.href,
|
|
270
|
+
)}
|
|
271
|
+
{@const is_pinned = pinned_dropdown === parsed_route.href}
|
|
272
|
+
{@const dropdown_open = hovered_dropdown === parsed_route.href || is_pinned}
|
|
244
273
|
<div
|
|
245
274
|
class="dropdown"
|
|
246
275
|
class:active={child_is_active}
|
|
@@ -248,13 +277,15 @@ function get_external_attrs(route) {
|
|
|
248
277
|
data-href={parsed_route.href}
|
|
249
278
|
role="group"
|
|
250
279
|
aria-current={child_is_active ? `true` : undefined}
|
|
251
|
-
onmouseenter={() =>
|
|
252
|
-
onmouseleave={() =>
|
|
253
|
-
|
|
280
|
+
onmouseenter={() => handle_dropdown_mouseenter(parsed_route.href)}
|
|
281
|
+
onmouseleave={() => {
|
|
282
|
+
if (!is_touch_device && !is_pinned) hovered_dropdown = null
|
|
283
|
+
}}
|
|
284
|
+
onfocusin={() => handle_dropdown_focusin(parsed_route.href)}
|
|
254
285
|
onfocusout={(event) => {
|
|
255
286
|
const next = event.relatedTarget as Node | null
|
|
256
287
|
if (!next || !(event.currentTarget as HTMLElement).contains(next)) {
|
|
257
|
-
hovered_dropdown = null
|
|
288
|
+
if (!is_pinned) hovered_dropdown = null
|
|
258
289
|
}
|
|
259
290
|
}}
|
|
260
291
|
>
|
|
@@ -287,9 +318,11 @@ function get_external_attrs(route) {
|
|
|
287
318
|
{/if}
|
|
288
319
|
<button
|
|
289
320
|
type="button"
|
|
321
|
+
class="dropdown-toggle"
|
|
322
|
+
class:open={dropdown_open}
|
|
290
323
|
data-dropdown-toggle
|
|
291
324
|
aria-label="Toggle {formatted.label} submenu"
|
|
292
|
-
aria-expanded={
|
|
325
|
+
aria-expanded={dropdown_open}
|
|
293
326
|
aria-haspopup="true"
|
|
294
327
|
onclick={() => toggle_dropdown(parsed_route.href, false)}
|
|
295
328
|
onkeydown={(event) =>
|
|
@@ -299,24 +332,15 @@ function get_external_attrs(route) {
|
|
|
299
332
|
filtered_sub_routes,
|
|
300
333
|
)}
|
|
301
334
|
>
|
|
302
|
-
<Icon
|
|
303
|
-
icon="ChevronExpand"
|
|
304
|
-
style="width: 0.8em; height: 0.8em"
|
|
305
|
-
/>
|
|
335
|
+
<Icon icon="ChevronDown" style="width: 0.7em; height: 0.7em" />
|
|
306
336
|
</button>
|
|
307
337
|
</div>
|
|
308
338
|
<div
|
|
309
|
-
class:visible={
|
|
339
|
+
class:visible={dropdown_open}
|
|
310
340
|
role="menu"
|
|
311
341
|
tabindex="-1"
|
|
312
|
-
onmouseenter={() =>
|
|
313
|
-
|
|
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
|
-
}
|
|
342
|
+
onmouseenter={() => {
|
|
343
|
+
if (!is_touch_device) hovered_dropdown = parsed_route.href
|
|
320
344
|
}}
|
|
321
345
|
>
|
|
322
346
|
{#each filtered_sub_routes as child_href (child_href)}
|
|
@@ -384,7 +408,10 @@ function get_external_attrs(route) {
|
|
|
384
408
|
margin: -0.75em auto 1.25em;
|
|
385
409
|
--nav-border-radius: 6pt;
|
|
386
410
|
--nav-surface-bg: light-dark(#fafafa, #1a1a1a);
|
|
387
|
-
--nav-surface-border: light-dark(
|
|
411
|
+
--nav-surface-border: light-dark(
|
|
412
|
+
rgba(128, 128, 128, 0.25),
|
|
413
|
+
rgba(200, 200, 200, 0.2)
|
|
414
|
+
);
|
|
388
415
|
--nav-surface-shadow: light-dark(
|
|
389
416
|
0 2px 8px rgba(0, 0, 0, 0.15),
|
|
390
417
|
0 4px 12px rgba(0, 0, 0, 0.5)
|
|
@@ -398,17 +425,21 @@ function get_external_attrs(route) {
|
|
|
398
425
|
flex-wrap: wrap;
|
|
399
426
|
padding: 0.5em;
|
|
400
427
|
}
|
|
401
|
-
.menu > span
|
|
428
|
+
.menu > span {
|
|
429
|
+
display: flex;
|
|
430
|
+
align-items: center;
|
|
431
|
+
border-radius: var(--nav-border-radius);
|
|
432
|
+
background-color: var(--nav-link-bg);
|
|
433
|
+
transition: background-color 0.2s;
|
|
434
|
+
}
|
|
435
|
+
.menu > span:hover {
|
|
436
|
+
background-color: var(--nav-link-bg-hover, rgba(0, 0, 0, 0.1));
|
|
437
|
+
}
|
|
402
438
|
.menu > span > a {
|
|
403
439
|
line-height: 1.3;
|
|
404
|
-
padding:
|
|
405
|
-
border-radius: var(--nav-border-radius);
|
|
440
|
+
padding: var(--nav-item-padding);
|
|
406
441
|
text-decoration: none;
|
|
407
442
|
color: inherit;
|
|
408
|
-
transition: background-color 0.2s;
|
|
409
|
-
}
|
|
410
|
-
.menu > span > a:hover {
|
|
411
|
-
background-color: var(--nav-link-bg-hover);
|
|
412
443
|
}
|
|
413
444
|
.menu > span > a[aria-current='page'] {
|
|
414
445
|
color: var(--nav-link-active-color);
|
|
@@ -450,24 +481,23 @@ function get_external_attrs(route) {
|
|
|
450
481
|
content: '';
|
|
451
482
|
position: absolute;
|
|
452
483
|
top: 100%;
|
|
453
|
-
left:
|
|
454
|
-
right:
|
|
455
|
-
height: var(--nav-dropdown-margin,
|
|
484
|
+
left: -5pt;
|
|
485
|
+
right: -5pt;
|
|
486
|
+
height: calc(var(--nav-dropdown-margin, 2pt) + 5pt);
|
|
456
487
|
}
|
|
457
488
|
.dropdown > div:first-child {
|
|
458
489
|
display: flex;
|
|
459
490
|
align-items: center;
|
|
460
|
-
gap: 0;
|
|
461
491
|
border-radius: var(--nav-border-radius);
|
|
492
|
+
background-color: var(--nav-link-bg);
|
|
462
493
|
transition: background-color 0.2s;
|
|
463
494
|
}
|
|
464
495
|
.dropdown > div:first-child:hover {
|
|
465
|
-
background-color: var(--nav-link-bg-hover);
|
|
496
|
+
background-color: var(--nav-link-bg-hover, rgba(0, 0, 0, 0.1));
|
|
466
497
|
}
|
|
467
|
-
.dropdown > div:first-child > a,
|
|
468
|
-
.dropdown > div:first-child > span {
|
|
498
|
+
.dropdown > div:first-child > a, .dropdown > div:first-child > span {
|
|
469
499
|
line-height: 1.3;
|
|
470
|
-
padding:
|
|
500
|
+
padding: var(--nav-item-padding);
|
|
471
501
|
text-decoration: none;
|
|
472
502
|
color: inherit;
|
|
473
503
|
border-radius: var(--nav-border-radius) 0 0 var(--nav-border-radius);
|
|
@@ -476,7 +506,7 @@ function get_external_attrs(route) {
|
|
|
476
506
|
color: var(--nav-link-active-color);
|
|
477
507
|
}
|
|
478
508
|
.dropdown > div:first-child > button {
|
|
479
|
-
padding:
|
|
509
|
+
padding: 2pt 4pt;
|
|
480
510
|
border: none;
|
|
481
511
|
background: transparent;
|
|
482
512
|
color: inherit;
|
|
@@ -486,40 +516,49 @@ function get_external_attrs(route) {
|
|
|
486
516
|
justify-content: center;
|
|
487
517
|
border-radius: 0 var(--nav-border-radius) var(--nav-border-radius) 0;
|
|
488
518
|
outline-offset: -1px;
|
|
519
|
+
opacity: 0.6;
|
|
520
|
+
transition: opacity 0.15s, transform 0.2s ease;
|
|
521
|
+
}
|
|
522
|
+
.dropdown > div:first-child > button:hover {
|
|
523
|
+
opacity: 1;
|
|
524
|
+
}
|
|
525
|
+
.dropdown > div:first-child > button.open {
|
|
526
|
+
opacity: 1;
|
|
527
|
+
transform: rotate(180deg);
|
|
489
528
|
}
|
|
490
529
|
.dropdown > div:first-child > button:focus-visible {
|
|
491
530
|
outline: 2px solid currentColor;
|
|
492
531
|
outline-offset: -2px;
|
|
532
|
+
opacity: 1;
|
|
493
533
|
}
|
|
494
534
|
.dropdown > div:last-child {
|
|
495
535
|
position: absolute;
|
|
496
536
|
top: 100%;
|
|
497
537
|
left: 0;
|
|
498
|
-
margin: var(--nav-dropdown-margin,
|
|
538
|
+
margin: var(--nav-dropdown-margin, 2pt) 0 0 0;
|
|
499
539
|
min-width: max-content;
|
|
500
540
|
background-color: var(--nav-dropdown-bg, var(--nav-surface-bg));
|
|
501
541
|
border: 1px solid var(--nav-dropdown-border-color, var(--nav-surface-border));
|
|
502
542
|
border-radius: var(--nav-border-radius, 6pt);
|
|
503
543
|
box-shadow: var(--nav-dropdown-shadow, var(--nav-surface-shadow));
|
|
504
|
-
padding: var(--nav-dropdown-padding,
|
|
544
|
+
padding: var(--nav-dropdown-padding, 3pt 0);
|
|
505
545
|
display: none;
|
|
506
546
|
flex-direction: column;
|
|
507
|
-
gap: var(--nav-dropdown-gap, 5pt);
|
|
508
547
|
z-index: var(--nav-dropdown-z-index, 100);
|
|
509
548
|
}
|
|
510
549
|
.dropdown > div:last-child.visible {
|
|
511
550
|
display: flex;
|
|
512
551
|
}
|
|
513
552
|
.dropdown > div:last-child a {
|
|
514
|
-
padding: var(--nav-dropdown-link-padding,
|
|
515
|
-
border-radius: var(--nav-border-radius);
|
|
553
|
+
padding: var(--nav-dropdown-link-padding, 2pt 6pt);
|
|
516
554
|
text-decoration: none;
|
|
517
555
|
color: inherit;
|
|
518
556
|
white-space: nowrap;
|
|
519
|
-
|
|
557
|
+
font-size: 0.92em;
|
|
558
|
+
transition: background-color 0.15s;
|
|
520
559
|
}
|
|
521
560
|
.dropdown > div:last-child a:hover {
|
|
522
|
-
background-color: var(--nav-link-bg-hover);
|
|
561
|
+
background-color: var(--nav-link-bg-hover, rgba(0, 0, 0, 0.1));
|
|
523
562
|
}
|
|
524
563
|
.dropdown > div:last-child a[aria-current='page'] {
|
|
525
564
|
color: var(--nav-link-active-color);
|
|
@@ -610,15 +649,29 @@ function get_external_attrs(route) {
|
|
|
610
649
|
nav.mobile .dropdown > div:first-child > button {
|
|
611
650
|
padding: 4pt 8pt;
|
|
612
651
|
border-radius: var(--nav-border-radius);
|
|
652
|
+
opacity: 0.6;
|
|
653
|
+
}
|
|
654
|
+
nav.mobile .dropdown > div:first-child > button.open {
|
|
655
|
+
opacity: 1;
|
|
613
656
|
}
|
|
614
657
|
nav.mobile .dropdown > div:last-child {
|
|
615
658
|
position: static;
|
|
616
659
|
border: none;
|
|
617
660
|
box-shadow: none;
|
|
618
|
-
margin-top:
|
|
619
|
-
padding: 0
|
|
661
|
+
margin-top: 2pt;
|
|
662
|
+
padding: 0;
|
|
620
663
|
background-color: transparent;
|
|
621
664
|
}
|
|
665
|
+
nav.mobile .dropdown > div:last-child a {
|
|
666
|
+
padding: 4pt 8pt 4pt 6pt;
|
|
667
|
+
margin-left: 8pt;
|
|
668
|
+
border-left: 2px solid transparent;
|
|
669
|
+
font-size: 0.9em;
|
|
670
|
+
}
|
|
671
|
+
nav.mobile .dropdown > div:last-child a:hover,
|
|
672
|
+
nav.mobile .dropdown > div:last-child a[aria-current='page'] {
|
|
673
|
+
border-left-color: var(--nav-link-active-color, currentColor);
|
|
674
|
+
}
|
|
622
675
|
/* Mobile right-aligned items stack normally */
|
|
623
676
|
nav.mobile .menu > .align-right,
|
|
624
677
|
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;
|
package/dist/attachments.d.ts
CHANGED
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
|
|
@@ -440,7 +443,7 @@ export const tooltip = (options = {}) => (node) => {
|
|
|
440
443
|
const placement = options.placement || `bottom`;
|
|
441
444
|
tooltip_el.setAttribute(`data-placement`, placement);
|
|
442
445
|
// Accessibility: link tooltip to trigger element
|
|
443
|
-
const tooltip_id = `tooltip-${
|
|
446
|
+
const tooltip_id = `tooltip-${get_uuid()}`;
|
|
444
447
|
tooltip_el.id = tooltip_id;
|
|
445
448
|
tooltip_el.setAttribute(`role`, `tooltip`);
|
|
446
449
|
element.setAttribute(`aria-describedby`, tooltip_id);
|
package/dist/heading-anchors.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
// Avoid matching inside src={...} attributes by requiring these specific contexts
|
|
7
7
|
// Note: [^>]* for attributes won't match if an attribute value contains > (e.g., data-foo="a>b")
|
|
8
8
|
// This edge case is rare in practice and would require significantly more complex parsing
|
|
9
|
-
const heading_regex_line_start = /^(\s*)<(h[
|
|
10
|
-
const heading_regex_after_tag = /(>)(\s*)<(h[
|
|
9
|
+
const heading_regex_line_start = /^(\s*)<(h[1-6])([^>]*)>([\s\S]*?)<\/\2>/gim;
|
|
10
|
+
const heading_regex_after_tag = /(>)(\s*)<(h[1-6])([^>]*)>([\s\S]*?)<\/\3>/gi;
|
|
11
11
|
// Remove Svelte expressions handling nested braces (e.g., {fn({a: 1})})
|
|
12
12
|
// Treats unmatched } as literal text to avoid dropping content
|
|
13
13
|
function strip_svelte_expressions(str) {
|
|
@@ -94,25 +94,19 @@ function add_anchor_to_heading(heading, icon_svg = link_svg) {
|
|
|
94
94
|
export const heading_anchors = (options = {}) => (node) => {
|
|
95
95
|
if (typeof document === `undefined`)
|
|
96
96
|
return;
|
|
97
|
-
|
|
97
|
+
// :scope refers to the element on which querySelectorAll is called
|
|
98
|
+
// This works whether the attachment is on <main> or a parent element
|
|
99
|
+
const selector = options.selector ??
|
|
100
|
+
`:scope > :is(h1, h2, h3, h4, h5, h6), :scope > * > :is(h1, h2, h3, h4, h5, h6)`;
|
|
98
101
|
const icon_svg = options.icon_svg ?? link_svg;
|
|
99
102
|
// Process existing headings
|
|
100
103
|
for (const heading of Array.from(node.querySelectorAll(selector))) {
|
|
101
104
|
add_anchor_to_heading(heading, icon_svg);
|
|
102
105
|
}
|
|
103
|
-
// Watch for new headings
|
|
104
|
-
const observer = new MutationObserver((
|
|
105
|
-
for (const
|
|
106
|
-
|
|
107
|
-
if (added.nodeType !== Node.ELEMENT_NODE)
|
|
108
|
-
continue;
|
|
109
|
-
const el = added;
|
|
110
|
-
if (el.matches?.(selector))
|
|
111
|
-
add_anchor_to_heading(el, icon_svg);
|
|
112
|
-
for (const hdn of Array.from(el.querySelectorAll(selector))) {
|
|
113
|
-
add_anchor_to_heading(hdn, icon_svg);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
106
|
+
// Watch for new headings - requery the container to respect nesting depth constraints
|
|
107
|
+
const observer = new MutationObserver(() => {
|
|
108
|
+
for (const heading of Array.from(node.querySelectorAll(selector))) {
|
|
109
|
+
add_anchor_to_heading(heading, icon_svg);
|
|
116
110
|
}
|
|
117
111
|
});
|
|
118
112
|
observer.observe(node, { childList: true, subtree: true });
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Starry-night highlighter for mdsvex
|
|
2
|
+
import { createStarryNight } from '@wooorm/starry-night';
|
|
3
|
+
import source_css from '@wooorm/starry-night/source.css';
|
|
4
|
+
import source_js from '@wooorm/starry-night/source.js';
|
|
5
|
+
import source_json from '@wooorm/starry-night/source.json';
|
|
6
|
+
import source_shell from '@wooorm/starry-night/source.shell';
|
|
7
|
+
import source_svelte from '@wooorm/starry-night/source.svelte';
|
|
8
|
+
import source_ts from '@wooorm/starry-night/source.ts';
|
|
9
|
+
import text_html_basic from '@wooorm/starry-night/text.html.basic';
|
|
10
|
+
// Escape HTML special characters in text content (not for attribute values)
|
|
11
|
+
const escape_html_text = (str) => str.replace(/&/g, `&`).replace(/</g, `<`).replace(/>/g, `>`);
|
|
12
|
+
// Convert HAST to HTML string (simplified - only handles what starry-night outputs)
|
|
13
|
+
export const hast_to_html = (node) => {
|
|
14
|
+
if (node.type === `text`)
|
|
15
|
+
return escape_html_text(node.value);
|
|
16
|
+
if (node.type === `root`)
|
|
17
|
+
return node.children?.map(hast_to_html).join(``) ?? ``;
|
|
18
|
+
const { tagName, properties, children } = node;
|
|
19
|
+
const cls = properties?.className?.join(` `);
|
|
20
|
+
const attrs = cls ? ` class="${cls}"` : ``;
|
|
21
|
+
const inner = children?.map(hast_to_html).join(``) ?? ``;
|
|
22
|
+
return `<${tagName}${attrs}>${inner}</${tagName}>`;
|
|
23
|
+
};
|
|
24
|
+
// Shared starry-night instance (grammars loaded once at build time)
|
|
25
|
+
export const starry_night = await createStarryNight([
|
|
26
|
+
source_svelte,
|
|
27
|
+
source_js,
|
|
28
|
+
source_ts,
|
|
29
|
+
source_css,
|
|
30
|
+
source_json,
|
|
31
|
+
source_shell,
|
|
32
|
+
text_html_basic,
|
|
33
|
+
]);
|
|
34
|
+
// Map code fence language to starry-night grammar scope
|
|
35
|
+
export const LANG_TO_SCOPE = {
|
|
36
|
+
svelte: `source.svelte`,
|
|
37
|
+
html: `text.html.basic`,
|
|
38
|
+
ts: `source.ts`,
|
|
39
|
+
typescript: `source.ts`,
|
|
40
|
+
js: `source.js`,
|
|
41
|
+
javascript: `source.js`,
|
|
42
|
+
css: `source.css`,
|
|
43
|
+
json: `source.json`,
|
|
44
|
+
shell: `source.shell`,
|
|
45
|
+
bash: `source.shell`,
|
|
46
|
+
sh: `source.shell`,
|
|
47
|
+
};
|
|
48
|
+
// Escape characters that would be interpreted as Svelte template syntax
|
|
49
|
+
const escape_svelte = (html) => html.replace(/\{/g, `{`).replace(/\}/g, `}`);
|
|
50
|
+
// mdsvex highlighter function
|
|
51
|
+
export function starry_night_highlighter(code, lang) {
|
|
52
|
+
const lang_key = lang?.toLowerCase();
|
|
53
|
+
const scope = lang_key ? LANG_TO_SCOPE[lang_key] : undefined;
|
|
54
|
+
if (!scope) {
|
|
55
|
+
// Return escaped code if language not supported
|
|
56
|
+
const escaped = escape_svelte(escape_html_text(code));
|
|
57
|
+
return `<pre class="highlight"><code>${escaped}</code></pre>`;
|
|
58
|
+
}
|
|
59
|
+
const tree = starry_night.highlight(code, scope);
|
|
60
|
+
const html = escape_svelte(hast_to_html(tree));
|
|
61
|
+
return `<pre class="highlight highlight-${lang_key}"><code>${html}</code></pre>`;
|
|
62
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { starry_night_highlighter } from './highlighter.js';
|
|
2
|
+
export { default as mdsvex_transform, EXAMPLE_COMPONENT_PREFIX, EXAMPLE_MODULE_PREFIX, } from './mdsvex-transform.js';
|
|
3
|
+
export { default as vite_plugin } from './vite-plugin.js';
|
|
4
|
+
import { sveltePreprocess as _sveltePreprocess } from 'svelte-preprocess';
|
|
5
|
+
type SveltePreprocessOptions = Parameters<typeof _sveltePreprocess>[0];
|
|
6
|
+
type PreprocessorGroup = ReturnType<typeof _sveltePreprocess>;
|
|
7
|
+
export declare function sveltePreprocess(opts?: SveltePreprocessOptions): PreprocessorGroup;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Live examples - transforms ```svelte example code blocks into rendered components
|
|
2
|
+
// with syntax highlighting and live preview
|
|
3
|
+
export { starry_night_highlighter } from './highlighter.js';
|
|
4
|
+
export { default as mdsvex_transform, EXAMPLE_COMPONENT_PREFIX, EXAMPLE_MODULE_PREFIX, } from './mdsvex-transform.js';
|
|
5
|
+
export { default as vite_plugin } from './vite-plugin.js';
|
|
6
|
+
import { sveltePreprocess as _sveltePreprocess } from 'svelte-preprocess';
|
|
7
|
+
// Wrap sveltePreprocess to skip markdown files - otherwise it transpiles code inside
|
|
8
|
+
// markdown code fences, losing whitespace formatting
|
|
9
|
+
const is_markdown = (filename) => /\.(md|mdx|svx)$/.test(filename ?? ``);
|
|
10
|
+
export function sveltePreprocess(opts) {
|
|
11
|
+
const base = _sveltePreprocess(opts);
|
|
12
|
+
return {
|
|
13
|
+
markup: async (args) => is_markdown(args.filename)
|
|
14
|
+
? { code: args.content }
|
|
15
|
+
: (await base.markup?.(args)) ?? { code: args.content },
|
|
16
|
+
script: async (args) => is_markdown(args.filename)
|
|
17
|
+
? { code: args.content }
|
|
18
|
+
: (await base.script?.(args)) ?? { code: args.content },
|
|
19
|
+
style: async (args) => is_markdown(args.filename)
|
|
20
|
+
? { code: args.content }
|
|
21
|
+
: (await base.style?.(args)) ?? { code: args.content },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const EXAMPLE_MODULE_PREFIX = "___live_example___";
|
|
2
|
+
export declare const EXAMPLE_COMPONENT_PREFIX = "LiveExample___";
|
|
3
|
+
interface RemarkMeta {
|
|
4
|
+
Wrapper?: string | [string, string];
|
|
5
|
+
filename?: string;
|
|
6
|
+
csr?: boolean;
|
|
7
|
+
example?: boolean;
|
|
8
|
+
hideScript?: boolean;
|
|
9
|
+
hideStyle?: boolean;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
interface RemarkOptions {
|
|
13
|
+
defaults?: Partial<RemarkMeta>;
|
|
14
|
+
}
|
|
15
|
+
interface RemarkTree {
|
|
16
|
+
type: string;
|
|
17
|
+
children: RemarkNode[];
|
|
18
|
+
}
|
|
19
|
+
interface RemarkNode {
|
|
20
|
+
type: string;
|
|
21
|
+
lang?: string;
|
|
22
|
+
meta?: string;
|
|
23
|
+
value?: string;
|
|
24
|
+
children?: RemarkNode[];
|
|
25
|
+
}
|
|
26
|
+
interface RemarkFile {
|
|
27
|
+
filename: string;
|
|
28
|
+
cwd: string;
|
|
29
|
+
}
|
|
30
|
+
type RemarkTransformer = (tree: RemarkTree, file: RemarkFile) => void;
|
|
31
|
+
declare function remark(options?: RemarkOptions): RemarkTransformer;
|
|
32
|
+
export default remark;
|