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/README.md +120 -5
- package/dist/chunk-7A3XUTXH.js +1314 -0
- package/dist/chunk-7A3XUTXH.js.map +1 -0
- package/dist/context-DQaAcRq-.d.cts +104 -0
- package/dist/context-DQaAcRq-.d.ts +104 -0
- package/dist/index.cjs +1155 -186
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +585 -127
- package/dist/index.d.ts +585 -127
- package/dist/index.js +146 -1
- package/dist/index.js.map +1 -1
- package/dist/web.cjs +2240 -130
- package/dist/web.cjs.map +1 -1
- package/dist/web.d.cts +37 -3
- package/dist/web.d.ts +37 -3
- package/dist/web.js +1460 -48
- package/dist/web.js.map +1 -1
- package/package.json +1 -1
- package/dist/a11y-D3SS74I3.d.cts +0 -25
- package/dist/a11y-D3SS74I3.d.ts +0 -25
- package/dist/chunk-AR2AM3BR.js +0 -557
- package/dist/chunk-AR2AM3BR.js.map +0 -1
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
|
|
66
|
-
let alpha =
|
|
67
|
-
let white =
|
|
68
|
-
let 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
|
|
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,
|
|
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(
|
|
236
|
-
const edgeY = mix(
|
|
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/
|
|
486
|
-
var
|
|
487
|
-
|
|
488
|
-
|
|
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
|
|
491
|
-
if (
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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(
|
|
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.#
|
|
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
|
|
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,
|
|
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)
|
|
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 =
|
|
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 },
|
|
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,
|
|
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 <=
|
|
1087
|
-
y: trigger.y + trigger.height / 2 <=
|
|
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,
|
|
1094
|
-
return at >= margin && at + size <=
|
|
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,
|
|
1961
|
+
function menuPlacement(trigger, menu, region, options = {}) {
|
|
1098
1962
|
const margin = options.margin ?? 8;
|
|
1099
|
-
const
|
|
1100
|
-
const
|
|
1101
|
-
const
|
|
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,
|
|
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,
|
|
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,
|
|
1117
|
-
y = clamp(y, margin,
|
|
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
|
-
|
|
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
|
|
1149
|
-
content: { x: menu.x -
|
|
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
|
|
1166
|
-
return { alpha:
|
|
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(
|
|
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
|
|
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
|
|
2360
|
+
if (isDisabled(item)) return drop2(e);
|
|
1484
2361
|
if (!this.#state.open) {
|
|
1485
|
-
if (this.#out)
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
2529
|
+
#setBox(box2) {
|
|
1651
2530
|
const s = this.#list.style;
|
|
1652
|
-
const
|
|
1653
|
-
s.setProperty("--vk-x",
|
|
1654
|
-
s.setProperty("--vk-y",
|
|
1655
|
-
s.setProperty("--vk-w",
|
|
1656
|
-
s.setProperty("--vk-h",
|
|
1657
|
-
s.setProperty("--vk-r",
|
|
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(
|
|
2604
|
+
#pushGlass(box2, morph, activity) {
|
|
1726
2605
|
const glass = this.#glass;
|
|
1727
2606
|
if (!glass) return;
|
|
1728
|
-
const
|
|
1729
|
-
const step =
|
|
1730
|
-
const geometry = { width:
|
|
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:
|
|
2611
|
+
smoothing: round2(morph.smoothing),
|
|
1733
2612
|
shape: {
|
|
1734
|
-
offsetX:
|
|
1735
|
-
offsetY:
|
|
1736
|
-
width:
|
|
1737
|
-
height:
|
|
1738
|
-
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
|
|
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
|
|
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(
|
|
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
|