vireuikit 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -27,135 +27,129 @@ function presentationFor(mode, wantsGlass) {
27
27
  function sameA11y(a, b) {
28
28
  return a.reduceTransparency === b.reduceTransparency && a.increaseContrast === b.increaseContrast && a.reduceMotion === b.reduceMotion;
29
29
  }
30
- var ATOM_AT_REST = {
31
- pressed: 0,
32
- hovered: 0,
33
- selected: 0,
34
- disabled: false
35
- };
36
- function atomLift(kind) {
37
- switch (kind) {
38
- case "iconButton":
39
- case "switch":
40
- case "slider":
41
- return 1;
42
- default:
43
- return 0;
44
- }
45
- }
46
- var DISABLED_PRESENCE = 0.25;
47
- var HOVER_GAIN = 0.35;
48
- function activation(state) {
49
- if (state.disabled) return 0;
50
- const clamp2 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
51
- return Math.max(clamp2(state.selected), clamp2(state.pressed), clamp2(state.hovered) * HOVER_GAIN);
30
+
31
+ // src/kit/adaptive/classes.ts
32
+ var WIDTH_CLASSES = ["compact", "medium", "expanded", "large", "xlarge"];
33
+ var HEIGHT_CLASSES = ["compact", "medium", "expanded"];
34
+ var WIDTH_BREAKPOINTS = { medium: 600, expanded: 840, large: 1200, xlarge: 1600 };
35
+ var HEIGHT_BREAKPOINTS = { medium: 480, expanded: 900 };
36
+ function widthClass(width) {
37
+ if (!(width >= WIDTH_BREAKPOINTS.medium)) return "compact";
38
+ if (width < WIDTH_BREAKPOINTS.expanded) return "medium";
39
+ if (width < WIDTH_BREAKPOINTS.large) return "expanded";
40
+ return width < WIDTH_BREAKPOINTS.xlarge ? "large" : "xlarge";
52
41
  }
53
- function atomMaterial(base, kind, state = ATOM_AT_REST) {
54
- const out = vireglass.activeMaterial(base, activation(state));
55
- if (!state.disabled) return out;
56
- return { ...out, presence: out.presence * DISABLED_PRESENCE };
42
+ function heightClass(height) {
43
+ if (!(height >= HEIGHT_BREAKPOINTS.medium)) return "compact";
44
+ return height < HEIGHT_BREAKPOINTS.expanded ? "medium" : "expanded";
57
45
  }
58
- function atomAtRest(state) {
59
- return state.pressed === 0 && state.hovered === 0 && state.selected === 0 && state.disabled === false;
46
+ function classifyWindow(size) {
47
+ return { width: widthClass(size.width), height: heightClass(size.height) };
60
48
  }
61
- var REST_BODY = { alpha: 0.25, white: 0.15, lift: 0.1 };
62
- var PRESSED_BODY = { alpha: 0.34, white: 0.35, lift: 0 };
63
- var FROST_LIFT = 0.26;
64
- function atomBody(base, state = ATOM_AT_REST, frost = false) {
65
- const on = activation(state);
66
- const mix2 = (key) => REST_BODY[key] + (PRESSED_BODY[key] - REST_BODY[key]) * on;
67
- let alpha = mix2("alpha");
68
- let white = mix2("white");
69
- let lift = mix2("lift");
70
- if (state.disabled) alpha *= DISABLED_PRESENCE;
71
- if (frost) {
72
- const density = vireglass.applyAccessibility(vireglass.resolveOptics(base), {
73
- reduceTransparency: true,
74
- increaseContrast: false,
75
- reduceMotion: false
76
- }).bodyDensity;
77
- alpha = Math.max(alpha, density);
78
- white = 0;
79
- lift = FROST_LIFT;
80
- }
81
- return { alpha, white, lift };
49
+ function widthAtLeast(value, minimum) {
50
+ return WIDTH_CLASSES.indexOf(value) >= WIDTH_CLASSES.indexOf(minimum);
82
51
  }
83
52
 
84
- // src/kit/behavior/focus.ts
85
- function stepFocus(event) {
86
- return event.type === "focus" && event.visible;
53
+ // src/kit/geometry.ts
54
+ function intersectRect(a, b) {
55
+ const x = Math.max(a.x, b.x);
56
+ const y = Math.max(a.y, b.y);
57
+ const right = Math.min(a.x + a.width, b.x + b.width);
58
+ const bottom = Math.min(a.y + a.height, b.y + b.height);
59
+ return right > x && bottom > y ? { x, y, width: right - x, height: bottom - y } : null;
87
60
  }
88
-
89
- // src/kit/behavior/hover.ts
90
- function stepHover(event, canHover) {
91
- return canHover && event.type === "enter";
61
+ function rectContains(outer, inner, tolerance = 0) {
62
+ return inner.x >= outer.x - tolerance && inner.y >= outer.y - tolerance && inner.x + inner.width <= outer.x + outer.width + tolerance && inner.y + inner.height <= outer.y + outer.height + tolerance;
92
63
  }
93
64
 
94
- // src/kit/behavior/press.ts
95
- var PRESS_IDLE = { by: null };
96
- function stepPress(state, event, enabled2) {
97
- switch (event.type) {
98
- case "down":
99
- if (!enabled2 || state.by !== null) return { state, activated: false };
100
- return { state: { by: event.source }, activated: false };
101
- case "up":
102
- if (state.by !== event.source) return { state, activated: false };
103
- return { state: PRESS_IDLE, activated: enabled2 };
104
- case "cancel":
105
- return { state: PRESS_IDLE, activated: false };
106
- case "leave":
107
- return state.by === "pointer" ? { state: PRESS_IDLE, activated: false } : { state, activated: false };
65
+ // src/kit/adaptive/context.ts
66
+ var INPUT_TOUCH = { primary: "coarse", anyCoarse: true, hover: false, keyboard: false };
67
+ var INPUT_POINTER = { primary: "fine", anyCoarse: false, hover: true, keyboard: true };
68
+ var INPUT_MIXED = { primary: "fine", anyCoarse: true, hover: true, keyboard: true };
69
+ var INPUT_TABLET_KEYBOARD = { primary: "coarse", anyCoarse: true, hover: true, keyboard: true };
70
+ var POSTURE_FLAT = { kind: "flat" };
71
+ function adaptiveContext(partial) {
72
+ return {
73
+ window: partial.window,
74
+ input: partial.input ?? INPUT_POINTER,
75
+ posture: partial.posture ?? POSTURE_FLAT,
76
+ reduceMotion: partial.reduceMotion ?? false
77
+ };
78
+ }
79
+ function halfOpen(kind, window, options = {}) {
80
+ const band = Math.max(options.band ?? 0, 0);
81
+ const span = kind === "tabletop" ? window.height : window.width;
82
+ const at = options.at ?? span / 2;
83
+ const start = at - band / 2;
84
+ const bounds = kind === "tabletop" ? { x: 0, y: start, width: window.width, height: band } : { x: start, y: 0, width: band, height: window.height };
85
+ return { kind, fold: { bounds, occluding: options.occluding ?? band > 0 } };
86
+ }
87
+ function windowSegments(ctx) {
88
+ const { width, height } = ctx.window;
89
+ const whole = { x: 0, y: 0, width, height };
90
+ if (ctx.posture.kind === "flat") return [whole];
91
+ const { bounds, occluding } = ctx.posture.fold;
92
+ const tabletop = ctx.posture.kind === "tabletop";
93
+ const span = tabletop ? height : width;
94
+ const lo = Math.min(Math.max(tabletop ? bounds.y : bounds.x, 0), span);
95
+ const hi = Math.min(Math.max(lo + (tabletop ? bounds.height : bounds.width), lo), span);
96
+ const end = occluding ? lo : (lo + hi) / 2;
97
+ const start = occluding ? hi : (lo + hi) / 2;
98
+ const parts = tabletop ? [
99
+ { x: 0, y: 0, width, height: end },
100
+ { x: 0, y: start, width, height: height - start }
101
+ ] : [
102
+ { x: 0, y: 0, width: end, height },
103
+ { x: start, y: 0, width: width - start, height }
104
+ ];
105
+ const kept = parts.filter((r) => r.width > 0 && r.height > 0);
106
+ return kept.length ? kept : [whole];
107
+ }
108
+ function postureFromSegments(window, segments) {
109
+ if (segments.length !== 2) return POSTURE_FLAT;
110
+ const [a, b] = [...segments].sort((p, q) => p.x - q.x || p.y - q.y);
111
+ const gapX = b.x - (a.x + a.width);
112
+ if (gapX >= 0 && a.y < b.y + b.height && b.y < a.y + a.height) {
113
+ return { kind: "book", fold: { bounds: { x: a.x + a.width, y: 0, width: gapX, height: window.height }, occluding: gapX > 0 } };
114
+ }
115
+ const [top, bottom] = [...segments].sort((p, q) => p.y - q.y);
116
+ const gapY = bottom.y - (top.y + top.height);
117
+ if (gapY >= 0) {
118
+ return { kind: "tabletop", fold: { bounds: { x: 0, y: top.y + top.height, width: window.width, height: gapY }, occluding: gapY > 0 } };
108
119
  }
120
+ return POSTURE_FLAT;
109
121
  }
110
-
111
- // src/kit/behavior/control.ts
112
- var TOUCH_INPUT = { hover: false };
113
- var POINTER_INPUT = { hover: true };
114
- var CONTROL_IDLE = {
115
- press: PRESS_IDLE,
116
- hovered: false,
117
- focused: false,
118
- disabled: false
119
- };
120
- function stepControl(state, event, caps) {
121
- switch (event.type) {
122
- case "down":
123
- case "up":
124
- case "cancel": {
125
- const next = stepPress(state.press, event, !state.disabled);
126
- return { state: { ...state, press: next.state }, activated: next.activated };
122
+ var area = (r) => r ? r.width * r.height : 0;
123
+ function segmentOf(box, segments) {
124
+ const cx = box.x + box.width / 2;
125
+ const cy = box.y + box.height / 2;
126
+ const holding = segments.find((s) => cx >= s.x && cx < s.x + s.width && cy >= s.y && cy < s.y + s.height);
127
+ if (holding) return holding;
128
+ let best;
129
+ let most = 0;
130
+ for (const s of segments) {
131
+ const overlap = area(intersectRect(s, box));
132
+ if (overlap > most) {
133
+ most = overlap;
134
+ best = s;
127
135
  }
128
- case "leave": {
129
- const next = stepPress(state.press, event, !state.disabled);
130
- return { state: { ...state, press: next.state, hovered: false }, activated: false };
131
- }
132
- case "enter":
133
- return {
134
- state: { ...state, hovered: !state.disabled && stepHover(event, caps.hover) },
135
- activated: false
136
- };
137
- case "focus":
138
- case "blur": {
139
- const press = event.type === "blur" && state.press.by === "keyboard" ? PRESS_IDLE : state.press;
140
- return {
141
- state: { ...state, press, focused: !state.disabled && stepFocus(event) },
142
- activated: false
143
- };
136
+ }
137
+ if (best) return best;
138
+ let nearest;
139
+ let distance = Number.POSITIVE_INFINITY;
140
+ for (const s of segments) {
141
+ const d = Math.hypot(s.x + s.width / 2 - cx, s.y + s.height / 2 - cy);
142
+ if (d < distance) {
143
+ distance = d;
144
+ nearest = s;
144
145
  }
145
- case "disable":
146
- return {
147
- state: event.disabled ? { press: PRESS_IDLE, hovered: false, focused: false, disabled: true } : { ...state, disabled: false },
148
- activated: false
149
- };
150
146
  }
147
+ return nearest;
151
148
  }
152
- function controlTargets(state) {
153
- return {
154
- pressed: state.press.by !== null ? 1 : 0,
155
- hovered: state.hovered ? 1 : 0,
156
- focus: state.focused ? 1 : 0,
157
- disabled: state.disabled
158
- };
149
+ function surfaceRegion(ctx, source) {
150
+ const segments = windowSegments(ctx);
151
+ if (source) return segmentOf(source, segments) ?? segments[0];
152
+ return segments.reduce((best, s) => area(s) > area(best) ? s : best, segments[0]);
159
153
  }
160
154
  function stepSpring(s, dt, response, damping, instant = false) {
161
155
  if (instant) {
@@ -311,6 +305,424 @@ function stepGrowth(state, dt, open, reduceMotion = false, timing = DEFAULT_GROW
311
305
  return movedShrink || movedGrow;
312
306
  }
313
307
 
308
+ // src/kit/adaptive/form-morph.ts
309
+ var HIDDEN = /* @__PURE__ */ new Set(["hidden", "closed", "none"]);
310
+ function formMorph(change, ctx, options = {}) {
311
+ const instant = ctx.reduceMotion;
312
+ if (options.anchorGone) return { ...change, mode: "retract", staysOpen: false, instant };
313
+ if (change.from === change.to) return { ...change, mode: "none", staysOpen: true, instant };
314
+ const mode = HIDDEN.has(change.from) ? "appear" : HIDDEN.has(change.to) ? "disappear" : "reshape";
315
+ return { ...change, mode, staysOpen: mode !== "disappear", instant };
316
+ }
317
+ var FORM_MORPH_TIMING = { response: DEFAULT_GROWTH_TIMING.growOpen, damping: 1 };
318
+ var createFormMorph = () => ({ t: { x: 0, v: 0, target: 0 }, fromInk: 1 });
319
+ function stepFormMorph(state, dt, toward, instant = false) {
320
+ state.t.target = toward;
321
+ return stepSpring(state.t, dt, FORM_MORPH_TIMING.response, FORM_MORPH_TIMING.damping, instant);
322
+ }
323
+ var OUT_END = 0.45;
324
+ var IN_START = 0.55;
325
+ function formMorphFrame(from, to, state) {
326
+ const t = Math.min(Math.max(state.t.x, 0), 1);
327
+ const mix2 = (a, b) => a + (b - a) * t;
328
+ return {
329
+ box: {
330
+ x: mix2(from.x, to.x),
331
+ y: mix2(from.y, to.y),
332
+ width: mix2(from.width, to.width),
333
+ height: mix2(from.height, to.height),
334
+ radius: mix2(from.radius, to.radius)
335
+ },
336
+ ink: inkAt(state, t),
337
+ showing: t < 0.5 ? "from" : "to"
338
+ };
339
+ }
340
+ function inkAt(state, t) {
341
+ return { outgoing: (state.fromInk ?? 1) * (1 - smoothstep(0, OUT_END, t)), incoming: smoothstep(IN_START, 1, t) };
342
+ }
343
+ function rebaseFormMorph(state) {
344
+ const t = Math.min(Math.max(state.t.x, 0), 1);
345
+ const ink = inkAt(state, t);
346
+ state.fromInk = t < 0.5 ? ink.outgoing : ink.incoming;
347
+ state.t = { x: 0, v: 0, target: 0 };
348
+ }
349
+ var CONTROL_SIZES = ["touch", "small", "medium", "large", "xlarge"];
350
+ var MIN_TOUCH_TARGET_DP = 44;
351
+ var MIN_POINTER_TARGET_DP = 24;
352
+ var capsule = (height, paddingX) => ({
353
+ height,
354
+ paddingX,
355
+ icon: Math.round(height / 2),
356
+ radius: height / 2,
357
+ shape: "capsule"
358
+ });
359
+ var rounded = (height, paddingX) => ({
360
+ height,
361
+ paddingX,
362
+ icon: Math.round(height / 2),
363
+ radius: height / 4,
364
+ shape: "rounded"
365
+ });
366
+ var BUTTON_SIZES = {
367
+ touch: capsule(44, 20),
368
+ small: rounded(24, 10),
369
+ medium: rounded(32, 14),
370
+ large: capsule(40, 18),
371
+ xlarge: capsule(48, 22)
372
+ };
373
+ function iconButtonSize(size) {
374
+ const spec = BUTTON_SIZES[size];
375
+ return { ...spec, width: spec.height, paddingX: 0 };
376
+ }
377
+ var MENU_PADDING = 0.3;
378
+ function menuSize(size) {
379
+ const spec = BUTTON_SIZES[size];
380
+ const padding = Math.round(spec.height * MENU_PADDING);
381
+ return {
382
+ row: spec.height,
383
+ padding,
384
+ itemRadius: spec.radius,
385
+ radius: spec.radius + padding,
386
+ icon: spec.icon,
387
+ labelPad: spec.paddingX
388
+ };
389
+ }
390
+ function nestedRadius(outerRadius, inset, minimum = 0) {
391
+ return vireglass.concentricRadius(outerRadius, inset, minimum);
392
+ }
393
+ function defaultSize(input) {
394
+ return input.touch ? "touch" : "medium";
395
+ }
396
+ function hitSlop(bodyDp, minTargetDp) {
397
+ return bodyDp >= minTargetDp ? 0 : (minTargetDp - bodyDp) / 2;
398
+ }
399
+ function minTarget(input) {
400
+ return input.touch ? MIN_TOUCH_TARGET_DP : MIN_POINTER_TARGET_DP;
401
+ }
402
+
403
+ // src/kit/adaptive/input.ts
404
+ function sizeTier(input) {
405
+ return defaultSize({ touch: input.primary === "coarse" });
406
+ }
407
+ function hitTarget(input) {
408
+ return minTarget({ touch: input.anyCoarse });
409
+ }
410
+ function hoversWith(pointerType) {
411
+ return pointerType !== "touch";
412
+ }
413
+
414
+ // src/kit/adaptive/navigation.ts
415
+ var BAR_SLOTS = 5;
416
+ function formFor(ctx, spec) {
417
+ const { width, height } = classifyWindow(ctx.window);
418
+ if (ctx.posture.kind === "tabletop") return "bar";
419
+ if (widthAtLeast(width, "large")) return spec.sidebarCollapsed ? "rail" : "sidebar";
420
+ if (height === "compact") return "rail";
421
+ if (width === "compact") return "bar";
422
+ if (width === "medium" && (spec.panes ?? 1) > 1 && ctx.posture.kind !== "book") return "bar";
423
+ return "rail";
424
+ }
425
+ function navigationForm(ctx, spec) {
426
+ const form = formFor(ctx, spec);
427
+ const width = classifyWindow(ctx.window).width;
428
+ const large = widthAtLeast(width, "large");
429
+ const slots = BAR_SLOTS - (spec.search ? 1 : 0);
430
+ const primary = Math.max(0, Math.min(spec.primary, slots));
431
+ const secondary = Math.max(0, spec.secondary ?? 0) + Math.max(0, spec.primary - primary);
432
+ let search = null;
433
+ if (spec.search) {
434
+ const shortcut = large || ctx.input.keyboard;
435
+ if (large) search = { form: "field", place: form === "sidebar" ? "sidebar" : "toolbar", shortcut };
436
+ else search = form === "bar" ? { form: "button", place: "bar", shortcut } : { form: "item", place: "rail", shortcut };
437
+ }
438
+ return {
439
+ form,
440
+ edge: form === "bar" ? "bottom" : "leading",
441
+ primary,
442
+ secondary,
443
+ secondaryVisible: form === "sidebar",
444
+ search,
445
+ expandable: form !== "rail" ? null : large ? "beside" : width === "expanded" ? "over" : null,
446
+ collapsible: form === "sidebar"
447
+ };
448
+ }
449
+ function bottomAccessory(nav, state = {}) {
450
+ if (nav.form !== "bar") return { placement: "content-bottom", compact: false };
451
+ return state.barMinimized ? { placement: "in-bar", compact: true } : { placement: "above-bar", compact: false };
452
+ }
453
+
454
+ // src/kit/adaptive/panes.ts
455
+ var LIST_PANE_WIDTH = { expanded: 360, large: 412 };
456
+ var SUPPORTING_PANE_MAX_WIDTH = 400;
457
+ function splitFor(ctx, sparse) {
458
+ const { width, height } = classifyWindow(ctx.window);
459
+ if (ctx.posture.kind === "book") return { kind: "fold", gap: ctx.posture.fold.bounds };
460
+ if (height === "compact") return null;
461
+ if (widthAtLeast(width, "large")) return { kind: "fixed", list: LIST_PANE_WIDTH.large };
462
+ if (width === "expanded") return { kind: "fixed", list: LIST_PANE_WIDTH.expanded };
463
+ return width === "medium" && sparse ? { kind: "half" } : null;
464
+ }
465
+ function paneLayout(ctx, state) {
466
+ const split = splitFor(ctx, state.sparse ?? false);
467
+ if (split) {
468
+ return { panes: 2, visible: ["list", "detail"], split, placeholder: !state.selected, back: false, selectionShown: state.selected };
469
+ }
470
+ const detail = state.selected && state.showing === "detail";
471
+ return { panes: 1, visible: [detail ? "detail" : "list"], split: null, placeholder: false, back: detail, selectionShown: false };
472
+ }
473
+ function supportingPane(ctx, state) {
474
+ const width = classifyWindow(ctx.window).width;
475
+ const base = { edge: "trailing", maxWidth: SUPPORTING_PANE_MAX_WIDTH, remembered: state.pinned };
476
+ if (widthAtLeast(width, "large")) return { ...base, form: state.pinned || state.open ? "beside" : "hidden", scrim: false };
477
+ if (width === "expanded" && state.open) return { ...base, form: "over", scrim: true };
478
+ return { ...base, form: "hidden", scrim: false };
479
+ }
480
+ function postureSplit(ctx) {
481
+ if (ctx.posture.kind === "flat") return { kind: "single" };
482
+ const segments = windowSegments(ctx);
483
+ if (segments.length < 2) return { kind: "single" };
484
+ return { kind: "split", axis: ctx.posture.kind === "tabletop" ? "horizontal" : "vertical", primary: segments[0], secondary: segments[1] };
485
+ }
486
+
487
+ // src/kit/adaptive/presentation.ts
488
+ var SURFACE_KINDS = ["menu", "picker", "popover", "sheet", "modal-sheet", "alert", "dialog"];
489
+ var BOTTOM_SHEET_MAX_WIDTH = 640;
490
+ var SIDE_SHEET_MAX_WIDTH = 400;
491
+ var DIALOG_MAX_WIDTH = 560;
492
+ function baseForm(kind, ctx) {
493
+ const { width, height } = classifyWindow(ctx.window);
494
+ const compactWidth = width === "compact";
495
+ const plain = { fromSource: false, scrim: false, compresses: false, maxWidth: null };
496
+ const sheet = (form) => height === "compact" ? { ...plain, form: "full-screen", scrim: true } : form;
497
+ switch (kind) {
498
+ case "menu":
499
+ return { ...plain, form: "grown", fromSource: true };
500
+ case "popover":
501
+ return compactWidth ? sheet({ ...plain, form: "bottom-sheet", fromSource: true, scrim: true }) : { ...plain, form: "popover", fromSource: true };
502
+ case "picker":
503
+ case "modal-sheet":
504
+ return sheet(
505
+ compactWidth ? { ...plain, form: "bottom-sheet", scrim: true } : { ...plain, form: "centered-sheet", scrim: true, maxWidth: DIALOG_MAX_WIDTH }
506
+ );
507
+ case "sheet":
508
+ if (width === "xlarge") return { ...plain, form: "side-pane", compresses: true, maxWidth: SIDE_SHEET_MAX_WIDTH };
509
+ if (widthAtLeast(width, "expanded")) return { ...plain, form: "side-sheet", compresses: true, maxWidth: SIDE_SHEET_MAX_WIDTH };
510
+ return sheet({ ...plain, form: "bottom-sheet", maxWidth: compactWidth ? null : BOTTOM_SHEET_MAX_WIDTH });
511
+ case "alert":
512
+ return { ...plain, form: "alert", scrim: true, maxWidth: DIALOG_MAX_WIDTH };
513
+ case "dialog":
514
+ return compactWidth ? { ...plain, form: "full-screen", scrim: true } : { ...plain, form: "basic-dialog", scrim: true, maxWidth: DIALOG_MAX_WIDTH };
515
+ }
516
+ }
517
+ function presentationForm(kind, ctx, source) {
518
+ const base = baseForm(kind, ctx);
519
+ const side = base.form === "side-sheet" || base.form === "side-pane";
520
+ const region = side && ctx.posture.kind === "book" ? windowSegments(ctx).at(-1) : surfaceRegion(ctx, source);
521
+ return { ...base, region };
522
+ }
523
+
524
+ // src/kit/adaptive/toolbar.ts
525
+ var TOOLBAR_GAP = 8;
526
+ var MORE_ID = "\u2026";
527
+ function toolbarPlacement(ctx) {
528
+ const { width, height } = classifyWindow(ctx.window);
529
+ if (height === "compact") {
530
+ return { edge: "trailing", orientation: "vertical", maxGroups: Number.POSITIVE_INFINITY };
531
+ }
532
+ if (width === "compact") return { edge: "bottom", orientation: "horizontal", maxGroups: 3 };
533
+ return { edge: "top", orientation: "horizontal", maxGroups: Number.POSITIVE_INFINITY };
534
+ }
535
+ function toolbarLayout(items, options) {
536
+ const orientation = options.orientation ?? "horizontal";
537
+ const gap = options.gap ?? TOOLBAR_GAP;
538
+ const maxGroups = options.maxGroups ?? Number.POSITIVE_INFINITY;
539
+ const side = BUTTON_SIZES[options.tier].height;
540
+ const vertical = orientation === "vertical";
541
+ const aside = vertical ? items.filter((i) => i.kind === "text").map((i) => i.id) : [];
542
+ const pool = items.map((item, index) => ({ item, index })).filter((e) => !vertical || e.item.kind === "icon");
543
+ const extentOf = (item) => item.kind === "icon" ? side : item.width ?? 3 * side;
544
+ const lay = (visible2, merge, more) => {
545
+ const ordered = [...visible2.filter((e) => !e.item.primary)];
546
+ const capsules = [];
547
+ let open = null;
548
+ const close = () => {
549
+ if (open) capsules.push({ kind: open.kind, items: open.items, extent: open.extent });
550
+ open = null;
551
+ };
552
+ for (const { item } of ordered) {
553
+ const kind = item.kind;
554
+ const group = item.group ?? `kind:${kind}`;
555
+ if (merge && open && open.kind === kind && open.group === group) {
556
+ open.items.push(item.id);
557
+ open.extent += extentOf(item);
558
+ } else {
559
+ close();
560
+ open = { kind, group, items: [item.id], extent: extentOf(item) };
561
+ }
562
+ }
563
+ close();
564
+ if (more) capsules.push({ kind: "more", items: [MORE_ID], extent: side });
565
+ for (const { item } of visible2) if (item.primary) capsules.push({ kind: "primary", items: [item.id], extent: extentOf(item) });
566
+ const extent = capsules.reduce((sum, c) => sum + c.extent, 0) + gap * Math.max(capsules.length - 1, 0);
567
+ return { capsules, extent, fits: extent <= options.available && capsules.length <= maxGroups };
568
+ };
569
+ const done = (stage, laid2, overflow) => ({
570
+ orientation,
571
+ stage,
572
+ capsules: laid2.capsules,
573
+ overflow,
574
+ aside,
575
+ extent: laid2.extent,
576
+ fits: laid2.fits
577
+ });
578
+ const separate = lay(pool, false, false);
579
+ if (separate.fits) return done("separate", separate, []);
580
+ const merged = lay(pool, true, false);
581
+ if (merged.fits) return done("merged", merged, []);
582
+ const candidates = pool.filter((e) => !e.item.badge && !e.item.primary).sort((a, b) => (a.item.priority ?? 0) - (b.item.priority ?? 0) || b.index - a.index);
583
+ let visible = pool;
584
+ const hidden = [];
585
+ let laid = merged;
586
+ for (const candidate of candidates) {
587
+ visible = visible.filter((e) => e !== candidate);
588
+ hidden.push(candidate);
589
+ laid = lay(visible, true, true);
590
+ if (laid.fits) break;
591
+ }
592
+ if (!hidden.length) return done("merged", merged, []);
593
+ return done("overflow", laid, hidden.sort((a, b) => a.index - b.index).map((e) => e.item.id));
594
+ }
595
+ var ATOM_AT_REST = {
596
+ pressed: 0,
597
+ hovered: 0,
598
+ selected: 0,
599
+ disabled: false
600
+ };
601
+ function atomLift(kind) {
602
+ switch (kind) {
603
+ case "iconButton":
604
+ case "switch":
605
+ case "slider":
606
+ return 1;
607
+ default:
608
+ return 0;
609
+ }
610
+ }
611
+ var DISABLED_PRESENCE = 0.25;
612
+ var HOVER_GAIN = 0.35;
613
+ function activation(state) {
614
+ if (state.disabled) return 0;
615
+ const clamp2 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
616
+ return Math.max(clamp2(state.selected), clamp2(state.pressed), clamp2(state.hovered) * HOVER_GAIN);
617
+ }
618
+ function atomMaterial(base, kind, state = ATOM_AT_REST) {
619
+ const out = vireglass.activeMaterial(base, activation(state));
620
+ if (!state.disabled) return out;
621
+ return { ...out, presence: out.presence * DISABLED_PRESENCE };
622
+ }
623
+ function atomAtRest(state) {
624
+ return state.pressed === 0 && state.hovered === 0 && state.selected === 0 && state.disabled === false;
625
+ }
626
+ var REST_BODY = { alpha: 0.25, white: 0.15, lift: 0.1 };
627
+ var PRESSED_BODY = { alpha: 0.34, white: 0.35, lift: 0 };
628
+ var FROST_LIFT = 0.26;
629
+ function atomBody(base, state = ATOM_AT_REST, frost = false) {
630
+ const on = activation(state);
631
+ const mix2 = (key) => REST_BODY[key] + (PRESSED_BODY[key] - REST_BODY[key]) * on;
632
+ let alpha = mix2("alpha");
633
+ let white = mix2("white");
634
+ let lift = mix2("lift");
635
+ if (state.disabled) alpha *= DISABLED_PRESENCE;
636
+ if (frost) {
637
+ const density = vireglass.applyAccessibility(vireglass.resolveOptics(base), {
638
+ reduceTransparency: true,
639
+ increaseContrast: false,
640
+ reduceMotion: false
641
+ }).bodyDensity;
642
+ alpha = Math.max(alpha, density);
643
+ white = 0;
644
+ lift = FROST_LIFT;
645
+ }
646
+ return { alpha, white, lift };
647
+ }
648
+
649
+ // src/kit/behavior/focus.ts
650
+ function stepFocus(event) {
651
+ return event.type === "focus" && event.visible;
652
+ }
653
+
654
+ // src/kit/behavior/hover.ts
655
+ function stepHover(event, canHover) {
656
+ return canHover && event.type === "enter";
657
+ }
658
+
659
+ // src/kit/behavior/press.ts
660
+ var PRESS_IDLE = { by: null };
661
+ function stepPress(state, event, enabled2) {
662
+ switch (event.type) {
663
+ case "down":
664
+ if (!enabled2 || state.by !== null) return { state, activated: false };
665
+ return { state: { by: event.source }, activated: false };
666
+ case "up":
667
+ if (state.by !== event.source) return { state, activated: false };
668
+ return { state: PRESS_IDLE, activated: enabled2 };
669
+ case "cancel":
670
+ return { state: PRESS_IDLE, activated: false };
671
+ case "leave":
672
+ return state.by === "pointer" ? { state: PRESS_IDLE, activated: false } : { state, activated: false };
673
+ }
674
+ }
675
+
676
+ // src/kit/behavior/control.ts
677
+ var TOUCH_INPUT = { hover: false };
678
+ var POINTER_INPUT = { hover: true };
679
+ var CONTROL_IDLE = {
680
+ press: PRESS_IDLE,
681
+ hovered: false,
682
+ focused: false,
683
+ disabled: false
684
+ };
685
+ function stepControl(state, event, caps) {
686
+ switch (event.type) {
687
+ case "down":
688
+ case "up":
689
+ case "cancel": {
690
+ const next = stepPress(state.press, event, !state.disabled);
691
+ return { state: { ...state, press: next.state }, activated: next.activated };
692
+ }
693
+ case "leave": {
694
+ const next = stepPress(state.press, event, !state.disabled);
695
+ return { state: { ...state, press: next.state, hovered: false }, activated: false };
696
+ }
697
+ case "enter":
698
+ return {
699
+ state: { ...state, hovered: !state.disabled && stepHover(event, caps.hover) },
700
+ activated: false
701
+ };
702
+ case "focus":
703
+ case "blur": {
704
+ const press = event.type === "blur" && state.press.by === "keyboard" ? PRESS_IDLE : state.press;
705
+ return {
706
+ state: { ...state, press, focused: !state.disabled && stepFocus(event) },
707
+ activated: false
708
+ };
709
+ }
710
+ case "disable":
711
+ return {
712
+ state: event.disabled ? { press: PRESS_IDLE, hovered: false, focused: false, disabled: true } : { ...state, disabled: false },
713
+ activated: false
714
+ };
715
+ }
716
+ }
717
+ function controlTargets(state) {
718
+ return {
719
+ pressed: state.press.by !== null ? 1 : 0,
720
+ hovered: state.hovered ? 1 : 0,
721
+ focus: state.focused ? 1 : 0,
722
+ disabled: state.disabled
723
+ };
724
+ }
725
+
314
726
  // src/kit/behavior/visual.ts
315
727
  var createControlVisual = () => ({
316
728
  pressed: { x: 0, v: 0, target: 0 },
@@ -403,40 +815,46 @@ var MENU_CORNERS = {
403
815
  "bottom-left": { x: -1, y: 1 },
404
816
  "bottom-right": { x: 1, y: 1 }
405
817
  };
406
- function menuCorner(trigger, viewport) {
818
+ function menuCorner(trigger, region) {
819
+ const x0 = region.x ?? 0;
820
+ const y0 = region.y ?? 0;
407
821
  return {
408
- x: trigger.x + trigger.width / 2 <= viewport.width / 2 ? -1 : 1,
409
- y: trigger.y + trigger.height / 2 <= viewport.height / 2 ? -1 : 1
822
+ x: trigger.x + trigger.width / 2 <= x0 + region.width / 2 ? -1 : 1,
823
+ y: trigger.y + trigger.height / 2 <= y0 + region.height / 2 ? -1 : 1
410
824
  };
411
825
  }
412
826
  function along(start, length, size, side) {
413
827
  return side < 0 ? start : start + length - size;
414
828
  }
415
- function fits(at, size, limit, margin) {
416
- return at >= margin && at + size <= limit - margin;
829
+ function fits(at, size, lo, hi, margin) {
830
+ return at >= lo + margin && at + size <= hi - margin;
417
831
  }
418
832
  var clamp = (v, lo, hi) => Math.min(Math.max(v, lo), Math.max(lo, hi));
419
- function menuPlacement(trigger, menu, viewport, options = {}) {
833
+ function menuPlacement(trigger, menu, region, options = {}) {
420
834
  const margin = options.margin ?? 8;
421
- const width = Math.min(menu.width, viewport.width - 2 * margin);
422
- const height = Math.min(menu.height, viewport.height - 2 * margin);
423
- const corner = { ...options.corner ?? menuCorner(trigger, viewport) };
835
+ const x0 = region.x ?? 0;
836
+ const y0 = region.y ?? 0;
837
+ const x1 = x0 + region.width;
838
+ const y1 = y0 + region.height;
839
+ const width = Math.max(0, Math.min(menu.width, region.width - 2 * margin));
840
+ const height = Math.max(0, Math.min(menu.height, region.height - 2 * margin));
841
+ const corner = { ...options.corner ?? menuCorner(trigger, region) };
424
842
  let x = along(trigger.x, trigger.width, width, corner.x);
425
843
  let y = along(trigger.y, trigger.height, height, corner.y);
426
844
  if (!options.corner) {
427
845
  const fx = along(trigger.x, trigger.width, width, corner.x === -1 ? 1 : -1);
428
- if (!fits(x, width, viewport.width, margin) && fits(fx, width, viewport.width, margin)) {
846
+ if (!fits(x, width, x0, x1, margin) && fits(fx, width, x0, x1, margin)) {
429
847
  corner.x = corner.x === -1 ? 1 : -1;
430
848
  x = fx;
431
849
  }
432
850
  const fy = along(trigger.y, trigger.height, height, corner.y === -1 ? 1 : -1);
433
- if (!fits(y, height, viewport.height, margin) && fits(fy, height, viewport.height, margin)) {
851
+ if (!fits(y, height, y0, y1, margin) && fits(fy, height, y0, y1, margin)) {
434
852
  corner.y = corner.y === -1 ? 1 : -1;
435
853
  y = fy;
436
854
  }
437
855
  }
438
- x = clamp(x, margin, viewport.width - margin - width);
439
- y = clamp(y, margin, viewport.height - margin - height);
856
+ x = clamp(x, x0 + margin, x1 - margin - width);
857
+ y = clamp(y, y0 + margin, y1 - margin - height);
440
858
  const rect = { x, y, width, height };
441
859
  return {
442
860
  rect,
@@ -450,7 +868,7 @@ function menuActivity(phase) {
450
868
  const grow = Math.min(Math.max(phase.grow, 0), 1);
451
869
  return shrink * (1 - grow);
452
870
  }
453
- function menuSurfaceFrame(trigger, menu, anchor, phase) {
871
+ function menuSurfaceFrame(trigger, menu, anchor, phase, bounds) {
454
872
  const frame = growthFrame(
455
873
  { geometry: { width: trigger.width, height: trigger.height, cornerRadius: trigger.radius }, center: center(trigger) },
456
874
  { geometry: { width: menu.width, height: menu.height, cornerRadius: menu.radius }, center: center(menu) },
@@ -458,16 +876,24 @@ function menuSurfaceFrame(trigger, menu, anchor, phase) {
458
876
  phase
459
877
  );
460
878
  const { geometry, center: c } = frame.body;
461
- const box = {
879
+ let box = {
462
880
  x: c.x - geometry.width / 2,
463
881
  y: c.y - geometry.height / 2,
464
882
  width: geometry.width,
465
883
  height: geometry.height,
466
884
  radius: geometry.cornerRadius
467
885
  };
886
+ let morph = frame.merge;
887
+ const held = bounds ? intersectRect(box, bounds) : null;
888
+ if (held && (held.width < box.width || held.height < box.height)) {
889
+ const was = center(box);
890
+ const now = center(held);
891
+ box = { ...held, radius: Math.min(box.radius, held.width / 2, held.height / 2) };
892
+ if (morph) morph = { ...morph, offsetX: morph.offsetX + was.x - now.x, offsetY: morph.offsetY + was.y - now.y };
893
+ }
468
894
  return {
469
895
  box,
470
- morph: frame.merge,
896
+ morph,
471
897
  content: { x: menu.x - box.x, y: menu.y - box.y },
472
898
  ink: frame.ink,
473
899
  activity: menuActivity(phase)
@@ -500,89 +926,66 @@ var SINKING = { grow: 0.06, defocus: 1 };
500
926
  function pressResponse(kind) {
501
927
  return atomLift(kind) === 1 ? LIFTING : SINKING;
502
928
  }
503
- var CONTROL_SIZES = ["touch", "small", "medium", "large", "xlarge"];
504
- var MIN_TOUCH_TARGET_DP = 44;
505
- var MIN_POINTER_TARGET_DP = 24;
506
- var capsule = (height, paddingX) => ({
507
- height,
508
- paddingX,
509
- icon: Math.round(height / 2),
510
- radius: height / 2,
511
- shape: "capsule"
512
- });
513
- var rounded = (height, paddingX) => ({
514
- height,
515
- paddingX,
516
- icon: Math.round(height / 2),
517
- radius: height / 4,
518
- shape: "rounded"
519
- });
520
- var BUTTON_SIZES = {
521
- touch: capsule(44, 20),
522
- small: rounded(24, 10),
523
- medium: rounded(32, 14),
524
- large: capsule(40, 18),
525
- xlarge: capsule(48, 22)
526
- };
527
- function iconButtonSize(size) {
528
- const spec = BUTTON_SIZES[size];
529
- return { ...spec, width: spec.height, paddingX: 0 };
530
- }
531
- var MENU_PADDING = 0.3;
532
- function menuSize(size) {
533
- const spec = BUTTON_SIZES[size];
534
- const padding = Math.round(spec.height * MENU_PADDING);
535
- return {
536
- row: spec.height,
537
- padding,
538
- itemRadius: spec.radius,
539
- radius: spec.radius + padding,
540
- icon: spec.icon,
541
- labelPad: spec.paddingX
542
- };
543
- }
544
- function nestedRadius(outerRadius, inset, minimum = 0) {
545
- return vireglass.concentricRadius(outerRadius, inset, minimum);
546
- }
547
- function defaultSize(input) {
548
- return input.touch ? "touch" : "medium";
549
- }
550
- function hitSlop(bodyDp, minTargetDp) {
551
- return bodyDp >= minTargetDp ? 0 : (minTargetDp - bodyDp) / 2;
552
- }
553
- function minTarget(input) {
554
- return input.touch ? MIN_TOUCH_TARGET_DP : MIN_POINTER_TARGET_DP;
555
- }
556
929
 
557
930
  exports.A11Y_NONE = A11Y_NONE;
558
931
  exports.ATOM_AT_REST = ATOM_AT_REST;
932
+ exports.BAR_SLOTS = BAR_SLOTS;
933
+ exports.BOTTOM_SHEET_MAX_WIDTH = BOTTOM_SHEET_MAX_WIDTH;
559
934
  exports.BUTTON_SIZES = BUTTON_SIZES;
560
935
  exports.CONTROL_IDLE = CONTROL_IDLE;
561
936
  exports.CONTROL_SIZES = CONTROL_SIZES;
562
937
  exports.DEFAULT_GROWTH_TIMING = DEFAULT_GROWTH_TIMING;
938
+ exports.DIALOG_MAX_WIDTH = DIALOG_MAX_WIDTH;
939
+ exports.FORM_MORPH_TIMING = FORM_MORPH_TIMING;
940
+ exports.HEIGHT_BREAKPOINTS = HEIGHT_BREAKPOINTS;
941
+ exports.HEIGHT_CLASSES = HEIGHT_CLASSES;
942
+ exports.INPUT_MIXED = INPUT_MIXED;
943
+ exports.INPUT_POINTER = INPUT_POINTER;
944
+ exports.INPUT_TABLET_KEYBOARD = INPUT_TABLET_KEYBOARD;
945
+ exports.INPUT_TOUCH = INPUT_TOUCH;
946
+ exports.LIST_PANE_WIDTH = LIST_PANE_WIDTH;
563
947
  exports.MENU_CLOSED = MENU_CLOSED;
564
948
  exports.MENU_CORNERS = MENU_CORNERS;
565
949
  exports.MENU_DROP_BODY = MENU_DROP_BODY;
566
950
  exports.MENU_ITEM_FILL = MENU_ITEM_FILL;
567
951
  exports.MIN_POINTER_TARGET_DP = MIN_POINTER_TARGET_DP;
568
952
  exports.MIN_TOUCH_TARGET_DP = MIN_TOUCH_TARGET_DP;
953
+ exports.MORE_ID = MORE_ID;
569
954
  exports.POINTER_INPUT = POINTER_INPUT;
955
+ exports.POSTURE_FLAT = POSTURE_FLAT;
570
956
  exports.PRESS_IDLE = PRESS_IDLE;
957
+ exports.SIDE_SHEET_MAX_WIDTH = SIDE_SHEET_MAX_WIDTH;
958
+ exports.SUPPORTING_PANE_MAX_WIDTH = SUPPORTING_PANE_MAX_WIDTH;
959
+ exports.SURFACE_KINDS = SURFACE_KINDS;
960
+ exports.TOOLBAR_GAP = TOOLBAR_GAP;
571
961
  exports.TOUCH_INPUT = TOUCH_INPUT;
572
962
  exports.TYPEAHEAD_SECONDS = TYPEAHEAD_SECONDS;
963
+ exports.WIDTH_BREAKPOINTS = WIDTH_BREAKPOINTS;
964
+ exports.WIDTH_CLASSES = WIDTH_CLASSES;
965
+ exports.adaptiveContext = adaptiveContext;
573
966
  exports.atomAtRest = atomAtRest;
574
967
  exports.atomBody = atomBody;
575
968
  exports.atomLift = atomLift;
576
969
  exports.atomMaterial = atomMaterial;
970
+ exports.bottomAccessory = bottomAccessory;
971
+ exports.classifyWindow = classifyWindow;
577
972
  exports.controlTargets = controlTargets;
578
973
  exports.createControlVisual = createControlVisual;
974
+ exports.createFormMorph = createFormMorph;
579
975
  exports.createGrowthState = createGrowthState;
580
976
  exports.defaultSize = defaultSize;
977
+ exports.formMorph = formMorph;
978
+ exports.formMorphFrame = formMorphFrame;
581
979
  exports.growthFrame = growthFrame;
582
980
  exports.growthInk = growthInk;
583
981
  exports.growthPhase = growthPhase;
982
+ exports.halfOpen = halfOpen;
983
+ exports.heightClass = heightClass;
584
984
  exports.hitSlop = hitSlop;
985
+ exports.hitTarget = hitTarget;
986
+ exports.hoversWith = hoversWith;
585
987
  exports.iconButtonSize = iconButtonSize;
988
+ exports.intersectRect = intersectRect;
586
989
  exports.menuActivity = menuActivity;
587
990
  exports.menuBody = menuBody;
588
991
  exports.menuCorner = menuCorner;
@@ -593,21 +996,38 @@ exports.menuPlacement = menuPlacement;
593
996
  exports.menuSize = menuSize;
594
997
  exports.menuSurfaceFrame = menuSurfaceFrame;
595
998
  exports.minTarget = minTarget;
999
+ exports.navigationForm = navigationForm;
596
1000
  exports.nestedRadius = nestedRadius;
1001
+ exports.paneLayout = paneLayout;
1002
+ exports.postureFromSegments = postureFromSegments;
1003
+ exports.postureSplit = postureSplit;
597
1004
  exports.presentationFor = presentationFor;
1005
+ exports.presentationForm = presentationForm;
598
1006
  exports.pressResponse = pressResponse;
1007
+ exports.rebaseFormMorph = rebaseFormMorph;
1008
+ exports.rectContains = rectContains;
599
1009
  exports.resolveA11y = resolveA11y;
600
1010
  exports.sameA11y = sameA11y;
1011
+ exports.segmentOf = segmentOf;
601
1012
  exports.settleControlVisual = settleControlVisual;
1013
+ exports.sizeTier = sizeTier;
602
1014
  exports.smoothstep = smoothstep;
603
1015
  exports.stepControl = stepControl;
604
1016
  exports.stepControlVisual = stepControlVisual;
605
1017
  exports.stepFocus = stepFocus;
1018
+ exports.stepFormMorph = stepFormMorph;
606
1019
  exports.stepGrowth = stepGrowth;
607
1020
  exports.stepHover = stepHover;
608
1021
  exports.stepMenu = stepMenu;
609
1022
  exports.stepPress = stepPress;
610
1023
  exports.stepSpring = stepSpring;
1024
+ exports.supportingPane = supportingPane;
1025
+ exports.surfaceRegion = surfaceRegion;
1026
+ exports.toolbarLayout = toolbarLayout;
1027
+ exports.toolbarPlacement = toolbarPlacement;
611
1028
  exports.visualAtomState = visualAtomState;
1029
+ exports.widthAtLeast = widthAtLeast;
1030
+ exports.widthClass = widthClass;
1031
+ exports.windowSegments = windowSegments;
612
1032
  //# sourceMappingURL=index.cjs.map
613
1033
  //# sourceMappingURL=index.cjs.map