vireuikit 0.7.0 → 0.9.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/web.cjs CHANGED
@@ -62,10 +62,10 @@ var PRESSED_BODY = { alpha: 0.34, white: 0.35, lift: 0 };
62
62
  var FROST_LIFT = 0.26;
63
63
  function atomBody(base, state = ATOM_AT_REST, frost = false) {
64
64
  const on = activation(state);
65
- const mix2 = (key) => REST_BODY[key] + (PRESSED_BODY[key] - REST_BODY[key]) * on;
66
- let alpha = mix2("alpha");
67
- let white = mix2("white");
68
- let lift = mix2("lift");
65
+ const mix3 = (key) => REST_BODY[key] + (PRESSED_BODY[key] - REST_BODY[key]) * on;
66
+ let alpha = mix3("alpha");
67
+ let white = mix3("white");
68
+ let lift = mix3("lift");
69
69
  if (state.disabled) alpha *= DISABLED_PRESENCE;
70
70
  if (frost) {
71
71
  const density = vireglass.applyAccessibility(vireglass.resolveOptics(base), {
@@ -79,6 +79,67 @@ function atomBody(base, state = ATOM_AT_REST, frost = false) {
79
79
  }
80
80
  return { alpha, white, lift };
81
81
  }
82
+ var CONTROL_SIZES = ["touch", "small", "medium", "large", "xlarge"];
83
+ var MIN_TOUCH_TARGET_DP = 44;
84
+ var MIN_POINTER_TARGET_DP = 24;
85
+ var capsule = (height, paddingX) => ({
86
+ height,
87
+ paddingX,
88
+ icon: Math.round(height / 2),
89
+ radius: height / 2,
90
+ shape: "capsule"
91
+ });
92
+ var rounded = (height, paddingX) => ({
93
+ height,
94
+ paddingX,
95
+ icon: Math.round(height / 2),
96
+ radius: height / 4,
97
+ shape: "rounded"
98
+ });
99
+ var BUTTON_SIZES = {
100
+ touch: capsule(44, 20),
101
+ small: rounded(24, 10),
102
+ medium: rounded(32, 14),
103
+ large: capsule(40, 18),
104
+ xlarge: capsule(48, 22)
105
+ };
106
+ function iconButtonSize(size) {
107
+ const spec = BUTTON_SIZES[size];
108
+ return { ...spec, width: spec.height, paddingX: 0 };
109
+ }
110
+ var MENU_PADDING = 0.3;
111
+ function menuSize(size) {
112
+ const spec = BUTTON_SIZES[size];
113
+ const padding = Math.round(spec.height * MENU_PADDING);
114
+ return {
115
+ row: spec.height,
116
+ padding,
117
+ itemRadius: spec.radius,
118
+ radius: spec.radius + padding,
119
+ icon: spec.icon,
120
+ labelPad: spec.paddingX
121
+ };
122
+ }
123
+ function defaultSize(input) {
124
+ return input.touch ? "touch" : "medium";
125
+ }
126
+ function hitSlop(bodyDp, minTargetDp) {
127
+ return bodyDp >= minTargetDp ? 0 : (minTargetDp - bodyDp) / 2;
128
+ }
129
+ function minTarget(input) {
130
+ return input.touch ? MIN_TOUCH_TARGET_DP : MIN_POINTER_TARGET_DP;
131
+ }
132
+
133
+ // src/kit/adaptive/input.ts
134
+ function sizeTier(input) {
135
+ return defaultSize({ touch: input.primary === "coarse" });
136
+ }
137
+ function hitTarget(input) {
138
+ return minTarget({ touch: input.anyCoarse });
139
+ }
140
+ function hoversWith(pointerType) {
141
+ return pointerType !== "touch";
142
+ }
82
143
 
83
144
  // src/kit/behavior/focus.ts
84
145
  function stepFocus(event) {
@@ -108,8 +169,6 @@ function stepPress(state, event, enabled2) {
108
169
  }
109
170
 
110
171
  // src/kit/behavior/control.ts
111
- var TOUCH_INPUT = { hover: false };
112
- var POINTER_INPUT = { hover: true };
113
172
  var CONTROL_IDLE = {
114
173
  press: PRESS_IDLE,
115
174
  hovered: false,
@@ -208,7 +267,7 @@ function growthFrame(source, target, anchor, phase) {
208
267
  const len = Math.hypot(dx, dy) || 1;
209
268
  const sourceSize = vireglass.halfMinDp(source.geometry) * 2;
210
269
  const lift = DROP_LIFT * sourceSize;
211
- const drop = {
270
+ const drop2 = {
212
271
  x: source.center.x + dx / len * lift,
213
272
  y: source.center.y + dy / len * lift
214
273
  };
@@ -227,13 +286,13 @@ function growthFrame(source, target, anchor, phase) {
227
286
  );
228
287
  const shrunk = {
229
288
  geometry: shrunkGeometry,
230
- center: { x: mix(source.center.x, drop.x, a), y: mix(source.center.y, drop.y, a) }
289
+ center: { x: mix(source.center.x, drop2.x, a), y: mix(source.center.y, drop2.y, a) }
231
290
  };
232
291
  const seed = d * Math.sqrt(a);
233
292
  const cornerX = Math.sign(anchor.x - source.center.x);
234
293
  const cornerY = Math.sign(anchor.y - source.center.y);
235
- const edgeX = mix(drop.x + cornerX * (seed / 2), anchor.x, Math.min(b, 1));
236
- const edgeY = mix(drop.y + cornerY * (seed / 2), anchor.y, Math.min(b, 1));
294
+ const edgeX = mix(drop2.x + cornerX * (seed / 2), anchor.x, Math.min(b, 1));
295
+ const edgeY = mix(drop2.y + cornerY * (seed / 2), anchor.y, Math.min(b, 1));
237
296
  const width = mix(seed, target.geometry.width, b);
238
297
  const height = mix(seed, target.geometry.height, b);
239
298
  const grownGeometry = { width, height, cornerRadius: 0 };
@@ -347,56 +406,6 @@ var SINKING = { grow: 0.06, defocus: 1 };
347
406
  function pressResponse(kind) {
348
407
  return atomLift(kind) === 1 ? LIFTING : SINKING;
349
408
  }
350
- var CONTROL_SIZES = ["touch", "small", "medium", "large", "xlarge"];
351
- var MIN_TOUCH_TARGET_DP = 44;
352
- var MIN_POINTER_TARGET_DP = 24;
353
- var capsule = (height, paddingX) => ({
354
- height,
355
- paddingX,
356
- icon: Math.round(height / 2),
357
- radius: height / 2,
358
- shape: "capsule"
359
- });
360
- var rounded = (height, paddingX) => ({
361
- height,
362
- paddingX,
363
- icon: Math.round(height / 2),
364
- radius: height / 4,
365
- shape: "rounded"
366
- });
367
- var BUTTON_SIZES = {
368
- touch: capsule(44, 20),
369
- small: rounded(24, 10),
370
- medium: rounded(32, 14),
371
- large: capsule(40, 18),
372
- xlarge: capsule(48, 22)
373
- };
374
- function iconButtonSize(size) {
375
- const spec = BUTTON_SIZES[size];
376
- return { ...spec, width: spec.height, paddingX: 0 };
377
- }
378
- var MENU_PADDING = 0.3;
379
- function menuSize(size) {
380
- const spec = BUTTON_SIZES[size];
381
- const padding = Math.round(spec.height * MENU_PADDING);
382
- return {
383
- row: spec.height,
384
- padding,
385
- itemRadius: spec.radius,
386
- radius: spec.radius + padding,
387
- icon: spec.icon,
388
- labelPad: spec.paddingX
389
- };
390
- }
391
- function defaultSize(input) {
392
- return input.touch ? "touch" : "medium";
393
- }
394
- function hitSlop(bodyDp, minTargetDp) {
395
- return bodyDp >= minTargetDp ? 0 : (minTargetDp - bodyDp) / 2;
396
- }
397
- function minTarget(input) {
398
- return input.touch ? MIN_TOUCH_TARGET_DP : MIN_POINTER_TARGET_DP;
399
- }
400
409
 
401
410
  // src/web/a11y.ts
402
411
  var QUERIES = {
@@ -443,6 +452,184 @@ function effectiveA11y(own) {
443
452
  return resolveA11y(systemA11y(), merged);
444
453
  }
445
454
 
455
+ // src/kit/geometry.ts
456
+ function intersectRect(a, b) {
457
+ const x = Math.max(a.x, b.x);
458
+ const y = Math.max(a.y, b.y);
459
+ const right = Math.min(a.x + a.width, b.x + b.width);
460
+ const bottom = Math.min(a.y + a.height, b.y + b.height);
461
+ return right > x && bottom > y ? { x, y, width: right - x, height: bottom - y } : null;
462
+ }
463
+
464
+ // src/kit/adaptive/context.ts
465
+ var POSTURE_FLAT = { kind: "flat" };
466
+ function postureFromSegments(window2, segments) {
467
+ if (segments.length !== 2) return POSTURE_FLAT;
468
+ const [a, b] = [...segments].sort((p, q) => p.x - q.x || p.y - q.y);
469
+ const gapX = b.x - (a.x + a.width);
470
+ if (gapX >= 0 && a.y < b.y + b.height && b.y < a.y + a.height) {
471
+ return { kind: "book", fold: { bounds: { x: a.x + a.width, y: 0, width: gapX, height: window2.height }, occluding: gapX > 0 } };
472
+ }
473
+ const [top, bottom] = [...segments].sort((p, q) => p.y - q.y);
474
+ const gapY = bottom.y - (top.y + top.height);
475
+ if (gapY >= 0) {
476
+ return { kind: "tabletop", fold: { bounds: { x: 0, y: top.y + top.height, width: window2.width, height: gapY }, occluding: gapY > 0 } };
477
+ }
478
+ return POSTURE_FLAT;
479
+ }
480
+ var area = (r) => r ? r.width * r.height : 0;
481
+ function segmentOf(box2, segments) {
482
+ const cx = box2.x + box2.width / 2;
483
+ const cy = box2.y + box2.height / 2;
484
+ const holding = segments.find((s) => cx >= s.x && cx < s.x + s.width && cy >= s.y && cy < s.y + s.height);
485
+ if (holding) return holding;
486
+ let best;
487
+ let most = 0;
488
+ for (const s of segments) {
489
+ const overlap = area(intersectRect(s, box2));
490
+ if (overlap > most) {
491
+ most = overlap;
492
+ best = s;
493
+ }
494
+ }
495
+ if (best) return best;
496
+ let nearest;
497
+ let distance = Number.POSITIVE_INFINITY;
498
+ for (const s of segments) {
499
+ const d = Math.hypot(s.x + s.width / 2 - cx, s.y + s.height / 2 - cy);
500
+ if (d < distance) {
501
+ distance = d;
502
+ nearest = s;
503
+ }
504
+ }
505
+ return nearest;
506
+ }
507
+
508
+ // src/web/environment.ts
509
+ var QUERIES2 = {
510
+ coarse: "(pointer: coarse)",
511
+ anyCoarse: "(any-pointer: coarse)",
512
+ anyHover: "(any-hover: hover)"
513
+ };
514
+ var media = (query) => typeof matchMedia !== "undefined" && matchMedia(query).matches;
515
+ var keyboardSeen = false;
516
+ var inputOverride = {};
517
+ var inputListeners = /* @__PURE__ */ new Set();
518
+ var viewportOverride = null;
519
+ var viewportListeners = /* @__PURE__ */ new Set();
520
+ function systemInput() {
521
+ if (typeof matchMedia === "undefined") return { primary: "fine", anyCoarse: false, hover: true, keyboard: keyboardSeen };
522
+ const coarse = media(QUERIES2.coarse);
523
+ return {
524
+ primary: coarse ? "coarse" : "fine",
525
+ anyCoarse: coarse || media(QUERIES2.anyCoarse),
526
+ hover: media(QUERIES2.anyHover),
527
+ keyboard: keyboardSeen
528
+ };
529
+ }
530
+ function setInputOverride(next) {
531
+ inputOverride = { ...inputOverride, ...next };
532
+ for (const listener of [...inputListeners]) listener();
533
+ }
534
+ function getInputOverride() {
535
+ return inputOverride;
536
+ }
537
+ function effectiveInput() {
538
+ const system = systemInput();
539
+ return {
540
+ primary: inputOverride.primary ?? system.primary,
541
+ anyCoarse: inputOverride.anyCoarse ?? system.anyCoarse,
542
+ hover: inputOverride.hover ?? system.hover,
543
+ keyboard: inputOverride.keyboard ?? system.keyboard
544
+ };
545
+ }
546
+ var isEditable = (target) => target instanceof Element && target.closest('input, textarea, select, [contenteditable]:not([contenteditable="false"])') !== null;
547
+ function onKey(e) {
548
+ const target = e.composedPath()[0] ?? e.target;
549
+ if (keyboardSeen || e.isComposing || e.key === "Unidentified" || e.key === "Process" || isEditable(target)) return;
550
+ keyboardSeen = true;
551
+ for (const listener of [...inputListeners]) listener();
552
+ }
553
+ function watchInput(onChange) {
554
+ inputListeners.add(onChange);
555
+ const lists = typeof matchMedia === "undefined" ? [] : Object.values(QUERIES2).map((q) => matchMedia(q));
556
+ for (const list of lists) list.addEventListener("change", onChange);
557
+ if (inputListeners.size === 1 && typeof window !== "undefined") window.addEventListener("keydown", onKey, true);
558
+ return () => {
559
+ inputListeners.delete(onChange);
560
+ for (const list of lists) list.removeEventListener("change", onChange);
561
+ if (inputListeners.size === 0 && typeof window !== "undefined") window.removeEventListener("keydown", onKey, true);
562
+ };
563
+ }
564
+ var pointerLayers = ({ primary, anyCoarse, hover }) => `${primary} ${anyCoarse} ${hover}`;
565
+ function watchPointerInput(onChange) {
566
+ let last2 = pointerLayers(effectiveInput());
567
+ return watchInput(() => {
568
+ const next = pointerLayers(effectiveInput());
569
+ if (next === last2) return;
570
+ last2 = next;
571
+ onChange();
572
+ });
573
+ }
574
+ function setViewportOverride(next) {
575
+ viewportOverride = next;
576
+ for (const listener of [...viewportListeners]) listener();
577
+ }
578
+ function viewportSegments() {
579
+ if (viewportOverride?.root.isConnected) {
580
+ const r = viewportOverride.root.getBoundingClientRect();
581
+ const viewport2 = { x: r.x, y: r.y, width: r.width, height: r.height };
582
+ const local = viewportOverride.segments ?? [{ x: 0, y: 0, width: r.width, height: r.height }];
583
+ return { viewport: viewport2, segments: local.map((s) => ({ x: s.x + r.x, y: s.y + r.y, width: s.width, height: s.height })) };
584
+ }
585
+ const root = typeof document === "undefined" ? null : document.documentElement;
586
+ const viewport = { x: 0, y: 0, width: root?.clientWidth ?? 0, height: root?.clientHeight ?? 0 };
587
+ const api = typeof window === "undefined" ? null : window.viewport?.segments;
588
+ const segments = api && api.length > 1 ? api.map((s) => ({ x: s.x, y: s.y, width: s.width, height: s.height })) : [viewport];
589
+ return { viewport, segments };
590
+ }
591
+ function watchViewport(onChange) {
592
+ viewportListeners.add(onChange);
593
+ if (typeof window !== "undefined") window.addEventListener("resize", onChange);
594
+ return () => {
595
+ viewportListeners.delete(onChange);
596
+ if (typeof window !== "undefined") window.removeEventListener("resize", onChange);
597
+ };
598
+ }
599
+ function contextOf(root) {
600
+ const r = root.getBoundingClientRect();
601
+ const window2 = { width: r.width, height: r.height };
602
+ const box2 = { x: r.x, y: r.y, width: r.width, height: r.height };
603
+ const local = viewportSegments().segments.map((s) => intersectRect(s, box2)).filter((s) => s !== null).map((s) => ({ x: s.x - r.x, y: s.y - r.y, width: s.width, height: s.height }));
604
+ return {
605
+ window: window2,
606
+ input: effectiveInput(),
607
+ posture: postureFromSegments(window2, local),
608
+ reduceMotion: effectiveA11y({}).reduceMotion
609
+ };
610
+ }
611
+ function watchAdaptiveContext(root, onChange) {
612
+ let last2 = "";
613
+ const emit = () => {
614
+ if (!root.isConnected) return;
615
+ const ctx = contextOf(root);
616
+ const key = JSON.stringify(ctx);
617
+ if (key === last2) return;
618
+ last2 = key;
619
+ onChange(ctx);
620
+ };
621
+ const cleanups = [watchInput(emit), watchViewport(emit), watchSystemA11y(emit), watchA11yOverride(emit)];
622
+ if (typeof ResizeObserver !== "undefined") {
623
+ const observer = new ResizeObserver(emit);
624
+ observer.observe(root);
625
+ cleanups.push(() => observer.disconnect());
626
+ }
627
+ emit();
628
+ return () => {
629
+ for (const cleanup of cleanups) cleanup();
630
+ };
631
+ }
632
+
446
633
  // src/web/glass.ts
447
634
  async function attachControlGlass(el, params, stillWanted) {
448
635
  const { attachGlass, probeBackdrop } = await import('vireglass/dom');
@@ -482,17 +669,618 @@ async function attachControlGlass(el, params, stillWanted) {
482
669
  };
483
670
  }
484
671
 
485
- // src/web/input.ts
486
- var COARSE = "(pointer: coarse)";
487
- function isCoarsePointer() {
488
- return typeof matchMedia !== "undefined" && matchMedia(COARSE).matches;
672
+ // src/kit/adaptive/classes.ts
673
+ var WIDTH_CLASSES = ["compact", "medium", "expanded", "large", "xlarge"];
674
+ var WIDTH_BREAKPOINTS = { medium: 600, expanded: 840, large: 1200, xlarge: 1600 };
675
+ var HEIGHT_BREAKPOINTS = { medium: 480, expanded: 900 };
676
+ function widthClass(width) {
677
+ if (!(width >= WIDTH_BREAKPOINTS.medium)) return "compact";
678
+ if (width < WIDTH_BREAKPOINTS.expanded) return "medium";
679
+ if (width < WIDTH_BREAKPOINTS.large) return "expanded";
680
+ return width < WIDTH_BREAKPOINTS.xlarge ? "large" : "xlarge";
489
681
  }
490
- function watchCoarsePointer(onChange) {
491
- if (typeof matchMedia === "undefined") return () => {
682
+ function heightClass(height) {
683
+ if (!(height >= HEIGHT_BREAKPOINTS.medium)) return "compact";
684
+ return height < HEIGHT_BREAKPOINTS.expanded ? "medium" : "expanded";
685
+ }
686
+ function classifyWindow(size) {
687
+ return { width: widthClass(size.width), height: heightClass(size.height) };
688
+ }
689
+ function widthAtLeast(value, minimum) {
690
+ return WIDTH_CLASSES.indexOf(value) >= WIDTH_CLASSES.indexOf(minimum);
691
+ }
692
+
693
+ // src/kit/adaptive/navigation.ts
694
+ var BAR_SLOTS = 5;
695
+ function formFor(ctx, spec) {
696
+ const { width, height } = classifyWindow(ctx.window);
697
+ if (ctx.posture.kind === "tabletop") return "bar";
698
+ if (widthAtLeast(width, "large")) return spec.sidebarCollapsed ? "rail" : "sidebar";
699
+ if (height === "compact") return "rail";
700
+ if (width === "compact") return "bar";
701
+ if (width === "medium" && (spec.panes ?? 1) > 1 && ctx.posture.kind !== "book") return "bar";
702
+ return "rail";
703
+ }
704
+ function navigationForm(ctx, spec) {
705
+ const form = formFor(ctx, spec);
706
+ const width = classifyWindow(ctx.window).width;
707
+ const large = widthAtLeast(width, "large");
708
+ const slots = BAR_SLOTS - (spec.search ? 1 : 0);
709
+ const primary = Math.max(0, Math.min(spec.primary, slots));
710
+ const secondary = Math.max(0, spec.secondary ?? 0) + Math.max(0, spec.primary - primary);
711
+ let search = null;
712
+ if (spec.search) {
713
+ const shortcut = large || ctx.input.keyboard;
714
+ if (large) search = { form: "field", place: form === "sidebar" ? "sidebar" : "toolbar", shortcut };
715
+ else search = form === "bar" ? { form: "button", place: "bar", shortcut } : { form: "item", place: "rail", shortcut };
716
+ }
717
+ return {
718
+ form,
719
+ edge: form === "bar" ? "bottom" : "leading",
720
+ primary,
721
+ secondary,
722
+ secondaryVisible: form === "sidebar",
723
+ search,
724
+ expandable: form !== "rail" ? null : large ? "beside" : width === "expanded" ? "over" : null,
725
+ collapsible: form === "sidebar"
726
+ };
727
+ }
728
+ function bottomAccessory(nav, state = {}) {
729
+ return state.barMinimized ? { placement: "in-bar", compact: true } : { placement: "above-bar", compact: false };
730
+ }
731
+
732
+ // src/kit/nav/layout.ts
733
+ var NAV_SIZES = {
734
+ bar: { height: 56, item: 74, minItem: 44, inset: 3, margin: 16, searchGap: 8, minimized: 44 },
735
+ rail: { width: 80, item: 56, gap: 4, padding: 8, margin: 8, head: 48 },
736
+ sidebar: { width: 280, row: { touch: 44, pointer: 36 }, padding: 8, margin: 8, head: 48, separator: 12 },
737
+ accessory: { height: 48, gap: 8, inline: 44, content: 56, margin: 8 },
738
+ lens: { inflate: 6 },
739
+ toggle: 44
740
+ };
741
+ var box = (x, y, width, height, radius) => ({ x, y, width, height, radius });
742
+ function barLayout(input, items, minimized) {
743
+ const { bar, accessory } = NAV_SIZES;
744
+ const { width, height } = input.window;
745
+ const bottom = Math.max(input.bottomInset ?? 0, bar.margin);
746
+ const hasSearch = input.search?.form === "button";
747
+ const placement = input.accessory ? bottomAccessory({ }, { barMinimized: minimized }).placement : null;
748
+ const selected = input.selected >= 0 && input.selected < items ? input.selected : -1;
749
+ if (minimized) {
750
+ const centre = height - bottom - bar.height / 2;
751
+ const d = bar.minimized;
752
+ const body2 = box(bar.margin, centre - d / 2, d, d, d / 2);
753
+ const search2 = hasSearch ? box(width - bar.margin - d, body2.y, d, d, d / 2) : null;
754
+ let acc2 = null;
755
+ if (placement) {
756
+ const x2 = body2.x + d + accessory.gap;
757
+ const right = search2 ? search2.x - accessory.gap : width - bar.margin;
758
+ acc2 = box(x2, body2.y, Math.max(0, right - x2), accessory.inline, accessory.inline / 2);
759
+ }
760
+ return {
761
+ form: "bar",
762
+ minimized: true,
763
+ body: body2,
764
+ items: Array.from({ length: items }, () => ({ ...body2 })),
765
+ pill: selected >= 0 ? { ...body2 } : null,
766
+ search: search2,
767
+ accessory: acc2,
768
+ toggle: null,
769
+ accessoryPlacement: placement,
770
+ insets: { bottom: height - body2.y, start: 0 }
771
+ };
772
+ }
773
+ const searchBlock = hasSearch ? bar.height + bar.searchGap : 0;
774
+ const fit = items > 0 ? (width - 2 * bar.margin - searchBlock - 2 * bar.inset) / items : bar.item;
775
+ const itemWidth = Math.max(bar.minItem, Math.min(bar.item, fit));
776
+ const bodyWidth = items * itemWidth + 2 * bar.inset;
777
+ const x = Math.max(bar.margin, (width - bodyWidth - searchBlock) / 2);
778
+ const body = box(x, height - bottom - bar.height, bodyWidth, bar.height, bar.height / 2);
779
+ const cellHeight = bar.height - 2 * bar.inset;
780
+ const cells = Array.from(
781
+ { length: items },
782
+ (_, i) => box(x + bar.inset + i * itemWidth, body.y + bar.inset, itemWidth, cellHeight, cellHeight / 2)
783
+ );
784
+ const search = hasSearch ? box(x + bodyWidth + bar.searchGap, body.y, bar.height, bar.height, bar.height / 2) : null;
785
+ let acc = null;
786
+ if (placement) {
787
+ acc = box(body.x, body.y - accessory.gap - accessory.height, bodyWidth + searchBlock, accessory.height, accessory.height / 2);
788
+ }
789
+ return {
790
+ form: "bar",
791
+ minimized: false,
792
+ body,
793
+ items: cells,
794
+ pill: selected >= 0 ? { ...cells[selected] } : null,
795
+ search,
796
+ accessory: acc,
797
+ toggle: null,
798
+ accessoryPlacement: placement,
799
+ insets: { bottom: height - body.y + (acc ? accessory.height + accessory.gap : 0), start: 0 }
800
+ };
801
+ }
802
+ function contentAccessory(input, start) {
803
+ const { accessory } = NAV_SIZES;
804
+ const base = input.bottomInset ?? 0;
805
+ if (!input.accessory) return { box: null, bottom: base };
806
+ const bottom = Math.max(base, accessory.margin);
807
+ const width = Math.max(0, input.window.width - start - accessory.margin);
808
+ const acc = box(start, input.window.height - bottom - accessory.content, width, accessory.content, accessory.content / 2);
809
+ return { box: acc, bottom: bottom + accessory.content };
810
+ }
811
+ function headToggle(body, padding, head, dx) {
812
+ const d = NAV_SIZES.toggle;
813
+ return box(body.x + dx, body.y + padding + (head - d) / 2, d, d, d / 2);
814
+ }
815
+ function inset(b, d) {
816
+ return box(b.x + d, b.y + d, b.width - 2 * d, b.height - 2 * d, (Math.min(b.width, b.height) - 2 * d) / 2);
817
+ }
818
+ function railLayout(input, items) {
819
+ const { rail, bar } = NAV_SIZES;
820
+ const hasSearch = input.search?.form === "item";
821
+ const n = items + (hasSearch ? 1 : 0);
822
+ const head = input.toggle ? rail.head : 0;
823
+ const natural = rail.padding * 2 + head + n * rail.item + Math.max(n - 1, 0) * rail.gap;
824
+ const height = Math.min(natural, Math.max(input.window.height - 2 * rail.margin, 0));
825
+ const body = box(rail.margin, rail.margin, rail.width, height, 28);
826
+ const cell = (i) => box(body.x + rail.padding, body.y + rail.padding + head + i * (rail.item + rail.gap), rail.width - 2 * rail.padding, rail.item, 24);
827
+ const cells = Array.from({ length: items }, (_, i) => cell(i));
828
+ const selected = input.selected >= 0 && input.selected < items ? input.selected : -1;
829
+ const start = body.x + body.width + rail.margin;
830
+ const acc = contentAccessory(input, start);
831
+ return {
832
+ form: "rail",
833
+ minimized: false,
834
+ body,
835
+ items: cells,
836
+ pill: selected >= 0 ? inset(cells[selected], bar.inset) : null,
837
+ search: hasSearch ? cell(items) : null,
838
+ accessory: acc.box,
839
+ toggle: input.toggle ? headToggle(body, rail.padding, rail.head, (rail.width - NAV_SIZES.toggle) / 2) : null,
840
+ accessoryPlacement: acc.box ? "content-bottom" : null,
841
+ insets: { bottom: acc.bottom, start }
842
+ };
843
+ }
844
+ function sidebarLayout(input, items, secondary) {
845
+ const { sidebar } = NAV_SIZES;
846
+ const row = sidebar.row[input.tier];
847
+ const body = box(sidebar.margin, sidebar.margin, sidebar.width, input.window.height - 2 * sidebar.margin, 24);
848
+ const rowWidth = sidebar.width - 2 * sidebar.padding;
849
+ const rowAt = (y2) => box(body.x + sidebar.padding, y2, rowWidth, row, row / 2);
850
+ let y = body.y + sidebar.padding + (input.toggle ? sidebar.head : 0);
851
+ let search = null;
852
+ if (input.search?.form === "field") {
853
+ search = rowAt(y);
854
+ y += row;
855
+ }
856
+ const cells = [];
857
+ for (let i = 0; i < items; i += 1, y += row) cells.push(rowAt(y));
858
+ if (secondary > 0) y += sidebar.separator;
859
+ for (let i = 0; i < secondary; i += 1, y += row) cells.push(rowAt(y));
860
+ const selected = input.selected >= 0 && input.selected < cells.length ? input.selected : -1;
861
+ const start = body.x + body.width + sidebar.margin;
862
+ const acc = contentAccessory(input, start);
863
+ return {
864
+ form: "sidebar",
865
+ minimized: false,
866
+ body,
867
+ items: cells,
868
+ pill: selected >= 0 ? { ...cells[selected] } : null,
869
+ search,
870
+ accessory: acc.box,
871
+ toggle: input.toggle ? headToggle(body, sidebar.padding, sidebar.head, sidebar.width - sidebar.padding - NAV_SIZES.toggle) : null,
872
+ accessoryPlacement: acc.box ? "content-bottom" : null,
873
+ insets: { bottom: acc.bottom, start }
492
874
  };
493
- const list = matchMedia(COARSE);
494
- list.addEventListener("change", onChange);
495
- return () => list.removeEventListener("change", onChange);
875
+ }
876
+ function navLayout(input) {
877
+ const items = Math.max(0, Math.floor(input.items));
878
+ const secondary = Math.max(0, Math.floor(input.secondary));
879
+ if (input.form === "bar") return barLayout(input, items, !!input.minimized);
880
+ return input.form === "rail" ? railLayout(input, items) : sidebarLayout(input, items, secondary);
881
+ }
882
+
883
+ // src/kit/nav/drag.ts
884
+ var MAX_STRETCH = 0.25;
885
+ var SATURATION_SPEED = 1500;
886
+ var STRETCH_TAU = 0.05;
887
+ var OVERHANG = 4;
888
+ var LAND = { response: 0.2, damping: 0.8 };
889
+ var LIFT = { response: 0.2, damping: 1 };
890
+ var createNavDrag = () => ({
891
+ active: false,
892
+ x: { x: 0, v: 0, target: 0 },
893
+ lift: { x: 0, v: 0, target: 0 },
894
+ stretch: 0,
895
+ index: 0,
896
+ lastX: 0
897
+ });
898
+ var draggable = (layout) => layout.form === "bar" && !layout.minimized && layout.items.length > 0;
899
+ function lensSize(layout) {
900
+ const cell = layout.items[0];
901
+ const pad = 2 * NAV_SIZES.lens.inflate;
902
+ return { width: cell.width + pad, height: cell.height + pad };
903
+ }
904
+ function clampCentre(layout, x) {
905
+ const half = lensSize(layout).width / 2;
906
+ const { body } = layout;
907
+ return Math.min(Math.max(x, body.x + half - OVERHANG), body.x + body.width - half + OVERHANG);
908
+ }
909
+ function indexAt(layout, x) {
910
+ const first2 = layout.items[0];
911
+ const i = Math.floor((x - first2.x) / first2.width);
912
+ return Math.min(Math.max(i, 0), layout.items.length - 1);
913
+ }
914
+ var centreOf = (layout, index) => layout.items[index].x + layout.items[index].width / 2;
915
+ function drop(state) {
916
+ state.active = false;
917
+ state.lift.x = 0;
918
+ state.lift.v = 0;
919
+ state.lift.target = 0;
920
+ }
921
+ function settleTowards(state, layout, index) {
922
+ state.active = false;
923
+ state.index = index;
924
+ state.x.target = centreOf(layout, index);
925
+ state.lift.target = 0;
926
+ }
927
+ function pressNavDrag(state, layout, pointerX) {
928
+ if (!draggable(layout)) return;
929
+ const x = clampCentre(layout, pointerX);
930
+ state.active = true;
931
+ state.x.x = x;
932
+ state.x.v = 0;
933
+ state.x.target = x;
934
+ state.lift.target = 1;
935
+ state.stretch = 0;
936
+ state.lastX = pointerX;
937
+ state.index = indexAt(layout, x);
938
+ }
939
+ function moveNavDrag(state, layout, pointerX, dt) {
940
+ if (!state.active || !draggable(layout)) return;
941
+ const x = clampCentre(layout, pointerX);
942
+ state.x.x = x;
943
+ state.x.v = 0;
944
+ state.x.target = x;
945
+ const speed = dt > 0 ? Math.abs(pointerX - state.lastX) / dt : 0;
946
+ const goal = MAX_STRETCH * Math.min(speed / SATURATION_SPEED, 1);
947
+ state.stretch += (goal - state.stretch) * (dt > 0 ? 1 - Math.exp(-dt / STRETCH_TAU) : 1);
948
+ state.lastX = pointerX;
949
+ state.index = indexAt(layout, x);
950
+ }
951
+ function releaseNavDrag(state, layout) {
952
+ if (!draggable(layout)) {
953
+ drop(state);
954
+ return state.index;
955
+ }
956
+ const index = indexAt(layout, state.x.x);
957
+ settleTowards(state, layout, index);
958
+ return index;
959
+ }
960
+ function cancelNavDrag(state, layout, selected) {
961
+ if (!draggable(layout)) {
962
+ drop(state);
963
+ return;
964
+ }
965
+ const index = selected >= 0 && selected < layout.items.length ? selected : indexAt(layout, state.x.x);
966
+ settleTowards(state, layout, index);
967
+ }
968
+ function stepNavDrag(state, dt, reduceMotion = false) {
969
+ let moving = state.active;
970
+ if (state.stretch > 0) {
971
+ state.stretch *= Math.exp(-Math.max(dt, 0) / STRETCH_TAU);
972
+ if (reduceMotion || state.stretch < 1e-3) state.stretch = 0;
973
+ else moving = true;
974
+ }
975
+ if (!state.active && stepSpring(state.x, dt, LAND.response, LAND.damping, reduceMotion)) moving = true;
976
+ if (stepSpring(state.lift, dt, LIFT.response, LIFT.damping, reduceMotion)) moving = true;
977
+ return moving;
978
+ }
979
+ function navDragFrame(state, layout) {
980
+ if (!draggable(layout)) return null;
981
+ const lift = Math.min(Math.max(state.lift.x, 0), 1);
982
+ if (!state.active && lift === 0 && state.x.x === state.x.target) return null;
983
+ const size = lensSize(layout);
984
+ const width = size.width * (1 + state.stretch);
985
+ const height = size.height * (1 - state.stretch / 2);
986
+ const cy = layout.body.y + layout.body.height / 2;
987
+ return {
988
+ lens: { x: state.x.x - width / 2, y: cy - height / 2, width, height, radius: height / 2 },
989
+ lift,
990
+ hover: indexAt(layout, state.x.x)
991
+ };
992
+ }
993
+
994
+ // src/kit/nav/form.ts
995
+ var SEARCH = {
996
+ bar: { form: "button", place: "bar" },
997
+ rail: { form: "item", place: "rail" },
998
+ sidebar: { form: "field", place: "sidebar" }
999
+ };
1000
+ function fixedNavigationForm(nav, form) {
1001
+ if (nav.form === form) return nav;
1002
+ return {
1003
+ ...nav,
1004
+ form,
1005
+ edge: form === "bar" ? "bottom" : "leading",
1006
+ secondaryVisible: form === "sidebar",
1007
+ search: nav.search ? { ...nav.search, ...SEARCH[form] } : null,
1008
+ expandable: form === "rail" ? nav.expandable : null,
1009
+ collapsible: form === "sidebar"
1010
+ };
1011
+ }
1012
+
1013
+ // src/kit/nav/minimize.ts
1014
+ var MINIMIZE_THRESHOLD = 24;
1015
+ var MINIMIZE_JITTER = 4;
1016
+ var createMinimize = (scrollTop = 0) => ({ minimized: false, run: 0, last: scrollTop });
1017
+ function stepMinimize(state, scrollTop) {
1018
+ const delta = scrollTop - state.last;
1019
+ state.last = scrollTop;
1020
+ if (scrollTop <= 0) {
1021
+ state.minimized = false;
1022
+ state.run = 0;
1023
+ return false;
1024
+ }
1025
+ if (delta === 0) return state.minimized;
1026
+ const along2 = Math.sign(delta) === Math.sign(state.run) || Math.abs(delta) < MINIMIZE_JITTER;
1027
+ state.run = along2 ? state.run + delta : delta;
1028
+ if (state.run >= MINIMIZE_THRESHOLD) state.minimized = true;
1029
+ else if (state.run <= -MINIMIZE_THRESHOLD) state.minimized = false;
1030
+ return state.minimized;
1031
+ }
1032
+ var clampScrollTop = (scrollTop, scrollHeight, clientHeight) => Math.min(Math.max(scrollTop, 0), Math.max(scrollHeight - clientHeight, 0));
1033
+ function expandMinimize(state) {
1034
+ state.minimized = false;
1035
+ state.run = 0;
1036
+ }
1037
+
1038
+ // src/kit/adaptive/form-morph.ts
1039
+ var FORM_MORPH_TIMING = { response: DEFAULT_GROWTH_TIMING.growOpen, damping: 1 };
1040
+ var createFormMorph = () => ({ t: { x: 0, v: 0, target: 0 }, fromInk: 1 });
1041
+ function stepFormMorph(state, dt, toward, instant = false) {
1042
+ state.t.target = toward;
1043
+ return stepSpring(state.t, dt, FORM_MORPH_TIMING.response, FORM_MORPH_TIMING.damping, instant);
1044
+ }
1045
+ var OUT_END = 0.45;
1046
+ var IN_START = 0.55;
1047
+ function formMorphFrame(from, to, state) {
1048
+ const t = Math.min(Math.max(state.t.x, 0), 1);
1049
+ const mix3 = (a, b) => a + (b - a) * t;
1050
+ return {
1051
+ box: {
1052
+ x: mix3(from.x, to.x),
1053
+ y: mix3(from.y, to.y),
1054
+ width: mix3(from.width, to.width),
1055
+ height: mix3(from.height, to.height),
1056
+ radius: mix3(from.radius, to.radius)
1057
+ },
1058
+ ink: inkAt(state, t),
1059
+ showing: t < 0.5 ? "from" : "to"
1060
+ };
1061
+ }
1062
+ function inkAt(state, t) {
1063
+ return { outgoing: (state.fromInk ?? 1) * (1 - smoothstep(0, OUT_END, t)), incoming: smoothstep(IN_START, 1, t) };
1064
+ }
1065
+
1066
+ // src/kit/nav/morph.ts
1067
+ var NAV_MORPH_TIMING = { response: 1.06, damping: 1 };
1068
+ var NAV_MINIMIZE_TIMING = FORM_MORPH_TIMING;
1069
+ var LIFT_END = 0.12;
1070
+ var CONTRACT = [0.08, 0.38];
1071
+ var TRAVEL = [0.36, 0.56];
1072
+ var GROW = [0.52, 0.86];
1073
+ var SETTLE_START = 0.82;
1074
+ var INK_OUT = [0.08, 0.28];
1075
+ var INK_IN = [0.62, 0.86];
1076
+ var SWAP_AT = 0.46;
1077
+ var SMOOTHING = 24;
1078
+ var SEED = 8;
1079
+ var STAND_IN = 44;
1080
+ var clamp01 = (x) => Math.min(Math.max(x, 0), 1);
1081
+ var mix2 = (a, b, t) => a + (b - a) * t;
1082
+ var mixBox = (a, b, t) => ({
1083
+ x: mix2(a.x, b.x, t),
1084
+ y: mix2(a.y, b.y, t),
1085
+ width: mix2(a.width, b.width, t),
1086
+ height: mix2(a.height, b.height, t),
1087
+ radius: mix2(a.radius, b.radius, t)
1088
+ });
1089
+ var inflate = (b, d) => ({
1090
+ x: b.x - d,
1091
+ y: b.y - d,
1092
+ width: b.width + 2 * d,
1093
+ height: b.height + 2 * d,
1094
+ radius: b.radius + d
1095
+ });
1096
+ var centred = (cx, cy, size) => ({ x: cx - size / 2, y: cy - size / 2, width: size, height: size, radius: size / 2 });
1097
+ var trailingEnd = (body, size) => centred(body.x + body.width - body.height / 2, body.y + body.height / 2, size);
1098
+ var pillOrStandIn = (layout) => layout.pill ?? centred(layout.body.x + layout.body.width / 2, layout.body.y + layout.body.height / 2, STAND_IN);
1099
+ var separateSearch = (layout) => layout.form === "bar" ? layout.search : null;
1100
+ function restFrame(layout, side) {
1101
+ const search = separateSearch(layout);
1102
+ return {
1103
+ phase: "rest",
1104
+ body: layout.body,
1105
+ second: search ? { box: search, smoothing: 0 } : null,
1106
+ lens: null,
1107
+ ink: side === "from" ? { outgoing: 1, incoming: 0 } : { outgoing: 0, incoming: 1 },
1108
+ showing: side
1109
+ };
1110
+ }
1111
+ var layoutOrigin = (layout) => {
1112
+ const search = separateSearch(layout);
1113
+ return {
1114
+ body: layout.body,
1115
+ second: search ? { box: search, smoothing: 0 } : null,
1116
+ lens: { box: pillOrStandIn(layout), lift: 0 },
1117
+ ink: 1,
1118
+ lead: 0,
1119
+ hold: null
1120
+ };
1121
+ };
1122
+ var frameOrigin = (frame) => ({
1123
+ body: frame.body,
1124
+ second: frame.second,
1125
+ lens: frame.lens ?? { box: centred(frame.body.x + frame.body.width / 2, frame.body.y + frame.body.height / 2, STAND_IN), lift: 0 },
1126
+ ink: Math.max(frame.ink.outgoing, frame.ink.incoming),
1127
+ lead: CONTRACT[0],
1128
+ hold: null
1129
+ });
1130
+ function phaseAt(tau) {
1131
+ if (tau < CONTRACT[0]) return "lift";
1132
+ if (tau < TRAVEL[0]) return "contract";
1133
+ if (tau < GROW[0]) return "travel";
1134
+ return tau < SETTLE_START ? "grow" : "settle";
1135
+ }
1136
+ function lensFrame(o, to, t) {
1137
+ const tau = o.lead + (1 - o.lead) * t;
1138
+ const u = smoothstep(o.lead, LIFT_END, tau);
1139
+ const base = inflate(o.lens.box, NAV_SIZES.lens.inflate * (1 - o.lens.lift) * u);
1140
+ const lift = mix2(o.lens.lift, 1, u);
1141
+ const toPill = pillOrStandIn(to);
1142
+ const k = smoothstep(TRAVEL[0], TRAVEL[1], tau);
1143
+ const kk = o.hold ? clamp01((k - o.hold.k0) / (1 - o.hold.k0)) : k;
1144
+ const travelling = mixBox(o.hold?.box ?? base, inflate(toPill, NAV_SIZES.lens.inflate), kk);
1145
+ const c = smoothstep(CONTRACT[0], CONTRACT[1], tau);
1146
+ const g = smoothstep(GROW[0], GROW[1], tau);
1147
+ const s = smoothstep(SETTLE_START, 1, tau);
1148
+ const contracted = mixBox(o.body, travelling, c);
1149
+ const body = mixBox(contracted, to.body, g);
1150
+ let second = null;
1151
+ const budding = separateSearch(to);
1152
+ if (o.second && c < 1) {
1153
+ second = {
1154
+ box: mixBox(o.second.box, trailingEnd(contracted, 0), c),
1155
+ smoothing: mix2(o.second.smoothing, SMOOTHING, c)
1156
+ };
1157
+ } else if (budding && g > 0) {
1158
+ second = { box: mixBox(trailingEnd(body, SEED), budding, g), smoothing: SMOOTHING * (1 - g) };
1159
+ }
1160
+ return {
1161
+ phase: phaseAt(tau),
1162
+ body,
1163
+ second,
1164
+ lens: { box: mixBox(travelling, toPill, s), lift: lift * (1 - s) },
1165
+ ink: {
1166
+ outgoing: o.ink * (1 - smoothstep(INK_OUT[0], INK_OUT[1], tau)),
1167
+ incoming: smoothstep(INK_IN[0], INK_IN[1], tau)
1168
+ },
1169
+ showing: tau < SWAP_AT ? "from" : "to"
1170
+ };
1171
+ }
1172
+ function contractFrame(o, to, t) {
1173
+ const body = mixBox(o.body, to.body, t);
1174
+ const target = separateSearch(to);
1175
+ let second = null;
1176
+ if (o.second && target) {
1177
+ second = { box: mixBox(o.second.box, target, t), smoothing: mix2(o.second.smoothing, 0, t) };
1178
+ } else if (o.second) {
1179
+ second = { box: mixBox(o.second.box, trailingEnd(body, 0), t), smoothing: mix2(o.second.smoothing, SMOOTHING, t) };
1180
+ } else if (target) {
1181
+ second = { box: mixBox(trailingEnd(body, SEED), target, t), smoothing: SMOOTHING * (1 - t) };
1182
+ }
1183
+ return {
1184
+ phase: "contract",
1185
+ body,
1186
+ second,
1187
+ lens: null,
1188
+ ink: { outgoing: o.ink * (1 - smoothstep(0, 0.45, t)), incoming: smoothstep(0.55, 1, t) },
1189
+ showing: t < 0.5 ? "from" : "to"
1190
+ };
1191
+ }
1192
+ var frameFrom = (o, to, t, via) => via === "lens" ? lensFrame(o, to, t) : contractFrame(o, to, t);
1193
+ function navMorphFrame(from, to, t, via = "lens") {
1194
+ if (t <= 0) return restFrame(from, "from");
1195
+ if (t >= 1) return restFrame(to, "to");
1196
+ return frameFrom(layoutOrigin(from), to, t, via);
1197
+ }
1198
+ var createNavMorph = (at) => ({
1199
+ t: { x: 0, v: 0, target: 0 },
1200
+ from: at,
1201
+ to: at,
1202
+ via: "lens",
1203
+ pivot: null,
1204
+ pivotAt: 0
1205
+ });
1206
+ var sameForm = (a, b) => a.form === b.form && a.minimized === b.minimized;
1207
+ var atRest = (s) => s.t.v === 0 && s.t.x === s.t.target && (s.t.x === 0 || s.t.x === 1);
1208
+ function currentNavFrame(state) {
1209
+ const x = clamp01(state.t.x);
1210
+ if (x >= 1) return restFrame(state.to, "to");
1211
+ const { pivot } = state;
1212
+ if (!pivot) return navMorphFrame(state.from, state.to, x, state.via);
1213
+ if (state.pivotAt > 0 && pivot.lens) {
1214
+ const origin = layoutOrigin(state.from);
1215
+ origin.hold = { box: pivot.lens.box, k0: smoothstep(TRAVEL[0], TRAVEL[1], state.pivotAt) };
1216
+ return lensFrame(origin, state.to, x);
1217
+ }
1218
+ return frameFrom(frameOrigin(pivot), state.to, x, state.via);
1219
+ }
1220
+ function aimNavMorph(state, next, via = "lens") {
1221
+ const { t } = state;
1222
+ if (atRest(state)) {
1223
+ const current = t.x >= 1 ? state.to : state.from;
1224
+ if (sameForm(current, next)) {
1225
+ if (t.x >= 1) state.to = next;
1226
+ else state.from = next;
1227
+ state.pivot = null;
1228
+ return;
1229
+ }
1230
+ state.from = current;
1231
+ state.to = next;
1232
+ state.via = via;
1233
+ state.pivot = null;
1234
+ state.pivotAt = 0;
1235
+ t.x = 0;
1236
+ t.v = 0;
1237
+ t.target = 1;
1238
+ return;
1239
+ }
1240
+ if (!state.pivot) {
1241
+ if (sameForm(next, state.from)) {
1242
+ state.from = next;
1243
+ t.target = 0;
1244
+ return;
1245
+ }
1246
+ if (sameForm(next, state.to)) {
1247
+ state.to = next;
1248
+ t.target = 1;
1249
+ return;
1250
+ }
1251
+ const x = clamp01(t.x);
1252
+ if (via === "lens" && state.via === "lens" && x < GROW[0]) {
1253
+ if (x >= TRAVEL[0]) {
1254
+ state.pivot = currentNavFrame(state);
1255
+ state.pivotAt = x;
1256
+ }
1257
+ state.to = next;
1258
+ t.target = 1;
1259
+ return;
1260
+ }
1261
+ } else if (sameForm(next, state.to)) {
1262
+ state.to = next;
1263
+ t.target = 1;
1264
+ return;
1265
+ }
1266
+ const frozen = currentNavFrame(state);
1267
+ if (frozen.showing === "to") state.from = state.to;
1268
+ state.pivot = frozen;
1269
+ state.pivotAt = 0;
1270
+ state.to = next;
1271
+ state.via = via;
1272
+ t.x = 0;
1273
+ t.v = 0;
1274
+ t.target = 1;
1275
+ }
1276
+ function stepNavMorph(state, dt, reduceMotion = false) {
1277
+ const timing = state.via === "lens" ? NAV_MORPH_TIMING : NAV_MINIMIZE_TIMING;
1278
+ const moved = stepSpring(state.t, dt, timing.response, timing.damping, reduceMotion);
1279
+ if (!moved && state.t.x >= 1) {
1280
+ state.pivot = null;
1281
+ state.pivotAt = 0;
1282
+ }
1283
+ return moved;
496
1284
  }
497
1285
 
498
1286
  // src/web/styles.ts
@@ -501,6 +1289,10 @@ var BODY = `${HOST}>button`;
501
1289
  var MENU = "vire-menu[data-vk]";
502
1290
  var SURFACE = `${MENU}>[popover]`;
503
1291
  var ITEM = `${SURFACE}>button`;
1292
+ var NAV = "vire-nav[data-vk]";
1293
+ var BODIES = `${NAV}>nav,${NAV}>[data-accessory]`;
1294
+ var LINKS = `${NAV}>nav a`;
1295
+ var CIRCLE = "[data-form=bar]>nav>a[data-search]";
504
1296
  var GLASS_BODY = `background:color-mix(in srgb,color-mix(in srgb,#fff calc((var(--vk-body-white) + var(--vk-body-lift)*2)*100%),var(--vireglass-tint-color,#fff)) calc(var(--vk-body-alpha)*100%),transparent);
505
1297
  background:oklab(from color-mix(in srgb,#fff calc(var(--vk-body-white)*100%),var(--vireglass-tint-color,#fff)) calc(l + var(--vk-body-lift)) a b / var(--vk-body-alpha))`;
506
1298
  var FROST_INK = "color:oklab(from var(--vireglass-tint-color,#fff) calc(.95 - .87*clamp(0,(l + var(--vk-body-lift) - .6)*20,1)) 0 0)";
@@ -571,6 +1363,72 @@ ${MENU}[data-surface=plain]>[popover]{background:Canvas;${SHADOW}}
571
1363
  ${MENU}[data-surface=frosted]>[popover]{background:color-mix(in srgb,Canvas 88%,currentColor);
572
1364
  backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);${SHADOW}}
573
1365
 
1366
+ ${NAV}{display:block;position:absolute;inset:0;pointer-events:none;--vk-ink:1;--vk-nav-icon:24px;--vk-body-alpha:0;--vk-body-white:0;--vk-body-lift:0}
1367
+ ${BODIES}{position:absolute;left:var(--vk-x,0px);top:var(--vk-y,0px);width:var(--vk-w,0px);height:var(--vk-h,0px);
1368
+ box-sizing:border-box;margin:0;padding:0;border-radius:var(--vk-r,0px);color:inherit;font:inherit}
1369
+ ${NAV}>nav{pointer-events:auto;touch-action:pan-y;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent;scrollbar-width:none}
1370
+ ${NAV}>[data-accessory]{pointer-events:auto;overflow:clip}
1371
+ ${NAV}>nav ul{position:absolute;inset:0;margin:0;padding:0;list-style:none;overflow:clip;border-radius:inherit;pointer-events:none}
1372
+ ${NAV}>nav li{display:block;height:0;margin:0;padding:0}
1373
+ ${NAV}>nav [hidden],${NAV}>[data-accessory][hidden]{display:none!important}
1374
+ ${NAV}[data-scroll]>nav{overflow-x:clip;overflow-y:auto}
1375
+ ${NAV}[data-scroll]>nav ul{position:static;overflow:visible}
1376
+ ${LINKS},${NAV}>nav>a{position:absolute;left:calc(var(--vk-lx,0px) - var(--vk-x,0px));top:calc(var(--vk-ly,0px) - var(--vk-y,0px));
1377
+ width:var(--vk-lw,0px);height:var(--vk-lh,0px);box-sizing:border-box;margin:0;padding:0;border-radius:var(--vk-lr,0px);
1378
+ display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
1379
+ color:inherit;text-decoration:none;font-size:11px;line-height:12px;font-weight:500;white-space:nowrap;
1380
+ pointer-events:auto;outline:none;-webkit-user-drag:none;touch-action:manipulation}
1381
+ ${LINKS}::before{content:'';position:absolute;inset:calc(var(--vk-slop-y,0)*-1px) calc(var(--vk-slop-x,0)*-1px)}
1382
+ ${LINKS}>svg{width:var(--vk-nav-icon);height:var(--vk-nav-icon);flex:none}
1383
+ ${LINKS}:focus-visible,${NAV}>nav>button:focus-visible{outline:2px solid currentColor;outline-offset:-2px}
1384
+ ${LINKS}[data-lens-over]{background:color-mix(in srgb,currentColor 12%,transparent)}
1385
+ @media (hover:hover){${LINKS}:hover{background:color-mix(in srgb,currentColor 7%,transparent)}}
1386
+ ${NAV}[data-form=sidebar]>nav a{flex-direction:row;justify-content:flex-start;gap:12px;padding-inline:12px;font-size:15px;line-height:20px}
1387
+ ${NAV}[data-form=sidebar]>nav>a[data-search]{background:color-mix(in srgb,currentColor 4%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,currentColor 18%,transparent)}
1388
+ ${NAV}[data-contrast][data-form=sidebar]>nav>a[data-search]{box-shadow:inset 0 0 0 1px currentColor}
1389
+ ${NAV}[data-form=bar]>nav>a[data-search],${NAV}[data-minimized]>nav a{font-size:0;line-height:0;gap:0;padding-inline:0;justify-content:center}
1390
+ ${LINKS} [data-badge]{position:absolute;top:4px;inset-inline-start:calc(50% + 6px);min-width:16px;height:16px;box-sizing:border-box;padding:0 4px;
1391
+ border-radius:8px;background:var(--vire-nav-badge,#e5484d);color:#fff;font-size:11px;line-height:16px;text-align:center}
1392
+ ${LINKS} [data-badge]:empty{min-width:8px;width:8px;height:8px;padding:0;top:8px}
1393
+ ${NAV} [data-badge-label]{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
1394
+ ${NAV}[data-form=sidebar]>nav [data-badge]{inset-inline-start:30px}
1395
+ ${NAV}[data-form=sidebar]>nav [data-badge]:not(:empty){inset-inline-start:auto;inset-inline-end:12px;top:calc(50% - 8px)}
1396
+ ${NAV}>nav>[data-vk-pill]{position:absolute;left:calc(var(--vk-px,0px) - var(--vk-x,0px));top:calc(var(--vk-py,0px) - var(--vk-y,0px));
1397
+ width:var(--vk-pw,0px);height:var(--vk-ph,0px);border-radius:var(--vk-pr,0px);box-sizing:border-box;pointer-events:none;
1398
+ background:color-mix(in srgb,currentColor 14%,transparent);opacity:var(--vk-pill-op,1)}
1399
+ ${NAV}>nav>button[data-vk-toggle]{appearance:none;-webkit-appearance:none;position:absolute;left:calc(var(--vk-lx,0px) - var(--vk-x,0px));top:calc(var(--vk-ly,0px) - var(--vk-y,0px));
1400
+ width:var(--vk-lw,0px);height:var(--vk-lh,0px);box-sizing:border-box;margin:0;padding:0;border:0;border-radius:50%;outline:none;
1401
+ display:grid;place-items:center;background:transparent;color:inherit;cursor:pointer;pointer-events:auto;touch-action:manipulation;-webkit-tap-highlight-color:transparent}
1402
+ ${NAV}>nav>button[data-vk-toggle][hidden]{display:none}
1403
+ ${NAV}>nav>button[data-vk-toggle]>svg{width:22px;height:22px}
1404
+ @media (hover:hover){${NAV}>nav>button[data-vk-toggle]:hover{background:color-mix(in srgb,currentColor 8%,transparent)}}
1405
+ ${NAV}[data-moving]>nav>:is(ul,a,button){opacity:var(--vk-ink);filter:blur(calc((1 - var(--vk-ink))*6px))}
1406
+ ${NAV}[data-moving]>[data-accessory][data-moving]>*{opacity:var(--vk-ink);filter:blur(calc((1 - var(--vk-ink))*6px))}
1407
+ ${NAV}>[data-vk-lens]{position:absolute;left:var(--vk-x,0px);top:var(--vk-y,0px);width:var(--vk-w,0px);height:var(--vk-h,0px);box-sizing:border-box;
1408
+ border-radius:var(--vk-r,0px);display:none;align-items:center;justify-content:center;overflow:clip;pointer-events:none;color:inherit;
1409
+ font-size:11px;line-height:12px;font-weight:500;white-space:nowrap}
1410
+ ${NAV}[data-lens]>[data-vk-lens]{display:flex}
1411
+ ${NAV}>[data-vk-lens]>div{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
1412
+ transform:scale(calc(1 + .15*var(--vk-lift,0)));opacity:var(--vk-lift,0)}
1413
+ ${NAV}>[data-vk-lens]>div>svg{width:var(--vk-nav-icon);height:var(--vk-nav-icon);flex:none}
1414
+ ${NAV}>[data-vk-lens][data-form=sidebar]{justify-content:flex-start}
1415
+ ${NAV}>[data-vk-lens][data-form=sidebar]>div{flex-direction:row;gap:12px;font-size:15px;line-height:20px;padding-inline-start:calc(12px + ${NAV_SIZES.lens.inflate}px*var(--vk-lift,0))}
1416
+ ${NAV}>[data-vk-lens][data-dragging]>div{opacity:0}
1417
+ ${NAV}[data-minimized]>[data-vk-lens]>div,${NAV}[data-form=bar][data-minimized]>[data-vk-lens]>div{font-size:0}
1418
+ ${NAV}>[data-vk-scrim]{position:absolute;inset:0;pointer-events:auto;background:color-mix(in srgb,CanvasText 28%,transparent)}
1419
+
1420
+ ${NAV}[data-surface=glass]>nav,${NAV}[data-surface=glass]>[data-accessory],${NAV}[data-surface=glass]>[data-vk-lens],${NAV}[data-surface=glass]${CIRCLE}{color:var(--vireglass-ink-color,inherit);
1421
+ ${GLASS_BODY}}
1422
+ ${NAV}[data-frost][data-surface=glass]>nav,${NAV}[data-frost][data-surface=glass]>[data-accessory],${NAV}[data-frost][data-surface=glass]>[data-vk-lens],${NAV}[data-frost][data-surface=glass]${CIRCLE}{${FROST_INK}}
1423
+ ${NAV}[data-surface=plain]>:is(nav,[data-accessory]),${NAV}[data-surface=plain]${CIRCLE}{background:Canvas;${SHADOW}}
1424
+ ${NAV}[data-surface=plain]>[data-vk-lens]{background:color-mix(in srgb,currentColor 16%,transparent)}
1425
+ ${NAV}[data-surface=frosted]>:is(nav,[data-accessory]),${NAV}[data-surface=frosted]${CIRCLE}{background:color-mix(in srgb,Canvas 88%,currentColor);
1426
+ backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);${SHADOW}}
1427
+ ${NAV}[data-surface=frosted]>[data-vk-lens]{background:color-mix(in srgb,currentColor 18%,transparent)}
1428
+ ${NAV}[data-contrast]:is([data-surface=plain],[data-surface=frosted])>:is(nav,[data-accessory]),${NAV}[data-contrast]:is([data-surface=plain],[data-surface=frosted])${CIRCLE}{background:Canvas;box-shadow:inset 0 0 0 2px currentColor}
1429
+ ${NAV}[data-contrast][data-surface=glass]>:is(nav,[data-accessory]),${NAV}[data-contrast][data-surface=glass]${CIRCLE}{${CONTRAST_GLASS}}
1430
+ ${NAV}[data-contrast]>nav>[data-vk-pill]{box-shadow:inset 0 0 0 2px currentColor}
1431
+
574
1432
  @supports not (color:oklab(from red l a b)){
575
1433
  ${HOST}[data-frost][data-surface=glass]>button,${MENU}[data-frost][data-surface=glass]>[popover]{color:inherit;background:color-mix(in srgb,Canvas 82%,currentColor)}
576
1434
  }
@@ -589,6 +1447,10 @@ ${BODY}{-webkit-text-fill-color:currentColor;text-shadow:none;color:ButtonText;b
589
1447
  ${BODY}>svg{opacity:1;filter:none}
590
1448
  ${HOST}[data-disabled]>button{-webkit-text-fill-color:currentColor;color:GrayText;border-color:GrayText}
591
1449
  ${HOST}[data-focus]>button{outline-color:Highlight}
1450
+ ${BODIES}{color:CanvasText!important;background:Canvas!important;border:1px solid CanvasText;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}
1451
+ ${NAV}>[data-vk-lens]{display:none!important}
1452
+ ${NAV}>nav>[data-vk-pill]{background:Highlight;opacity:.4}
1453
+ ${NAV}>nav>:is(ul,a,button){filter:none!important;opacity:1!important}
592
1454
  ${SURFACE}{color:CanvasText!important;background:Canvas!important;border:1px solid CanvasText;backdrop-filter:none!important;-webkit-backdrop-filter:none!important}
593
1455
  ${SURFACE}>*{filter:none!important;opacity:1!important}
594
1456
  ${ITEM}{-webkit-text-fill-color:currentColor!important;color:CanvasText;background:Canvas}
@@ -620,13 +1482,14 @@ var FORCED = {
620
1482
  };
621
1483
  var MATERIAL_STEPS = 10;
622
1484
  var SUPPRESS_MS = 100;
1485
+ var HOVER2 = { hover: true };
1486
+ var NO_HOVER = { hover: false };
623
1487
  function createControlElementClass(kind, options = {}) {
624
1488
  const response = pressResponse(kind);
625
1489
  return class VireControl extends HTMLElement {
626
1490
  static observedAttributes = ["glass", "size", "disabled", FORCE_ATTRIBUTE];
627
1491
  #control = null;
628
1492
  #state = CONTROL_IDLE;
629
- #caps = POINTER_INPUT;
630
1493
  #visual = createControlVisual();
631
1494
  #own = {};
632
1495
  #mode = null;
@@ -681,7 +1544,7 @@ function createControlElementClass(kind, options = {}) {
681
1544
  }
682
1545
  this.#hadStyle.host = this.hasAttribute("style");
683
1546
  ensureStyles(this, options.nonce);
684
- this.#cleanups.push(watchCoarsePointer(() => this.#onInputChange()));
1547
+ this.#cleanups.push(watchPointerInput(() => this.#onInputChange()));
685
1548
  this.#cleanups.push(watchSystemA11y(() => this.#refreshMode()));
686
1549
  this.#cleanups.push(watchA11yOverride(() => this.#refreshMode()));
687
1550
  this.#awaitControl();
@@ -736,7 +1599,6 @@ function createControlElementClass(kind, options = {}) {
736
1599
  this.#control = control;
737
1600
  this.#hadStyle.control = control.hasAttribute("style");
738
1601
  this.setAttribute("data-vk", "");
739
- this.#caps = isCoarsePointer() ? TOUCH_INPUT : POINTER_INPUT;
740
1602
  this.#listen(control);
741
1603
  this.#cleanups.push(this.#observeSize(control));
742
1604
  this.#syncDisabled();
@@ -748,13 +1610,13 @@ function createControlElementClass(kind, options = {}) {
748
1610
  }
749
1611
  #onInputChange() {
750
1612
  if (!this.#control) return;
751
- this.#caps = isCoarsePointer() ? TOUCH_INPUT : POINTER_INPUT;
1613
+ if (this.#state.hovered && !effectiveInput().hover) this.#dispatch({ type: "enter" }, NO_HOVER);
752
1614
  this.#refreshMode(true);
753
1615
  }
754
1616
  #size() {
755
1617
  const wanted = this.getAttribute("size");
756
1618
  if (wanted && CONTROL_SIZES.includes(wanted)) return wanted;
757
- return defaultSize({ touch: isCoarsePointer() });
1619
+ return sizeTier(effectiveInput());
758
1620
  }
759
1621
  #glassMode() {
760
1622
  const wanted = this.getAttribute("glass");
@@ -765,9 +1627,9 @@ function createControlElementClass(kind, options = {}) {
765
1627
  const wanted = this.getAttribute(FORCE_ATTRIBUTE);
766
1628
  return wanted !== null && Object.hasOwn(FORCED, wanted) ? wanted : null;
767
1629
  }
768
- #dispatch(event) {
1630
+ #dispatch(event, caps = HOVER2) {
769
1631
  if (this.#forced()) return false;
770
- const step = stepControl(this.#state, event, this.#caps);
1632
+ const step = stepControl(this.#state, event, caps);
771
1633
  this.#state = step.state;
772
1634
  this.#render();
773
1635
  return step.activated;
@@ -811,7 +1673,7 @@ function createControlElementClass(kind, options = {}) {
811
1673
  if (mine(e)) this.#dispatch({ type: "cancel" });
812
1674
  });
813
1675
  on(target, "pointerenter", (e) => {
814
- if (mine(e) && e.pointerType !== "touch") this.#dispatch({ type: "enter" });
1676
+ if (mine(e)) this.#dispatch({ type: "enter" }, hoversWith(e.pointerType) ? HOVER2 : NO_HOVER);
815
1677
  });
816
1678
  on(target, "pointerleave", (e) => {
817
1679
  if (mine(e) && !control.hasPointerCapture?.(e.pointerId)) this.#dispatch({ type: "leave" });
@@ -873,7 +1735,7 @@ function createControlElementClass(kind, options = {}) {
873
1735
  #applySlop() {
874
1736
  const control = this.#control;
875
1737
  if (!control) return;
876
- const floor = minTarget({ touch: isCoarsePointer() });
1738
+ const floor = hitTarget(effectiveInput());
877
1739
  this.#slop = { x: hitSlop(control.offsetWidth, floor), y: hitSlop(control.offsetHeight, floor) };
878
1740
  this.style.setProperty("--vk-slop-x", String(this.#slop.x));
879
1741
  this.style.setProperty("--vk-slop-y", String(this.#slop.y));
@@ -885,7 +1747,7 @@ function createControlElementClass(kind, options = {}) {
885
1747
  const disabled = control.disabled;
886
1748
  if (this.#forced()) return;
887
1749
  if (disabled !== this.#state.disabled) {
888
- this.#state = stepControl(this.#state, { type: "disable", disabled }, this.#caps).state;
1750
+ this.#state = stepControl(this.#state, { type: "disable", disabled }, HOVER2).state;
889
1751
  this.#render();
890
1752
  }
891
1753
  }
@@ -1081,40 +1943,46 @@ var MENU_CORNERS = {
1081
1943
  "bottom-left": { x: -1, y: 1 },
1082
1944
  "bottom-right": { x: 1, y: 1 }
1083
1945
  };
1084
- function menuCorner(trigger, viewport) {
1946
+ function menuCorner(trigger, region) {
1947
+ const x0 = region.x ?? 0;
1948
+ const y0 = region.y ?? 0;
1085
1949
  return {
1086
- x: trigger.x + trigger.width / 2 <= viewport.width / 2 ? -1 : 1,
1087
- y: trigger.y + trigger.height / 2 <= viewport.height / 2 ? -1 : 1
1950
+ x: trigger.x + trigger.width / 2 <= x0 + region.width / 2 ? -1 : 1,
1951
+ y: trigger.y + trigger.height / 2 <= y0 + region.height / 2 ? -1 : 1
1088
1952
  };
1089
1953
  }
1090
1954
  function along(start, length, size, side) {
1091
1955
  return side < 0 ? start : start + length - size;
1092
1956
  }
1093
- function fits(at, size, limit, margin) {
1094
- return at >= margin && at + size <= limit - margin;
1957
+ function fits(at, size, lo, hi, margin) {
1958
+ return at >= lo + margin && at + size <= hi - margin;
1095
1959
  }
1096
1960
  var clamp = (v, lo, hi) => Math.min(Math.max(v, lo), Math.max(lo, hi));
1097
- function menuPlacement(trigger, menu, viewport, options = {}) {
1961
+ function menuPlacement(trigger, menu, region, options = {}) {
1098
1962
  const margin = options.margin ?? 8;
1099
- const width = Math.min(menu.width, viewport.width - 2 * margin);
1100
- const height = Math.min(menu.height, viewport.height - 2 * margin);
1101
- const corner = { ...options.corner ?? menuCorner(trigger, viewport) };
1963
+ const x0 = region.x ?? 0;
1964
+ const y0 = region.y ?? 0;
1965
+ const x1 = x0 + region.width;
1966
+ const y1 = y0 + region.height;
1967
+ const width = Math.max(0, Math.min(menu.width, region.width - 2 * margin));
1968
+ const height = Math.max(0, Math.min(menu.height, region.height - 2 * margin));
1969
+ const corner = { ...options.corner ?? menuCorner(trigger, region) };
1102
1970
  let x = along(trigger.x, trigger.width, width, corner.x);
1103
1971
  let y = along(trigger.y, trigger.height, height, corner.y);
1104
1972
  if (!options.corner) {
1105
1973
  const fx = along(trigger.x, trigger.width, width, corner.x === -1 ? 1 : -1);
1106
- if (!fits(x, width, viewport.width, margin) && fits(fx, width, viewport.width, margin)) {
1974
+ if (!fits(x, width, x0, x1, margin) && fits(fx, width, x0, x1, margin)) {
1107
1975
  corner.x = corner.x === -1 ? 1 : -1;
1108
1976
  x = fx;
1109
1977
  }
1110
1978
  const fy = along(trigger.y, trigger.height, height, corner.y === -1 ? 1 : -1);
1111
- if (!fits(y, height, viewport.height, margin) && fits(fy, height, viewport.height, margin)) {
1979
+ if (!fits(y, height, y0, y1, margin) && fits(fy, height, y0, y1, margin)) {
1112
1980
  corner.y = corner.y === -1 ? 1 : -1;
1113
1981
  y = fy;
1114
1982
  }
1115
1983
  }
1116
- x = clamp(x, margin, viewport.width - margin - width);
1117
- y = clamp(y, margin, viewport.height - margin - height);
1984
+ x = clamp(x, x0 + margin, x1 - margin - width);
1985
+ y = clamp(y, y0 + margin, y1 - margin - height);
1118
1986
  const rect = { x, y, width, height };
1119
1987
  return {
1120
1988
  rect,
@@ -1128,7 +1996,7 @@ function menuActivity(phase) {
1128
1996
  const grow = Math.min(Math.max(phase.grow, 0), 1);
1129
1997
  return shrink * (1 - grow);
1130
1998
  }
1131
- function menuSurfaceFrame(trigger, menu, anchor, phase) {
1999
+ function menuSurfaceFrame(trigger, menu, anchor, phase, bounds) {
1132
2000
  const frame = growthFrame(
1133
2001
  { geometry: { width: trigger.width, height: trigger.height, cornerRadius: trigger.radius }, center: center(trigger) },
1134
2002
  { geometry: { width: menu.width, height: menu.height, cornerRadius: menu.radius }, center: center(menu) },
@@ -1136,17 +2004,25 @@ function menuSurfaceFrame(trigger, menu, anchor, phase) {
1136
2004
  phase
1137
2005
  );
1138
2006
  const { geometry, center: c } = frame.body;
1139
- const box = {
2007
+ let box2 = {
1140
2008
  x: c.x - geometry.width / 2,
1141
2009
  y: c.y - geometry.height / 2,
1142
2010
  width: geometry.width,
1143
2011
  height: geometry.height,
1144
2012
  radius: geometry.cornerRadius
1145
2013
  };
2014
+ let morph = frame.merge;
2015
+ const held = bounds ? intersectRect(box2, bounds) : null;
2016
+ if (held && (held.width < box2.width || held.height < box2.height)) {
2017
+ const was = center(box2);
2018
+ const now = center(held);
2019
+ box2 = { ...held, radius: Math.min(box2.radius, held.width / 2, held.height / 2) };
2020
+ if (morph) morph = { ...morph, offsetX: morph.offsetX + was.x - now.x, offsetY: morph.offsetY + was.y - now.y };
2021
+ }
1146
2022
  return {
1147
- box,
1148
- morph: frame.merge,
1149
- content: { x: menu.x - box.x, y: menu.y - box.y },
2023
+ box: box2,
2024
+ morph,
2025
+ content: { x: menu.x - box2.x, y: menu.y - box2.y },
1150
2026
  ink: frame.ink,
1151
2027
  activity: menuActivity(phase)
1152
2028
  };
@@ -1162,8 +2038,8 @@ function menuBody(base, activity, frost = false) {
1162
2038
  const rest = atomBody(base, ATOM_AT_REST, frost);
1163
2039
  if (frost) return rest;
1164
2040
  const k = Math.min(Math.max(activity, 0), 1);
1165
- const mix2 = (key) => rest[key] + (MENU_DROP_BODY[key] - rest[key]) * k;
1166
- return { alpha: mix2("alpha"), white: mix2("white"), lift: mix2("lift") };
2041
+ const mix3 = (key) => rest[key] + (MENU_DROP_BODY[key] - rest[key]) * k;
2042
+ return { alpha: mix3("alpha"), white: mix3("white"), lift: mix3("lift") };
1167
2043
  }
1168
2044
  var MENU_ITEM_FILL = { active: 0.1, pressed: 0.18 };
1169
2045
 
@@ -1212,6 +2088,7 @@ function createMenuElementClass(options = {}) {
1212
2088
  #from = null;
1213
2089
  #to = null;
1214
2090
  #anchor = null;
2091
+ #region = null;
1215
2092
  #natural = null;
1216
2093
  #out = false;
1217
2094
  #viaPointer = false;
@@ -1262,7 +2139,7 @@ function createMenuElementClass(options = {}) {
1262
2139
  }
1263
2140
  this.#hadStyle.host = this.hasAttribute("style");
1264
2141
  ensureStyles(this, options.nonce);
1265
- this.#cleanups.push(watchCoarsePointer(() => this.#list && this.#refreshMode(true)));
2142
+ this.#cleanups.push(watchPointerInput(() => this.#list && this.#refreshMode(true)));
1266
2143
  this.#cleanups.push(watchSystemA11y(() => this.#list && this.#refreshMode()));
1267
2144
  this.#cleanups.push(watchA11yOverride(() => this.#list && this.#refreshMode()));
1268
2145
  this.#awaitParts();
@@ -1293,7 +2170,7 @@ function createMenuElementClass(options = {}) {
1293
2170
  this.#state = MENU_CLOSED;
1294
2171
  this.#growth = createGrowthState();
1295
2172
  this.#out = false;
1296
- this.#from = this.#to = this.#anchor = null;
2173
+ this.#from = this.#to = this.#anchor = this.#region = null;
1297
2174
  this.#natural = null;
1298
2175
  this.#held = null;
1299
2176
  this.#readySince = null;
@@ -1427,7 +2304,7 @@ function createMenuElementClass(options = {}) {
1427
2304
  const item = target instanceof Element ? target.closest("button") : null;
1428
2305
  return item && item.parentElement === list ? item : null;
1429
2306
  };
1430
- const drop = (e) => {
2307
+ const drop2 = (e) => {
1431
2308
  e.preventDefault();
1432
2309
  e.stopImmediatePropagation();
1433
2310
  };
@@ -1480,14 +2357,14 @@ function createMenuElementClass(options = {}) {
1480
2357
  (e) => {
1481
2358
  const item = itemOf(e.target);
1482
2359
  if (!item || this.#forced()) return;
1483
- if (isDisabled(item)) return drop(e);
2360
+ if (isDisabled(item)) return drop2(e);
1484
2361
  if (!this.#state.open) {
1485
- if (this.#out) drop(e);
2362
+ if (this.#out) drop2(e);
1486
2363
  return;
1487
2364
  }
1488
2365
  const ready = this.#readySince;
1489
2366
  const pointerEarly = e.detail > 0 && (ready === null || this.#downAt < ready);
1490
- if (e.detail > 1 || pointerEarly || this.#inSince === null) return drop(e);
2367
+ if (e.detail > 1 || pointerEarly || this.#inSince === null) return drop2(e);
1491
2368
  this.#close({ type: "choose", index: this.#items().indexOf(item) });
1492
2369
  },
1493
2370
  true
@@ -1532,7 +2409,7 @@ function createMenuElementClass(options = {}) {
1532
2409
  );
1533
2410
  on(root, "scroll", (e) => this.#follow(e), true);
1534
2411
  });
1535
- on(window, "resize", (e) => this.#follow(e));
2412
+ this.#cleanups.push(watchViewport(() => this.#follow()));
1536
2413
  if (typeof ResizeObserver !== "undefined") {
1537
2414
  const resized = new ResizeObserver(() => this.#out && this.#follow());
1538
2415
  resized.observe(this.#carrier ?? trigger);
@@ -1625,9 +2502,11 @@ function createMenuElementClass(options = {}) {
1625
2502
  const spec = menuSize(this.#size());
1626
2503
  this.#natural ??= this.#measure();
1627
2504
  const natural = this.#natural;
1628
- const viewport = { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight };
1629
2505
  const trigger = this.#triggerBox();
1630
- const placement = menuPlacement(trigger, natural, viewport, { corner: this.#corner(), margin: VIEWPORT_MARGIN });
2506
+ const { viewport, segments } = viewportSegments();
2507
+ const region = segmentOf(trigger, segments) ?? viewport;
2508
+ this.#region = region;
2509
+ const placement = menuPlacement(trigger, natural, region, { corner: this.#corner(), margin: VIEWPORT_MARGIN });
1631
2510
  this.#from = trigger;
1632
2511
  this.#to = { ...placement.rect, radius: spec.radius };
1633
2512
  this.#anchor = placement.anchor;
@@ -1647,14 +2526,14 @@ function createMenuElementClass(options = {}) {
1647
2526
  glass.setSample(sample);
1648
2527
  this.#paint();
1649
2528
  }
1650
- #setBox(box) {
2529
+ #setBox(box2) {
1651
2530
  const s = this.#list.style;
1652
- const px = (v) => `${v.toFixed(2)}px`;
1653
- s.setProperty("--vk-x", px(box.x));
1654
- s.setProperty("--vk-y", px(box.y));
1655
- s.setProperty("--vk-w", px(box.width));
1656
- s.setProperty("--vk-h", px(box.height));
1657
- s.setProperty("--vk-r", px(box.radius));
2531
+ const px2 = (v) => `${v.toFixed(2)}px`;
2532
+ s.setProperty("--vk-x", px2(box2.x));
2533
+ s.setProperty("--vk-y", px2(box2.y));
2534
+ s.setProperty("--vk-w", px2(box2.width));
2535
+ s.setProperty("--vk-h", px2(box2.height));
2536
+ s.setProperty("--vk-r", px2(box2.radius));
1658
2537
  }
1659
2538
  #run() {
1660
2539
  const instant = this.#presentation?.instantSprings ?? false;
@@ -1693,7 +2572,7 @@ function createMenuElementClass(options = {}) {
1693
2572
  this.removeAttribute("data-phase");
1694
2573
  this.#out = false;
1695
2574
  this.#held = null;
1696
- this.#from = this.#to = this.#anchor = null;
2575
+ this.#from = this.#to = this.#anchor = this.#region = null;
1697
2576
  this.#glass?.setSample(this.#sample);
1698
2577
  this.#carrier?.setCarried?.(null);
1699
2578
  }
@@ -1701,7 +2580,7 @@ function createMenuElementClass(options = {}) {
1701
2580
  const list = this.#list;
1702
2581
  if (!list || !this.#out || !this.#from || !this.#to || !this.#anchor) return;
1703
2582
  const phase = growthPhase(this.#growth);
1704
- const f = menuSurfaceFrame(this.#from, this.#to, this.#anchor, phase);
2583
+ const f = menuSurfaceFrame(this.#from, this.#to, this.#anchor, phase, this.#region ?? void 0);
1705
2584
  const settled = phase.shrink === 1 && phase.grow === 1;
1706
2585
  const inkIn = Math.round(f.ink.incoming * 1e3) / 1e3;
1707
2586
  if (this.#state.open && inkIn >= 1) {
@@ -1722,20 +2601,20 @@ function createMenuElementClass(options = {}) {
1722
2601
  this.#pushGlass(f.box, f.morph, f.activity);
1723
2602
  }
1724
2603
  /** Hands the lens only what changed: a new morph or material rebuilds its filter, a moved box needs nothing. */
1725
- #pushGlass(box, morph, activity) {
2604
+ #pushGlass(box2, morph, activity) {
1726
2605
  const glass = this.#glass;
1727
2606
  if (!glass) return;
1728
- const round = Math.round;
1729
- const step = round(Math.min(Math.max(activity, 0), 1) * MATERIAL_STEPS2) / MATERIAL_STEPS2;
1730
- const geometry = { width: round(box.width), height: round(box.height), cornerRadius: round(box.radius) };
2607
+ const round2 = Math.round;
2608
+ const step = round2(Math.min(Math.max(activity, 0), 1) * MATERIAL_STEPS2) / MATERIAL_STEPS2;
2609
+ const geometry = { width: round2(box2.width), height: round2(box2.height), cornerRadius: round2(box2.radius) };
1731
2610
  const lens = morph ? {
1732
- smoothing: round(morph.smoothing),
2611
+ smoothing: round2(morph.smoothing),
1733
2612
  shape: {
1734
- offsetX: round(morph.offsetX),
1735
- offsetY: round(morph.offsetY),
1736
- width: round(morph.width),
1737
- height: round(morph.height),
1738
- cornerRadius: round(morph.cornerRadius)
2613
+ offsetX: round2(morph.offsetX),
2614
+ offsetY: round2(morph.offsetY),
2615
+ width: round2(morph.width),
2616
+ height: round2(morph.height),
2617
+ cornerRadius: round2(morph.cornerRadius)
1739
2618
  }
1740
2619
  } : null;
1741
2620
  const key = {
@@ -1781,7 +2660,7 @@ function createMenuElementClass(options = {}) {
1781
2660
  #size() {
1782
2661
  const wanted = this.getAttribute("size");
1783
2662
  if (wanted && CONTROL_SIZES.includes(wanted)) return wanted;
1784
- return defaultSize({ touch: isCoarsePointer() });
2663
+ return sizeTier(effectiveInput());
1785
2664
  }
1786
2665
  #wantsGlass() {
1787
2666
  const wanted = this.getAttribute("glass");
@@ -1835,12 +2714,12 @@ function createMenuElementClass(options = {}) {
1835
2714
  this.#glassKey = key;
1836
2715
  if (!want || !list) return;
1837
2716
  const token = this.#glassToken;
1838
- const box = this.#from ?? this.#triggerBox();
2717
+ const box2 = this.#from ?? this.#triggerBox();
1839
2718
  void attachControlGlass(
1840
2719
  list,
1841
2720
  {
1842
2721
  material: menuMaterial(vireglass.VIREGLASS_CONTROL_MATERIAL, 0),
1843
- geometry: { width: Math.round(box.width), height: Math.round(box.height), cornerRadius: menuSize(this.#size()).radius },
2722
+ geometry: { width: Math.round(box2.width), height: Math.round(box2.height), cornerRadius: menuSize(this.#size()).radius },
1844
2723
  accessibility: mode,
1845
2724
  onTouch: false,
1846
2725
  sample: this.#held ?? this.#sample,
@@ -1862,11 +2741,1233 @@ function createMenuElementClass(options = {}) {
1862
2741
  }
1863
2742
  };
1864
2743
  }
2744
+ var FORCE_ATTRIBUTE3 = "data-vire-force-state";
2745
+ var GLASS_ON2 = /* @__PURE__ */ new Set(["", "present"]);
2746
+ var FORMS = ["bar", "rail", "sidebar"];
2747
+ var MATERIAL_STEPS3 = 10;
2748
+ var DRAG_SLOP = 6;
2749
+ var SWALLOW_MS = 100;
2750
+ var DEFAULT_TOGGLE_LABEL = "Sidebar";
2751
+ var TOGGLE_GLYPH = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="3"/><path d="M9 4v16"/></svg>';
2752
+ var px = (v) => `${v.toFixed(2)}px`;
2753
+ var round = Math.round;
2754
+ var clamp012 = (v) => Math.min(Math.max(v, 0), 1);
2755
+ var emptyLast = () => ({ material: "", morph: "", geometry: "" });
2756
+ var sameMorph = (a, b) => {
2757
+ if (typeof a === "string" || typeof b === "string") return a === b;
2758
+ const p = a.shape;
2759
+ const q = b.shape;
2760
+ return a.smoothing === b.smoothing && p?.offsetX === q?.offsetX && p?.offsetY === q?.offsetY && p?.width === q?.width && p?.height === q?.height && p?.cornerRadius === q?.cornerRadius;
2761
+ };
2762
+ var newSlot = (el) => ({ el, session: null, token: 0, key: "", last: emptyLast(), appear: -1 });
2763
+ var isCurrent = (link) => {
2764
+ const value = link.getAttribute("aria-current");
2765
+ return value !== null && value !== "false";
2766
+ };
2767
+ function restoreAria(badge, was) {
2768
+ if (was === null) badge.removeAttribute("aria-hidden");
2769
+ else badge.setAttribute("aria-hidden", was);
2770
+ }
2771
+ function stripPlacement(link, hadStyle) {
2772
+ for (const name of Array.from(link.style)) if (name.startsWith("--vk-")) link.style.removeProperty(name);
2773
+ dropEmptyStyle2(link, hadStyle);
2774
+ }
2775
+ function dropEmptyStyle2(el, hadStyle) {
2776
+ if (!hadStyle && el.getAttribute("style") === "") el.removeAttribute("style");
2777
+ }
2778
+ function parseForced(value) {
2779
+ if (!value) return null;
2780
+ if (value === "minimized") return { kind: "minimized" };
2781
+ const morph = /^morph:(bar|rail|sidebar|minimized):(\d*\.?\d+)$/.exec(value);
2782
+ if (morph) return { kind: "morph", from: morph[1], t: clamp012(Number(morph[2])) };
2783
+ const drag = /^drag:(\d*\.?\d+)$/.exec(value);
2784
+ return drag ? { kind: "drag", at: clamp012(Number(drag[1])) } : null;
2785
+ }
2786
+ function createNavElementClass(options = {}) {
2787
+ return class VireNav extends HTMLElement {
2788
+ static observedAttributes = [
2789
+ "glass",
2790
+ "form",
2791
+ "collapsed",
2792
+ "panes",
2793
+ "minimize",
2794
+ "scroller",
2795
+ "search-key",
2796
+ "toggle-label",
2797
+ FORCE_ATTRIBUTE3
2798
+ ];
2799
+ #navEl = null;
2800
+ #pill = null;
2801
+ #lens = null;
2802
+ #lensInner = null;
2803
+ #toggle = null;
2804
+ #scrim = null;
2805
+ #accessory = null;
2806
+ #cleanups = [];
2807
+ #hadStyle = { host: false, nav: false, parent: false };
2808
+ #linkStyle = /* @__PURE__ */ new Map();
2809
+ #hiddenWas = /* @__PURE__ */ new Map();
2810
+ #badgeWas = /* @__PURE__ */ new Map();
2811
+ #mine = /* @__PURE__ */ new Set();
2812
+ #own = {};
2813
+ #mode = null;
2814
+ #modeKey = "";
2815
+ #presentation = null;
2816
+ #glassBroken = false;
2817
+ #body = null;
2818
+ #lensSlot = null;
2819
+ #accSlot = null;
2820
+ #sample = null;
2821
+ #ctx = null;
2822
+ #plan = null;
2823
+ #plans = /* @__PURE__ */ new WeakMap();
2824
+ #shown = null;
2825
+ #morph = null;
2826
+ #drag = createNavDrag();
2827
+ #press = null;
2828
+ #minState = createMinimize();
2829
+ #scrollerProp = null;
2830
+ #scrollerEl = null;
2831
+ #unbindScroller = null;
2832
+ #over = false;
2833
+ #pillAnim = {
2834
+ state: { ...createFormMorph(), t: { x: 1, v: 0, target: 1 } },
2835
+ from: null,
2836
+ to: null
2837
+ };
2838
+ #accAnim = { state: { ...createFormMorph(), t: { x: 1, v: 0, target: 1 } }, from: null, to: null };
2839
+ #frame = 0;
2840
+ #lastFrame = 0;
2841
+ #lensFor = null;
2842
+ #lastKey = "";
2843
+ #parent = null;
2844
+ #parentWas = null;
2845
+ // Leaving the scrolling state resets the offset, and that reset's own scroll event must not overwrite what is remembered.
2846
+ #scrollReset = false;
2847
+ #scrollTops = /* @__PURE__ */ new Map();
2848
+ #dirty = false;
2849
+ #swallow = null;
2850
+ #tapping = false;
2851
+ #tapTimer = 0;
2852
+ get a11y() {
2853
+ return this.#own;
2854
+ }
2855
+ set a11y(next) {
2856
+ this.#own = { ...next };
2857
+ if (this.#navEl) this.#refreshMode();
2858
+ }
2859
+ get sample() {
2860
+ return this.#sample;
2861
+ }
2862
+ set sample(next) {
2863
+ this.#sample = next;
2864
+ for (const slot of [this.#body, this.#lensSlot, this.#accSlot]) slot?.session?.setSample(next);
2865
+ }
2866
+ get scroller() {
2867
+ return this.#scrollerProp;
2868
+ }
2869
+ set scroller(next) {
2870
+ this.#scrollerProp = next;
2871
+ if (!this.#navEl) return;
2872
+ this.#bindScroller();
2873
+ this.#replan();
2874
+ }
2875
+ connectedCallback() {
2876
+ for (const key of ["a11y", "sample", "scroller"]) {
2877
+ if (!Object.prototype.hasOwnProperty.call(this, key)) continue;
2878
+ const value = Reflect.get(this, key);
2879
+ Reflect.deleteProperty(this, key);
2880
+ Reflect.set(this, key, value);
2881
+ }
2882
+ this.#hadStyle.host = this.hasAttribute("style");
2883
+ ensureStyles(this, options.nonce);
2884
+ this.#cleanups.push(watchSystemA11y(() => this.#navEl && this.#refreshMode()));
2885
+ this.#cleanups.push(watchA11yOverride(() => this.#navEl && this.#refreshMode()));
2886
+ this.#awaitParts();
2887
+ }
2888
+ disconnectedCallback() {
2889
+ for (const cleanup of this.#cleanups.splice(0)) cleanup();
2890
+ cancelAnimationFrame(this.#frame);
2891
+ this.#frame = 0;
2892
+ this.#swallow?.();
2893
+ this.#unhold?.();
2894
+ window.clearTimeout(this.#tapTimer);
2895
+ this.#tapping = false;
2896
+ this.#unbindScroller?.();
2897
+ this.#unbindScroller = null;
2898
+ for (const slot of [this.#body, this.#lensSlot, this.#accSlot]) if (slot) this.#detach(slot);
2899
+ this.#releaseShortcut();
2900
+ this.#hovered?.removeAttribute("data-lens-over");
2901
+ this.#hovered = null;
2902
+ this.#accessory?.removeAttribute("data-placement");
2903
+ this.#accessory?.removeAttribute("data-moving");
2904
+ for (const el of [this.#accessory, this.#lens]) {
2905
+ if (!el) continue;
2906
+ for (const name of Array.from(el.style)) if (name.startsWith("--vk-")) el.style.removeProperty(name);
2907
+ }
2908
+ if (this.#accessory) dropEmptyStyle2(this.#accessory, this.#accessoryHadStyle);
2909
+ for (const [el, was] of this.#hiddenWas) el.toggleAttribute("hidden", was);
2910
+ this.#hiddenWas.clear();
2911
+ for (const [badge, was] of this.#badgeWas) restoreAria(badge, was);
2912
+ this.#badgeWas.clear();
2913
+ for (const [link, had] of this.#linkStyle) stripPlacement(link, had);
2914
+ this.#linkStyle.clear();
2915
+ for (const node of [this.#pill, this.#lens, this.#toggle, this.#scrim]) node?.remove();
2916
+ if (this.#navEl) {
2917
+ for (const name of Array.from(this.#navEl.style)) if (name.startsWith("--vk-")) this.#navEl.style.removeProperty(name);
2918
+ dropEmptyStyle2(this.#navEl, this.#hadStyle.nav);
2919
+ }
2920
+ this.#clearInsets();
2921
+ for (const name of [
2922
+ "data-vk",
2923
+ "data-surface",
2924
+ "data-glass",
2925
+ "data-frost",
2926
+ "data-contrast",
2927
+ "data-form",
2928
+ "data-search",
2929
+ "data-minimized",
2930
+ "data-moving",
2931
+ "data-lens",
2932
+ "data-scroll"
2933
+ ]) {
2934
+ this.removeAttribute(name);
2935
+ }
2936
+ for (const name of Array.from(this.style)) if (name.startsWith("--vk-")) this.style.removeProperty(name);
2937
+ dropEmptyStyle2(this, this.#hadStyle.host);
2938
+ this.#navEl = this.#pill = this.#lens = this.#lensInner = this.#toggle = this.#scrim = this.#accessory = null;
2939
+ this.#body = this.#lensSlot = this.#accSlot = null;
2940
+ this.#ctx = this.#plan = this.#shown = this.#morph = null;
2941
+ this.#plans = /* @__PURE__ */ new WeakMap();
2942
+ this.#scrollTops.clear();
2943
+ this.#drag = createNavDrag();
2944
+ this.#press = null;
2945
+ this.#minState = createMinimize();
2946
+ this.#scrollerEl = null;
2947
+ this.#over = false;
2948
+ this.#lensFor = null;
2949
+ this.#lastKey = "";
2950
+ this.#mine.clear();
2951
+ this.#probe = null;
2952
+ this.#safeBottom = 0;
2953
+ this.#linkStyle = /* @__PURE__ */ new Map();
2954
+ this.#presentation = null;
2955
+ this.#mode = null;
2956
+ this.#modeKey = "";
2957
+ this.#frame = 0;
2958
+ this.#lastFrame = 0;
2959
+ }
2960
+ attributeChangedCallback(name) {
2961
+ if (name === FORCE_ATTRIBUTE3) this.#forcedState = parseForced(this.getAttribute(name));
2962
+ if (!this.#navEl) return;
2963
+ if (name === "glass") this.#refreshMode(true);
2964
+ else if (name === FORCE_ATTRIBUTE3) this.#applyForced();
2965
+ else if (name === "minimize" || name === "scroller") {
2966
+ this.#bindScroller();
2967
+ this.#replan();
2968
+ } else if (name === "toggle-label") this.#syncToggle();
2969
+ else this.#replan();
2970
+ }
2971
+ // The parser connects the element before its children exist, so the markup is waited for, not made up.
2972
+ #awaitParts() {
2973
+ if (this.#tryUpgrade()) return;
2974
+ const observer = new MutationObserver(() => {
2975
+ if (this.#tryUpgrade()) observer.disconnect();
2976
+ });
2977
+ observer.observe(this, { childList: true, subtree: true });
2978
+ this.#cleanups.push(() => observer.disconnect());
2979
+ }
2980
+ #tryUpgrade() {
2981
+ const nav = this.querySelector(":scope > nav");
2982
+ if (!nav || !nav.querySelector("a")) return false;
2983
+ this.#upgrade(nav);
2984
+ return true;
2985
+ }
2986
+ #accessoryHadStyle = false;
2987
+ #upgrade(nav) {
2988
+ this.#navEl = nav;
2989
+ this.#hadStyle.nav = nav.hasAttribute("style");
2990
+ this.setAttribute("data-vk", "");
2991
+ const own = (el) => {
2992
+ this.#mine.add(el);
2993
+ return el;
2994
+ };
2995
+ const pill = own(this.ownerDocument.createElement("span"));
2996
+ pill.setAttribute("data-vk-pill", "");
2997
+ pill.setAttribute("aria-hidden", "true");
2998
+ nav.prepend(pill);
2999
+ this.#pill = pill;
3000
+ const toggle = own(this.ownerDocument.createElement("button"));
3001
+ toggle.type = "button";
3002
+ toggle.setAttribute("data-vk-toggle", "");
3003
+ toggle.hidden = true;
3004
+ toggle.innerHTML = TOGGLE_GLYPH;
3005
+ nav.append(toggle);
3006
+ this.#toggle = toggle;
3007
+ const lens = own(this.ownerDocument.createElement("div"));
3008
+ lens.setAttribute("data-vk-lens", "");
3009
+ lens.setAttribute("aria-hidden", "true");
3010
+ lens.inert = true;
3011
+ const inner = own(this.ownerDocument.createElement("div"));
3012
+ lens.append(inner);
3013
+ this.append(lens);
3014
+ this.#lens = lens;
3015
+ this.#lensInner = inner;
3016
+ const accessory = this.querySelector(":scope > [data-accessory]");
3017
+ this.#accessory = accessory;
3018
+ if (accessory) this.#accessoryHadStyle = accessory.hasAttribute("style");
3019
+ this.#body = newSlot(nav);
3020
+ this.#lensSlot = newSlot(lens);
3021
+ this.#accSlot = accessory ? newSlot(accessory) : null;
3022
+ this.#listen(nav, toggle);
3023
+ const markup = new MutationObserver((records) => {
3024
+ let changed = false;
3025
+ const selected = this.#plan?.order[this.#plan.selected] ?? null;
3026
+ for (const record of records) {
3027
+ if (this.#isOwn(record.target)) continue;
3028
+ if (record.type === "attributes") {
3029
+ this.#lensFor = null;
3030
+ changed = true;
3031
+ } else if (record.type === "characterData" || selected && selected.contains(record.target)) {
3032
+ if (selected?.contains(record.target)) this.#lensFor = null;
3033
+ changed = true;
3034
+ } else if (![...record.addedNodes, ...record.removedNodes].every((node) => this.#mine.has(node))) {
3035
+ changed = true;
3036
+ }
3037
+ }
3038
+ if (this.#syncScroller()) changed = true;
3039
+ if (changed) this.#scheduleReplan();
3040
+ });
3041
+ markup.observe(this, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ["aria-current"] });
3042
+ this.#cleanups.push(() => markup.disconnect());
3043
+ this.#mode = null;
3044
+ this.#modeKey = "";
3045
+ this.#refreshMode();
3046
+ this.#bindScroller();
3047
+ this.#cleanups.push(
3048
+ watchAdaptiveContext(this, (ctx) => {
3049
+ this.#ctx = ctx;
3050
+ this.#replan();
3051
+ })
3052
+ );
3053
+ }
3054
+ #isOwn(node) {
3055
+ return node === this.#pill || [this.#lens, this.#toggle, this.#scrim, this.#probe].some((own) => own?.contains(node));
3056
+ }
3057
+ #scheduleReplan() {
3058
+ if (this.#dirty) return;
3059
+ this.#dirty = true;
3060
+ queueMicrotask(() => {
3061
+ this.#dirty = false;
3062
+ this.#replan();
3063
+ });
3064
+ }
3065
+ #listen(nav, toggle) {
3066
+ const on = (target, type, handler, options2) => {
3067
+ target.addEventListener(type, handler, options2);
3068
+ this.#cleanups.push(() => target.removeEventListener(type, handler, options2));
3069
+ };
3070
+ const doc = this.ownerDocument;
3071
+ on(nav, "pointerdown", (e) => {
3072
+ this.#tapping = true;
3073
+ window.clearTimeout(this.#tapTimer);
3074
+ this.#tapTimer = window.setTimeout(() => this.#tapping = false, 600);
3075
+ this.#pressDown(e);
3076
+ });
3077
+ on(nav, "pointercancel", () => this.#tapping = false);
3078
+ on(nav, "dragstart", (e) => e.preventDefault());
3079
+ on(
3080
+ nav,
3081
+ "click",
3082
+ (e) => {
3083
+ const link = e.target instanceof Element ? e.target.closest("a") : null;
3084
+ this.#tapping = false;
3085
+ if (!link || this.#forced()) return;
3086
+ const plan = this.#plan;
3087
+ if (plan && plan.layout.minimized && plan.visible.has(link) && link !== plan.search) {
3088
+ e.preventDefault();
3089
+ expandMinimize(this.#minState);
3090
+ this.#replan();
3091
+ } else if (this.#over) {
3092
+ this.#setOver(false);
3093
+ }
3094
+ },
3095
+ true
3096
+ );
3097
+ on(nav, "focusin", () => {
3098
+ if (this.#handing || !this.#minState.minimized || this.#forced() || this.#tapping) return;
3099
+ expandMinimize(this.#minState);
3100
+ this.#replan();
3101
+ });
3102
+ this.#cleanups.push(() => this.#endPress?.());
3103
+ on(toggle, "click", () => this.#toggleClick());
3104
+ on(nav, "scroll", () => {
3105
+ const form = this.#shown?.layout.form;
3106
+ if (this.#scrollReset && nav.scrollTop === 0) {
3107
+ this.#scrollReset = false;
3108
+ return;
3109
+ }
3110
+ if (form && this.hasAttribute("data-scroll")) this.#scrollTops.set(form, nav.scrollTop);
3111
+ });
3112
+ on(doc, "keydown", (e) => {
3113
+ if (e.key === "Escape" && this.#over) {
3114
+ e.preventDefault();
3115
+ this.#closeOver();
3116
+ return;
3117
+ }
3118
+ const plan = this.#plan;
3119
+ if (!plan?.search || !plan.nav.search?.shortcut) return;
3120
+ if (e.key !== this.#searchKey() || e.ctrlKey || e.metaKey || e.altKey || e.isComposing || e.repeat) return;
3121
+ if (isEditable(e.composedPath()[0] ?? e.target)) return;
3122
+ e.preventDefault();
3123
+ plan.search.click();
3124
+ });
3125
+ }
3126
+ #searchKey() {
3127
+ return this.getAttribute("search-key") || "/";
3128
+ }
3129
+ // ---- the form -----------------------------------------------------------------------------------------------
3130
+ #collect() {
3131
+ const nav = this.#navEl;
3132
+ const lists = Array.from(nav.querySelectorAll(":scope > ul"));
3133
+ const links = (list) => Array.from(list.querySelectorAll("a"));
3134
+ const secondaryList = lists.filter((list) => list.hasAttribute("data-secondary"));
3135
+ const primary = lists.filter((list) => !list.hasAttribute("data-secondary")).flatMap(links);
3136
+ const secondary = secondaryList.flatMap(links);
3137
+ const search = nav.querySelector(":scope > a[data-search]");
3138
+ return { lists, secondaryList, primary, secondary, search };
3139
+ }
3140
+ /** What the element did to a node that has left the navigation is undone and forgotten, so nothing retains it. */
3141
+ #forgetGone() {
3142
+ const nav = this.#navEl;
3143
+ if (!nav) return;
3144
+ const gone = (node) => node !== this.#accessory && !nav.contains(node);
3145
+ for (const [el, was] of this.#hiddenWas) {
3146
+ if (!gone(el)) continue;
3147
+ el.toggleAttribute("hidden", was);
3148
+ this.#hiddenWas.delete(el);
3149
+ }
3150
+ for (const [badge, was] of this.#badgeWas) {
3151
+ if (!gone(badge)) continue;
3152
+ restoreAria(badge, was);
3153
+ this.#badgeWas.delete(badge);
3154
+ }
3155
+ for (const [link, had] of this.#linkStyle) {
3156
+ if (!gone(link)) continue;
3157
+ stripPlacement(link, had);
3158
+ this.#linkStyle.delete(link);
3159
+ }
3160
+ if (this.#hovered && gone(this.#hovered)) {
3161
+ this.#hovered.removeAttribute("data-lens-over");
3162
+ this.#hovered = null;
3163
+ }
3164
+ }
3165
+ /** A badge is a picture; what it says reaches a screen reader through a [data-badge-label] the author puts in the link. */
3166
+ #hideBadges() {
3167
+ for (const badge of this.#navEl.querySelectorAll("a [data-badge]")) {
3168
+ if (!this.#badgeWas.has(badge)) this.#badgeWas.set(badge, badge.getAttribute("aria-hidden"));
3169
+ if (badge.getAttribute("aria-hidden") !== "true") badge.setAttribute("aria-hidden", "true");
3170
+ }
3171
+ }
3172
+ /** The bottom safe area, read through a probe that is in the element only while it is read. */
3173
+ #readSafeBottom() {
3174
+ const probe = this.#probe ??= this.#makeProbe();
3175
+ this.append(probe);
3176
+ const value = parseFloat(getComputedStyle(probe).paddingBottom);
3177
+ probe.remove();
3178
+ return Number.isFinite(value) ? Math.max(value, 0) : 0;
3179
+ }
3180
+ #makeProbe() {
3181
+ const probe = this.ownerDocument.createElement("div");
3182
+ probe.setAttribute("data-vk-probe", "");
3183
+ probe.setAttribute("aria-hidden", "true");
3184
+ probe.style.cssText = "position:absolute;visibility:hidden;pointer-events:none;width:0;height:0;padding-bottom:env(safe-area-inset-bottom,0px)";
3185
+ this.#mine.add(probe);
3186
+ return probe;
3187
+ }
3188
+ #probe = null;
3189
+ #safeBottom = 0;
3190
+ #tier() {
3191
+ return sizeTier(effectiveInput()) === "touch" ? "touch" : "pointer";
3192
+ }
3193
+ #makePlan(ctx, as) {
3194
+ const { primary, secondary, search } = this.#collect();
3195
+ let nav = navigationForm(ctx, {
3196
+ primary: primary.length,
3197
+ secondary: secondary.length,
3198
+ search: search !== null,
3199
+ sidebarCollapsed: this.hasAttribute("collapsed"),
3200
+ panes: Number(this.getAttribute("panes")) || 1
3201
+ });
3202
+ const attr = this.getAttribute("form");
3203
+ if (attr && FORMS.includes(attr)) nav = fixedNavigationForm(nav, attr);
3204
+ const over = as.over && nav.form === "rail" && nav.expandable === "over";
3205
+ const resting = nav;
3206
+ if (over) nav = fixedNavigationForm(nav, "sidebar");
3207
+ if (as.form) nav = fixedNavigationForm(nav, as.form);
3208
+ const order = [...primary, ...secondary];
3209
+ const selected = order.findIndex(isCurrent);
3210
+ const layout = this.#layoutOf(ctx, nav, selected, as.minimized);
3211
+ const rail = over ? this.#layoutOf(ctx, resting, selected, as.minimized) : layout;
3212
+ const visible = /* @__PURE__ */ new Set();
3213
+ if (layout.minimized) {
3214
+ const at = selected >= 0 && selected < nav.primary ? selected : 0;
3215
+ const link = order[at];
3216
+ if (link) visible.add(link);
3217
+ } else {
3218
+ const count = nav.form === "sidebar" ? order.length : nav.primary;
3219
+ for (const link of order.slice(0, count)) visible.add(link);
3220
+ }
3221
+ return { nav, layout, order, search, selected, visible, secondaryShown: nav.form === "sidebar", insets: rail.insets, over };
3222
+ }
3223
+ #layoutOf(ctx, nav, selected, minimized) {
3224
+ return navLayout({
3225
+ form: nav.form,
3226
+ minimized: minimized && nav.form === "bar",
3227
+ window: ctx.window,
3228
+ items: nav.primary,
3229
+ secondary: nav.secondary,
3230
+ search: nav.search,
3231
+ selected,
3232
+ tier: this.#tier(),
3233
+ toggle: nav.collapsible || nav.expandable !== null,
3234
+ accessory: this.#accessory !== null,
3235
+ bottomInset: this.#safeBottom
3236
+ });
3237
+ }
3238
+ #forcedState = null;
3239
+ #forced() {
3240
+ return this.#forcedState;
3241
+ }
3242
+ #replan() {
3243
+ const ctx = this.#ctx;
3244
+ if (!this.#navEl || !ctx) return;
3245
+ this.#syncScroller();
3246
+ this.#forgetGone();
3247
+ this.#hideBadges();
3248
+ this.#safeBottom = this.#readSafeBottom();
3249
+ const forced = this.#forced();
3250
+ const minimized = forced?.kind === "minimized" || this.#minState.minimized;
3251
+ const plan = this.#makePlan(ctx, { minimized, over: this.#over });
3252
+ if (this.#over && !plan.over) this.#setOver(false, false);
3253
+ const prev = this.#plan;
3254
+ this.#plans.set(plan.layout, plan);
3255
+ this.#plan = plan;
3256
+ if (forced?.kind === "morph") {
3257
+ const from = forced.from === "minimized" ? this.#makePlan(ctx, { form: "bar", minimized: true, over: false }) : this.#makePlan(ctx, { form: forced.from, minimized: false, over: false });
3258
+ this.#plans.set(from.layout, from);
3259
+ const state = createNavMorph(from.layout);
3260
+ const via = from.layout.form === "bar" && plan.layout.form === "bar" ? "contract" : "lens";
3261
+ aimNavMorph(state, plan.layout, via);
3262
+ state.t.x = forced.t;
3263
+ state.t.v = 0;
3264
+ state.t.target = forced.t;
3265
+ this.#morph = state;
3266
+ } else if (!this.#morph || forced?.kind === "drag" || forced?.kind === "minimized") {
3267
+ this.#morph = createNavMorph(plan.layout);
3268
+ } else if (prev) {
3269
+ const contract = prev.layout.form === "bar" && plan.layout.form === "bar" && prev.layout.minimized !== plan.layout.minimized;
3270
+ aimNavMorph(this.#morph, plan.layout, contract ? "contract" : "lens");
3271
+ } else {
3272
+ this.#morph = createNavMorph(plan.layout);
3273
+ }
3274
+ this.#retargetPill(prev, plan);
3275
+ this.#retargetAccessory(prev, plan);
3276
+ if (forced?.kind === "drag") {
3277
+ this.#drag = createNavDrag();
3278
+ const body = plan.layout.body;
3279
+ pressNavDrag(this.#drag, plan.layout, body.x + forced.at * body.width);
3280
+ this.#drag.lift.x = 1;
3281
+ this.#drag.lift.target = 1;
3282
+ }
3283
+ this.#syncKeyShortcut(plan);
3284
+ this.#ensureGlass();
3285
+ this.#publish(plan);
3286
+ this.#run();
3287
+ }
3288
+ #retargetPill(prev, next) {
3289
+ const anim = this.#pillAnim;
3290
+ const to = next.layout.pill;
3291
+ const sameForm2 = prev !== null && prev.layout.form === next.layout.form && prev.layout.minimized === next.layout.minimized;
3292
+ const instant = this.#presentation?.instantSprings ?? false;
3293
+ if (sameForm2 && prev.selected !== next.selected && prev.layout.pill && to && !instant) {
3294
+ anim.from = this.#pillBox() ?? prev.layout.pill;
3295
+ anim.to = to;
3296
+ anim.state.t = { x: 0, v: 0, target: 1 };
3297
+ } else if (sameForm2 && anim.state.t.x < 1 && to) {
3298
+ anim.to = to;
3299
+ } else {
3300
+ anim.from = anim.to = to;
3301
+ anim.state.t = { x: 1, v: 0, target: 1 };
3302
+ }
3303
+ }
3304
+ #pillBox() {
3305
+ const { from, to, state } = this.#pillAnim;
3306
+ if (!to) return null;
3307
+ return from && state.t.x < 1 ? formMorphFrame(from, to, state).box : to;
3308
+ }
3309
+ #retargetAccessory(prev, next) {
3310
+ const anim = this.#accAnim;
3311
+ const box2 = next.layout.accessory;
3312
+ const placement = next.layout.accessoryPlacement;
3313
+ if (!box2 || !placement) {
3314
+ anim.from = anim.to = null;
3315
+ anim.state.t = { x: 1, v: 0, target: 1 };
3316
+ return;
3317
+ }
3318
+ const target = { box: box2, placement };
3319
+ const instant = this.#presentation?.instantSprings ?? false;
3320
+ if (anim.to && anim.to.placement !== placement && !instant && prev) {
3321
+ anim.from = this.#accBox() ?? anim.to;
3322
+ anim.to = target;
3323
+ anim.state.t = { x: 0, v: 0, target: 1 };
3324
+ anim.state.fromInk = 1;
3325
+ } else if (anim.to && anim.state.t.x < 1 && anim.to.placement === placement) {
3326
+ anim.to = target;
3327
+ } else {
3328
+ anim.from = anim.to = target;
3329
+ anim.state.t = { x: 1, v: 0, target: 1 };
3330
+ }
3331
+ }
3332
+ #accBox() {
3333
+ const { from, to, state } = this.#accAnim;
3334
+ if (!to) return null;
3335
+ if (!from || state.t.x >= 1) return to;
3336
+ const frame = formMorphFrame(from.box, to.box, state);
3337
+ return { box: frame.box, placement: frame.showing === "from" ? from.placement : to.placement };
3338
+ }
3339
+ // ---- what the content must clear ----------------------------------------------------------------------------
3340
+ #publish(plan) {
3341
+ const { layout } = plan;
3342
+ const bottom = round(plan.insets.bottom);
3343
+ const start = round(plan.insets.start);
3344
+ const key = JSON.stringify([layout.form, layout.minimized, bottom, start, layout.accessoryPlacement]);
3345
+ if (key === this.#lastKey) return;
3346
+ this.#lastKey = key;
3347
+ const parent = this.parentElement;
3348
+ if (parent) {
3349
+ if (this.#parent !== parent) {
3350
+ this.#clearInsets();
3351
+ this.#parent = parent;
3352
+ this.#hadStyle.parent = parent.hasAttribute("style");
3353
+ this.#parentWas = {
3354
+ bottom: parent.style.getPropertyValue("--vire-nav-inset-bottom"),
3355
+ start: parent.style.getPropertyValue("--vire-nav-inset-start")
3356
+ };
3357
+ }
3358
+ parent.style.setProperty("--vire-nav-inset-bottom", `${bottom}px`);
3359
+ parent.style.setProperty("--vire-nav-inset-start", `${start}px`);
3360
+ }
3361
+ this.dispatchEvent(
3362
+ new CustomEvent("vire-nav-layout", {
3363
+ bubbles: true,
3364
+ detail: { form: layout.form, minimized: layout.minimized, insets: { bottom, start }, accessoryPlacement: layout.accessoryPlacement }
3365
+ })
3366
+ );
3367
+ }
3368
+ #clearInsets() {
3369
+ const parent = this.#parent;
3370
+ if (!parent) return;
3371
+ const was = this.#parentWas;
3372
+ for (const [name, value] of [
3373
+ ["--vire-nav-inset-bottom", was?.bottom],
3374
+ ["--vire-nav-inset-start", was?.start]
3375
+ ]) {
3376
+ if (value) parent.style.setProperty(name, value);
3377
+ else parent.style.removeProperty(name);
3378
+ }
3379
+ dropEmptyStyle2(parent, this.#hadStyle.parent);
3380
+ this.#parent = null;
3381
+ this.#parentWas = null;
3382
+ }
3383
+ // ---- scrolling ----------------------------------------------------------------------------------------------
3384
+ /** The property wins while its node is in the document; the id is looked up again each time, as the node may be parsed late or replaced. */
3385
+ #wantedScroller() {
3386
+ if (this.getAttribute("minimize") !== "scroll") return null;
3387
+ if (this.#scrollerProp?.isConnected) return this.#scrollerProp;
3388
+ const id = this.getAttribute("scroller");
3389
+ const root = this.getRootNode();
3390
+ return id && "getElementById" in root ? root.getElementById(id) : null;
3391
+ }
3392
+ #isPage(el) {
3393
+ const doc = this.ownerDocument;
3394
+ return el === doc.documentElement || el === doc.body || el === doc.scrollingElement;
3395
+ }
3396
+ #bindScroller() {
3397
+ this.#unbindScroller?.();
3398
+ this.#unbindScroller = null;
3399
+ const el = this.#wantedScroller();
3400
+ this.#scrollerEl = el;
3401
+ this.#minState = createMinimize(el ? this.#scrollPosition(el) : 0);
3402
+ if (!el) return;
3403
+ const target = this.#isPage(el) ? this.ownerDocument.defaultView : el;
3404
+ const onScroll = () => {
3405
+ const was = this.#minState.minimized;
3406
+ stepMinimize(this.#minState, this.#scrollPosition(el));
3407
+ if (this.#minState.minimized !== was) this.#replan();
3408
+ };
3409
+ target.addEventListener("scroll", onScroll, { passive: true });
3410
+ this.#unbindScroller = () => target.removeEventListener("scroll", onScroll);
3411
+ }
3412
+ #scrollPosition(el) {
3413
+ const source = this.#isPage(el) ? this.ownerDocument.scrollingElement ?? el : el;
3414
+ return clampScrollTop(source.scrollTop, source.scrollHeight, source.clientHeight);
3415
+ }
3416
+ /** Follows the scroller to what it is now; true when that took the bar out of the minimised state. */
3417
+ #syncScroller() {
3418
+ if (this.#wantedScroller() === this.#scrollerEl) return false;
3419
+ const was = this.#minState.minimized;
3420
+ this.#bindScroller();
3421
+ return was;
3422
+ }
3423
+ // ---- the toggle and the sidebar over the content ------------------------------------------------------------
3424
+ #toggleClick() {
3425
+ const plan = this.#plan;
3426
+ if (!plan) return;
3427
+ if (this.#over) {
3428
+ this.#setOver(false);
3429
+ } else if (plan.nav.collapsible) {
3430
+ this.setAttribute("collapsed", "");
3431
+ this.#fireCollapse(true);
3432
+ } else if (plan.nav.expandable === "beside") {
3433
+ this.removeAttribute("collapsed");
3434
+ this.#fireCollapse(false);
3435
+ } else if (plan.nav.expandable === "over") {
3436
+ this.#setOver(true);
3437
+ }
3438
+ }
3439
+ #fireCollapse(collapsed) {
3440
+ this.dispatchEvent(new CustomEvent("vire-nav-collapse", { bubbles: true, detail: { collapsed } }));
3441
+ }
3442
+ /** Closing by Escape or the scrim hands focus back to the toggle that opened the sidebar. */
3443
+ #closeOver(pressed = false) {
3444
+ const inside = this.#navEl?.contains(this.#activeElement()) ?? false;
3445
+ this.#setOver(false);
3446
+ if (!inside || !this.#toggle || this.#toggle.hidden) return;
3447
+ this.#focusQuietly(this.#toggle);
3448
+ if (pressed) this.#holdFocus();
3449
+ }
3450
+ // The scrim is gone by the time the press's mousedown lands on the content, whose default would blur the toggle.
3451
+ #holdFocus() {
3452
+ this.#unhold?.();
3453
+ const doc = this.ownerDocument;
3454
+ const hold = (e) => e.preventDefault();
3455
+ const timer = window.setTimeout(() => off(), 300);
3456
+ const off = () => {
3457
+ doc.removeEventListener("mousedown", hold, true);
3458
+ window.clearTimeout(timer);
3459
+ this.#unhold = null;
3460
+ };
3461
+ doc.addEventListener("mousedown", hold, { capture: true, once: true });
3462
+ this.#unhold = off;
3463
+ }
3464
+ #unhold = null;
3465
+ #activeElement() {
3466
+ return this.getRootNode().activeElement;
3467
+ }
3468
+ #focusQuietly(el) {
3469
+ this.#handing = true;
3470
+ try {
3471
+ el.focus({ preventScroll: true });
3472
+ } finally {
3473
+ this.#handing = false;
3474
+ }
3475
+ }
3476
+ #handing = false;
3477
+ #setOver(open, replan = true) {
3478
+ if (this.#over === open) return;
3479
+ this.#over = open;
3480
+ if (open && this.#navEl) {
3481
+ const scrim = this.ownerDocument.createElement("div");
3482
+ scrim.setAttribute("data-vk-scrim", "");
3483
+ scrim.addEventListener("pointerdown", () => this.#closeOver(true));
3484
+ this.#mine.add(scrim);
3485
+ this.insertBefore(scrim, this.#navEl);
3486
+ this.#scrim = scrim;
3487
+ } else {
3488
+ this.#scrim?.remove();
3489
+ this.#scrim = null;
3490
+ }
3491
+ if (replan) this.#replan();
3492
+ }
3493
+ #syncToggle() {
3494
+ const toggle = this.#toggle;
3495
+ const shown = this.#shown;
3496
+ if (!toggle || !shown) return;
3497
+ const expanded = shown.nav.form === "sidebar";
3498
+ toggle.setAttribute("aria-expanded", String(expanded));
3499
+ toggle.setAttribute("aria-label", this.getAttribute("toggle-label") || DEFAULT_TOGGLE_LABEL);
3500
+ }
3501
+ #syncKeyShortcut(plan) {
3502
+ const link = plan.search && plan.nav.search?.shortcut && plan.layout.search ? plan.search : null;
3503
+ if (link !== this.#shortcut) this.#releaseShortcut();
3504
+ if (!link) return;
3505
+ if (!this.#shortcut) {
3506
+ this.#shortcut = link;
3507
+ this.#shortcutWas = link.getAttribute("aria-keyshortcuts");
3508
+ }
3509
+ link.setAttribute("aria-keyshortcuts", this.#searchKey());
3510
+ }
3511
+ #releaseShortcut() {
3512
+ const link = this.#shortcut;
3513
+ if (link) {
3514
+ if (this.#shortcutWas === null) link.removeAttribute("aria-keyshortcuts");
3515
+ else link.setAttribute("aria-keyshortcuts", this.#shortcutWas);
3516
+ }
3517
+ this.#shortcut = null;
3518
+ this.#shortcutWas = null;
3519
+ }
3520
+ #shortcut = null;
3521
+ #shortcutWas = null;
3522
+ // ---- applying a form to the markup --------------------------------------------------------------------------
3523
+ #setHidden(el, hidden) {
3524
+ if (!this.#hiddenWas.has(el)) this.#hiddenWas.set(el, el.hasAttribute("hidden"));
3525
+ el.toggleAttribute("hidden", hidden);
3526
+ }
3527
+ #slotOf(link) {
3528
+ const item = link.closest("li");
3529
+ return item && this.#navEl.contains(item) ? item : link;
3530
+ }
3531
+ #place(el, box2, hit) {
3532
+ if (!this.#linkStyle.has(el)) this.#linkStyle.set(el, el.hasAttribute("style"));
3533
+ const s = el.style;
3534
+ s.setProperty("--vk-lx", px(box2.x));
3535
+ s.setProperty("--vk-ly", px(box2.y));
3536
+ s.setProperty("--vk-lw", px(box2.width));
3537
+ s.setProperty("--vk-lh", px(box2.height));
3538
+ s.setProperty("--vk-lr", px(box2.radius));
3539
+ s.setProperty("--vk-slop-x", String(Math.max(0, (hit - box2.width) / 2)));
3540
+ s.setProperty("--vk-slop-y", String(Math.max(0, (hit - box2.height) / 2)));
3541
+ }
3542
+ /** Turns the markup into this plan's form: which links show, where each sits, what the head holds. */
3543
+ #applyShown(shown) {
3544
+ this.#shown = shown;
3545
+ const { layout, nav } = shown;
3546
+ const { lists, secondaryList, primary, secondary, search } = this.#collect();
3547
+ const focused = this.#navEl?.contains(this.#activeElement()) ? this.#activeElement() : null;
3548
+ this.setAttribute("data-form", layout.form);
3549
+ if (nav.search) this.setAttribute("data-search", nav.search.form);
3550
+ else this.removeAttribute("data-search");
3551
+ this.toggleAttribute("data-minimized", layout.minimized);
3552
+ const hit = hitTarget(effectiveInput());
3553
+ for (const link of [...primary, ...secondary]) {
3554
+ this.#setHidden(this.#slotOf(link), !shown.visible.has(link));
3555
+ const box2 = layout.items[shown.order.indexOf(link)];
3556
+ if (box2 && shown.visible.has(link)) this.#place(link, box2, hit);
3557
+ }
3558
+ for (const list of lists) {
3559
+ const secondaryOnly = secondaryList.includes(list);
3560
+ this.#setHidden(list, secondaryOnly && !shown.secondaryShown);
3561
+ }
3562
+ if (search) {
3563
+ this.#setHidden(search, layout.search === null);
3564
+ if (layout.search) this.#place(search, layout.search, hit);
3565
+ }
3566
+ const toggle = this.#toggle;
3567
+ if (toggle) {
3568
+ toggle.hidden = layout.toggle === null;
3569
+ if (layout.toggle) this.#place(toggle, layout.toggle, hit);
3570
+ this.#syncToggle();
3571
+ }
3572
+ const rows = layout.form === "bar" ? [] : [...layout.items, ...layout.search ? [layout.search] : []];
3573
+ const bottom = Math.max(0, ...rows.map((row) => row.y + row.height)) + 8;
3574
+ this.#fits = rows.length === 0 || bottom <= layout.body.y + layout.body.height + 0.5;
3575
+ if (focused && this.#loses(focused, shown)) this.#handFocus(shown);
3576
+ }
3577
+ #loses(el, shown) {
3578
+ if (el === this.#toggle) return shown.layout.toggle === null;
3579
+ if (el === shown.search) return shown.layout.search === null;
3580
+ return shown.order.includes(el) && !shown.visible.has(el);
3581
+ }
3582
+ /** A link the form hides hands focus to the selected link, else the toggle, else the first link shown. */
3583
+ #handFocus(shown) {
3584
+ const selected = shown.order[shown.selected];
3585
+ const first2 = shown.order.find((link) => shown.visible.has(link));
3586
+ const target = selected && shown.visible.has(selected) ? selected : shown.layout.toggle ? this.#toggle : first2 ?? null;
3587
+ if (target) this.#focusQuietly(target);
3588
+ }
3589
+ #fits = true;
3590
+ // ---- drawing ------------------------------------------------------------------------------------------------
3591
+ #restLayout() {
3592
+ const morph = this.#morph;
3593
+ if (!morph || currentNavFrame(morph).phase !== "rest") return null;
3594
+ return morph.t.x >= 1 ? morph.to : morph.from;
3595
+ }
3596
+ #run() {
3597
+ const instant = this.#presentation?.instantSprings ?? false;
3598
+ if (instant && this.#morph && !this.#forced()) {
3599
+ stepNavMorph(this.#morph, 0, true);
3600
+ const layout = this.#restLayout();
3601
+ if (layout) stepNavDrag(this.#drag, 0, true);
3602
+ stepFormMorph(this.#pillAnim.state, 0, 1, true);
3603
+ stepFormMorph(this.#accAnim.state, 0, 1, true);
3604
+ }
3605
+ this.#paint();
3606
+ if (this.#frame || this.#forced()) return;
3607
+ this.#lastFrame = 0;
3608
+ this.#frame = requestAnimationFrame((t) => this.#tick(t));
3609
+ }
3610
+ #tick(now) {
3611
+ this.#frame = 0;
3612
+ const morph = this.#morph;
3613
+ if (!this.isConnected || !morph || this.#forced()) return;
3614
+ const dt = this.#lastFrame ? Math.min((now - this.#lastFrame) / 1e3, 0.05) : 1 / 60;
3615
+ this.#lastFrame = now;
3616
+ const instant = this.#presentation?.instantSprings ?? false;
3617
+ let moving = stepNavMorph(morph, dt, instant);
3618
+ const layout = this.#restLayout();
3619
+ if (layout && stepNavDrag(this.#drag, dt, instant)) moving = true;
3620
+ if (stepFormMorph(this.#pillAnim.state, dt, 1, instant)) moving = true;
3621
+ if (stepFormMorph(this.#accAnim.state, dt, 1, instant)) moving = true;
3622
+ this.#paint();
3623
+ if (moving) this.#frame = requestAnimationFrame((t) => this.#tick(t));
3624
+ }
3625
+ #paint() {
3626
+ const plan = this.#plan;
3627
+ const morph = this.#morph;
3628
+ const nav = this.#navEl;
3629
+ if (!plan || !morph || !nav) return;
3630
+ const frame = currentNavFrame(morph);
3631
+ const shownLayout = frame.showing === "from" ? morph.from : morph.to;
3632
+ const shown = this.#plans.get(shownLayout) ?? plan;
3633
+ if (shown !== this.#shown) this.#applyShown(shown);
3634
+ const rest = frame.phase === "rest";
3635
+ const dragFrame = rest ? navDragFrame(this.#drag, shownLayout) : null;
3636
+ const lens = frame.lens ? { box: frame.lens.box, lift: frame.lens.lift, dragging: false, hover: -1 } : dragFrame ? { box: dragFrame.lens, lift: dragFrame.lift, dragging: true, hover: dragFrame.hover } : null;
3637
+ this.toggleAttribute("data-moving", !rest || dragFrame !== null);
3638
+ const scrolls = rest && dragFrame === null && !this.#fits;
3639
+ let restore = false;
3640
+ if (this.hasAttribute("data-scroll") !== scrolls) {
3641
+ this.toggleAttribute("data-scroll", scrolls);
3642
+ this.#scrollReset = !scrolls;
3643
+ restore = scrolls;
3644
+ }
3645
+ const ink = frame.showing === "from" ? frame.ink.outgoing : frame.ink.incoming;
3646
+ this.style.setProperty("--vk-ink", clamp012(ink).toFixed(3));
3647
+ const box2 = frame.body;
3648
+ const s = nav.style;
3649
+ s.setProperty("--vk-x", px(box2.x));
3650
+ s.setProperty("--vk-y", px(box2.y));
3651
+ s.setProperty("--vk-w", px(box2.width));
3652
+ s.setProperty("--vk-h", px(box2.height));
3653
+ s.setProperty("--vk-r", px(box2.radius));
3654
+ if (restore) nav.scrollTop = this.#scrollTops.get(shown.layout.form) ?? 0;
3655
+ const pill = shown === plan ? this.#pillBox() : shownLayout.pill;
3656
+ this.#paintPill(shown.layout.minimized ? null : pill, ink * (1 - (lens?.lift ?? 0)));
3657
+ this.#paintLens(lens, shown, plan);
3658
+ this.#paintAccessory();
3659
+ if (this.#body?.session) {
3660
+ const second = frame.second;
3661
+ const centre = (b) => ({ x: b.x + b.width / 2, y: b.y + b.height / 2 });
3662
+ const morphShape = second ? {
3663
+ smoothing: round(second.smoothing),
3664
+ shape: {
3665
+ offsetX: round(centre(second.box).x - centre(box2).x),
3666
+ offsetY: round(centre(second.box).y - centre(box2).y),
3667
+ width: round(second.box.width),
3668
+ height: round(second.box.height),
3669
+ cornerRadius: round(second.box.radius)
3670
+ }
3671
+ } : null;
3672
+ const soft = shown.layout.form === "sidebar";
3673
+ this.#push(this.#body, box2, soft ? "s" : "c", () => this.#bodyMaterial(soft), morphShape);
3674
+ }
3675
+ }
3676
+ #bodyMaterial(soft) {
3677
+ return soft ? menuMaterial(vireglass.VIREGLASS_CONTROL_MATERIAL, 0) : atomMaterial(vireglass.VIREGLASS_CONTROL_MATERIAL, "iconButton", ATOM_AT_REST);
3678
+ }
3679
+ #paintPill(box2, opacity) {
3680
+ const pill = this.#pill;
3681
+ if (!pill) return;
3682
+ pill.hidden = box2 === null;
3683
+ if (!box2) return;
3684
+ const s = pill.style;
3685
+ s.setProperty("--vk-px", px(box2.x));
3686
+ s.setProperty("--vk-py", px(box2.y));
3687
+ s.setProperty("--vk-pw", px(box2.width));
3688
+ s.setProperty("--vk-ph", px(box2.height));
3689
+ s.setProperty("--vk-pr", px(box2.radius));
3690
+ s.setProperty("--vk-pill-op", clamp012(opacity).toFixed(3));
3691
+ }
3692
+ #paintLens(lens, shown, plan) {
3693
+ const el = this.#lens;
3694
+ const slot = this.#lensSlot;
3695
+ if (!el || !slot) return;
3696
+ const visible = lens !== null;
3697
+ this.toggleAttribute("data-lens", visible);
3698
+ if (!lens) {
3699
+ this.#detach(slot);
3700
+ this.#markHover(-1, shown);
3701
+ return;
3702
+ }
3703
+ const s = el.style;
3704
+ s.setProperty("--vk-x", px(lens.box.x));
3705
+ s.setProperty("--vk-y", px(lens.box.y));
3706
+ s.setProperty("--vk-w", px(lens.box.width));
3707
+ s.setProperty("--vk-h", px(lens.box.height));
3708
+ s.setProperty("--vk-r", px(lens.box.radius));
3709
+ s.setProperty("--vk-lift", clamp012(lens.lift).toFixed(3));
3710
+ el.setAttribute("data-form", shown.layout.form);
3711
+ el.toggleAttribute("data-dragging", lens.dragging);
3712
+ this.#markHover(lens.dragging ? lens.hover : -1, shown);
3713
+ const selected = plan.order[plan.selected] ?? null;
3714
+ if (selected !== this.#lensFor) {
3715
+ this.#lensFor = selected;
3716
+ const inner = this.#lensInner;
3717
+ inner.replaceChildren(...selected ? Array.from(selected.childNodes).map((node) => node.cloneNode(true)) : []);
3718
+ for (const withId of Array.from(inner.querySelectorAll("[id]"))) withId.removeAttribute("id");
3719
+ }
3720
+ if (this.#presentation?.glass && !this.#glassBroken) {
3721
+ const key = this.#modeKey;
3722
+ if (slot.key !== key) this.#attach(slot, key, () => this.#lensMaterial(0), lens.box);
3723
+ const step = round(clamp012(lens.lift) * MATERIAL_STEPS3) / MATERIAL_STEPS3;
3724
+ if (slot.session) {
3725
+ const body = atomBody(vireglass.VIREGLASS_CONTROL_MATERIAL, { ...ATOM_AT_REST, pressed: step }, this.#presentation.frost);
3726
+ s.setProperty("--vk-body-alpha", body.alpha.toFixed(3));
3727
+ s.setProperty("--vk-body-white", body.white.toFixed(3));
3728
+ s.setProperty("--vk-body-lift", body.lift.toFixed(3));
3729
+ if (Math.abs(slot.appear - lens.lift) > 0.02) {
3730
+ slot.appear = lens.lift;
3731
+ slot.session.setAppear(clamp012(lens.lift));
3732
+ }
3733
+ this.#push(slot, lens.box, String(step), () => this.#lensMaterial(step), void 0);
3734
+ }
3735
+ }
3736
+ }
3737
+ #lensMaterial(step) {
3738
+ return atomMaterial(vireglass.VIREGLASS_CONTROL_MATERIAL, "iconButton", { ...ATOM_AT_REST, pressed: step });
3739
+ }
3740
+ #hovered = null;
3741
+ #markHover(index, shown) {
3742
+ const link = index >= 0 ? shown.order[index] ?? null : null;
3743
+ if (link === this.#hovered) return;
3744
+ this.#hovered?.removeAttribute("data-lens-over");
3745
+ this.#hovered = link;
3746
+ link?.setAttribute("data-lens-over", "");
3747
+ }
3748
+ #paintAccessory() {
3749
+ const el = this.#accessory;
3750
+ const slot = this.#accSlot;
3751
+ if (!el || !slot) return;
3752
+ const anim = this.#accAnim;
3753
+ const frame = anim.to ? this.#accBox() : null;
3754
+ if (!frame || !anim.to) {
3755
+ this.#setHidden(el, true);
3756
+ return;
3757
+ }
3758
+ this.#setHidden(el, false);
3759
+ const moving = anim.state.t.x < 1 && anim.from !== null;
3760
+ el.toggleAttribute("data-moving", moving);
3761
+ el.setAttribute("data-placement", frame.placement);
3762
+ const box2 = frame.box;
3763
+ const s = el.style;
3764
+ s.setProperty("--vk-x", px(box2.x));
3765
+ s.setProperty("--vk-y", px(box2.y));
3766
+ s.setProperty("--vk-w", px(box2.width));
3767
+ s.setProperty("--vk-h", px(box2.height));
3768
+ s.setProperty("--vk-r", px(box2.radius));
3769
+ if (moving && anim.from) {
3770
+ const f = formMorphFrame(anim.from.box, anim.to.box, anim.state);
3771
+ s.setProperty("--vk-ink", clamp012(f.showing === "from" ? f.ink.outgoing : f.ink.incoming).toFixed(3));
3772
+ }
3773
+ this.#push(slot, box2, "c", () => this.#bodyMaterial(false), void 0);
3774
+ }
3775
+ // ---- the glass ----------------------------------------------------------------------------------------------
3776
+ #detach(slot) {
3777
+ slot.token += 1;
3778
+ slot.session?.destroy();
3779
+ slot.session = null;
3780
+ slot.key = "";
3781
+ slot.last = emptyLast();
3782
+ slot.appear = -1;
3783
+ }
3784
+ #attach(slot, key, material, box2) {
3785
+ if (key === slot.key) return;
3786
+ this.#detach(slot);
3787
+ slot.key = key;
3788
+ if (!key || !this.#mode) return;
3789
+ const token = slot.token;
3790
+ void attachControlGlass(
3791
+ slot.el,
3792
+ {
3793
+ material: material(),
3794
+ geometry: { width: round(box2.width), height: round(box2.height), cornerRadius: round(box2.radius) },
3795
+ accessibility: this.#mode,
3796
+ onTouch: false,
3797
+ sample: this.#sample,
3798
+ interactive: false
3799
+ },
3800
+ () => token === slot.token && this.isConnected
3801
+ ).then((session) => {
3802
+ if (!session) return;
3803
+ slot.session = session;
3804
+ slot.last = emptyLast();
3805
+ this.#paint();
3806
+ if (slot === this.#body) this.dispatchEvent(new CustomEvent("vire-glass-ready"));
3807
+ }).catch(() => {
3808
+ if (token !== slot.token) return;
3809
+ slot.key = "";
3810
+ this.#glassBroken = true;
3811
+ this.#refreshMode(false, true);
3812
+ });
3813
+ }
3814
+ /** Hands the lens only what changed: a new morph or material rebuilds its filter, a moved box needs nothing. */
3815
+ #push(slot, box2, materialKey, material, lens) {
3816
+ const session = slot.session;
3817
+ if (!session) return;
3818
+ const geometry = { width: round(box2.width), height: round(box2.height), cornerRadius: round(box2.radius) };
3819
+ const key = {
3820
+ material: materialKey,
3821
+ morph: lens === void 0 ? "-" : lens ?? "none",
3822
+ geometry: `${geometry.width}x${geometry.height}r${geometry.cornerRadius}`
3823
+ };
3824
+ const last2 = slot.last;
3825
+ if (key.material === last2.material && sameMorph(key.morph, last2.morph) && key.geometry === last2.geometry) return;
3826
+ const next = {};
3827
+ if (key.geometry !== last2.geometry) next.geometry = geometry;
3828
+ if (key.material !== last2.material) next.material = material();
3829
+ if (lens !== void 0 && !sameMorph(key.morph, last2.morph)) next.morph = lens;
3830
+ slot.last = key;
3831
+ session.frame(next);
3832
+ }
3833
+ // ---- modes --------------------------------------------------------------------------------------------------
3834
+ #wantsGlass() {
3835
+ const wanted = this.getAttribute("glass");
3836
+ return wanted !== null && GLASS_ON2.has(wanted);
3837
+ }
3838
+ #refreshMode(force = false, afterFailure = false) {
3839
+ if (!afterFailure) this.#glassBroken = false;
3840
+ const mode = effectiveA11y(this.#own);
3841
+ let next = presentationFor(mode, this.#wantsGlass());
3842
+ if (this.#glassBroken && next.glass) next = { ...next, glass: false, surface: next.frost ? "frosted" : "plain" };
3843
+ const changed = force || !this.#mode || !sameA11y(this.#mode, mode) || next.surface !== this.#presentation?.surface;
3844
+ this.#mode = mode;
3845
+ this.#modeKey = JSON.stringify(mode);
3846
+ this.#presentation = next;
3847
+ this.setAttribute("data-surface", next.surface);
3848
+ this.toggleAttribute("data-contrast", next.contrastBorder);
3849
+ this.toggleAttribute("data-frost", next.frost);
3850
+ this.setAttribute("data-glass", next.glass ? "on" : "off");
3851
+ const body = atomBody(vireglass.VIREGLASS_CONTROL_MATERIAL, ATOM_AT_REST, next.frost);
3852
+ this.style.setProperty("--vk-body-alpha", body.alpha.toFixed(3));
3853
+ this.style.setProperty("--vk-body-white", body.white.toFixed(3));
3854
+ this.style.setProperty("--vk-body-lift", body.lift.toFixed(3));
3855
+ if (changed) {
3856
+ for (const slot of [this.#body, this.#lensSlot, this.#accSlot]) if (slot) this.#detach(slot);
3857
+ this.#ensureGlass();
3858
+ }
3859
+ if (this.#ctx) this.#run();
3860
+ }
3861
+ /** Attaches the body's and the accessory's glass once the first form is known; the lens waits for its first move. */
3862
+ #ensureGlass() {
3863
+ const layout = this.#plan?.layout;
3864
+ if (!layout) return;
3865
+ const key = this.#presentation?.glass && this.#mode ? this.#modeKey : "";
3866
+ if (this.#body) this.#attach(this.#body, key, () => this.#bodyMaterial(false), layout.body);
3867
+ if (this.#accSlot && layout.accessory) this.#attach(this.#accSlot, key, () => this.#bodyMaterial(false), layout.accessory);
3868
+ }
3869
+ // ---- the drag -----------------------------------------------------------------------------------------------
3870
+ #pressDown(e) {
3871
+ if (e.button !== 0 || !e.isPrimary || this.#forced() || this.#press) return;
3872
+ const target = e.target instanceof Element ? e.target : null;
3873
+ if (target?.closest("button, a[data-search]")) return;
3874
+ const layout = this.#restLayout();
3875
+ if (!layout || layout.form !== "bar" || layout.minimized) return;
3876
+ const left = this.getBoundingClientRect().left;
3877
+ pressNavDrag(this.#drag, layout, e.clientX - left);
3878
+ if (!this.#drag.active) return;
3879
+ this.#press = { id: e.pointerId, startX: e.clientX, left, dragging: false, last: e.timeStamp, onLink: target?.closest("a") != null };
3880
+ const win = this.ownerDocument.defaultView;
3881
+ const move = (ev) => this.#pressMove(ev);
3882
+ const up = (ev) => this.#pressUp(ev, false);
3883
+ const cancel = (ev) => this.#pressUp(ev, true);
3884
+ win.addEventListener("pointermove", move);
3885
+ win.addEventListener("pointerup", up);
3886
+ win.addEventListener("pointercancel", cancel);
3887
+ this.#endPress = () => {
3888
+ win.removeEventListener("pointermove", move);
3889
+ win.removeEventListener("pointerup", up);
3890
+ win.removeEventListener("pointercancel", cancel);
3891
+ this.#endPress = null;
3892
+ };
3893
+ this.#run();
3894
+ }
3895
+ #endPress = null;
3896
+ #pressMove(e) {
3897
+ const press = this.#press;
3898
+ const layout = this.#restLayout();
3899
+ if (!press || e.pointerId !== press.id || !layout) return;
3900
+ if (!press.dragging) {
3901
+ if (Math.abs(e.clientX - press.startX) <= DRAG_SLOP) return;
3902
+ press.dragging = true;
3903
+ try {
3904
+ this.#navEl?.setPointerCapture(e.pointerId);
3905
+ } catch {
3906
+ }
3907
+ }
3908
+ const dt = Math.min(Math.max((e.timeStamp - press.last) / 1e3, 1 / 240), 0.05);
3909
+ press.last = e.timeStamp;
3910
+ moveNavDrag(this.#drag, layout, e.clientX - press.left, dt);
3911
+ this.#run();
3912
+ }
3913
+ #pressUp(e, cancelled) {
3914
+ const press = this.#press;
3915
+ const plan = this.#plan;
3916
+ const layout = this.#restLayout();
3917
+ if (!press || e.pointerId !== press.id) return;
3918
+ this.#press = null;
3919
+ this.#endPress?.();
3920
+ try {
3921
+ this.#navEl?.releasePointerCapture(e.pointerId);
3922
+ } catch {
3923
+ }
3924
+ if (!plan) return;
3925
+ const bar = layout !== null && layout.form === "bar" && !layout.minimized;
3926
+ if (cancelled || !bar || !press.dragging && !press.onLink) {
3927
+ cancelNavDrag(this.#drag, layout ?? plan.layout, plan.selected);
3928
+ } else {
3929
+ const index = releaseNavDrag(this.#drag, layout);
3930
+ if (press.dragging) {
3931
+ plan.order[index]?.click();
3932
+ this.#swallowClick();
3933
+ }
3934
+ }
3935
+ this.#run();
3936
+ }
3937
+ #swallowClick() {
3938
+ this.#swallow?.();
3939
+ const win = this.ownerDocument.defaultView;
3940
+ const swallow = (e) => {
3941
+ e.preventDefault();
3942
+ e.stopImmediatePropagation();
3943
+ off();
3944
+ };
3945
+ const timer = win.setTimeout(() => off(), SWALLOW_MS);
3946
+ const off = () => {
3947
+ win.removeEventListener("click", swallow, true);
3948
+ win.clearTimeout(timer);
3949
+ this.#swallow = null;
3950
+ };
3951
+ win.addEventListener("click", swallow, true);
3952
+ this.#swallow = off;
3953
+ }
3954
+ // ---- forced states ------------------------------------------------------------------------------------------
3955
+ #applyForced() {
3956
+ cancelAnimationFrame(this.#frame);
3957
+ this.#frame = 0;
3958
+ this.#drag = createNavDrag();
3959
+ this.#morph = null;
3960
+ this.#shown = null;
3961
+ this.#replan();
3962
+ }
3963
+ };
3964
+ }
1865
3965
 
1866
3966
  // src/web/index.ts
1867
3967
  var BUTTON_TAG = "vire-button";
1868
3968
  var ICON_BUTTON_TAG = "vire-icon-button";
1869
3969
  var MENU_TAG = "vire-menu";
3970
+ var NAV_TAG = "vire-nav";
1870
3971
  function defineVireElements(options = {}) {
1871
3972
  const registry = options.registry ?? (typeof customElements === "undefined" ? void 0 : customElements);
1872
3973
  if (!registry) return;
@@ -1878,15 +3979,24 @@ function defineVireElements(options = {}) {
1878
3979
  if (!registry.get(tag)) registry.define(tag, createControlElementClass(kind, { nonce: options.nonce }));
1879
3980
  }
1880
3981
  if (!registry.get(MENU_TAG)) registry.define(MENU_TAG, createMenuElementClass({ nonce: options.nonce }));
3982
+ if (!registry.get(NAV_TAG)) registry.define(NAV_TAG, createNavElementClass({ nonce: options.nonce }));
1881
3983
  }
1882
3984
 
1883
3985
  exports.BUTTON_TAG = BUTTON_TAG;
1884
3986
  exports.ICON_BUTTON_TAG = ICON_BUTTON_TAG;
1885
3987
  exports.MENU_TAG = MENU_TAG;
3988
+ exports.NAV_TAG = NAV_TAG;
1886
3989
  exports.STYLES = STYLES;
1887
3990
  exports.defineVireElements = defineVireElements;
3991
+ exports.effectiveInput = effectiveInput;
1888
3992
  exports.getA11yOverride = getA11yOverride;
3993
+ exports.getInputOverride = getInputOverride;
1889
3994
  exports.setA11yOverride = setA11yOverride;
3995
+ exports.setInputOverride = setInputOverride;
3996
+ exports.setViewportOverride = setViewportOverride;
1890
3997
  exports.systemA11y = systemA11y;
3998
+ exports.systemInput = systemInput;
3999
+ exports.viewportSegments = viewportSegments;
4000
+ exports.watchAdaptiveContext = watchAdaptiveContext;
1891
4001
  //# sourceMappingURL=web.cjs.map
1892
4002
  //# sourceMappingURL=web.cjs.map