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/web.cjs CHANGED
@@ -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,
@@ -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(box, segments) {
482
+ const cx = box.x + box.width / 2;
483
+ const cy = box.y + box.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, box));
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 editable = (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" || editable(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 box = { x: r.x, y: r.y, width: r.width, height: r.height };
603
+ const local = viewportSegments().segments.map((s) => intersectRect(s, box)).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,19 +669,6 @@ async function attachControlGlass(el, params, stillWanted) {
482
669
  };
483
670
  }
484
671
 
485
- // src/web/input.ts
486
- var COARSE = "(pointer: coarse)";
487
- function isCoarsePointer() {
488
- return typeof matchMedia !== "undefined" && matchMedia(COARSE).matches;
489
- }
490
- function watchCoarsePointer(onChange) {
491
- if (typeof matchMedia === "undefined") return () => {
492
- };
493
- const list = matchMedia(COARSE);
494
- list.addEventListener("change", onChange);
495
- return () => list.removeEventListener("change", onChange);
496
- }
497
-
498
672
  // src/web/styles.ts
499
673
  var HOST = ":is(vire-button,vire-icon-button)[data-vk]";
500
674
  var BODY = `${HOST}>button`;
@@ -620,13 +794,14 @@ var FORCED = {
620
794
  };
621
795
  var MATERIAL_STEPS = 10;
622
796
  var SUPPRESS_MS = 100;
