@andesine/ui 0.4.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.
@@ -0,0 +1,1354 @@
1
+ import { template as _$template } from "solid-js/web";
2
+ import { use as _$use } from "solid-js/web";
3
+ import { memo as _$memo } from "solid-js/web";
4
+ import { createComponent as _$createComponent } from "solid-js/web";
5
+ import { getNextElement as _$getNextElement } from "solid-js/web";
6
+ import { runHydrationEvents as _$runHydrationEvents } from "solid-js/web";
7
+ import { spread as _$spread } from "solid-js/web";
8
+ import { mergeProps as _$mergeProps } from "solid-js/web";
9
+ var _tmpl$ = _$template(`<nav aria-label=Documentation>`),
10
+ _tmpl$2 = _$template(`<ul>`),
11
+ _tmpl$3 = _$template(`<li>`),
12
+ _tmpl$4 = _$template(`<a>`),
13
+ _tmpl$5 = _$template(`<button type=button>`),
14
+ _tmpl$6 = _$template(`<div>`),
15
+ _tmpl$7 = _$template(`<nav aria-label="On this page">`),
16
+ _tmpl$8 = _$template(`<details>`),
17
+ _tmpl$9 = _$template(`<summary>`),
18
+ _tmpl$0 = _$template(`<nav aria-label=Breadcrumbs>`),
19
+ _tmpl$1 = _$template(`<ol>`),
20
+ _tmpl$10 = _$template(`<span>`),
21
+ _tmpl$11 = _$template(`<span aria-hidden=true>`),
22
+ _tmpl$12 = _$template(`<nav aria-label=Pages>`);
23
+ import { createContext, createEffect, createSignal, createUniqueId, on, onCleanup, onMount, splitProps, useContext } from "solid-js";
24
+ import { isServer } from "solid-js/web";
25
+ import { Tabs as Tabs$1 } from "@ark-ui/solid/tabs";
26
+ const readSession = key => {
27
+ try {
28
+ return JSON.parse(sessionStorage.getItem(key) ?? "null") ?? void 0;
29
+ } catch {
30
+ return;
31
+ }
32
+ };
33
+ const writeSession = (key, value) => {
34
+ try {
35
+ sessionStorage.setItem(key, JSON.stringify(value));
36
+ } catch {}
37
+ };
38
+ const syncEvent$1 = "andesine:navigation-tree";
39
+ const getActiveGroups = items => {
40
+ return items.flatMap(item => {
41
+ return item.children?.length && item.active ? [item.id, ...getActiveGroups(item.children)] : [];
42
+ });
43
+ };
44
+ const getDefaultGroups = (items, depth) => {
45
+ return depth > 0 ? items.flatMap(item => {
46
+ return item.children?.length ? [item.id, ...getDefaultGroups(item.children, depth - 1)] : [];
47
+ }) : [];
48
+ };
49
+ const getInitialGroups = options => {
50
+ const items = options.items();
51
+ const saved = !isServer && options.storageKey ? readSession(options.storageKey) : void 0;
52
+ const base = Array.isArray(saved) ? saved.map(String) : getDefaultGroups(items, options.defaultDepth ?? 0);
53
+ return new Set([...base, ...getActiveGroups(items)]);
54
+ };
55
+ const createNavigationTree = options => {
56
+ const [expanded, setExpanded] = createSignal(getInitialGroups(options));
57
+ const save = ids => {
58
+ if (!options.storageKey) return;
59
+ writeSession(options.storageKey, [...ids]);
60
+ window.dispatchEvent(new CustomEvent(syncEvent$1, {
61
+ detail: {
62
+ key: options.storageKey,
63
+ ids: [...ids]
64
+ }
65
+ }));
66
+ };
67
+ onMount(() => {
68
+ if (!options.storageKey) return;
69
+ const sync = event => {
70
+ const {
71
+ key,
72
+ ids
73
+ } = event.detail;
74
+ if (key === options.storageKey) setExpanded(new Set(ids));
75
+ };
76
+ writeSession(options.storageKey, [...expanded()]);
77
+ window.addEventListener(syncEvent$1, sync);
78
+ onCleanup(() => window.removeEventListener(syncEvent$1, sync));
79
+ });
80
+ return {
81
+ isExpanded: id => expanded().has(id),
82
+ setExpanded: (id, open) => {
83
+ const ids = new Set(expanded());
84
+ if (open) ids.add(id);else ids.delete(id);
85
+ setExpanded(ids);
86
+ save(ids);
87
+ }
88
+ };
89
+ };
90
+ const TreeContext = createContext();
91
+ const ItemContext = createContext();
92
+ const useTree = () => {
93
+ const tree = useContext(TreeContext);
94
+ if (!tree) throw new Error("NavigationTree parts must be inside NavigationTree.Root.");
95
+ return tree;
96
+ };
97
+ const useItem = () => {
98
+ const item = useContext(ItemContext);
99
+ if (!item) throw new Error("NavigationTree item parts must be inside NavigationTree.Item.");
100
+ return item;
101
+ };
102
+ const Root$5 = props => {
103
+ const [local, rest] = splitProps(props, ["items", "storageKey", "defaultDepth"]);
104
+ const tree = createNavigationTree({
105
+ items: () => local.items,
106
+ storageKey: local.storageKey,
107
+ defaultDepth: local.defaultDepth
108
+ });
109
+ return _$createComponent(TreeContext.Provider, {
110
+ value: tree,
111
+ get children() {
112
+ var _el$ = _$getNextElement(_tmpl$);
113
+ _$spread(_el$, _$mergeProps(rest, {
114
+ "data-scope": "navigation-tree",
115
+ "data-part": "root",
116
+ get ["data-storage-key"]() {
117
+ return local.storageKey;
118
+ }
119
+ }), false, false);
120
+ _$runHydrationEvents();
121
+ return _el$;
122
+ }
123
+ });
124
+ };
125
+ const List$2 = props => {
126
+ return (() => {
127
+ var _el$2 = _$getNextElement(_tmpl$2);
128
+ _$spread(_el$2, _$mergeProps(props, {
129
+ "data-scope": "navigation-tree",
130
+ "data-part": "list"
131
+ }), false, false);
132
+ _$runHydrationEvents();
133
+ return _el$2;
134
+ })();
135
+ };
136
+ const Item$2 = props => {
137
+ const [local, rest] = splitProps(props, ["item"]);
138
+ const tree = useTree();
139
+ const contentID = createUniqueId();
140
+ const isGroup = () => Boolean(local.item.children?.length);
141
+ return _$createComponent(ItemContext.Provider, {
142
+ get value() {
143
+ return {
144
+ item: local.item,
145
+ contentID
146
+ };
147
+ },
148
+ get children() {
149
+ var _el$3 = _$getNextElement(_tmpl$3);
150
+ _$spread(_el$3, _$mergeProps(rest, {
151
+ "data-scope": "navigation-tree",
152
+ "data-part": "item",
153
+ get ["data-id"]() {
154
+ return local.item.id;
155
+ },
156
+ get ["data-current"]() {
157
+ return local.item.current || void 0;
158
+ },
159
+ get ["data-active"]() {
160
+ return local.item.active || void 0;
161
+ },
162
+ get ["data-state"]() {
163
+ return _$memo(() => !!isGroup())() ? tree.isExpanded(local.item.id) ? "open" : "closed" : void 0;
164
+ }
165
+ }), false, false);
166
+ _$runHydrationEvents();
167
+ return _el$3;
168
+ }
169
+ });
170
+ };
171
+ const Link$3 = props => {
172
+ const {
173
+ item
174
+ } = useItem();
175
+ return (() => {
176
+ var _el$4 = _$getNextElement(_tmpl$4);
177
+ _$spread(_el$4, _$mergeProps({
178
+ get href() {
179
+ return item.href;
180
+ },
181
+ get ["aria-current"]() {
182
+ return item.current ? "page" : void 0;
183
+ }
184
+ }, props, {
185
+ "data-scope": "navigation-tree",
186
+ "data-part": "link"
187
+ }), false, false);
188
+ _$runHydrationEvents();
189
+ return _el$4;
190
+ })();
191
+ };
192
+ const GroupTrigger = props => {
193
+ const tree = useTree();
194
+ const {
195
+ item,
196
+ contentID
197
+ } = useItem();
198
+ const expanded = () => tree.isExpanded(item.id);
199
+ return (() => {
200
+ var _el$5 = _$getNextElement(_tmpl$5);
201
+ _$spread(_el$5, _$mergeProps(props, {
202
+ get ["aria-expanded"]() {
203
+ return expanded();
204
+ },
205
+ "aria-controls": contentID,
206
+ "data-scope": "navigation-tree",
207
+ "data-part": "group-trigger",
208
+ get ["data-state"]() {
209
+ return expanded() ? "open" : "closed";
210
+ },
211
+ "onClick": () => tree.setExpanded(item.id, !expanded())
212
+ }), false, false);
213
+ _$runHydrationEvents();
214
+ return _el$5;
215
+ })();
216
+ };
217
+ const GroupContent = props => {
218
+ const tree = useTree();
219
+ const {
220
+ item,
221
+ contentID
222
+ } = useItem();
223
+ const expanded = () => tree.isExpanded(item.id);
224
+ return (() => {
225
+ var _el$6 = _$getNextElement(_tmpl$6);
226
+ _$spread(_el$6, _$mergeProps(props, {
227
+ "id": contentID,
228
+ get hidden() {
229
+ return !expanded();
230
+ },
231
+ "data-scope": "navigation-tree",
232
+ "data-part": "group-content",
233
+ get ["data-state"]() {
234
+ return expanded() ? "open" : "closed";
235
+ }
236
+ }), false, false);
237
+ _$runHydrationEvents();
238
+ return _el$6;
239
+ })();
240
+ };
241
+ const NavigationTree = {
242
+ Root: Root$5,
243
+ List: List$2,
244
+ Item: Item$2,
245
+ Link: Link$3,
246
+ GroupTrigger,
247
+ GroupContent
248
+ };
249
+ const focusableSelector = "a[href], button, input, select, textarea, summary, [tabindex]";
250
+ const focusTarget = target => {
251
+ const temporary = !target.matches(focusableSelector);
252
+ if (temporary) target.setAttribute("tabindex", "-1");
253
+ target.focus({
254
+ preventScroll: true
255
+ });
256
+ if (temporary) target.addEventListener("blur", () => target.removeAttribute("tabindex"), {
257
+ once: true
258
+ });
259
+ };
260
+ const getFocusable = root => {
261
+ return Array.from(root.querySelectorAll(focusableSelector)).filter(element => {
262
+ return element.tabIndex >= 0 && !element.matches(":disabled, [aria-disabled='true']") && !element.closest("[inert], [hidden]") && element.getClientRects().length > 0;
263
+ });
264
+ };
265
+ const getScrollOffset = () => {
266
+ return parseFloat(getComputedStyle(document.documentElement).scrollPaddingTop) || 0;
267
+ };
268
+ const scrollIntoContainer = (container, element, margin = 8) => {
269
+ const bounds = container.getBoundingClientRect();
270
+ const target = element.getBoundingClientRect();
271
+ if (target.top < bounds.top + margin) container.scrollTop += target.top - bounds.top - margin;
272
+ if (target.bottom > bounds.bottom - margin) container.scrollTop += target.bottom - bounds.bottom + margin;
273
+ };
274
+ const scrollKeys = new Set(["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " "]);
275
+ const intentEvents = ["wheel", "touchmove", "keydown", "pointerdown"];
276
+ const createTableOfContents = options => {
277
+ const [activeID, setActiveID] = createSignal(options.headingIDs()[0]);
278
+ const [visibleIDs, setVisibleIDs] = createSignal([]);
279
+ let locked = false;
280
+ let userScroll = false;
281
+ onMount(() => {
282
+ let headings = [];
283
+ let frame = 0;
284
+ const update = () => {
285
+ const offset = getScrollOffset() + 1;
286
+ const atBottom = window.scrollY > 0 && window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 2;
287
+ const tops = headings.map(heading => heading.getBoundingClientRect().top);
288
+ const passed = headings.filter((_, index) => tops[index] <= offset);
289
+ const visible = headings.filter((heading, index) => {
290
+ const end = tops[index + 1] ?? heading.parentElement.getBoundingClientRect().bottom;
291
+ return tops[index] < window.innerHeight && end > offset;
292
+ });
293
+ frame = 0;
294
+ setVisibleIDs(visible.map(heading => heading.id));
295
+ if (locked) return;
296
+ setActiveID((atBottom ? headings.at(-1) : passed.at(-1) ?? headings[0])?.id);
297
+ };
298
+ const schedule = () => {
299
+ frame ||= requestAnimationFrame(update);
300
+ };
301
+ const onIntent = event => {
302
+ if (!(event instanceof KeyboardEvent) || scrollKeys.has(event.key)) userScroll = true;
303
+ };
304
+ const onScroll = () => {
305
+ if (userScroll) locked = false;
306
+ userScroll = false;
307
+ schedule();
308
+ };
309
+ const observer = new ResizeObserver(schedule);
310
+ createEffect(() => {
311
+ headings = options.headingIDs().flatMap(id => document.getElementById(id) ?? []);
312
+ schedule();
313
+ });
314
+ observer.observe(document.body);
315
+ intentEvents.forEach(name => window.addEventListener(name, onIntent, {
316
+ passive: true
317
+ }));
318
+ window.addEventListener("scroll", onScroll, {
319
+ passive: true
320
+ });
321
+ window.addEventListener("hashchange", schedule);
322
+ onCleanup(() => {
323
+ cancelAnimationFrame(frame);
324
+ observer.disconnect();
325
+ intentEvents.forEach(name => window.removeEventListener(name, onIntent));
326
+ window.removeEventListener("scroll", onScroll);
327
+ window.removeEventListener("hashchange", schedule);
328
+ });
329
+ });
330
+ return {
331
+ activeID,
332
+ visibleIDs,
333
+ select: id => {
334
+ locked = true;
335
+ userScroll = false;
336
+ setActiveID(id);
337
+ }
338
+ };
339
+ };
340
+ const Context = createContext();
341
+ const useTableOfContents = () => {
342
+ const state = useContext(Context);
343
+ if (!state) throw new Error("TableOfContents parts must be inside TableOfContents.Root.");
344
+ return state;
345
+ };
346
+ const Root$4 = props => {
347
+ const [local, rest] = splitProps(props, ["headingIDs"]);
348
+ const state = createTableOfContents({
349
+ headingIDs: () => local.headingIDs
350
+ });
351
+ return _$createComponent(Context.Provider, {
352
+ value: state,
353
+ get children() {
354
+ var _el$7 = _$getNextElement(_tmpl$7);
355
+ _$spread(_el$7, _$mergeProps(rest, {
356
+ "data-scope": "table-of-contents",
357
+ "data-part": "root"
358
+ }), false, false);
359
+ _$runHydrationEvents();
360
+ return _el$7;
361
+ }
362
+ });
363
+ };
364
+ const List$1 = props => {
365
+ return (() => {
366
+ var _el$8 = _$getNextElement(_tmpl$2);
367
+ _$spread(_el$8, _$mergeProps(props, {
368
+ "data-scope": "table-of-contents",
369
+ "data-part": "list"
370
+ }), false, false);
371
+ _$runHydrationEvents();
372
+ return _el$8;
373
+ })();
374
+ };
375
+ const Item$1 = props => {
376
+ return (() => {
377
+ var _el$9 = _$getNextElement(_tmpl$3);
378
+ _$spread(_el$9, _$mergeProps(props, {
379
+ "data-scope": "table-of-contents",
380
+ "data-part": "item"
381
+ }), false, false);
382
+ _$runHydrationEvents();
383
+ return _el$9;
384
+ })();
385
+ };
386
+ const Link$2 = props => {
387
+ const [local, rest] = splitProps(props, ["headingID"]);
388
+ const state = useTableOfContents();
389
+ const onClick = event => {
390
+ if (!(event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey)) return;
391
+ state.select(local.headingID);
392
+ requestAnimationFrame(() => {
393
+ const heading = document.getElementById(local.headingID);
394
+ if (heading) focusTarget(heading);
395
+ });
396
+ };
397
+ return (() => {
398
+ var _el$0 = _$getNextElement(_tmpl$4);
399
+ _$spread(_el$0, _$mergeProps(rest, {
400
+ get href() {
401
+ return `#${encodeURIComponent(local.headingID)}`;
402
+ },
403
+ get ["aria-current"]() {
404
+ return state.activeID() === local.headingID ? "location" : void 0;
405
+ },
406
+ get ["data-visible"]() {
407
+ return state.visibleIDs().includes(local.headingID) || void 0;
408
+ },
409
+ "data-scope": "table-of-contents",
410
+ "data-part": "link",
411
+ "onClick": onClick
412
+ }), false, false);
413
+ _$runHydrationEvents();
414
+ return _el$0;
415
+ })();
416
+ };
417
+ const TableOfContents = {
418
+ Root: Root$4,
419
+ List: List$1,
420
+ Item: Item$1,
421
+ Link: Link$2
422
+ };
423
+ const createTableOfContentsGuide = options => {
424
+ const [shape, setShape] = createSignal();
425
+ const [stroke, setStroke] = createSignal();
426
+ const links = new Map();
427
+ const inset = options.inset ?? 6;
428
+ const radius = options.radius ?? 8;
429
+ const edgePoints = options.edgePoints ?? 16;
430
+ let track;
431
+ let marker;
432
+ onMount(() => {
433
+ let frame = 0;
434
+ const measure = () => {
435
+ const bounds = track?.getBoundingClientRect();
436
+ const segments = options.ids().flatMap(id => {
437
+ const rect = links.get(id)?.getBoundingClientRect();
438
+ const x = options.position(id);
439
+ return rect && bounds ? [{
440
+ id,
441
+ x,
442
+ top: rect.top - bounds.top,
443
+ bottom: rect.bottom - bounds.top
444
+ }] : [];
445
+ });
446
+ const highlighted = segments.filter(segment => {
447
+ return options.highlightedIDs().includes(segment.id);
448
+ });
449
+ const first = highlighted[0];
450
+ const last = highlighted.at(-1);
451
+ const width = bounds?.width ?? 0;
452
+ const path = segments.map((segment, index) => {
453
+ const previous = segments[index - 1];
454
+ const line = `L ${segment.x} ${segment.bottom - inset}`;
455
+ if (!previous) return `M ${segment.x} ${segment.top + inset} ${line}`;
456
+ if (previous.x === segment.x) return line;
457
+ const middle = (previous.bottom + segment.top) / 2;
458
+ return `C ${previous.x} ${middle} ${segment.x} ${middle} ${segment.x} ${segment.top + inset} ${line}`;
459
+ }).join(" ");
460
+ frame = 0;
461
+ setShape(segments.length ? {
462
+ path,
463
+ width,
464
+ range: first && last && {
465
+ top: first.top,
466
+ height: last.bottom - first.top
467
+ }
468
+ } : void 0);
469
+ };
470
+ const schedule = () => {
471
+ frame ||= requestAnimationFrame(measure);
472
+ };
473
+ const observer = new ResizeObserver(schedule);
474
+ createEffect(() => {
475
+ options.highlightedIDs();
476
+ schedule();
477
+ });
478
+ if (track) observer.observe(track);
479
+ onCleanup(() => {
480
+ cancelAnimationFrame(frame);
481
+ observer.disconnect();
482
+ });
483
+ });
484
+ createEffect(() => {
485
+ const current = shape();
486
+ if (!current || !marker) return setStroke(void 0);
487
+ marker.setAttribute("d", current.path);
488
+ const total = marker.getTotalLength();
489
+ const lengthAt = y => {
490
+ let low = 0;
491
+ let high = total;
492
+ for (let step = 0; step < 20; step += 1) {
493
+ const middle = (low + high) / 2;
494
+ if (marker.getPointAtLength(middle).y < y) low = middle;else high = middle;
495
+ }
496
+ return (low + high) / 2;
497
+ };
498
+ if (!current.range) return setStroke({
499
+ ...current,
500
+ offset: 0,
501
+ length: 0,
502
+ total
503
+ });
504
+ const {
505
+ top,
506
+ height
507
+ } = current.range;
508
+ const bottom = top + height;
509
+ const r = Math.min(radius, height / 2);
510
+ const xAt = y => marker.getPointAtLength(lengthAt(y)).x;
511
+ const edge = Array.from({
512
+ length: edgePoints
513
+ }, (_, index) => {
514
+ const y = bottom - r - (height - 2 * r) * index / (edgePoints - 1);
515
+ return `L ${xAt(y)} ${y}`;
516
+ });
517
+ const [xTop, xBottom] = [xAt(top + r), xAt(bottom - r)];
518
+ const backdrop = [`M ${xTop + r} ${top} L ${current.width - r} ${top}`, `Q ${current.width} ${top} ${current.width} ${top + r} L ${current.width} ${bottom - r}`, `Q ${current.width} ${bottom} ${current.width - r} ${bottom} L ${xBottom + r} ${bottom}`, `Q ${xBottom} ${bottom} ${xBottom} ${bottom - r}`, ...edge, `Q ${xTop} ${top} ${xTop + r} ${top} Z`].join(" ");
519
+ const start = lengthAt(top + inset);
520
+ const end = lengthAt(bottom - inset);
521
+ setStroke({
522
+ ...current,
523
+ offset: -start,
524
+ length: end - start,
525
+ total,
526
+ backdrop
527
+ });
528
+ });
529
+ createEffect(() => {
530
+ const viewport = options.viewport?.();
531
+ const first = links.get(options.highlightedIDs()[0] ?? "");
532
+ if (viewport && first) scrollIntoContainer(viewport, first, 12);
533
+ });
534
+ return {
535
+ stroke,
536
+ trackRef: element => track = element,
537
+ linkRef: (id, element) => links.set(id, element),
538
+ pathRef: element => marker = element
539
+ };
540
+ };
541
+ const enhanceMobileNavigation = (root, options = {}) => {
542
+ const trigger = root.querySelector(":scope > summary");
543
+ const content = root.querySelector(":scope > :not(summary)");
544
+ const media = options.mediaQuery ? window.matchMedia(options.mediaQuery) : void 0;
545
+ const inertElements = new Map();
546
+ let opened = false;
547
+ let previousOverflow = "";
548
+ if (!trigger || !content) throw new Error("Mobile navigation needs a summary and content.");
549
+ const setModal = modal => {
550
+ if (!modal) {
551
+ inertElements.forEach((inert, element) => element.inert = inert);
552
+ inertElements.clear();
553
+ document.body.style.overflow = previousOverflow;
554
+ return;
555
+ }
556
+ for (let branch = root; branch.parentElement; branch = branch.parentElement) for (const sibling of branch.parentElement.children) {
557
+ if (sibling === branch || !(sibling instanceof HTMLElement)) continue;
558
+ inertElements.set(sibling, sibling.inert);
559
+ sibling.inert = true;
560
+ }
561
+ previousOverflow = document.body.style.overflow;
562
+ document.body.style.overflow = "hidden";
563
+ };
564
+ const sync = () => {
565
+ const open = root.open && (media?.matches ?? true);
566
+ if (root.open && !open) root.open = false;
567
+ if (opened === open) return;
568
+ opened = open;
569
+ setModal(open);
570
+ if (open) (getFocusable(content)[0] ?? trigger).focus({
571
+ preventScroll: true
572
+ });
573
+ options.onOpenChange?.(open);
574
+ };
575
+ const close = () => {
576
+ root.open = false;
577
+ sync();
578
+ };
579
+ const onToggle = event => {
580
+ if (event.target !== root) return;
581
+ const hadFocus = content.contains(document.activeElement);
582
+ sync();
583
+ if (!opened && hadFocus) trigger.focus({
584
+ preventScroll: true
585
+ });
586
+ };
587
+ const onKeyDown = event => {
588
+ if (!opened || event.defaultPrevented) return;
589
+ if (event.key === "Escape") {
590
+ event.preventDefault();
591
+ close();
592
+ trigger.focus({
593
+ preventScroll: true
594
+ });
595
+ }
596
+ if (event.key !== "Tab") return;
597
+ const items = [trigger, ...getFocusable(content)];
598
+ const edge = event.shiftKey ? items[0] : items.at(-1);
599
+ if (document.activeElement !== edge) return;
600
+ event.preventDefault();
601
+ (event.shiftKey ? items.at(-1) : items[0])?.focus();
602
+ };
603
+ const onClick = event => {
604
+ const link = event.target.closest("a[href]");
605
+ const isPlainClick = event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;
606
+ if (!(opened && isPlainClick && link !== null && content.contains(link))) return;
607
+ const url = new URL(link.href);
608
+ const samePage = url.origin === location.origin && url.pathname === location.pathname;
609
+ close();
610
+ if (samePage) requestAnimationFrame(() => {
611
+ const target = url.hash && document.getElementById(decodeURIComponent(url.hash.slice(1))) || options.navigationTarget?.();
612
+ if (target) focusTarget(target);else trigger.focus({
613
+ preventScroll: true
614
+ });
615
+ });
616
+ };
617
+ root.addEventListener("toggle", onToggle);
618
+ root.addEventListener("keydown", onKeyDown);
619
+ root.addEventListener("click", onClick);
620
+ media?.addEventListener("change", sync);
621
+ sync();
622
+ return () => {
623
+ root.removeEventListener("toggle", onToggle);
624
+ root.removeEventListener("keydown", onKeyDown);
625
+ root.removeEventListener("click", onClick);
626
+ media?.removeEventListener("change", sync);
627
+ if (opened) setModal(false);
628
+ };
629
+ };
630
+ const Root$3 = props => {
631
+ const [local, rest] = splitProps(props, ["mediaQuery", "navigationTarget", "onOpenChange"]);
632
+ let root;
633
+ onMount(() => onCleanup(enhanceMobileNavigation(root, local)));
634
+ return (() => {
635
+ var _el$1 = _$getNextElement(_tmpl$8);
636
+ var _ref$ = root;
637
+ typeof _ref$ === "function" ? _$use(_ref$, _el$1) : root = _el$1;
638
+ _$spread(_el$1, _$mergeProps(rest, {
639
+ "data-scope": "mobile-navigation",
640
+ "data-part": "root"
641
+ }), false, false);
642
+ _$runHydrationEvents();
643
+ return _el$1;
644
+ })();
645
+ };
646
+ const Trigger = props => {
647
+ return (() => {
648
+ var _el$10 = _$getNextElement(_tmpl$9);
649
+ _$spread(_el$10, _$mergeProps(props, {
650
+ "data-scope": "mobile-navigation",
651
+ "data-part": "trigger"
652
+ }), false, false);
653
+ _$runHydrationEvents();
654
+ return _el$10;
655
+ })();
656
+ };
657
+ const Content = props => {
658
+ return (() => {
659
+ var _el$11 = _$getNextElement(_tmpl$6);
660
+ _$spread(_el$11, _$mergeProps(props, {
661
+ "data-scope": "mobile-navigation",
662
+ "data-part": "content"
663
+ }), false, false);
664
+ _$runHydrationEvents();
665
+ return _el$11;
666
+ })();
667
+ };
668
+ const MobileNavigation = {
669
+ Root: Root$3,
670
+ Trigger,
671
+ Content
672
+ };
673
+ const Root$2 = props => {
674
+ return (() => {
675
+ var _el$12 = _$getNextElement(_tmpl$0);
676
+ _$spread(_el$12, _$mergeProps(props, {
677
+ "data-scope": "breadcrumbs",
678
+ "data-part": "root"
679
+ }), false, false);
680
+ _$runHydrationEvents();
681
+ return _el$12;
682
+ })();
683
+ };
684
+ const List = props => {
685
+ return (() => {
686
+ var _el$13 = _$getNextElement(_tmpl$1);
687
+ _$spread(_el$13, _$mergeProps(props, {
688
+ "data-scope": "breadcrumbs",
689
+ "data-part": "list"
690
+ }), false, false);
691
+ _$runHydrationEvents();
692
+ return _el$13;
693
+ })();
694
+ };
695
+ const Item = props => {
696
+ return (() => {
697
+ var _el$14 = _$getNextElement(_tmpl$3);
698
+ _$spread(_el$14, _$mergeProps(props, {
699
+ "data-scope": "breadcrumbs",
700
+ "data-part": "item"
701
+ }), false, false);
702
+ _$runHydrationEvents();
703
+ return _el$14;
704
+ })();
705
+ };
706
+ const Link$1 = props => {
707
+ return (() => {
708
+ var _el$15 = _$getNextElement(_tmpl$4);
709
+ _$spread(_el$15, _$mergeProps(props, {
710
+ "data-scope": "breadcrumbs",
711
+ "data-part": "link"
712
+ }), false, false);
713
+ _$runHydrationEvents();
714
+ return _el$15;
715
+ })();
716
+ };
717
+ const Label = props => {
718
+ return (() => {
719
+ var _el$16 = _$getNextElement(_tmpl$10);
720
+ _$spread(_el$16, _$mergeProps(props, {
721
+ "data-scope": "breadcrumbs",
722
+ "data-part": "label"
723
+ }), false, false);
724
+ _$runHydrationEvents();
725
+ return _el$16;
726
+ })();
727
+ };
728
+ const Separator = props => {
729
+ return (() => {
730
+ var _el$17 = _$getNextElement(_tmpl$11);
731
+ _$spread(_el$17, _$mergeProps(props, {
732
+ "data-scope": "breadcrumbs",
733
+ "data-part": "separator"
734
+ }), false, false);
735
+ _$runHydrationEvents();
736
+ return _el$17;
737
+ })();
738
+ };
739
+ const Breadcrumbs = {
740
+ Root: Root$2,
741
+ List,
742
+ Item,
743
+ Link: Link$1,
744
+ Label,
745
+ Separator
746
+ };
747
+ const Root$1 = props => {
748
+ return (() => {
749
+ var _el$18 = _$getNextElement(_tmpl$12);
750
+ _$spread(_el$18, _$mergeProps(props, {
751
+ "data-scope": "pagination",
752
+ "data-part": "root"
753
+ }), false, false);
754
+ _$runHydrationEvents();
755
+ return _el$18;
756
+ })();
757
+ };
758
+ const Link = props => {
759
+ const [local, rest] = splitProps(props, ["direction"]);
760
+ return (() => {
761
+ var _el$19 = _$getNextElement(_tmpl$4);
762
+ _$spread(_el$19, _$mergeProps(rest, {
763
+ get rel() {
764
+ return local.direction === "previous" ? "prev" : "next";
765
+ },
766
+ "data-scope": "pagination",
767
+ "data-part": "link",
768
+ get ["data-direction"]() {
769
+ return local.direction;
770
+ }
771
+ }), false, false);
772
+ _$runHydrationEvents();
773
+ return _el$19;
774
+ })();
775
+ };
776
+ const Pagination = {
777
+ Root: Root$1,
778
+ Link
779
+ };
780
+ const syncEvent = "andesine:tabs";
781
+ const Root = props => {
782
+ const [local, rest] = splitProps(props, ["values", "defaultValue", "syncKey", "onValueChange"]);
783
+ const [value, setValue] = createSignal(local.defaultValue ?? local.values[0] ?? "");
784
+ const storageKey = () => `andesine-tabs:${local.syncKey}`;
785
+ const select = next => {
786
+ if (typeof next === "string" && local.values.includes(next)) setValue(next);
787
+ };
788
+ onMount(() => {
789
+ if (!local.syncKey) return;
790
+ const sync = event => {
791
+ const {
792
+ key,
793
+ value
794
+ } = event.detail;
795
+ if (key === local.syncKey) select(value);
796
+ };
797
+ select(readSession(storageKey()));
798
+ window.addEventListener(syncEvent, sync);
799
+ onCleanup(() => window.removeEventListener(syncEvent, sync));
800
+ });
801
+ return _$createComponent(Tabs$1.Root, _$mergeProps(rest, {
802
+ get value() {
803
+ return value();
804
+ },
805
+ onValueChange: details => {
806
+ setValue(details.value);
807
+ local.onValueChange?.(details);
808
+ if (!local.syncKey) return;
809
+ writeSession(storageKey(), details.value);
810
+ window.dispatchEvent(new CustomEvent(syncEvent, {
811
+ detail: {
812
+ key: local.syncKey,
813
+ value: details.value
814
+ }
815
+ }));
816
+ }
817
+ }));
818
+ };
819
+ const Tabs = {
820
+ Root,
821
+ List: Tabs$1.List,
822
+ Trigger: Tabs$1.Trigger,
823
+ Content: Tabs$1.Content,
824
+ Indicator: Tabs$1.Indicator
825
+ };
826
+ const createClipboard = (timeout = 2e3) => {
827
+ const [status, setStatus] = createSignal();
828
+ let timer;
829
+ onCleanup(() => clearTimeout(timer));
830
+ return {
831
+ status,
832
+ copy: async text => {
833
+ clearTimeout(timer);
834
+ try {
835
+ if (typeof text !== "string" && typeof ClipboardItem !== "undefined") {
836
+ const blob = text.then(value => new Blob([value], {
837
+ type: "text/plain"
838
+ }));
839
+ await navigator.clipboard.write([new ClipboardItem({
840
+ "text/plain": blob
841
+ })]);
842
+ } else await navigator.clipboard.writeText(await text);
843
+ setStatus(true);
844
+ } catch {
845
+ setStatus(false);
846
+ }
847
+ timer = setTimeout(() => setStatus(void 0), timeout);
848
+ }
849
+ };
850
+ };
851
+ const createSearch = options => {
852
+ const [query, setQueryValue] = createSignal("");
853
+ const [results, setResults] = createSignal();
854
+ const [status, setStatus] = createSignal("idle");
855
+ const [error, setError] = createSignal();
856
+ let timer;
857
+ let controller;
858
+ const cancel = () => {
859
+ clearTimeout(timer);
860
+ controller?.abort();
861
+ controller = void 0;
862
+ if (status() === "loading") setStatus(results() ? "done" : "idle");
863
+ };
864
+ const run = async value => {
865
+ const current = new AbortController();
866
+ controller = current;
867
+ try {
868
+ const found = await options.search(value, current.signal);
869
+ if (controller !== current) return;
870
+ setResults(() => found);
871
+ setStatus("done");
872
+ } catch (reason) {
873
+ if (controller !== current || current.signal.aborted) return;
874
+ setError(reason);
875
+ setStatus("error");
876
+ }
877
+ };
878
+ onCleanup(cancel);
879
+ return {
880
+ query,
881
+ results,
882
+ status,
883
+ error,
884
+ cancel,
885
+ setQuery: value => {
886
+ cancel();
887
+ setQueryValue(value);
888
+ if (!value.trim()) {
889
+ setResults(void 0);
890
+ setStatus("idle");
891
+ return;
892
+ }
893
+ setStatus("loading");
894
+ timer = setTimeout(() => run(value.trim()), options.delay ?? 200);
895
+ }
896
+ };
897
+ };
898
+ const createListNavigation = options => {
899
+ const [active, setActive] = createSignal(0);
900
+ const moves = {
901
+ ArrowDown: (index, count) => (index + 1) % count,
902
+ ArrowUp: (index, count) => (index - 1 + count) % count,
903
+ Home: () => 0,
904
+ End: (_, count) => count - 1
905
+ };
906
+ return {
907
+ active,
908
+ setActive,
909
+ onKeyDown: event => {
910
+ const count = options.count();
911
+ const move = moves[event.key];
912
+ if (!count || event.isComposing) return;
913
+ if (event.key === "Enter") {
914
+ event.preventDefault();
915
+ options.onSelect(Math.min(active(), count - 1));
916
+ }
917
+ if (!move) return;
918
+ event.preventDefault();
919
+ setActive(move(Math.min(active(), count - 1), count));
920
+ }
921
+ };
922
+ };
923
+ const createAnswer = options => {
924
+ const [turns, setTurns] = createSignal([]);
925
+ let controller;
926
+ const pending = () => turns().at(-1)?.status === "loading";
927
+ const updateLast = (current, turn) => {
928
+ if (controller !== current) return;
929
+ setTurns(list => [...list.slice(0, -1), {
930
+ ...list.at(-1),
931
+ ...turn
932
+ }]);
933
+ };
934
+ const cancel = () => {
935
+ controller?.abort();
936
+ controller = void 0;
937
+ if (pending()) setTurns(list => list.slice(0, -1));
938
+ };
939
+ const run = async question => {
940
+ const current = new AbortController();
941
+ cancel();
942
+ controller = current;
943
+ const history = turns().filter(turn => turn.status === "done");
944
+ setTurns([...history, {
945
+ question,
946
+ status: "loading"
947
+ }]);
948
+ try {
949
+ const answer = await options.ask(question, history, current.signal, partial => {
950
+ return updateLast(current, {
951
+ answer: partial
952
+ });
953
+ });
954
+ updateLast(current, {
955
+ answer,
956
+ status: "done"
957
+ });
958
+ } catch (error) {
959
+ if (current.signal.aborted) return;
960
+ updateLast(current, {
961
+ status: "error",
962
+ error
963
+ });
964
+ } finally {
965
+ if (controller === current) controller = void 0;
966
+ }
967
+ };
968
+ onCleanup(cancel);
969
+ return {
970
+ turns,
971
+ pending,
972
+ cancel,
973
+ ask: question => {
974
+ run(question).catch(() => {});
975
+ },
976
+ reset: () => {
977
+ cancel();
978
+ setTurns([]);
979
+ }
980
+ };
981
+ };
982
+ const isApplePlatform = () => {
983
+ return typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent);
984
+ };
985
+ const createSearchPalette = options => {
986
+ const [open, setOpen] = createSignal(false);
987
+ const [isApple, setIsApple] = createSignal(true);
988
+ const [input, setInput] = createSignal();
989
+ const search = createSearch({
990
+ search: options.search,
991
+ delay: options.delay ?? 250
992
+ });
993
+ const answer = options.ask && createAnswer({
994
+ ask: options.ask
995
+ });
996
+ const items = () => {
997
+ const results = search.results();
998
+ return results === void 0 ? [] : options.items(results);
999
+ };
1000
+ const canAsk = () => Boolean(answer && search.query().trim());
1001
+ const showingAnswer = () => {
1002
+ return !search.query().trim() && Boolean(answer && answer.turns().length);
1003
+ };
1004
+ const openItem = item => {
1005
+ setOpen(false);
1006
+ window.location.assign(item.href);
1007
+ };
1008
+ const askQuery = () => {
1009
+ const question = search.query().trim();
1010
+ if (!question || !answer) return;
1011
+ follow = true;
1012
+ answer.ask(question);
1013
+ search.setQuery("");
1014
+ };
1015
+ const navigation = createListNavigation({
1016
+ count: () => search.status() === "loading" ? 0 : items().length + (canAsk() ? 1 : 0),
1017
+ onSelect: index => {
1018
+ const item = items()[index];
1019
+ if (item) openItem(item);else askQuery();
1020
+ }
1021
+ });
1022
+ let results;
1023
+ let resumeSearch = false;
1024
+ let follow = true;
1025
+ createEffect(() => {
1026
+ search.results();
1027
+ navigation.setActive(0);
1028
+ });
1029
+ createEffect(on(open, isOpen => {
1030
+ if (isOpen) {
1031
+ if (resumeSearch) search.setQuery(search.query());
1032
+ resumeSearch = false;
1033
+ return;
1034
+ }
1035
+ resumeSearch = search.status() === "loading";
1036
+ search.cancel();
1037
+ answer?.cancel();
1038
+ }));
1039
+ createEffect(() => {
1040
+ answer?.turns();
1041
+ if (follow && results) queueMicrotask(() => results.scrollTo({
1042
+ top: results.scrollHeight
1043
+ }));
1044
+ });
1045
+ onMount(() => {
1046
+ const url = new URL(window.location.href);
1047
+ const param = options.queryParam ?? "q";
1048
+ const query = url.searchParams.get(param)?.trim();
1049
+ const onShortcut = event => {
1050
+ if (!(event.key.toLowerCase() === "k" && (event.metaKey || event.ctrlKey))) return;
1051
+ event.preventDefault();
1052
+ setOpen(true);
1053
+ };
1054
+ const onClick = event => {
1055
+ if (event.target.closest(options.triggerSelector ?? "[data-search-trigger]")) setOpen(true);
1056
+ };
1057
+ setIsApple(isApplePlatform());
1058
+ if (query) {
1059
+ url.searchParams.delete(param);
1060
+ window.history.replaceState(window.history.state, "", url);
1061
+ search.setQuery(query.slice(0, options.maxLength ?? 500));
1062
+ setOpen(true);
1063
+ }
1064
+ document.addEventListener("keydown", onShortcut);
1065
+ document.addEventListener("click", onClick);
1066
+ onCleanup(() => {
1067
+ document.removeEventListener("keydown", onShortcut);
1068
+ document.removeEventListener("click", onClick);
1069
+ });
1070
+ });
1071
+ return {
1072
+ open,
1073
+ setOpen,
1074
+ search,
1075
+ answer,
1076
+ items,
1077
+ canAsk,
1078
+ showingAnswer,
1079
+ navigation,
1080
+ isApple,
1081
+ askQuery,
1082
+ openItem,
1083
+ input,
1084
+ inputRef: setInput,
1085
+ resultsRef: element => {
1086
+ results = element;
1087
+ element.addEventListener("scroll", () => {
1088
+ follow = element.scrollHeight - element.scrollTop - element.clientHeight < 48;
1089
+ });
1090
+ },
1091
+ onInputKeyDown: event => {
1092
+ const isTouch = window.matchMedia("(hover: none) and (pointer: coarse)").matches;
1093
+ if (event.key === "Enter" && isTouch) {
1094
+ event.preventDefault();
1095
+ input()?.blur();
1096
+ return;
1097
+ }
1098
+ if (showingAnswer()) return;
1099
+ navigation.onKeyDown(event);
1100
+ queueMicrotask(() => {
1101
+ results?.querySelector(`[data-search-result="${navigation.active()}"]`)?.scrollIntoView({
1102
+ block: "nearest"
1103
+ });
1104
+ });
1105
+ }
1106
+ };
1107
+ };
1108
+ const escapeRegExp = value => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1109
+ const getMatchTerms = query => {
1110
+ const phrase = query.trim().toLowerCase();
1111
+ if (!phrase) return [];
1112
+ return [...new Set([phrase, ...phrase.split(/\s+/).filter(term => term.length >= 2)])].sort((a, b) => b.length - a.length);
1113
+ };
1114
+ const matchesQuery = (text, query) => {
1115
+ return getMatchTerms(query).some(term => text.toLowerCase().includes(term));
1116
+ };
1117
+ const splitMatches = (text, query) => {
1118
+ const terms = getMatchTerms(query);
1119
+ if (!terms.length) return [{
1120
+ text,
1121
+ match: false
1122
+ }];
1123
+ return text.split(new RegExp(`(${terms.map(escapeRegExp).join("|")})`, "giu")).filter(Boolean).map(part => ({
1124
+ text: part,
1125
+ match: terms.includes(part.toLowerCase())
1126
+ }));
1127
+ };
1128
+ const getMatchPreview = (text, query, options = {}) => {
1129
+ const lower = text.toLowerCase();
1130
+ const matches = getMatchTerms(query).map(term => lower.indexOf(term)).filter(index => index >= 0);
1131
+ const start = matches.length ? Math.max(Math.min(...matches) - (options.prefix ?? 24), 0) : 0;
1132
+ const end = Math.min(start + (options.length ?? 180), text.length);
1133
+ return `${start > 0 ? "…" : ""}${text.slice(start, end).trim()}${end < text.length ? "…" : ""}`;
1134
+ };
1135
+ const createPageActions = options => {
1136
+ const [item, setItem] = createSignal("link");
1137
+ const clipboard = createClipboard(options.timeout);
1138
+ let markdown;
1139
+ const loadMarkdown = () => {
1140
+ markdown ??= fetch(options.markdown).then(response => {
1141
+ if (!response.ok) throw new Error(`Markdown request failed (${response.status}).`);
1142
+ return response.text();
1143
+ });
1144
+ markdown.catch(() => markdown = void 0);
1145
+ return markdown;
1146
+ };
1147
+ const result = () => {
1148
+ const success = clipboard.status();
1149
+ return success === void 0 ? void 0 : {
1150
+ item: item(),
1151
+ success
1152
+ };
1153
+ };
1154
+ return {
1155
+ result,
1156
+ copyLink: () => {
1157
+ setItem("link");
1158
+ return clipboard.copy(window.location.href);
1159
+ },
1160
+ copyMarkdown: () => {
1161
+ setItem("markdown");
1162
+ return clipboard.copy(loadMarkdown());
1163
+ }
1164
+ };
1165
+ };
1166
+ const createScrollMemory = options => {
1167
+ onMount(() => {
1168
+ const container = options.container();
1169
+ const current = options.current?.();
1170
+ if (!container) return;
1171
+ const save = () => writeSession(options.key, container.scrollTop);
1172
+ container.scrollTop = Number(readSession(options.key)) || 0;
1173
+ if (current) scrollIntoContainer(container, current);
1174
+ container.addEventListener("scroll", save, {
1175
+ passive: true
1176
+ });
1177
+ onCleanup(() => container.removeEventListener("scroll", save));
1178
+ });
1179
+ };
1180
+ const createScrollEdges = () => {
1181
+ const [start, setStart] = createSignal(false);
1182
+ const [end, setEnd] = createSignal(false);
1183
+ onMount(() => {
1184
+ const root = document.documentElement;
1185
+ const update = () => {
1186
+ setStart(root.scrollTop > 0);
1187
+ setEnd(root.scrollHeight - root.clientHeight - root.scrollTop >= 10);
1188
+ };
1189
+ const observer = new ResizeObserver(update);
1190
+ observer.observe(document.body);
1191
+ window.addEventListener("scroll", update, {
1192
+ passive: true
1193
+ });
1194
+ update();
1195
+ onCleanup(() => {
1196
+ observer.disconnect();
1197
+ window.removeEventListener("scroll", update);
1198
+ });
1199
+ });
1200
+ return {
1201
+ start,
1202
+ end
1203
+ };
1204
+ };
1205
+ const createPageScrollbar = (options = {}) => {
1206
+ const [metrics, setMetrics] = createSignal();
1207
+ const [scrolling, setScrolling] = createSignal(false);
1208
+ const [near, setNear] = createSignal(false);
1209
+ const [dragging, setDragging] = createSignal(false);
1210
+ const inset = options.inset ?? 4;
1211
+ const interactive = () => near() || dragging();
1212
+ let dragStart = 0;
1213
+ let scrollStart = 0;
1214
+ const scrollTo = top => window.scrollTo({
1215
+ top,
1216
+ behavior: "instant"
1217
+ });
1218
+ const stopDragging = () => {
1219
+ setDragging(false);
1220
+ };
1221
+ onMount(() => {
1222
+ const edgeZone = options.edgeZone ?? 16;
1223
+ const minThumbHeight = options.minThumbHeight ?? 24;
1224
+ let frame = 0;
1225
+ let timer;
1226
+ const measure = () => {
1227
+ const root = document.documentElement;
1228
+ const viewport = window.innerHeight;
1229
+ const maximum = root.scrollHeight - viewport;
1230
+ const locked = getComputedStyle(document.body).overflow === "hidden";
1231
+ const track = viewport - inset * 2;
1232
+ const height = Math.max(minThumbHeight, track * viewport / root.scrollHeight);
1233
+ frame = 0;
1234
+ setMetrics(maximum > 0 && !locked ? {
1235
+ top: inset + window.scrollY / maximum * (track - height),
1236
+ height,
1237
+ ratio: maximum / (track - height)
1238
+ } : void 0);
1239
+ };
1240
+ const schedule = () => {
1241
+ frame ||= requestAnimationFrame(measure);
1242
+ };
1243
+ const onScroll = () => {
1244
+ schedule();
1245
+ setScrolling(true);
1246
+ clearTimeout(timer);
1247
+ timer = setTimeout(() => setScrolling(false), options.hideDelay ?? 1e3);
1248
+ };
1249
+ const onPointerMove = event => {
1250
+ setNear(event.pointerType === "mouse" && event.clientX >= window.innerWidth - edgeZone);
1251
+ };
1252
+ const observer = new ResizeObserver(schedule);
1253
+ const styleObserver = new MutationObserver(schedule);
1254
+ observer.observe(document.body);
1255
+ styleObserver.observe(document.body, {
1256
+ attributes: true,
1257
+ attributeFilter: ["style"]
1258
+ });
1259
+ window.addEventListener("scroll", onScroll, {
1260
+ passive: true
1261
+ });
1262
+ window.addEventListener("resize", schedule);
1263
+ window.addEventListener("pointermove", onPointerMove, {
1264
+ passive: true
1265
+ });
1266
+ measure();
1267
+ onCleanup(() => {
1268
+ cancelAnimationFrame(frame);
1269
+ clearTimeout(timer);
1270
+ observer.disconnect();
1271
+ styleObserver.disconnect();
1272
+ window.removeEventListener("scroll", onScroll);
1273
+ window.removeEventListener("resize", schedule);
1274
+ window.removeEventListener("pointermove", onPointerMove);
1275
+ });
1276
+ });
1277
+ return {
1278
+ thumb: () => {
1279
+ const current = metrics();
1280
+ return current && {
1281
+ top: current.top,
1282
+ height: current.height
1283
+ };
1284
+ },
1285
+ visible: () => Boolean(metrics() && (scrolling() || interactive())),
1286
+ interactive,
1287
+ onTrackPointerDown: event => {
1288
+ const current = metrics();
1289
+ if (event.button !== 0 || !current) return;
1290
+ scrollTo((event.clientY - inset - current.height / 2) * current.ratio);
1291
+ },
1292
+ thumbHandlers: {
1293
+ onPointerDown: event => {
1294
+ if (event.button !== 0) return;
1295
+ event.preventDefault();
1296
+ event.stopPropagation();
1297
+ event.currentTarget.setPointerCapture(event.pointerId);
1298
+ dragStart = event.clientY;
1299
+ scrollStart = window.scrollY;
1300
+ setDragging(true);
1301
+ },
1302
+ onPointerMove: event => {
1303
+ const current = metrics();
1304
+ if (dragging() && current) scrollTo(scrollStart + (event.clientY - dragStart) * current.ratio);
1305
+ },
1306
+ onPointerUp: stopDragging,
1307
+ onPointerCancel: stopDragging,
1308
+ onLostPointerCapture: stopDragging
1309
+ }
1310
+ };
1311
+ };
1312
+ const createTabIndicator = options => {
1313
+ const [position, setPosition] = createSignal();
1314
+ const tabs = new Map();
1315
+ let root;
1316
+ let scroll;
1317
+ onMount(() => {
1318
+ let frame = 0;
1319
+ const measure = () => {
1320
+ const rootBounds = root?.getBoundingClientRect();
1321
+ const tabBounds = tabs.get(options.current())?.getBoundingClientRect();
1322
+ frame = 0;
1323
+ setPosition(rootBounds && tabBounds && {
1324
+ left: tabBounds.left - rootBounds.left,
1325
+ width: tabBounds.width
1326
+ });
1327
+ };
1328
+ const schedule = () => {
1329
+ frame ||= requestAnimationFrame(measure);
1330
+ };
1331
+ const observer = new ResizeObserver(schedule);
1332
+ tabs.get(options.current())?.scrollIntoView({
1333
+ block: "nearest",
1334
+ inline: "nearest"
1335
+ });
1336
+ if (root) observer.observe(root);
1337
+ scroll?.addEventListener("scroll", schedule, {
1338
+ passive: true
1339
+ });
1340
+ schedule();
1341
+ onCleanup(() => {
1342
+ cancelAnimationFrame(frame);
1343
+ observer.disconnect();
1344
+ scroll?.removeEventListener("scroll", schedule);
1345
+ });
1346
+ });
1347
+ return {
1348
+ position,
1349
+ rootRef: element => root = element,
1350
+ scrollRef: element => scroll = element,
1351
+ tabRef: (index, element) => tabs.set(index, element)
1352
+ };
1353
+ };
1354
+ export { Breadcrumbs, MobileNavigation, NavigationTree, Pagination, TableOfContents, Tabs, createAnswer, createClipboard, createListNavigation, createNavigationTree, createPageActions, createPageScrollbar, createScrollEdges, createScrollMemory, createSearch, createSearchPalette, createTabIndicator, createTableOfContents, createTableOfContentsGuide, enhanceMobileNavigation, getMatchPreview, getMatchTerms, matchesQuery, splitMatches, useTableOfContents };