797
+ var HOVER2 = { hover: true };
798
+ var NO_HOVER = { hover: false };
623
799
  function createControlElementClass(kind, options = {}) {
624
800
  const response = pressResponse(kind);
625
801
  return class VireControl extends HTMLElement {
626
802
  static observedAttributes = ["glass", "size", "disabled", FORCE_ATTRIBUTE];
627
803
  #control = null;
628
804
  #state = CONTROL_IDLE;
629
- #caps = POINTER_INPUT;
630
805
  #visual = createControlVisual();
631
806
  #own = {};
632
807
  #mode = null;
@@ -681,7 +856,7 @@ function createControlElementClass(kind, options = {}) {
681
856
  }
682
857
  this.#hadStyle.host = this.hasAttribute("style");
683
858
  ensureStyles(this, options.nonce);
684
- this.#cleanups.push(watchCoarsePointer(() => this.#onInputChange()));
859
+ this.#cleanups.push(watchPointerInput(() => this.#onInputChange()));
685
860
  this.#cleanups.push(watchSystemA11y(() => this.#refreshMode()));
686
861
  this.#cleanups.push(watchA11yOverride(() => this.#refreshMode()));
687
862
  this.#awaitControl();
@@ -736,7 +911,6 @@ function createControlElementClass(kind, options = {}) {
736
911
  this.#control = control;
737
912
  this.#hadStyle.control = control.hasAttribute("style");
738
913
  this.setAttribute("data-vk", "");
739
- this.#caps = isCoarsePointer() ? TOUCH_INPUT : POINTER_INPUT;
740
914
  this.#listen(control);
741
915
  this.#cleanups.push(this.#observeSize(control));
742
916
  this.#syncDisabled();
@@ -748,13 +922,13 @@ function createControlElementClass(kind, options = {}) {
748
922
  }
749
923
  #onInputChange() {
750
924
  if (!this.#control) return;
751
- this.#caps = isCoarsePointer() ? TOUCH_INPUT : POINTER_INPUT;
925
+ if (this.#state.hovered && !effectiveInput().hover) this.#dispatch({ type: "enter" }, NO_HOVER);
752
926
  this.#refreshMode(true);
753
927
  }
754
928
  #size() {
755
929
  const wanted = this.getAttribute("size");
756
930
  if (wanted && CONTROL_SIZES.includes(wanted)) return wanted;
757
- return defaultSize({ touch: isCoarsePointer() });
931
+ return sizeTier(effectiveInput());
758
932
  }
759
933
  #glassMode() {
760
934
  const wanted = this.getAttribute("glass");
@@ -765,9 +939,9 @@ function createControlElementClass(kind, options = {}) {
765
939
  const wanted = this.getAttribute(FORCE_ATTRIBUTE);
766
940
  return wanted !== null && Object.hasOwn(FORCED, wanted) ? wanted : null;
767
941
  }
768
- #dispatch(event) {
942
+ #dispatch(event, caps = HOVER2) {
769
943
  if (this.#forced()) return false;
770
- const step = stepControl(this.#state, event, this.#caps);
944
+ const step = stepControl(this.#state, event, caps);
771
945
  this.#state = step.state;
772
946
  this.#render();
773
947
  return step.activated;
@@ -811,7 +985,7 @@ function createControlElementClass(kind, options = {}) {
811
985
  if (mine(e)) this.#dispatch({ type: "cancel" });
812
986
  });
813
987
  on(target, "pointerenter", (e) => {
814
- if (mine(e) && e.pointerType !== "touch") this.#dispatch({ type: "enter" });
988
+ if (mine(e)) this.#dispatch({ type: "enter" }, hoversWith(e.pointerType) ? HOVER2 : NO_HOVER);
815
989
  });
816
990
  on(target, "pointerleave", (e) => {
817
991
  if (mine(e) && !control.hasPointerCapture?.(e.pointerId)) this.#dispatch({ type: "leave" });
@@ -873,7 +1047,7 @@ function createControlElementClass(kind, options = {}) {
873
1047
  #applySlop() {
874
1048
  const control = this.#control;
875
1049
  if (!control) return;
876
- const floor = minTarget({ touch: isCoarsePointer() });
1050
+ const floor = hitTarget(effectiveInput());
877
1051
  this.#slop = { x: hitSlop(control.offsetWidth, floor), y: hitSlop(control.offsetHeight, floor) };
878
1052
  this.style.setProperty("--vk-slop-x", String(this.#slop.x));
879
1053
  this.style.setProperty("--vk-slop-y", String(this.#slop.y));
@@ -885,7 +1059,7 @@ function createControlElementClass(kind, options = {}) {
885
1059
  const disabled = control.disabled;
886
1060
  if (this.#forced()) return;
887
1061
  if (disabled !== this.#state.disabled) {
888
- this.#state = stepControl(this.#state, { type: "disable", disabled }, this.#caps).state;
1062
+ this.#state = stepControl(this.#state, { type: "disable", disabled }, HOVER2).state;
889
1063
  this.#render();
890
1064
  }
891
1065
  }
@@ -1081,40 +1255,46 @@ var MENU_CORNERS = {
1081
1255
  "bottom-left": { x: -1, y: 1 },
1082
1256
  "bottom-right": { x: 1, y: 1 }
1083
1257
  };
1084
- function menuCorner(trigger, viewport) {
1258
+ function menuCorner(trigger, region) {
1259
+ const x0 = region.x ?? 0;
1260
+ const y0 = region.y ?? 0;
1085
1261
  return {
1086
- x: trigger.x + trigger.width / 2 <= viewport.width / 2 ? -1 : 1,
1087
- y: trigger.y + trigger.height / 2 <= viewport.height / 2 ? -1 : 1
1262
+ x: trigger.x + trigger.width / 2 <= x0 + region.width / 2 ? -1 : 1,
1263
+ y: trigger.y + trigger.height / 2 <= y0 + region.height / 2 ? -1 : 1
1088
1264
  };
1089
1265
  }
1090
1266
  function along(start, length, size, side) {
1091
1267
  return side < 0 ? start : start + length - size;
1092
1268
  }
1093
- function fits(at, size, limit, margin) {
1094
- return at >= margin && at + size <= limit - margin;
1269
+ function fits(at, size, lo, hi, margin) {
1270
+ return at >= lo + margin && at + size <= hi - margin;
1095
1271
  }
1096
1272
  var clamp = (v, lo, hi) => Math.min(Math.max(v, lo), Math.max(lo, hi));
1097
- function menuPlacement(trigger, menu, viewport, options = {}) {
1273
+ function menuPlacement(trigger, menu, region, options = {}) {
1098
1274
  const margin = options.margin ?? 8;
1099
- const width = Math.min(menu.width, viewport.width - 2 * margin);
1100
- const height = Math.min(menu.height, viewport.height - 2 * margin);
1101
- const corner = { ...options.corner ?? menuCorner(trigger, viewport) };
1275
+ const x0 = region.x ?? 0;
1276
+ const y0 = region.y ?? 0;
1277
+ const x1 = x0 + region.width;
1278
+ const y1 = y0 + region.height;
1279
+ const width = Math.max(0, Math.min(menu.width, region.width - 2 * margin));
1280
+ const height = Math.max(0, Math.min(menu.height, region.height - 2 * margin));
1281
+ const corner = { ...options.corner ?? menuCorner(trigger, region) };
1102
1282
  let x = along(trigger.x, trigger.width, width, corner.x);
1103
1283
  let y = along(trigger.y, trigger.height, height, corner.y);
1104
1284
  if (!options.corner) {
1105
1285
  const fx = along(trigger.x, trigger.width, width, corner.x === -1 ? 1 : -1);
1106
- if (!fits(x, width, viewport.width, margin) && fits(fx, width, viewport.width, margin)) {
1286
+ if (!fits(x, width, x0, x1, margin) && fits(fx, width, x0, x1, margin)) {
1107
1287
  corner.x = corner.x === -1 ? 1 : -1;
1108
1288
  x = fx;
1109
1289
  }
1110
1290
  const fy = along(trigger.y, trigger.height, height, corner.y === -1 ? 1 : -1);
1111
- if (!fits(y, height, viewport.height, margin) && fits(fy, height, viewport.height, margin)) {
1291
+ if (!fits(y, height, y0, y1, margin) && fits(fy, height, y0, y1, margin)) {
1112
1292
  corner.y = corner.y === -1 ? 1 : -1;
1113
1293
  y = fy;
1114
1294
  }
1115
1295
  }
1116
- x = clamp(x, margin, viewport.width - margin - width);
1117
- y = clamp(y, margin, viewport.height - margin - height);
1296
+ x = clamp(x, x0 + margin, x1 - margin - width);
1297
+ y = clamp(y, y0 + margin, y1 - margin - height);
1118
1298
  const rect = { x, y, width, height };
1119
1299
  return {
1120
1300
  rect,
@@ -1128,7 +1308,7 @@ function menuActivity(phase) {
1128
1308
  const grow = Math.min(Math.max(phase.grow, 0), 1);
1129
1309
  return shrink * (1 - grow);
1130
1310
  }
1131
- function menuSurfaceFrame(trigger, menu, anchor, phase) {
1311
+ function menuSurfaceFrame(trigger, menu, anchor, phase, bounds) {
1132
1312
  const frame = growthFrame(
1133
1313
  { geometry: { width: trigger.width, height: trigger.height, cornerRadius: trigger.radius }, center: center(trigger) },
1134
1314
  { geometry: { width: menu.width, height: menu.height, cornerRadius: menu.radius }, center: center(menu) },
@@ -1136,16 +1316,24 @@ function menuSurfaceFrame(trigger, menu, anchor, phase) {
1136
1316
  phase
1137
1317
  );
1138
1318
  const { geometry, center: c } = frame.body;
1139
- const box = {
1319
+ let box = {
1140
1320
  x: c.x - geometry.width / 2,
1141
1321
  y: c.y - geometry.height / 2,
1142
1322
  width: geometry.width,
1143
1323
  height: geometry.height,
1144
1324
  radius: geometry.cornerRadius
1145
1325
  };
1326
+ let morph = frame.merge;
1327
+ const held = bounds ? intersectRect(box, bounds) : null;
1328
+ if (held && (held.width < box.width || held.height < box.height)) {
1329
+ const was = center(box);
1330
+ const now = center(held);
1331
+ box = { ...held, radius: Math.min(box.radius, held.width / 2, held.height / 2) };
1332
+ if (morph) morph = { ...morph, offsetX: morph.offsetX + was.x - now.x, offsetY: morph.offsetY + was.y - now.y };
1333
+ }
1146
1334
  return {
1147
1335
  box,
1148
- morph: frame.merge,
1336
+ morph,
1149
1337
  content: { x: menu.x - box.x, y: menu.y - box.y },
1150
1338
  ink: frame.ink,
1151
1339
  activity: menuActivity(phase)
@@ -1212,6 +1400,7 @@ function createMenuElementClass(options = {}) {
1212
1400
  #from = null;
1213
1401
  #to = null;
1214
1402
  #anchor = null;
1403
+ #region = null;
1215
1404
  #natural = null;
1216
1405
  #out = false;
1217
1406
  #viaPointer = false;
@@ -1262,7 +1451,7 @@ function createMenuElementClass(options = {}) {
1262
1451
  }
1263
1452
  this.#hadStyle.host = this.hasAttribute("style");
1264
1453
  ensureStyles(this, options.nonce);
1265
- this.#cleanups.push(watchCoarsePointer(() => this.#list && this.#refreshMode(true)));
1454
+ this.#cleanups.push(watchPointerInput(() => this.#list && this.#refreshMode(true)));
1266
1455
  this.#cleanups.push(watchSystemA11y(() => this.#list && this.#refreshMode()));
1267
1456
  this.#cleanups.push(watchA11yOverride(() => this.#list && this.#refreshMode()));
1268
1457
  this.#awaitParts();
@@ -1293,7 +1482,7 @@ function createMenuElementClass(options = {}) {
1293
1482
  this.#state = MENU_CLOSED;
1294
1483
  this.#growth = createGrowthState();
1295
1484
  this.#out = false;
1296
- this.#from = this.#to = this.#anchor = null;
1485
+ this.#from = this.#to = this.#anchor = this.#region = null;
1297
1486
  this.#natural = null;
1298
1487
  this.#held = null;
1299
1488
  this.#readySince = null;
@@ -1532,7 +1721,7 @@ function createMenuElementClass(options = {}) {
1532
1721
  );
1533
1722
  on(root, "scroll", (e) => this.#follow(e), true);
1534
1723
  });
1535
- on(window, "resize", (e) => this.#follow(e));
1724
+ this.#cleanups.push(watchViewport(() => this.#follow()));
1536
1725
  if (typeof ResizeObserver !== "undefined") {
1537
1726
  const resized = new ResizeObserver(() => this.#out && this.#follow());
1538
1727
  resized.observe(this.#carrier ?? trigger);
@@ -1625,9 +1814,11 @@ function createMenuElementClass(options = {}) {
1625
1814
  const spec = menuSize(this.#size());
1626
1815
  this.#natural ??= this.#measure();
1627
1816
  const natural = this.#natural;
1628
- const viewport = { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight };
1629
1817
  const trigger = this.#triggerBox();
1630
- const placement = menuPlacement(trigger, natural, viewport, { corner: this.#corner(), margin: VIEWPORT_MARGIN });
1818
+ const { viewport, segments } = viewportSegments();
1819
+ const region = segmentOf(trigger, segments) ?? viewport;
1820
+ this.#region = region;
1821
+ const placement = menuPlacement(trigger, natural, region, { corner: this.#corner(), margin: VIEWPORT_MARGIN });
1631
1822
  this.#from = trigger;
1632
1823
  this.#to = { ...placement.rect, radius: spec.radius };
1633
1824
  this.#anchor = placement.anchor;
@@ -1693,7 +1884,7 @@ function createMenuElementClass(options = {}) {
1693
1884
  this.removeAttribute("data-phase");
1694
1885
  this.#out = false;
1695
1886
  this.#held = null;
1696
- this.#from = this.#to = this.#anchor = null;
1887
+ this.#from = this.#to = this.#anchor = this.#region = null;
1697
1888
  this.#glass?.setSample(this.#sample);
1698
1889
  this.#carrier?.setCarried?.(null);
1699
1890
  }
@@ -1701,7 +1892,7 @@ function createMenuElementClass(options = {}) {
1701
1892
  const list = this.#list;
1702
1893
  if (!list || !this.#out || !this.#from || !this.#to || !this.#anchor) return;
1703
1894
  const phase = growthPhase(this.#growth);
1704
- const f = menuSurfaceFrame(this.#from, this.#to, this.#anchor, phase);
1895
+ const f = menuSurfaceFrame(this.#from, this.#to, this.#anchor, phase, this.#region ?? void 0);
1705
1896
  const settled = phase.shrink === 1 && phase.grow === 1;
1706
1897
  const inkIn = Math.round(f.ink.incoming * 1e3) / 1e3;
1707
1898
  if (this.#state.open && inkIn >= 1) {
@@ -1781,7 +1972,7 @@ function createMenuElementClass(options = {}) {
1781
1972
  #size() {
1782
1973
  const wanted = this.getAttribute("size");
1783
1974
  if (wanted && CONTROL_SIZES.includes(wanted)) return wanted;
1784
- return defaultSize({ touch: isCoarsePointer() });
1975
+ return sizeTier(effectiveInput());
1785
1976
  }
1786
1977
  #wantsGlass() {
1787
1978
  const wanted = this.getAttribute("glass");
@@ -1885,8 +2076,15 @@ exports.ICON_BUTTON_TAG = ICON_BUTTON_TAG;
1885
2076
  exports.MENU_TAG = MENU_TAG;
1886
2077
  exports.STYLES = STYLES;
1887
2078
  exports.defineVireElements = defineVireElements;
2079
+ exports.effectiveInput = effectiveInput;
1888
2080
  exports.getA11yOverride = getA11yOverride;
2081
+ exports.getInputOverride = getInputOverride;
1889
2082
  exports.setA11yOverride = setA11yOverride;
2083
+ exports.setInputOverride = setInputOverride;
2084
+ exports.setViewportOverride = setViewportOverride;
1890
2085
  exports.systemA11y = systemA11y;
2086
+ exports.systemInput = systemInput;
2087
+ exports.viewportSegments = viewportSegments;
2088
+ exports.watchAdaptiveContext = watchAdaptiveContext;
1891
2089
  //# sourceMappingURL=web.cjs.map
1892
2090
  //# sourceMappingURL=web.cjs.map