elements-kit 0.27.4 → 0.27.6

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.
Files changed (103) hide show
  1. package/README.md +1 -0
  2. package/dist/attributes.d.mts +1 -1
  3. package/dist/await.d.mts +1 -1
  4. package/dist/await.mjs +1 -1
  5. package/dist/{children-_cMdyX7Q.d.mts → children-CjlmAzxx.d.mts} +3 -3
  6. package/dist/chunk-CfYAbeIz.mjs +13 -0
  7. package/dist/{custom-elements-C7oiqw-y.d.mts → custom-elements-CTqSkWI7.d.mts} +1 -1
  8. package/dist/custom-elements.d.mts +1 -1
  9. package/dist/direction-EwIAAn1f.d.mts +29 -0
  10. package/dist/{element-DgQvp-49.mjs → element-bMvwF1ue.mjs} +2 -2
  11. package/dist/for.d.mts +1 -1
  12. package/dist/for.mjs +2 -2
  13. package/dist/{fragment-BJfcQdsL.mjs → fragment-DV0hrE_A.mjs} +2 -2
  14. package/dist/{hot-Zl0s17uq.mjs → hot-kRKLQS6B.mjs} +2 -2
  15. package/dist/hydrate/index.mjs +1 -1
  16. package/dist/{hydrate-ICXw1FIY.mjs → hydrate-Vx_iBMqJ.mjs} +3 -3
  17. package/dist/integrations/astro-client.mjs +2 -2
  18. package/dist/integrations/astro-server.mjs +3 -3
  19. package/dist/integrations/astro-slots.mjs +2 -2
  20. package/dist/integrations/hmr-runtime.mjs +2 -2
  21. package/dist/integrations/react.d.mts +1 -1
  22. package/dist/integrations/react.mjs +2 -2
  23. package/dist/jsx-runtime/dev.d.mts +2 -2
  24. package/dist/jsx-runtime/dev.mjs +2 -2
  25. package/dist/jsx-runtime/index.d.mts +2 -2
  26. package/dist/jsx-runtime/index.mjs +2 -2
  27. package/dist/render.mjs +1 -1
  28. package/dist/{scope-C1vELXqP.mjs → scope-B1kDu4YX.mjs} +1 -1
  29. package/dist/server/index.mjs +1 -1
  30. package/dist/{server-Qm9JTZ-J.mjs → server-DxiXDjQU.mjs} +3 -3
  31. package/dist/signals/index.d.mts +1 -1
  32. package/dist/signals/index.mjs +2 -2
  33. package/dist/slot.mjs +1 -1
  34. package/dist/ui/otp-input/index.d.mts +2 -2
  35. package/dist/ui/otp-input/index.mjs +1 -1
  36. package/dist/ui/overlay/handle.css +134 -0
  37. package/dist/ui/overlay/index.css +13 -88
  38. package/dist/ui/overlay/index.d.mts +232 -347
  39. package/dist/ui/overlay/index.mjs +676 -1550
  40. package/dist/ui/overlay/overlay.css +74 -135
  41. package/dist/utilities/_observe.mjs +1 -1
  42. package/dist/utilities/active-element.d.mts +1 -1
  43. package/dist/utilities/active-element.mjs +1 -1
  44. package/dist/utilities/async.d.mts +7 -1
  45. package/dist/utilities/async.mjs +13 -1
  46. package/dist/utilities/context.mjs +1 -1
  47. package/dist/utilities/debounced.d.mts +1 -1
  48. package/dist/utilities/debounced.mjs +1 -1
  49. package/dist/utilities/direction.d.mts +2 -0
  50. package/dist/utilities/direction.mjs +42 -0
  51. package/dist/utilities/dom-lifecycle.bench.mjs +2 -2
  52. package/dist/utilities/dom-lifecycle.d.mts +1 -1
  53. package/dist/utilities/dom-lifecycle.mjs +1 -1
  54. package/dist/utilities/element-rect.d.mts +1 -1
  55. package/dist/utilities/element-rect.mjs +1 -1
  56. package/dist/utilities/element-scroll.d.mts +1 -1
  57. package/dist/utilities/environment.d.mts +1 -1
  58. package/dist/utilities/event-driven.d.mts +1 -1
  59. package/dist/utilities/event-driven.mjs +1 -1
  60. package/dist/utilities/event-listener.d.mts +1 -1
  61. package/dist/utilities/event-listener.mjs +1 -1
  62. package/dist/utilities/focus-within.d.mts +1 -1
  63. package/dist/utilities/focus-within.mjs +1 -1
  64. package/dist/utilities/hover.d.mts +1 -1
  65. package/dist/utilities/hover.mjs +1 -1
  66. package/dist/utilities/interval.d.mts +1 -1
  67. package/dist/utilities/interval.mjs +1 -1
  68. package/dist/utilities/location.d.mts +1 -1
  69. package/dist/utilities/location.mjs +1 -1
  70. package/dist/utilities/media-devices.d.mts +1 -1
  71. package/dist/utilities/media-devices.mjs +1 -1
  72. package/dist/utilities/media-player.d.mts +1 -1
  73. package/dist/utilities/media-query.d.mts +1 -1
  74. package/dist/utilities/media-query.mjs +1 -1
  75. package/dist/utilities/network.d.mts +1 -1
  76. package/dist/utilities/network.mjs +1 -1
  77. package/dist/utilities/orientation.d.mts +1 -1
  78. package/dist/utilities/orientation.mjs +1 -1
  79. package/dist/utilities/previous.d.mts +1 -1
  80. package/dist/utilities/previous.mjs +1 -1
  81. package/dist/utilities/promise.d.mts +1 -1
  82. package/dist/utilities/promise.mjs +1 -1
  83. package/dist/utilities/retry.mjs +1 -1
  84. package/dist/utilities/routing.d.mts +1 -1
  85. package/dist/utilities/routing.mjs +1 -1
  86. package/dist/utilities/search-params.d.mts +1 -1
  87. package/dist/utilities/storage.d.mts +1 -1
  88. package/dist/utilities/storage.mjs +1 -1
  89. package/dist/utilities/throttled.d.mts +1 -1
  90. package/dist/utilities/throttled.mjs +1 -1
  91. package/dist/utilities/timeout.d.mts +1 -1
  92. package/dist/utilities/timeout.mjs +1 -1
  93. package/dist/utilities/window-focus.d.mts +1 -1
  94. package/dist/utilities/window-focus.mjs +1 -1
  95. package/dist/utilities/window-size.d.mts +1 -1
  96. package/dist/utilities/window-size.mjs +1 -1
  97. package/package.json +9 -6
  98. /package/dist/{attributes-3r7Diua4.d.mts → attributes-YQZNO15D.d.mts} +0 -0
  99. /package/dist/{environment-c2ftMYT0.d.mts → environment-EQugnOcg.d.mts} +0 -0
  100. /package/dist/{index-BrVMftR8.d.mts → index-BSAqa0-0.d.mts} +0 -0
  101. /package/dist/{lib-DYypKhxk.mjs → lib-Dw0g-vex.mjs} +0 -0
  102. /package/dist/{polyfill-CdZVCxdo.mjs → polyfill-B1lNNcum.mjs} +0 -0
  103. /package/dist/{polyfill-BVNd6ogU.d.mts → polyfill-_7qvybvt.d.mts} +0 -0
@@ -1,1598 +1,724 @@
1
- import { _ as onCleanup, u as effect } from "../../lib-DYypKhxk.mjs";
2
- import "../../signals/index.mjs";
1
+ import { t as __exportAll } from "../../chunk-CfYAbeIz.mjs";
2
+ import { c as batch, u as effect, x as signal } from "../../lib-Dw0g-vex.mjs";
3
+ import { reactive } from "../../signals/index.mjs";
4
+ import { t as scope } from "../../scope-B1kDu4YX.mjs";
5
+ import { direction } from "../../utilities/direction.mjs";
3
6
  import { createElementRect } from "../../utilities/element-rect.mjs";
4
- import { arrow, autoUpdate, computePosition, flip, offset, shift } from "@floating-ui/dom";
5
- //#region src/ui/overlay/constraint.ts
7
+ import { windowSize } from "../../utilities/window-size.mjs";
8
+ //#region src/ui/overlay/area.ts
9
+ /** One axis of a {@link Region.place}: where a box `n` long, currently at
10
+ * `own`, starts once `pin` is honoured. */
11
+ function placeAxis(pin, own, n) {
12
+ if (!pin) return own;
13
+ if (pin.align === "start") return pin.at;
14
+ if (pin.align === "end") return pin.at - n;
15
+ return pin.at - n / 2;
16
+ }
17
+ /** The inset a pin yields on one edge: its coordinate if it pins that edge. */
18
+ function inset(pin, edge) {
19
+ return pin?.align === edge ? pin.at : null;
20
+ }
6
21
  /**
7
- * Builds a {@link Region}: from an `Element` (observed via
8
- * `ResizeObserver` through `createElementRect`; cleanup routes through
9
- * the current scope), from a plain rect (static), or with no argument —
10
- * the live viewport (read at call time, not observed).
22
+ * A {@link Region} driven by writes for gestures, where the box grows away
23
+ * from the edge the user is not dragging. Assigning an inset pins that edge
24
+ * and releases its opposite; assigning `null` frees the axis.
11
25
  */
12
- function constraint(source) {
13
- if (source === void 0) return {
14
- top: () => 0,
15
- left: () => 0,
16
- width: () => window.innerWidth,
17
- height: () => window.innerHeight
18
- };
19
- if (source instanceof Element) {
20
- const rect = createElementRect(source);
21
- onCleanup(() => rect[Symbol.dispose]());
26
+ var MutableRegion = class {
27
+ #x = signal(null);
28
+ #y = signal(null);
29
+ constructor(boundary) {
30
+ const { left, right, top, bottom } = boundary;
31
+ if (left !== void 0) this.left = left;
32
+ if (right !== void 0) this.right = right;
33
+ if (top !== void 0) this.top = top;
34
+ if (bottom !== void 0) this.bottom = bottom;
35
+ }
36
+ get left() {
37
+ return inset(this.#x(), "start");
38
+ }
39
+ get right() {
40
+ return inset(this.#x(), "end");
41
+ }
42
+ get top() {
43
+ return inset(this.#y(), "start");
44
+ }
45
+ get bottom() {
46
+ return inset(this.#y(), "end");
47
+ }
48
+ set left(v) {
49
+ this.#x(v === null ? null : {
50
+ align: "start",
51
+ at: v
52
+ });
53
+ }
54
+ set right(v) {
55
+ this.#x(v === null ? null : {
56
+ align: "end",
57
+ at: v
58
+ });
59
+ }
60
+ set top(v) {
61
+ this.#y(v === null ? null : {
62
+ align: "start",
63
+ at: v
64
+ });
65
+ }
66
+ set bottom(v) {
67
+ this.#y(v === null ? null : {
68
+ align: "end",
69
+ at: v
70
+ });
71
+ }
72
+ place(box) {
22
73
  return {
23
- top: () => rect().top,
24
- left: () => rect().left,
25
- width: () => rect().width,
26
- height: () => rect().height
74
+ x: placeAxis(this.#x(), box.x, box.w),
75
+ y: placeAxis(this.#y(), box.y, box.h),
76
+ w: box.w,
77
+ h: box.h
27
78
  };
28
79
  }
29
- return {
30
- top: () => source.top,
31
- left: () => source.left,
32
- width: () => source.width,
33
- height: () => source.height
34
- };
35
- }
36
- /**
37
- * Resolves a custom property holding a length to pixels. Plain `px` values
38
- * parse directly; anything else (`svh` / `calc()` / fractions of the
39
- * constraint) resolves natively by measuring a hidden probe. `fallback` is
40
- * a CSS length expression used when the property is unset.
41
- */
42
- function resolveVarPx(overlay, name, axis, fallback) {
43
- const raw = getComputedStyle(overlay).getPropertyValue(name).trim();
44
- if (/^-?\d+(\.\d+)?px$/.test(raw)) return parseFloat(raw);
45
- const probe = document.createElement("div");
46
- probe.style.position = "absolute";
47
- probe.style.visibility = "hidden";
48
- probe.style[axis] = fallback ? `var(${name}, ${fallback})` : `var(${name})`;
49
- overlay.appendChild(probe);
50
- const px = probe.getBoundingClientRect()[axis];
51
- probe.remove();
52
- return px;
53
- }
54
- /** Resolves the constraint rect every gesture bound derives from. */
55
- function resolveConstraint(overlay) {
56
- return {
57
- top: resolveVarPx(overlay, "--overlay-constraint-top", "height"),
58
- left: resolveVarPx(overlay, "--overlay-constraint-left", "width"),
59
- width: resolveVarPx(overlay, "--overlay-constraint-width", "width"),
60
- height: resolveVarPx(overlay, "--overlay-constraint-height", "height")
61
- };
62
- }
63
- /**
64
- * Confines an overlay to a {@link Region} by syncing it into the
65
- * `--overlay-constraint-*` variables. Every location clamp and gesture
66
- * bound derives from those variables, so the overlay re-clamps when the
67
- * region changes.
68
- *
69
- * Caveat: an element region observes size changes (`ResizeObserver`) — a
70
- * container that moves without resizing (e.g. page scroll) does not
71
- * retrigger the sync.
72
- *
73
- * Registers its cleanup with the current scope (`onCleanup`) and also
74
- * returns it as `dispose` / `Symbol.dispose`; disposing removes the
75
- * variables, restoring the viewport constraint.
76
- *
77
- * @example
78
- * ```ts
79
- * import { constraint, confine } from "elements-kit/ui/overlay";
80
- *
81
- * const panel = document.querySelector("dialog.x-overlay")!;
82
- * confine(panel, constraint(document.querySelector("main")!));
83
- * ```
84
- */
85
- function confine(overlay, region) {
86
- const stop = effect(() => {
87
- overlay.style.setProperty("--overlay-constraint-top", `${region.top()}px`);
88
- overlay.style.setProperty("--overlay-constraint-left", `${region.left()}px`);
89
- overlay.style.setProperty("--overlay-constraint-width", `${region.width()}px`);
90
- overlay.style.setProperty("--overlay-constraint-height", `${region.height()}px`);
91
- });
92
- const dispose = () => {
93
- stop();
94
- overlay.style.removeProperty("--overlay-constraint-top");
95
- overlay.style.removeProperty("--overlay-constraint-left");
96
- overlay.style.removeProperty("--overlay-constraint-width");
97
- overlay.style.removeProperty("--overlay-constraint-height");
98
- };
99
- onCleanup(dispose);
100
- return {
101
- dispose,
102
- [Symbol.dispose]: dispose
103
- };
104
- }
80
+ };
105
81
  //#endregion
106
- //#region src/ui/overlay/anchor.ts
107
- /** The same compound gate as the index.css ANCHORED MODE block — both
108
- * halves or neither, so the CSS and JS tiers can never disagree. */
109
- const nativeAnchorSupport = () => typeof CSS !== "undefined" && CSS.supports("anchor-name: --x") && CSS.supports("position-area: block-end");
110
- /** Measure whether the chain rule actually lands on the anchor's VISUAL
111
- * box — `CSS.supports` is a parser check, and Firefox 151 passes it
112
- * while resolving `anchor()` against the pre-transform layout box (a
113
- * `translate`d trigger pins half a box off). The probe host is
114
- * deliberately translated to catch exactly that. Inconclusive without a
115
- * layout engine (tests) — then trust the declared support. */
116
- let chainMeasured;
117
- function probeChain() {
118
- const host = document.createElement("div");
119
- host.style.cssText = "position:fixed;top:80px;left:60px;width:40px;height:20px;translate:10px 5px;visibility:hidden;pointer-events:none;anchor-name:--ek-chain-probe";
120
- const pin = document.createElement("div");
121
- pin.style.cssText = "position:fixed;visibility:hidden;pointer-events:none;position-anchor:--ek-chain-probe;top:anchor(top);left:anchor(left);width:anchor-size(width);height:anchor-size(height)";
122
- document.body.append(host, pin);
123
- const hostRect = host.getBoundingClientRect();
124
- const rect = pin.getBoundingClientRect();
125
- host.remove();
126
- pin.remove();
127
- if (Math.abs(hostRect.top - 85) > 1) return true;
128
- return Math.abs(rect.top - 85) < 1 && Math.abs(rect.left - 70) < 1 && Math.abs(rect.width - 40) < 1;
129
- }
130
- /** The follow pin needs more than the placement gate — the chain rule
131
- * mirrors the followed box with `anchor()`/`anchor-size()`. A browser
132
- * that places but can't chain falls back to the JS rect copy. */
133
- const chainSupport = () => {
134
- if (!nativeAnchorSupport() || !CSS.supports("top: anchor(top)") || !CSS.supports("width: anchor-size(width)")) return false;
135
- chainMeasured ??= probeChain();
136
- return chainMeasured;
82
+ //#region src/ui/overlay/box.ts
83
+ var WindowBox = class {
84
+ get x() {
85
+ return 0;
86
+ }
87
+ get y() {
88
+ return 0;
89
+ }
90
+ get w() {
91
+ return windowSize.width();
92
+ }
93
+ get h() {
94
+ return windowSize.height();
95
+ }
96
+ get direction() {
97
+ return direction();
98
+ }
137
99
  };
138
- /** While the Floating UI loop tracks, geometry writes must land
139
- * instantly but ONLY geometry. Enter/exit (opacity, scale) and
140
- * close (display) keep transitioning, so `@starting-style` still plays. */
141
- const TRACKING_TRANSITIONS = "opacity, scale, display";
142
- let anchorNames = 0;
143
- /**
144
- * Maps a `position-area` value to the Floating UI placement. Spanning
145
- * toward an edge leaves the box flush with the *opposite* edge, so
146
- * `span-*-end` is a `-start` alignment (and vice versa) — Floating UI
147
- * resolves `-start`/`-end` logically, so the span tokens need no dir
148
- * check; only the physical inline sides do. Unknown values fall back to
149
- * `bottom` (the CSS default, `block-end`).
150
- */
151
- function areaToPlacement(area, rtl = false) {
152
- const tokens = area.trim().split(/\s+/);
153
- const main = tokens.find((t) => !t.startsWith("span-"));
154
- const span = tokens.find((t) => t.startsWith("span-")) ?? "";
155
- return `${main === "block-start" ? "top" : main === "inline-start" ? rtl ? "right" : "left" : main === "inline-end" ? rtl ? "left" : "right" : "bottom"}${span.endsWith("-end") ? "-start" : span.endsWith("-start") ? "-end" : ""}`;
156
- }
157
- /**
158
- * Gives `overlay` its anchor element and returns it. The overlay follows
159
- * the anchor for life; the anchor follows `follow` (element or rect)
160
- * until something moves it. Pass the returned element to `draggable()`
161
- * to make the composition tearable — dragging moves the anchor.
162
- *
163
- * `follow` may be a getter reading a signal — re-pinning on change is
164
- * how a shared popover slides between nav triggers: the native chain
165
- * glides there on the anchor element's CSS transition; the channel
166
- * engine lets that one write animate.
167
- *
168
- * Registers all cleanup with the current scope (`onCleanup`): the anchor
169
- * element, the wiring, and the stamped attributes are removed together.
170
- *
171
- * @example
172
- * ```ts
173
- * import { anchor, draggable, rubber } from "elements-kit/ui/overlay";
174
- *
175
- * const a = anchor(panel, trigger);
176
- * draggable(a, undefined, rubber()).attach(panel);
177
- *
178
- * // shared nav popover — re-anchors (and glides) when the signal changes
179
- * const active = signal(firstTrigger);
180
- * anchor(menu, () => active(), { arrow: true });
181
- * ```
182
- */
183
- function anchor(overlay, follow, opts = {}) {
184
- const native = nativeAnchorSupport() && !opts.within && !opts.arrow;
185
- let disposed = false;
186
- const resolveFollow = () => (typeof follow === "function" ? follow() : follow) ?? void 0;
187
- const el = document.createElement("span");
188
- el.className = "x-overlay-anchor";
189
- el.setAttribute("aria-hidden", "true");
190
- if (!native) el.style.transitionProperty = "none";
191
- document.body.append(el);
192
- const proxyName = `--overlay-anchor-${anchorNames++}`;
193
- const followName = `--overlay-follow-${anchorNames++}`;
194
- const placeAtRect = (rect) => {
195
- el.style.top = `${rect.top}px`;
196
- el.style.left = `${rect.left}px`;
197
- el.style.width = `${rect.width}px`;
198
- el.style.height = `${rect.height}px`;
199
- };
200
- /** Pin the anchor element to a follow target (the `data-follow`
201
- * contract — `draggable()` tears it on the first pointer-down).
202
- * Re-pinning to another element keeps the SAME `position-anchor` name,
203
- * so under the native chain the proxy's `anchor()` insets resolve to
204
- * the new box and its CSS transition GLIDES there — the nav slide. */
205
- let pinnedEl;
206
- let hasPinned = false;
207
- let stopFollowSync;
208
- const releasePinMachinery = () => {
209
- stopFollowSync?.();
210
- stopFollowSync = void 0;
211
- pinnedEl?.style.removeProperty("anchor-name");
212
- pinnedEl = void 0;
213
- };
214
- const pin = (target) => {
215
- const repositioning = hasPinned;
216
- const flip = repositioning && target instanceof Element && chainSupport();
217
- if (flip) placeAtRect(el.getBoundingClientRect());
218
- releasePinMachinery();
219
- if (!target) {
220
- el.style.top = "50vh";
221
- el.style.left = "50vw";
222
- return;
223
- }
224
- hasPinned = true;
225
- if (!(target instanceof Element)) {
226
- el.removeAttribute("data-follow");
227
- placeAtRect(target);
228
- return;
229
- }
230
- el.setAttribute("data-follow", "");
231
- if (chainSupport()) {
232
- target.style?.setProperty("anchor-name", followName);
233
- pinnedEl = target;
234
- el.style.setProperty("position-anchor", followName);
235
- if (flip) el.offsetTop;
236
- el.style.removeProperty("top");
237
- el.style.removeProperty("left");
238
- el.style.removeProperty("width");
239
- el.style.removeProperty("height");
240
- } else {
241
- let first = !repositioning;
242
- const sync = () => {
243
- if (first) {
244
- first = false;
245
- const prev = el.style.transitionProperty;
246
- el.style.transitionProperty = "none";
247
- placeAtRect(target.getBoundingClientRect());
248
- el.offsetTop;
249
- if (prev) el.style.transitionProperty = prev;
250
- else el.style.removeProperty("transition-property");
251
- return;
252
- }
253
- placeAtRect(target.getBoundingClientRect());
254
- };
255
- stopFollowSync = autoUpdate(target, el, sync);
256
- }
257
- };
258
- const unpinCleanup = () => releasePinMachinery();
259
- const followObserver = new MutationObserver(() => {
260
- if (!el.hasAttribute("data-follow")) unpinCleanup();
261
- });
262
- followObserver.observe(el, {
263
- attributes: true,
264
- attributeFilter: ["data-follow"]
265
- });
266
- const stampPlacedHint = () => {
267
- const style = getComputedStyle(overlay);
268
- const hint = areaToPlacement(style.getPropertyValue("--overlay-area"), style.direction === "rtl");
269
- overlay.setAttribute("data-placed", hint.split("-")[0]);
270
- };
271
- const isOpen = () => {
272
- if (overlay.hasAttribute("open")) return true;
273
- try {
274
- return overlay.matches(":popover-open");
275
- } catch {
276
- return false;
277
- }
278
- };
279
- let haltLoop;
280
- let engineDispose;
281
- /** Channel engine only: reposition against a re-pinned target, letting
282
- * that one write animate (the morph to the new anchor). */
283
- let onRepoint;
284
- if (native) {
285
- overlay.setAttribute("data-anchor", "element");
286
- el.style.setProperty("anchor-name", proxyName);
287
- overlay.style.setProperty("position-anchor", proxyName);
288
- engineDispose = () => {
289
- const rect = overlay.getBoundingClientRect();
290
- if (isOpen() && rect.width > 0) {
291
- const c = resolveConstraint(overlay);
292
- overlay.style.setProperty("--overlay-x", `${rect.left + rect.width / 2 - c.left}px`);
293
- overlay.style.setProperty("--overlay-y", `${rect.top + rect.height / 2 - c.top}px`);
294
- }
295
- overlay.style.removeProperty("position-anchor");
296
- overlay.removeAttribute("data-anchor");
297
- };
298
- } else {
299
- let arrowEl = null;
300
- let arrowOwned = false;
301
- if (opts.arrow) {
302
- arrowEl = overlay.querySelector(":scope > .x-overlay-arrow");
303
- if (!arrowEl) {
304
- arrowEl = document.createElement("span");
305
- arrowEl.className = "x-overlay-arrow";
306
- arrowEl.setAttribute("aria-hidden", "true");
307
- overlay.append(arrowEl);
308
- arrowOwned = true;
309
- }
310
- if (typeof opts.arrow === "number") overlay.style.setProperty("--overlay-arrow-size", `${opts.arrow}px`);
311
- }
312
- const boundaryRect = () => {
313
- const w = opts.within;
314
- if (!w) return void 0;
315
- return {
316
- x: w.left(),
317
- y: w.top(),
318
- width: w.width(),
319
- height: w.height()
320
- };
321
- };
322
- const update = async () => {
323
- const style = getComputedStyle(overlay);
324
- const placementHint = areaToPlacement(style.getPropertyValue("--overlay-area"), style.direction === "rtl");
325
- const gapPx = resolveVarPx(overlay, "--overlay-gap", "width", "var(--space-2, 8px)");
326
- const boundary = boundaryRect();
327
- const middleware = [
328
- offset(gapPx),
329
- flip(boundary ? { boundary } : void 0),
330
- shift(boundary ? { boundary } : void 0)
331
- ];
332
- if (arrowEl) middleware.push(arrow({ element: arrowEl }));
333
- const { x, y, placement, middlewareData } = await computePosition(el, overlay, {
334
- strategy: "fixed",
335
- placement: placementHint,
336
- middleware
337
- });
338
- if (disposed) return;
339
- const constraint = resolveConstraint(overlay);
340
- overlay.style.setProperty("--overlay-x", `${x + overlay.offsetWidth / 2 - constraint.left}px`);
341
- overlay.style.setProperty("--overlay-y", `${y + overlay.offsetHeight / 2 - constraint.top}px`);
342
- if (placement) overlay.setAttribute("data-placed", placement.split("-")[0]);
343
- const a = middlewareData.arrow;
344
- if (a) {
345
- if (a.x !== void 0) overlay.style.setProperty("--overlay-arrow-x", `${a.x}px`);
346
- if (a.y !== void 0) overlay.style.setProperty("--overlay-arrow-y", `${a.y}px`);
347
- }
348
- };
349
- let stop;
350
- let morphIn = isOpen();
351
- const suppress = () => overlay.style.setProperty("transition-property", TRACKING_TRANSITIONS);
352
- const release = () => overlay.style.removeProperty("transition-property");
353
- const startLoop = () => {
354
- if (stop) return;
355
- const instant = !morphIn;
356
- morphIn = false;
357
- if (instant) suppress();
358
- else if (!overlay.style.getPropertyValue("--overlay-x")) {
359
- const rect = overlay.getBoundingClientRect();
360
- const c = resolveConstraint(overlay);
361
- overlay.style.setProperty("--overlay-x", `${rect.left + rect.width / 2 - c.left}px`);
362
- overlay.style.setProperty("--overlay-y", `${rect.top + rect.height / 2 - c.top}px`);
363
- }
364
- let pending = instant;
365
- stop = autoUpdate(el, overlay, () => update().then(() => {
366
- if (pending && !disposed) {
367
- pending = false;
368
- release();
369
- }
370
- }));
371
- };
372
- haltLoop = () => {
373
- stop?.();
374
- stop = void 0;
375
- release();
376
- };
377
- onRepoint = () => {
378
- if (stop) update();
379
- };
380
- const sync = () => {
381
- if (disposed) return;
382
- if (isOpen()) startLoop();
383
- else haltLoop?.();
384
- };
385
- const onDragMove = () => {
386
- suppress();
387
- update();
388
- };
389
- const onDragEnd = () => {
390
- if (disposed || !stop) return;
391
- release();
392
- update();
393
- };
394
- overlay.addEventListener("toggle", sync);
395
- overlay.addEventListener("close", sync);
396
- el.addEventListener("dragmove", onDragMove);
397
- el.addEventListener("dragend", onDragEnd);
398
- const openObserver = new MutationObserver(sync);
399
- openObserver.observe(overlay, {
400
- attributes: true,
401
- attributeFilter: ["open"]
402
- });
403
- sync();
404
- engineDispose = () => {
405
- haltLoop?.();
406
- openObserver.disconnect();
407
- overlay.removeEventListener("toggle", sync);
408
- overlay.removeEventListener("close", sync);
409
- el.removeEventListener("dragmove", onDragMove);
410
- el.removeEventListener("dragend", onDragEnd);
411
- overlay.style.removeProperty("--overlay-arrow-x");
412
- overlay.style.removeProperty("--overlay-arrow-y");
413
- if (typeof opts.arrow === "number") overlay.style.removeProperty("--overlay-arrow-size");
414
- if (arrowOwned) arrowEl?.remove();
415
- };
100
+ const WINDOW_BOX = new WindowBox();
101
+ var ElementBox = class {
102
+ #rect;
103
+ constructor(el) {
104
+ this.#rect = createElementRect(el);
416
105
  }
417
- const repin = (event) => {
418
- if (disposed) return;
419
- const target = resolveFollow();
420
- if (!target) return;
421
- if ((event.newState === "open" || event.type !== "toggle" && isOpen()) && !el.hasAttribute("data-follow")) {
422
- el.style.removeProperty("top");
423
- el.style.removeProperty("left");
424
- el.style.removeProperty("width");
425
- el.style.removeProperty("height");
426
- pin(target);
427
- }
428
- };
429
- overlay.addEventListener("toggle", repin);
430
- const repinObserver = new MutationObserver((mutations) => {
431
- if (mutations.some((m) => m.attributeName === "open")) repin(new Event("open"));
432
- });
433
- repinObserver.observe(overlay, {
434
- attributes: true,
435
- attributeFilter: ["open"]
436
- });
437
- stampPlacedHint();
438
- let stopFollowEffect;
439
- if (typeof follow === "function") {
440
- let first = true;
441
- stopFollowEffect = effect(() => {
442
- pin(resolveFollow());
443
- if (!first) onRepoint?.();
444
- first = false;
445
- });
446
- } else pin(resolveFollow());
447
- const dispose = () => {
448
- disposed = true;
449
- stopFollowEffect?.();
450
- engineDispose?.();
451
- unpinCleanup();
452
- followObserver.disconnect();
453
- repinObserver.disconnect();
454
- overlay.removeEventListener("toggle", repin);
455
- overlay.removeAttribute("data-placed");
456
- el.remove();
457
- };
458
- onCleanup(dispose);
459
- return el;
460
- }
461
- //#endregion
462
- //#region src/ui/overlay/resize-strategy.ts
463
- /**
464
- * Resize strategies — pluggable policy for a resize drag's live bounds
465
- * and resting size. Pure (no DOM); the gesture injects the context.
466
- * Built-in: `freeResize` (default). `detents()` (detents.ts) quantizes a
467
- * constraint region and doubles as a strategy.
468
- */
469
- /** How far (ms) a release velocity is projected when picking a rest. */
470
- const PROJECTION_MS = 160;
471
- /** Clamp `value` into `[min, max]`. */
472
- function clamp(value, min, max) {
473
- return Math.min(Math.max(value, min), max);
474
- }
475
- /**
476
- * Picks the index of the detent closest to the released size, projected
477
- * along the release velocity (px/ms, positive = shrinking). Returns `-1`
478
- * when the gesture should dismiss instead.
479
- */
480
- function closestDetent(sizePx, detentsPx, velocityPxPerMs = 0, dismissible = false, velocityThreshold = .5) {
481
- const projected = sizePx - velocityPxPerMs * 160;
482
- if (dismissible) {
483
- const smallest = detentsPx[0] ?? 0;
484
- if (projected < smallest / 2) return -1;
485
- if (sizePx < smallest && velocityPxPerMs > velocityThreshold) return -1;
486
- }
487
- let best = 0;
488
- for (let i = 1; i < detentsPx.length; i++) if (Math.abs(detentsPx[i] - projected) < Math.abs(detentsPx[best] - projected)) best = i;
489
- return best;
490
- }
491
- /**
492
- * Free resize: drag to any size within the room; a flick or shrink past
493
- * the minimum dismisses. The default strategy.
494
- */
495
- function freeResize(opts) {
496
- return {
497
- bounds: (ctx) => [opts?.min ?? ctx.min, ctx.max],
498
- rest: (ctx) => {
499
- const lo = opts?.min ?? ctx.min;
500
- const projected = ctx.size - ctx.velocity * 160;
501
- if (ctx.dismissible && (projected < lo / 2 || ctx.size < lo && ctx.velocity > ctx.velocityThreshold)) return null;
502
- return clamp(ctx.size, lo, ctx.max);
503
- }
504
- };
505
- }
106
+ [Symbol.dispose]() {
107
+ this.#rect[Symbol.dispose]();
108
+ }
109
+ get x() {
110
+ return this.#rect().left;
111
+ }
112
+ get y() {
113
+ return this.#rect().top;
114
+ }
115
+ get w() {
116
+ return this.#rect().width;
117
+ }
118
+ get h() {
119
+ return this.#rect().height;
120
+ }
121
+ };
506
122
  //#endregion
507
- //#region src/ui/overlay/detents.ts
508
- /**
509
- * Quantizes a region: each stop is a fraction of the region along the
510
- * axis (number `0–1`) or a CSS length (string, resize-only). Flick-aware
511
- * on release; shrinking past the smallest stop dismisses (when the
512
- * consumer allows dismissal).
513
- *
514
- * @example
515
- * ```ts
516
- * import { constraint, detents } from "elements-kit/ui/overlay";
517
- *
518
- * const d = detents(constraint(), [0.25, 0.6, 0.9]); // of the viewport
519
- * ```
520
- */
521
- function detents(region, stops) {
522
- const resolved = (ctx) => stops.map((s) => clamp(ctx.resolve(s), ctx.min, ctx.max)).sort((a, b) => a - b);
523
- return {
524
- region,
525
- stops,
526
- positions(axis) {
527
- const size = axis === "width" ? region.width() : region.height();
528
- return stops.filter((s) => typeof s === "number").map((s) => s * size).sort((a, b) => a - b);
529
- },
530
- bounds(ctx) {
531
- const s = resolved(ctx);
532
- return [s[0] ?? ctx.min, s[s.length - 1] ?? ctx.max];
533
- },
534
- rest(ctx) {
535
- const s = resolved(ctx);
536
- const i = closestDetent(ctx.size, s, ctx.velocity, ctx.dismissible, ctx.velocityThreshold);
537
- return i === -1 ? null : s[i];
538
- }
539
- };
540
- }
541
- /** Clamp with rubber-band resistance past either bound. */
542
- function resist(value, min, max, resistance = 3) {
543
- if (value > max) return max + (value - max) / resistance;
544
- if (value < min) return min - (min - value) / resistance;
545
- return value;
546
- }
123
+ //#region src/ui/overlay/anchor.ts
547
124
  /**
548
- * The block / inline handle sides a `data-resize` value encodes. Edges
549
- * name one axis (`block-start`, `inline-end` the other side `null`);
550
- * corners name both as a `start`/`end` pair, block side first
551
- * (`end-end`, `start-end`).
125
+ * The reactive `anchor()` function: the viewport coordinate of an anchor line
126
+ * on `box`, in the context of the inset property being computed. Reads the
127
+ * box's reactive geometry, so calling it inside an `effect` tracks the anchor.
552
128
  */
553
- function parseResize(resize) {
554
- const value = resize.trim();
555
- const corner = /^(start|end)-(start|end)$/.exec(value);
556
- if (corner) return {
557
- block: corner[1],
558
- inline: corner[2]
559
- };
560
- const block = /^block-(start|end)$/.exec(value);
561
- if (block) return {
562
- block: block[1],
563
- inline: null
564
- };
565
- const inline = /^inline-(start|end)$/.exec(value);
566
- if (inline) return {
567
- block: null,
568
- inline: inline[1]
569
- };
570
- return {
571
- block: null,
572
- inline: null
573
- };
129
+ function anchor_length(box, inset, side) {
130
+ const physical = resolveInset(inset);
131
+ const block = physical === "top" || physical === "bottom";
132
+ const lo = block ? box.y : box.x;
133
+ const size = block ? box.h : box.w;
134
+ return lo + fraction(box, block, physical, side) * size;
135
+ }
136
+ /** A side, as a fraction of the axis measured from its top/left edge. */
137
+ function fraction(box, block, inset, side) {
138
+ switch (side) {
139
+ case "top":
140
+ case "left": return 0;
141
+ case "bottom":
142
+ case "right": return 1;
143
+ case "inside": return inset === "top" || inset === "left" ? 0 : 1;
144
+ case "outside": return inset === "top" || inset === "left" ? 1 : 0;
145
+ case "center": return .5;
146
+ }
147
+ const logical = typeof side === "number" ? side : side.endsWith("end") ? 1 : 0;
148
+ const rtl = side === "self-start" || side === "self-end" ? (box.direction ?? rootDirection()) === "rtl" : rootDirection() === "rtl";
149
+ return !block && rtl ? 1 - logical : logical;
150
+ }
151
+ /** The document root's direction — the fallback when a box has no own. */
152
+ function rootDirection() {
153
+ return getComputedStyle(document.documentElement).direction === "rtl" ? "rtl" : "ltr";
154
+ }
155
+ /** A logical inset longhand → its physical side, through the containing
156
+ * block's direction (horizontal writing modes — only inline flips). */
157
+ function resolveInset(inset) {
158
+ switch (inset) {
159
+ case "inset-block-start": return "top";
160
+ case "inset-block-end": return "bottom";
161
+ case "inset-inline-start": return getComputedStyle(document.documentElement).direction === "rtl" ? "right" : "left";
162
+ case "inset-inline-end": return getComputedStyle(document.documentElement).direction === "rtl" ? "left" : "right";
163
+ default: return inset;
164
+ }
574
165
  }
575
- /** New per-axis velocity (px/ms) from a move; keeps the prior value when
576
- * the timestamp doesn't advance (synthetic events). */
577
- function updateVelocity(prev, client, timeStamp) {
578
- const dt = timeStamp - prev.lastTime;
579
- if (dt > 0) return {
580
- x: (client.x - prev.prev.x) / dt,
581
- y: (client.y - prev.prev.y) / dt
582
- };
583
- return prev.velocity;
166
+ const B = (region, physical = false) => ({
167
+ axis: "block",
168
+ region,
169
+ physical,
170
+ self: false
171
+ });
172
+ const I = (region, physical = false) => ({
173
+ axis: "inline",
174
+ region,
175
+ physical,
176
+ self: false
177
+ });
178
+ const A = (region, self = false) => ({
179
+ axis: "ambiguous",
180
+ region,
181
+ physical: false,
182
+ self
183
+ });
184
+ /** The full `position-area` keyword grammar → axis + physical region. */
185
+ const KEYWORDS = {
186
+ top: B("start", true),
187
+ bottom: B("end", true),
188
+ "span-top": B("span-start", true),
189
+ "span-bottom": B("span-end", true),
190
+ left: I("start", true),
191
+ right: I("end", true),
192
+ "span-left": I("span-start", true),
193
+ "span-right": I("span-end", true),
194
+ center: {
195
+ axis: "ambiguous",
196
+ region: "center",
197
+ physical: true,
198
+ self: false
199
+ },
200
+ "span-all": {
201
+ axis: "ambiguous",
202
+ region: "span-all",
203
+ physical: true,
204
+ self: false
205
+ },
206
+ "block-start": B("start"),
207
+ "block-end": B("end"),
208
+ "span-block-start": B("span-start"),
209
+ "span-block-end": B("span-end"),
210
+ "y-start": B("start"),
211
+ "y-end": B("end"),
212
+ "span-y-start": B("span-start"),
213
+ "span-y-end": B("span-end"),
214
+ "inline-start": I("start"),
215
+ "inline-end": I("end"),
216
+ "span-inline-start": I("span-start"),
217
+ "span-inline-end": I("span-end"),
218
+ "x-start": I("start"),
219
+ "x-end": I("end"),
220
+ "span-x-start": I("span-start"),
221
+ "span-x-end": I("span-end"),
222
+ start: A("start"),
223
+ end: A("end"),
224
+ "span-start": A("span-start"),
225
+ "span-end": A("span-end"),
226
+ "self-start": A("start", true),
227
+ "self-end": A("end", true),
228
+ "span-self-start": A("span-start", true),
229
+ "span-self-end": A("span-end", true)
230
+ };
231
+ const SPAN_ALL = KEYWORDS["span-all"];
232
+ function flipRegion(r) {
233
+ return r === "start" ? "end" : r === "end" ? "start" : r === "span-start" ? "span-end" : r === "span-end" ? "span-start" : r;
584
234
  }
585
- /**
586
- * Which gesture a pointerdown engages, by zone priority: corner grip →
587
- * move strip whole-surface edge drag. Returns `null` when the pointer
588
- * misses every zone.
589
- */
590
- function detectEngagement(args) {
591
- const { block, inline, draggable, rect, pointer, dir } = args;
592
- const corner = block !== null && inline !== null;
593
- const handleRight = inline === "end" === (dir === 1);
594
- if (corner && Math.abs(pointer.x - (handleRight ? rect.right : rect.left)) <= 28 && Math.abs(pointer.y - (block === "end" ? rect.bottom : rect.top)) <= 28) return "resize";
595
- const topCenterResize = block === "start" && inline === null;
596
- if (draggable && pointer.y - rect.top >= 0 && pointer.y - rect.top <= 28 && (!topCenterResize || Math.abs(pointer.x - (dir === 1 ? rect.left : rect.right)) <= 28)) return "move";
597
- if (block !== null && inline === null) return "block";
598
- if (inline !== null && block === null) return "inline";
599
- return null;
235
+ /** Resolve a keyword to a physical region: block never flips; inline flips
236
+ * in RTL unless the keyword is physical. */
237
+ function toPhysical(kw, axis, dir, selfDir) {
238
+ const r = kw.region;
239
+ if (r === "center" || r === "span-all" || axis === "block" || kw.physical) return r;
240
+ return (kw.self ? selfDir : dir) === "rtl" ? flipRegion(r) : r;
600
241
  }
601
242
  /**
602
- * Edge-drag setup: the growth `sign` (handle grows toward the pointer),
603
- * the `anchorSign` for the location shift, and whether the handle-less
604
- * edge already sits flush against the constraint (`docked`the CSS clamp
605
- * holds it, skip the shift).
243
+ * Parse a `position-area` value into a physical {@link Area}. One or two
244
+ * keywords, order-independent; an axis-specific single keyword spans the
245
+ * other axis, an ambiguous one applies to both. Unknown/empty `block-end`.
606
246
  */
607
- function edgeSetup(args) {
608
- const { axis, side, rect, constraint, dir } = args;
609
- if (axis === "block") {
610
- const sign = side === "end" ? 1 : -1;
611
- const anchorEdge = side === "start" ? rect.bottom : rect.top;
612
- const constraintEdge = side === "start" ? constraint.top + constraint.height : constraint.top;
613
- return {
614
- sign,
615
- anchorSign: sign,
616
- docked: Math.abs(anchorEdge - constraintEdge) < 1
617
- };
247
+ function resolveArea(area, dir = "ltr", selfDir = dir) {
248
+ const tokens = area.trim().toLowerCase().split(/\s+/).filter(Boolean);
249
+ const fallback = () => ({
250
+ block: "end",
251
+ inline: "center"
252
+ });
253
+ if (tokens.length === 0 || tokens.length > 2) return fallback();
254
+ const kws = [];
255
+ for (const token of tokens) {
256
+ const keyword = KEYWORDS[token];
257
+ if (!keyword) return fallback();
258
+ kws.push(keyword);
259
+ }
260
+ const [first, second] = kws;
261
+ if (first && second && first.axis !== "ambiguous" && first.axis === second.axis) return fallback();
262
+ let block;
263
+ let inline;
264
+ if (kws.length === 1) {
265
+ const k = kws[0];
266
+ if (k.axis === "block") block = k, inline = SPAN_ALL;
267
+ else if (k.axis === "inline") inline = k, block = SPAN_ALL;
268
+ else block = k, inline = k;
269
+ } else if (kws.length === 2) {
270
+ for (const k of kws) if (k.axis === "block") block = k;
271
+ else if (k.axis === "inline") inline = k;
272
+ const ambs = kws.filter((k) => k.axis === "ambiguous");
273
+ if (!block && !inline) block = ambs[0], inline = ambs[1] ?? ambs[0];
274
+ else if (!block) block = ambs[0];
275
+ else if (!inline) inline = ambs[0];
618
276
  }
619
- const handleRight = side === "end" === (dir === 1);
620
- const anchorEdge = handleRight ? rect.left : rect.right;
621
- const constraintEdge = handleRight ? constraint.left : constraint.left + constraint.width;
622
277
  return {
623
- sign: (side === "end" ? 1 : -1) * dir,
624
- anchorSign: handleRight ? 1 : -1,
625
- docked: Math.abs(anchorEdge - constraintEdge) < 1
626
- };
278
+ block: block ? toPhysical(block, "block", dir, selfDir) : "end",
279
+ inline: inline ? toPhysical(inline, "inline", dir, selfDir) : "center"
280
+ };
281
+ }
282
+ /** One axis of the position-area containing block, as `[lo, hi]` viewport
283
+ * coordinates. The anchor's two edges cut `[bLo, bHi]` into before/over/after. */
284
+ function axisSpan(lo, hi, bLo, bHi, region) {
285
+ switch (region) {
286
+ case "start": return [bLo, lo];
287
+ case "end": return [hi, bHi];
288
+ case "center": return [lo, hi];
289
+ case "span-start": return [bLo, hi];
290
+ case "span-end": return [lo, bHi];
291
+ default: return [bLo, bHi];
292
+ }
627
293
  }
628
294
  /**
629
- * Edge-drag resolution: from a target size + soft bounds, the rendered
630
- * size and the slide offset (the value to write to `--overlay-dx/-dy`, or
631
- * `null` to remove it). Both bounds rubber-band, but in different channels:
295
+ * The reactive `position-area` property: the region of the viewport an
296
+ * overlay anchored to `anchor` may occupy, with the region's default
297
+ * self-alignment as {@link Region.place} outward regions hug the anchor,
298
+ * spans go flush against its far edge, center is `anchor-center`. Nothing is
299
+ * written; the caller takes what it wants from `place`:
632
300
  *
633
- * - Above `hi` the size grows with resistance, but only up to the hard `max`
634
- * (the room from the anchored edge to the constraint). Past the room the
635
- * box cannot grow — `max-width`/`-height` caps it and the location clamp
636
- * would shove the *anchored* edge inward — so the size pins to `max` and
637
- * the resisted overshoot rides the unclamped slide instead, translating
638
- * the whole surface past the edge (handle-side rubber-band). On release the
639
- * strategy rests at the room and the slide clears, snapping it back in.
640
- * - Below `lo` the size pins to `lo` and the surface slides toward its edge
641
- * (the dismiss preview).
301
+ * const area = new PositionArea(a, "top span-left");
302
+ * effect(() => { overlay.x = area.place(overlay).x; });
642
303
  *
643
- * With a soft `hi` below the room (e.g. a `detents` max) the size-band
644
- * between `hi` and `max` renders normally before the slide takes over.
645
- */
646
- function edgeDrag(args) {
647
- const { target, lo, hi, sign, max = Infinity } = args;
648
- if (target > hi) return slidePastRoom(hi + (target - hi) / 3, max, sign);
649
- if (target < lo) return {
650
- size: lo,
651
- slide: -sign * (lo - Math.max(target, 0))
652
- };
653
- return {
654
- size: target,
655
- slide: null
656
- };
657
- }
658
- /**
659
- * Cap a resisted size at the room `max`; route the resisted overshoot beyond
660
- * it to the slide channel (translating the surface past the edge instead of
661
- * letting the CSS clamp shove the anchored edge). `null` slide within the room.
662
- * The handle-side rubber-band shared by the edge ({@link edgeDrag}) and corner
663
- * resizes — both can only grow until the handle reaches the constraint.
664
- */
665
- function slidePastRoom(resisted, max, sign) {
666
- if (resisted <= max) return {
667
- size: resisted,
668
- slide: null
669
- };
670
- return {
671
- size: max,
672
- slide: sign * (resisted - max)
673
- };
674
- }
675
- /**
676
- * Pin the opposite edge along one axis: shift the center-anchored location
677
- * by half the size change. Returns the rect-relative center coordinate, or
678
- * `null` when docked (the CSS clamp already holds that edge). The single
679
- * source of anchoring truth for block, inline, and corner resizes.
304
+ * The value is parsed once, at construction; the geometry reads the anchor
305
+ * and window on every access, so reading it inside an `effect` tracks both.
306
+ *
307
+ * Bounded by the window. A tighter bound (a `Constraint`, a scroll container)
308
+ * is an intersection with the region, so it composes afterwards rather than
309
+ * being a parameter here.
310
+ *
311
+ * There is no gap parameter, for the same reason CSS has none: the offset off
312
+ * the anchor is the overlay's own `margin`.
680
313
  */
681
- function anchor$1(args) {
682
- const { axis, center0, constraint, anchorSign, startSize, size, docked } = args;
683
- if (docked) return null;
684
- const origin = axis === "width" ? constraint.left : constraint.top;
685
- return (axis === "width" ? center0.x : center0.y) - origin + anchorSign * (size - startSize) / 2;
686
- }
687
- //#endregion
688
- //#region src/ui/overlay/draggable.ts
689
- const isSpace = (s) => "positions" in s;
690
- function draggable(target, space, ...effects) {
691
- const region = space && isSpace(space) ? space.region : space;
692
- const boundsFor = (axis) => {
693
- if (!region) return axis === "x" ? [0, window.innerWidth] : [0, window.innerHeight];
694
- return axis === "x" ? [region.left(), region.left() + region.width()] : [region.top(), region.top() + region.height()];
695
- };
696
- const during = (value, axis) => {
697
- const bounds = boundsFor(axis);
698
- let v = value;
699
- for (const e of effects) if (e.during) v = e.during(v, bounds);
700
- return v;
701
- };
702
- const write = (x, y) => {
703
- target.style.left = `${x}px`;
704
- target.style.top = `${y}px`;
705
- };
706
- const emit = (type, detail) => target.dispatchEvent(new CustomEvent(type, {
707
- detail,
708
- bubbles: true
709
- }));
710
- /** Tear a `data-follow` pin: freeze the current rect inline (inline
711
- * wins over the pin rule — no jump), then drop the pin machinery. */
712
- const takeOver = () => {
713
- const rect = target.getBoundingClientRect();
714
- write(rect.left, rect.top);
715
- if (target.hasAttribute("data-follow")) {
716
- target.style.width = `${rect.width}px`;
717
- target.style.height = `${rect.height}px`;
718
- target.removeAttribute("data-follow");
719
- target.style.removeProperty("position-anchor");
720
- }
314
+ var PositionArea = class {
315
+ anchor;
316
+ #area;
317
+ constructor(anchor, area) {
318
+ this.anchor = anchor;
319
+ this.#area = resolveArea(area, rootDirection());
320
+ }
321
+ /** The inline axis as `[lo, hi]`, re-read from the anchor and window. */
322
+ get #ix() {
323
+ const a = this.anchor;
324
+ const w = WINDOW_BOX;
325
+ return axisSpan(a.x, a.x + a.w, w.x, w.x + w.w, this.#area.inline);
326
+ }
327
+ /** The block axis as `[lo, hi]`. */
328
+ get #iy() {
329
+ const a = this.anchor;
330
+ const w = WINDOW_BOX;
331
+ return axisSpan(a.y, a.y + a.h, w.y, w.y + w.h, this.#area.block);
332
+ }
333
+ get #px() {
334
+ const a = this.anchor;
335
+ return pinOf(this.#area.inline, a.x, a.x + a.w);
336
+ }
337
+ get #py() {
338
+ const a = this.anchor;
339
+ return pinOf(this.#area.block, a.y, a.y + a.h);
340
+ }
341
+ get x() {
342
+ return this.#ix[0];
343
+ }
344
+ get y() {
345
+ return this.#iy[0];
346
+ }
347
+ get w() {
348
+ const [lo, hi] = this.#ix;
349
+ return hi - lo;
350
+ }
351
+ get h() {
352
+ const [lo, hi] = this.#iy;
353
+ return hi - lo;
354
+ }
355
+ get left() {
356
+ return inset(this.#px, "start");
357
+ }
358
+ get right() {
359
+ return inset(this.#px, "end");
360
+ }
361
+ get top() {
362
+ return inset(this.#py, "start");
363
+ }
364
+ get bottom() {
365
+ return inset(this.#py, "end");
366
+ }
367
+ place(box) {
721
368
  return {
722
- left: rect.left,
723
- top: rect.top
369
+ x: placeAxis(this.#px, box.x, box.w),
370
+ y: placeAxis(this.#py, box.y, box.h),
371
+ w: box.w,
372
+ h: box.h
724
373
  };
725
- };
726
- const settleAxis = (value, velocity, axis) => {
727
- const bounds = boundsFor(axis);
728
- let v = value;
729
- for (const e of effects) {
730
- if (!e.settle) continue;
731
- const s = e.settle(v, velocity, bounds);
732
- if (s === null) return null;
733
- v = s;
734
- }
735
- if (space && isSpace(space)) {
736
- const stops = space.positions(axis === "x" ? "width" : "height");
737
- if (stops.length && region) {
738
- const origin = axis === "x" ? region.left() : region.top();
739
- const projected = v + velocity * 160;
740
- let best = origin + stops[0];
741
- for (const stop of stops) {
742
- const candidate = origin + stop;
743
- if (Math.abs(candidate - projected) < Math.abs(best - projected)) best = candidate;
744
- }
745
- v = best;
746
- }
747
- }
748
- return v;
749
- };
750
- const attachments = [];
751
- const attach = (handle) => {
752
- let engaged = null;
753
- const onDown = (event) => {
754
- if (engaged || event.button !== 0) return;
755
- if (event.target?.closest("button, a, label, input, select, textarea, [contenteditable]")) return;
756
- engaged = {
757
- start: takeOver(),
758
- down: {
759
- x: event.clientX,
760
- y: event.clientY
761
- },
762
- tracker: {
763
- prev: {
764
- x: event.clientX,
765
- y: event.clientY
766
- },
767
- lastTime: event.timeStamp,
768
- velocity: {
769
- x: 0,
770
- y: 0
771
- }
772
- },
773
- transition: target.style.transitionProperty
774
- };
775
- target.style.transitionProperty = "none";
776
- handle.setPointerCapture(event.pointerId);
374
+ }
375
+ };
376
+ /** A region's default self-alignment as a pin on the anchor's `[lo, hi]` —
377
+ * outward regions hug the anchor's near edge, spans its far edge; `center`
378
+ * and `span-all` are `anchor-center`, which may overflow the rect. */
379
+ function pinOf(region, lo, hi) {
380
+ switch (region) {
381
+ case "start": return {
382
+ align: "end",
383
+ at: lo
777
384
  };
778
- const onMove = (event) => {
779
- if (!engaged) return;
780
- const client = {
781
- x: event.clientX,
782
- y: event.clientY
783
- };
784
- engaged.tracker.velocity = updateVelocity(engaged.tracker, client, event.timeStamp);
785
- engaged.tracker.prev = client;
786
- engaged.tracker.lastTime = event.timeStamp;
787
- const x = during(engaged.start.left + (client.x - engaged.down.x), "x");
788
- const y = during(engaged.start.top + (client.y - engaged.down.y), "y");
789
- write(x, y);
790
- emit("dragmove", {
791
- x,
792
- y
793
- });
385
+ case "span-start": return {
386
+ align: "end",
387
+ at: hi
794
388
  };
795
- const onUp = (event) => {
796
- if (!engaged) return;
797
- const { start, down, tracker, transition } = engaged;
798
- engaged = null;
799
- target.style.transitionProperty = transition;
800
- const raw = {
801
- x: start.left + (event.clientX - down.x),
802
- y: start.top + (event.clientY - down.y)
803
- };
804
- const x = settleAxis(raw.x, tracker.velocity.x, "x");
805
- const y = settleAxis(raw.y, tracker.velocity.y, "y");
806
- const rest = x === null || y === null ? null : {
807
- x,
808
- y
809
- };
810
- if (rest) write(rest.x, rest.y);
811
- emit("dragend", {
812
- ...raw,
813
- velocity: tracker.velocity,
814
- rest
815
- });
389
+ case "end": return {
390
+ align: "start",
391
+ at: hi
816
392
  };
817
- handle.addEventListener("pointerdown", onDown);
818
- handle.addEventListener("pointermove", onMove);
819
- handle.addEventListener("pointerup", onUp);
820
- handle.addEventListener("pointercancel", onUp);
821
- const dispose = () => {
822
- handle.removeEventListener("pointerdown", onDown);
823
- handle.removeEventListener("pointermove", onMove);
824
- handle.removeEventListener("pointerup", onUp);
825
- handle.removeEventListener("pointercancel", onUp);
393
+ case "span-end": return {
394
+ align: "start",
395
+ at: lo
826
396
  };
827
- attachments.push(dispose);
828
- return { dispose };
829
- };
830
- const update = (point) => {
831
- takeOver();
832
- const x = during(point.x, "x");
833
- const y = during(point.y, "y");
834
- write(x, y);
835
- emit("dragmove", {
836
- x,
837
- y
838
- });
839
- };
840
- const dispose = () => {
841
- for (const detach of attachments.splice(0)) detach();
842
- };
843
- onCleanup(dispose);
844
- return {
845
- update,
846
- attach,
847
- dispose,
848
- [Symbol.dispose]: dispose
849
- };
850
- }
851
- //#endregion
852
- //#region src/ui/overlay/move-session.ts
853
- /** Move bounds on the box center — the same clamp the stylesheet applies to
854
- * the persisted location point (center floor = half the box). */
855
- function moveBounds(args) {
856
- const { rect, constraint } = args;
857
- const minX = constraint.left + rect.width / 2;
858
- const minY = constraint.top + rect.height / 2;
859
- return {
860
- minX,
861
- minY,
862
- maxX: Math.max(constraint.left + constraint.width - rect.width / 2, minX),
863
- maxY: Math.max(constraint.top + constraint.height - rect.height / 2, minY)
864
- };
865
- }
866
- /** Live move offset (`--overlay-dx/-dy`): 1:1 inside the bounds, rubber-band
867
- * resistance beyond them. */
868
- function moveOffset(args) {
869
- const { center0, client, start, bounds } = args;
870
- return {
871
- dx: resist(center0.x + (client.x - start.x), bounds.minX, bounds.maxX) - center0.x,
872
- dy: resist(center0.y + (client.y - start.y), bounds.minY, bounds.maxY) - center0.y
873
- };
874
- }
875
- /** Rested move location (rect-relative), clamped inside the bounds. */
876
- function moveRest(args) {
877
- const { center0, client, start, bounds, constraint } = args;
878
- const cx = clamp(center0.x + (client.x - start.x), bounds.minX, bounds.maxX);
879
- const cy = clamp(center0.y + (client.y - start.y), bounds.minY, bounds.maxY);
880
- return {
881
- x: cx - constraint.left,
882
- y: cy - constraint.top
883
- };
884
- }
885
- /** Whether a flicked move's projected center leaves the constraint (close). */
886
- function projectedOutOfBounds(args) {
887
- const { rect, velocity, constraint } = args;
888
- const px = rect.left + rect.width / 2 + velocity.x * 160;
889
- const py = rect.top + rect.height / 2 + velocity.y * 160;
890
- return px < constraint.left || px > constraint.left + constraint.width || py < constraint.top || py > constraint.top + constraint.height;
891
- }
892
- /**
893
- * Window move — x/y drag from the top strip, flick off the constraint to
894
- * dismiss. Doesn't resize: writes only `offset` live and the rested x/y on
895
- * release (so the I/O layer fires no `resizechange`).
896
- */
897
- function moveSession(snap, deps, io) {
898
- const bounds = moveBounds({
899
- rect: snap.rect,
900
- constraint: snap.constraint
901
- });
902
- return {
903
- move(p) {
904
- const { dx, dy } = moveOffset({
905
- center0: snap.center0,
906
- client: p.current,
907
- start: p.start,
908
- bounds
909
- });
910
- io.sync({ offset: {
911
- dx,
912
- dy
913
- } });
914
- },
915
- release(p) {
916
- if (deps.dismissible && projectedOutOfBounds({
917
- rect: deps.liveRect(),
918
- velocity: p.velocity,
919
- constraint: snap.constraint
920
- })) return io.dismiss();
921
- const r = moveRest({
922
- center0: snap.center0,
923
- client: p.current,
924
- start: p.start,
925
- bounds,
926
- constraint: snap.constraint
927
- });
928
- io.commit({
929
- x: r.x,
930
- y: r.y
931
- });
932
- },
933
- cancel() {
934
- io.revert([]);
935
- }
936
- };
397
+ default: return {
398
+ align: "center",
399
+ at: (lo + hi) / 2
400
+ };
401
+ }
937
402
  }
938
403
  //#endregion
939
- //#region src/ui/overlay/overlay-dom.ts
940
- /**
941
- * Overlay DOM — the only element-touching code. Two halves:
942
- *
943
- * - `createFrameIO`: reads the engage-time environment off the element
944
- * (`engage`, turning layout into the plain-number {@link Snapshot} /
945
- * {@link Resizer} / {@link MoveDeps} the pure modes consume) and renders
946
- * their output back to the `--overlay-*` channels (`sync` / `commit`) or
947
- * reverts/dismisses.
948
- * - `createGestureRecognizer`: generic single-pointer drag plumbing
949
- * (capture, velocity, the touch-scroll block) that drives a {@link Session}.
950
- *
951
- * Everything between — the mode reducers and their math — stays pure and
952
- * element-free (`gesture-model` + the mode files).
953
- */
954
- const CHANNEL = {
955
- x: "--overlay-x",
956
- y: "--overlay-y",
957
- w: "--overlay-w",
958
- h: "--overlay-h"
404
+ //#region src/ui/overlay/overlay.ts
405
+ var __create = Object.create;
406
+ var __defProp = Object.defineProperty;
407
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
408
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
409
+ var __typeError = (msg) => {
410
+ throw TypeError(msg);
959
411
  };
960
- function createFrameIO(overlay, options) {
961
- const { strategy, dismissible, velocityThreshold } = options;
962
- const getProp = (name) => overlay.style.getPropertyValue(name);
963
- const setLen = (name, px) => overlay.style.setProperty(name, `${px}px`);
964
- /** Channels at engage — `revert`/`dismiss` restore these. */
965
- let prev = {
966
- x: "",
967
- y: "",
968
- w: "",
969
- h: ""
970
- };
971
- const restoreChannel = (name, value) => {
972
- if (value) overlay.style.setProperty(name, value);
973
- else overlay.style.removeProperty(name);
974
- };
975
- const clearDrag = () => {
976
- overlay.style.removeProperty("height");
977
- overlay.style.removeProperty("width");
978
- overlay.style.removeProperty("--overlay-dy");
979
- overlay.style.removeProperty("--overlay-dx");
980
- overlay.style.removeProperty("transition");
981
- overlay.style.removeProperty("user-select");
982
- overlay.style.removeProperty("-webkit-user-select");
983
- };
984
- const emitResize = () => {
985
- overlay.dispatchEvent(new CustomEvent("resizechange", {
986
- bubbles: true,
987
- composed: true,
988
- detail: {
989
- width: getProp("--overlay-w") || void 0,
990
- height: getProp("--overlay-h") || void 0
991
- }
992
- }));
993
- };
994
- /** Resolves a CSS length to px in the overlay's context (strategy steps). */
995
- const probeLength = (value, axis) => {
996
- const probe = document.createElement("div");
997
- probe.style.position = "absolute";
998
- probe.style.visibility = "hidden";
999
- probe.style[axis] = value;
1000
- overlay.appendChild(probe);
1001
- const px = probe.getBoundingClientRect()[axis];
1002
- probe.remove();
1003
- return px;
1004
- };
1005
- const writeFrame = (frame) => {
1006
- for (const k of [
1007
- "x",
1008
- "y",
1009
- "w",
1010
- "h"
1011
- ]) {
1012
- const v = frame[k];
1013
- if (v !== void 0) setLen(CHANNEL[k], v);
1014
- }
1015
- };
1016
- const applyOffset = (name, v) => {
1017
- if (v === void 0) return;
1018
- if (v === null) overlay.style.removeProperty(name);
1019
- else setLen(name, v);
1020
- };
1021
- return {
1022
- engage() {
1023
- const constraint = resolveConstraint(overlay);
1024
- const rect = overlay.getBoundingClientRect();
1025
- prev = {
1026
- x: getProp("--overlay-x"),
1027
- y: getProp("--overlay-y"),
1028
- w: getProp("--overlay-w"),
1029
- h: getProp("--overlay-h")
1030
- };
1031
- const dir = getComputedStyle(overlay).direction === "rtl" ? -1 : 1;
1032
- const ctx = (q) => ({
1033
- ...q,
1034
- dismissible,
1035
- velocityThreshold,
1036
- resolve: (value) => typeof value === "number" ? value * (q.axis === "width" ? constraint.width : constraint.height) : probeLength(value, q.axis)
1037
- });
1038
- return {
1039
- snapshot: {
1040
- constraint,
1041
- rect,
1042
- center0: {
1043
- x: rect.left + rect.width / 2,
1044
- y: rect.top + rect.height / 2
1045
- },
1046
- dir
1047
- },
1048
- resizer: {
1049
- bounds: (a) => strategy.bounds ? strategy.bounds(ctx({
1050
- ...a,
1051
- size: a.startSize,
1052
- velocity: 0
1053
- })) : [a.min, a.max],
1054
- rest: (a, drag) => strategy.rest(ctx({
1055
- ...a,
1056
- ...drag
1057
- }))
1058
- },
1059
- move: {
1060
- dismissible,
1061
- liveRect: () => overlay.getBoundingClientRect()
1062
- }
1063
- };
1064
- },
1065
- sync(patch) {
1066
- overlay.style.transition = "none";
1067
- if (patch.frame) {
1068
- const f = patch.frame;
1069
- if (f.w !== void 0) overlay.style.width = `${f.w}px`;
1070
- if (f.h !== void 0) overlay.style.height = `${f.h}px`;
1071
- if (f.x !== void 0) setLen("--overlay-x", f.x);
1072
- if (f.y !== void 0) setLen("--overlay-y", f.y);
1073
- }
1074
- if (patch.offset) {
1075
- applyOffset("--overlay-dx", patch.offset.dx);
1076
- applyOffset("--overlay-dy", patch.offset.dy);
1077
- }
1078
- },
1079
- commit(frame) {
1080
- clearDrag();
1081
- writeFrame(frame);
1082
- if (frame.w !== void 0 || frame.h !== void 0) emitResize();
1083
- },
1084
- dismiss() {
1085
- clearDrag();
1086
- restoreChannel("--overlay-x", prev.x);
1087
- restoreChannel("--overlay-y", prev.y);
1088
- restoreChannel("--overlay-w", prev.w);
1089
- restoreChannel("--overlay-h", prev.h);
1090
- if (overlay instanceof HTMLDialogElement && overlay.open) overlay.close();
1091
- else overlay.hidePopover?.();
412
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
413
+ enumerable: true,
414
+ configurable: true,
415
+ writable: true,
416
+ value
417
+ }) : obj[key] = value;
418
+ var __name = (target, value) => __defProp(target, "name", {
419
+ value,
420
+ configurable: true
421
+ });
422
+ var __decoratorStart = (base) => [
423
+ ,
424
+ ,
425
+ ,
426
+ __create(base?.[__knownSymbol("metadata")] ?? null)
427
+ ];
428
+ var __decoratorStrings = [
429
+ "class",
430
+ "method",
431
+ "getter",
432
+ "setter",
433
+ "accessor",
434
+ "field",
435
+ "value",
436
+ "get",
437
+ "set"
438
+ ];
439
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
440
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({
441
+ kind: __decoratorStrings[kind],
442
+ name,
443
+ metadata,
444
+ addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null))
445
+ });
446
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
447
+ var __runInitializers = (array, flags, self, value) => {
448
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
449
+ return value;
450
+ };
451
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
452
+ var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
453
+ var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
454
+ var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
455
+ var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : {
456
+ get [name]() {
457
+ return __privateGet(this, extra);
1092
458
  },
1093
- revert(keys) {
1094
- clearDrag();
1095
- for (const k of keys) restoreChannel(CHANNEL[k], prev[k]);
459
+ set [name](x) {
460
+ return __privateSet(this, extra, x);
1096
461
  }
1097
- };
1098
- }
1099
- /**
1100
- * The pointer loop for `target`: single-pointer capture, velocity tracking,
1101
- * text-selection suppression, and a non-passive `touchmove` block while a
1102
- * drag is live. It owns the "is a drag active" state and drives the engaged
1103
- * {@link Session} `move`/`release`/`cancel` on pointermove/up/cancel. The
1104
- * session owns the DOM writes (through the `io` it closed over); the
1105
- * recognizer knows nothing about channels.
1106
- */
1107
- function createGestureRecognizer(target, { canEngage, engage }) {
1108
- let active = null;
1109
- let pointer = null;
1110
- const onPointerDown = (event) => {
1111
- if (active) return;
1112
- if (!canEngage(event)) return;
1113
- const session = engage(event);
1114
- if (!session) return;
1115
- active = session;
1116
- const c = {
1117
- x: event.clientX,
1118
- y: event.clientY
1119
- };
1120
- pointer = {
1121
- start: { ...c },
1122
- prev: { ...c },
1123
- current: { ...c },
1124
- lastTime: event.timeStamp,
1125
- velocity: {
1126
- x: 0,
1127
- y: 0
1128
- }
1129
- };
1130
- target.style.userSelect = "none";
1131
- target.style.setProperty("-webkit-user-select", "none");
1132
- target.setPointerCapture?.(event.pointerId);
1133
- };
1134
- const onPointerMove = (event) => {
1135
- if (!active || !pointer) return;
1136
- const c = {
1137
- x: event.clientX,
1138
- y: event.clientY
1139
- };
1140
- pointer.velocity = updateVelocity(pointer, c, event.timeStamp);
1141
- pointer.prev = c;
1142
- pointer.lastTime = event.timeStamp;
1143
- pointer.current = c;
1144
- active.move(pointer);
1145
- };
1146
- const onPointerUp = (event) => {
1147
- if (!active || !pointer) return;
1148
- pointer.current = {
1149
- x: event.clientX,
1150
- y: event.clientY
1151
- };
1152
- const session = active;
1153
- active = null;
1154
- target.releasePointerCapture?.(event.pointerId);
1155
- session.release(pointer);
1156
- pointer = null;
1157
- };
1158
- const onPointerCancel = () => {
1159
- if (!active) return;
1160
- const session = active;
1161
- active = null;
1162
- pointer = null;
1163
- session.cancel();
1164
- };
1165
- const blockScroll = (event) => {
1166
- if (active) event.preventDefault();
1167
- };
1168
- target.addEventListener("pointerdown", onPointerDown);
1169
- target.addEventListener("pointermove", onPointerMove);
1170
- target.addEventListener("pointerup", onPointerUp);
1171
- target.addEventListener("pointercancel", onPointerCancel);
1172
- target.addEventListener("touchmove", blockScroll, { passive: false });
1173
- return { dispose() {
1174
- target.removeEventListener("pointerdown", onPointerDown);
1175
- target.removeEventListener("pointermove", onPointerMove);
1176
- target.removeEventListener("pointerup", onPointerUp);
1177
- target.removeEventListener("pointercancel", onPointerCancel);
1178
- target.removeEventListener("touchmove", blockScroll);
1179
- } };
1180
- }
1181
- //#endregion
1182
- //#region src/ui/overlay/resize-height-session.ts
1183
- /**
1184
- * Height resize — the block-axis edge handle (sheets). One edge moves toward
1185
- * the pointer; the opposite edge is pinned by the location shift, or held by
1186
- * the CSS clamp when docked. Below the lower bound the surface slides away via
1187
- * `--overlay-dy`. Self-contained: the height-axis wiring lives here.
1188
- */
1189
- function resizeHeightSession(snap, resizer, io, side) {
1190
- const startSize = snap.rect.height;
1191
- const { sign, anchorSign, docked } = edgeSetup({
1192
- axis: "block",
1193
- side,
1194
- rect: snap.rect,
1195
- constraint: snap.constraint,
1196
- dir: snap.dir
1197
- });
1198
- const hardMax = anchorSign > 0 ? snap.constraint.top + snap.constraint.height - snap.rect.top : snap.rect.bottom - snap.constraint.top;
1199
- const ax = {
1200
- axis: "height",
1201
- startSize,
1202
- min: 0,
1203
- max: hardMax
1204
- };
1205
- const anchorAt = (size) => anchor$1({
1206
- axis: "height",
1207
- center0: snap.center0,
1208
- constraint: snap.constraint,
1209
- anchorSign,
1210
- startSize,
1211
- size,
1212
- docked
1213
- });
1214
- return {
1215
- move(p) {
1216
- const target = startSize + sign * (p.current.y - p.start.y);
1217
- const [lo, hi] = resizer.bounds(ax);
1218
- const { size, slide } = edgeDrag({
1219
- target,
1220
- lo,
1221
- hi,
1222
- sign,
1223
- max: hardMax
1224
- });
1225
- const a = anchorAt(size);
1226
- io.sync({
1227
- frame: {
1228
- h: size,
1229
- ...a !== null ? { y: a } : {}
1230
- },
1231
- offset: { dy: slide }
1232
- });
1233
- },
1234
- release(p) {
1235
- const target = startSize + sign * (p.current.y - p.start.y);
1236
- const r = resizer.rest(ax, {
1237
- size: target,
1238
- velocity: -sign * p.velocity.y
1239
- });
1240
- if (r === null) return io.dismiss();
1241
- const a = anchorAt(r);
1242
- io.commit({
1243
- h: r,
1244
- ...a !== null ? { y: a } : {}
1245
- });
1246
- },
1247
- cancel() {
1248
- io.revert(["x", "y"]);
462
+ }, name));
463
+ k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
464
+ for (var i = decorators.length - 1; i >= 0; i--) {
465
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
466
+ if (k) {
467
+ ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
468
+ if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
469
+ if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
1249
470
  }
1250
- };
1251
- }
1252
- //#endregion
1253
- //#region src/ui/overlay/resize-session.ts
1254
- /** Corner resize: minimum width / height (free mode). */
1255
- const MIN_RESIZE_W = 240;
1256
- const MIN_RESIZE_H = 160;
1257
- /** Corner-resize signs + which physical corner the handle is on. */
1258
- function cornerSetup(args) {
1259
- const { block, inline, dir } = args;
1260
- return {
1261
- signX: (inline === "end" ? 1 : -1) * dir,
1262
- signY: block === "end" ? 1 : -1,
1263
- handleRight: inline === "end" === (dir === 1)
1264
- };
1265
- }
1266
- /**
1267
- * Corner-resize bounds: the opposite corner stays anchored, so the room
1268
- * toward the handle-side constraint edges caps the size (the grip never
1269
- * leaves the constraint). The strategy bounds the width; the height is a
1270
- * free clamp.
1271
- */
1272
- function cornerBounds(args) {
1273
- const { rect, constraint, block, handleRight } = args;
1274
- const maxW = handleRight ? constraint.left + constraint.width - rect.left : rect.right - constraint.left;
1275
- return {
1276
- maxW,
1277
- maxH: block === "end" ? constraint.top + constraint.height - rect.top : rect.bottom - constraint.top,
1278
- hardMin: MIN_RESIZE_W,
1279
- hardMax: maxW
1280
- };
1281
- }
1282
- /**
1283
- * Corner grip — free 2D resize, opposite corner anchored. The two axes are
1284
- * asymmetric: the width follows the strategy (and alone decides dismissal),
1285
- * the height is a free clamp `[MIN_RESIZE_H, maxH]` that never dismisses.
1286
- */
1287
- function resizeSession(snap, resizer, io, block, inline) {
1288
- const { signX, signY, handleRight } = cornerSetup({
1289
- block,
1290
- inline,
1291
- dir: snap.dir
1292
- });
1293
- const { maxH, hardMin, hardMax } = cornerBounds({
1294
- rect: snap.rect,
1295
- constraint: snap.constraint,
1296
- block,
1297
- handleRight
1298
- });
1299
- const startW = snap.rect.width;
1300
- const startH = snap.rect.height;
1301
- const ax = {
1302
- axis: "width",
1303
- startSize: startW,
1304
- min: hardMin,
1305
- max: hardMax
1306
- };
1307
- const frameFor = (w, h) => ({
1308
- w,
1309
- h,
1310
- x: anchor$1({
1311
- axis: "width",
1312
- center0: snap.center0,
1313
- constraint: snap.constraint,
1314
- anchorSign: signX,
1315
- startSize: startW,
1316
- size: w,
1317
- docked: false
1318
- }),
1319
- y: anchor$1({
1320
- axis: "height",
1321
- center0: snap.center0,
1322
- constraint: snap.constraint,
1323
- anchorSign: signY,
1324
- startSize: startH,
1325
- size: h,
1326
- docked: false
1327
- })
1328
- });
1329
- return {
1330
- move(p) {
1331
- const [lo, hi] = resizer.bounds(ax);
1332
- const { size: w, slide: dx } = slidePastRoom(resist(startW + signX * (p.current.x - p.start.x), lo, hi), hardMax, signX);
1333
- const { size: h, slide: dy } = slidePastRoom(resist(startH + signY * (p.current.y - p.start.y), MIN_RESIZE_H, maxH), maxH, signY);
1334
- io.sync({
1335
- frame: frameFor(w, h),
1336
- offset: {
1337
- dx,
1338
- dy
1339
- }
471
+ it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : {
472
+ get: desc.get,
473
+ set: desc.set
474
+ } : target, ctx), done._ = 1;
475
+ if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
476
+ else if (typeof it !== "object" || it === null) __typeError("Object expected");
477
+ else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
478
+ }
479
+ return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
480
+ };
481
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
482
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
483
+ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError("Cannot use the \"in\" operator on this value") : member.has(obj);
484
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
485
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
486
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
487
+ var _h_dec, _w_dec, _y_dec, _x_dec = [reactive()], _init;
488
+ _y_dec = [reactive()], _w_dec = [reactive()], _h_dec = [reactive()];
489
+ var PartialBox = class {
490
+ constructor(x = void 0, y = void 0, w = void 0, h = void 0) {
491
+ __publicField(this, "x", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
492
+ __publicField(this, "y", __runInitializers(_init, 12, this)), __runInitializers(_init, 15, this);
493
+ __publicField(this, "w", __runInitializers(_init, 16, this)), __runInitializers(_init, 19, this);
494
+ __publicField(this, "h", __runInitializers(_init, 20, this)), __runInitializers(_init, 23, this);
495
+ this.x = x;
496
+ this.y = y;
497
+ this.w = w;
498
+ this.h = h;
499
+ }
500
+ };
501
+ _init = __decoratorStart(null);
502
+ __decorateElement(_init, 5, "x", _x_dec, PartialBox);
503
+ __decorateElement(_init, 5, "y", _y_dec, PartialBox);
504
+ __decorateElement(_init, 5, "w", _w_dec, PartialBox);
505
+ __decorateElement(_init, 5, "h", _h_dec, PartialBox);
506
+ __decoratorMetadata(_init, PartialBox);
507
+ var TransformableBox = class {
508
+ transform;
509
+ displacement;
510
+ constructor(transform = new PartialBox()) {
511
+ this.transform = transform;
512
+ this.displacement = new Displacement(this);
513
+ }
514
+ get x() {
515
+ return (this.transform.x ?? 0) + (this.displacement.x ?? 0);
516
+ }
517
+ set x(value) {
518
+ this.transform.x = value;
519
+ }
520
+ get y() {
521
+ return (this.transform.y ?? 0) + (this.displacement.y ?? 0);
522
+ }
523
+ set y(value) {
524
+ this.transform.y = value;
525
+ }
526
+ get w() {
527
+ return (this.transform.w ?? 0) + (this.displacement.w ?? 0);
528
+ }
529
+ set w(value) {
530
+ this.transform.w = value;
531
+ }
532
+ get h() {
533
+ return (this.transform.h ?? 0) + (this.displacement.h ?? 0);
534
+ }
535
+ set h(value) {
536
+ this.transform.h = value;
537
+ }
538
+ };
539
+ var Displacement = class extends PartialBox {
540
+ box;
541
+ constructor(box) {
542
+ super();
543
+ this.box = box;
544
+ }
545
+ apply() {
546
+ batch(() => {
547
+ this.box.transform.x = (this.box.transform.x ?? 0) + (this.x ?? 0);
548
+ this.box.transform.y = (this.box.transform.y ?? 0) + (this.y ?? 0);
549
+ this.box.transform.w = (this.box.transform.w ?? 0) + (this.w ?? 0);
550
+ this.box.transform.h = (this.box.transform.h ?? 0) + (this.h ?? 0);
551
+ this.clear();
552
+ });
553
+ }
554
+ clear() {
555
+ batch(() => {
556
+ this.x = void 0;
557
+ this.y = void 0;
558
+ this.w = void 0;
559
+ this.h = void 0;
560
+ });
561
+ }
562
+ };
563
+ var OverlayBox = class extends TransformableBox {
564
+ element;
565
+ #rect;
566
+ constructor(element) {
567
+ super();
568
+ this.element = element;
569
+ this.#rect = createElementRect(element);
570
+ element.style.setProperty("top", "0");
571
+ element.style.setProperty("left", "0");
572
+ element.style.setProperty("translate", "calc(var(--x, 0px) + var(--dx, 0px) + var(--_ex, 0px)) calc(var(--y, 0px) + var(--dy, 0px) + var(--_ey, 0px))");
573
+ const [, stop] = scope(() => {
574
+ effect(() => {
575
+ this.#project("--x", this.transform.x);
1340
576
  });
1341
- },
1342
- release(p) {
1343
- const w = resizer.rest(ax, {
1344
- size: startW + signX * (p.current.x - p.start.x),
1345
- velocity: -p.velocity.x * signX
577
+ effect(() => {
578
+ this.#project("--y", this.transform.y);
1346
579
  });
1347
- if (w === null) return io.dismiss();
1348
- const h = clamp(startH + signY * (p.current.y - p.start.y), MIN_RESIZE_H, maxH);
1349
- io.commit(frameFor(w, h));
1350
- },
1351
- cancel() {
1352
- io.revert([
1353
- "x",
1354
- "y",
1355
- "w",
1356
- "h"
1357
- ]);
1358
- }
1359
- };
1360
- }
1361
- //#endregion
1362
- //#region src/ui/overlay/resize-width-session.ts
1363
- /**
1364
- * Width resize — the inline-axis edge handle (drawers). Mirror of
1365
- * `resizeHeightSession` on the width axis (the RTL sign flip is folded into
1366
- * `edgeSetup`); the opposite edge is pinned, and below the lower bound the
1367
- * surface slides via `--overlay-dx`. Self-contained: the width-axis wiring.
1368
- */
1369
- function resizeWidthSession(snap, resizer, io, side) {
1370
- const startSize = snap.rect.width;
1371
- const { sign, anchorSign, docked } = edgeSetup({
1372
- axis: "inline",
1373
- side,
1374
- rect: snap.rect,
1375
- constraint: snap.constraint,
1376
- dir: snap.dir
1377
- });
1378
- const hardMax = anchorSign > 0 ? snap.constraint.left + snap.constraint.width - snap.rect.left : snap.rect.right - snap.constraint.left;
1379
- const ax = {
1380
- axis: "width",
1381
- startSize,
1382
- min: 0,
1383
- max: hardMax
1384
- };
1385
- const anchorAt = (size) => anchor$1({
1386
- axis: "width",
1387
- center0: snap.center0,
1388
- constraint: snap.constraint,
1389
- anchorSign,
1390
- startSize,
1391
- size,
1392
- docked
1393
- });
1394
- return {
1395
- move(p) {
1396
- const target = startSize + sign * (p.current.x - p.start.x);
1397
- const [lo, hi] = resizer.bounds(ax);
1398
- const { size, slide } = edgeDrag({
1399
- target,
1400
- lo,
1401
- hi,
1402
- sign,
1403
- max: hardMax
580
+ effect(() => {
581
+ this.#project("--w", this.transform.w, this.displacement.w);
1404
582
  });
1405
- const a = anchorAt(size);
1406
- io.sync({
1407
- frame: {
1408
- w: size,
1409
- ...a !== null ? { x: a } : {}
1410
- },
1411
- offset: { dx: slide }
583
+ effect(() => {
584
+ this.#project("--h", this.transform.h, this.displacement.h);
1412
585
  });
1413
- },
1414
- release(p) {
1415
- const target = startSize + sign * (p.current.x - p.start.x);
1416
- const r = resizer.rest(ax, {
1417
- size: target,
1418
- velocity: -sign * p.velocity.x
586
+ effect(() => {
587
+ this.#project("--dx", this.displacement.x);
1419
588
  });
1420
- if (r === null) return io.dismiss();
1421
- const a = anchorAt(r);
1422
- io.commit({
1423
- w: r,
1424
- ...a !== null ? { x: a } : {}
589
+ effect(() => {
590
+ this.#project("--dy", this.displacement.y);
1425
591
  });
1426
- },
1427
- cancel() {
1428
- io.revert(["x", "y"]);
592
+ });
593
+ this.dispose = () => {
594
+ stop();
595
+ this.#rect[Symbol.dispose]();
596
+ };
597
+ }
598
+ /** Write a size channel, or unset it when the axis is AUTO (NaN) so the
599
+ * element falls back to content sizing (`var(--w, auto)`). */
600
+ #project(name, ...value) {
601
+ if (value.every((v) => v === void 0 || Number.isNaN(v))) {
602
+ this.element.style.removeProperty(name);
603
+ return;
1429
604
  }
1430
- };
1431
- }
605
+ const sum = value.reduce((a, b) => a + (b ?? 0), 0);
606
+ this.element.style.setProperty(name, `${sum}px`);
607
+ }
608
+ get w() {
609
+ return this.#rect().width;
610
+ }
611
+ set w(value) {
612
+ this.transform.w = value;
613
+ }
614
+ get h() {
615
+ return this.#rect().height;
616
+ }
617
+ set h(value) {
618
+ this.transform.h = value;
619
+ }
620
+ get x() {
621
+ return this.#rect().left;
622
+ }
623
+ set x(value) {
624
+ this.transform.x = value;
625
+ }
626
+ get y() {
627
+ return this.#rect().top;
628
+ }
629
+ set y(value) {
630
+ this.transform.y = value;
631
+ }
632
+ /** The element's computed direction, read at call time. */
633
+ get direction() {
634
+ return getComputedStyle(this.element).direction === "rtl" ? "rtl" : "ltr";
635
+ }
636
+ dispose;
637
+ [Symbol.dispose]() {
638
+ this.dispose();
639
+ }
640
+ };
1432
641
  //#endregion
1433
642
  //#region src/ui/overlay/gestures.ts
1434
- /** The size a `data-resize` value persists: block edges drive the height,
1435
- * everything else (inline edges, corners) the width. */
1436
- const resizeKey = (parsed) => parsed.block !== null && parsed.inline === null ? "h" : "w";
1437
- /** Build the drag session for an engaged zone. */
1438
- function selectSession(key, parsed, snapshot, resizer, move, io) {
1439
- if (key === "block") return resizeHeightSession(snapshot, resizer, io, parsed.block);
1440
- if (key === "inline") return resizeWidthSession(snapshot, resizer, io, parsed.inline);
1441
- if (key === "resize") return resizeSession(snapshot, resizer, io, parsed.block, parsed.inline);
1442
- return moveSession(snapshot, move, io);
1443
- }
1444
- /**
1445
- * Opt-in pointer gestures for `.x-overlay`, dispatched by the gesture
1446
- * attributes (structure stays in markup; policy is options):
1447
- *
1448
- * - An edge word (`block-*` / `inline-*`) is a whole-surface size drag
1449
- * along that axis block drags the height (sheets), inline the width
1450
- * (drawers), `:dir(rtl)` flips the inline sign. The side names the
1451
- * handle; the opposite edge stays put.
1452
- * - A corner word (`start-start` / … — block side first) is a desktop-
1453
- * window resize from a square zone at that corner, anchored at the
1454
- * opposite corner so the surface never grows past the constraint. The
1455
- * width follows the `resize` strategy; the height is a free clamp.
1456
- * - `data-draggable` moves the surface in x/y from the top strip,
1457
- * rubber-banding at the edges; flinging it off the constraint dismisses
1458
- * (when `dismissible`).
1459
- *
1460
- * Layered for testability: `gesture-model` owns the pure mode reducers +
1461
- * math, and `overlay-dom` owns all DOM contact (pointer plumbing + channel
1462
- * I/O). This function is the wiring — it picks a pure `Session` from
1463
- * `detectEngagement` and adapts it to the recognizer through the io.
1464
- * The `resize` strategy (`freeResize` by default, or `detents`) decides the
1465
- * rested size, written to the public `--overlay-w`/`--overlay-h` channels;
1466
- * CSS renders and animates them. JS never touches `translate`/`top`/`left`.
1467
- *
1468
- * Registers its cleanup with the current scope (`onCleanup`) and also
1469
- * returns it as `dispose` / `Symbol.dispose`.
1470
- *
1471
- * @example
1472
- * ```ts
1473
- * import { constraint, createOverlayGestures, detents } from "elements-kit/ui/overlay";
1474
- *
1475
- * const el = document.querySelector("dialog.x-overlay")!;
1476
- * createOverlayGestures(el, { resize: detents(constraint(), [0.25, 0.6, 0.9]) });
1477
- * el.addEventListener("resizechange", (e) => console.log(e.detail));
1478
- * ```
1479
- */
1480
- function createOverlayGestures(overlay, options) {
1481
- const io = createFrameIO(overlay, {
1482
- strategy: options?.resize ?? freeResize(),
1483
- dismissible: options?.dismissible ?? true,
1484
- velocityThreshold: options?.velocityThreshold ?? .5
1485
- });
1486
- const canEngage = (event) => {
1487
- const resize = overlay.getAttribute("data-resize") ?? "";
1488
- const draggable = overlay.hasAttribute("data-draggable");
1489
- if (!resize && !draggable) return false;
1490
- if (overlay.getAttribute("data-anchor") === "element") return false;
1491
- const target = event.target;
1492
- if (target?.closest("button, a, label, input, select, textarea, [contenteditable]")) return false;
1493
- for (let el = target; el !== null && el !== overlay; el = el.parentElement) if (el.scrollTop > 0) return false;
1494
- return true;
1495
- };
1496
- const engage = (event) => {
1497
- const parsed = parseResize(overlay.getAttribute("data-resize") ?? "");
1498
- const draggable = overlay.hasAttribute("data-draggable");
1499
- const { snapshot, resizer, move } = io.engage();
1500
- const key = detectEngagement({
1501
- ...parsed,
1502
- draggable,
1503
- rect: snapshot.rect,
1504
- pointer: {
1505
- x: event.clientX,
1506
- y: event.clientY
1507
- },
1508
- dir: snapshot.dir
1509
- });
1510
- if (!key) return null;
1511
- return selectSession(key, parsed, snapshot, resizer, move, io);
1512
- };
1513
- const recognizer = createGestureRecognizer(overlay, {
1514
- canEngage,
1515
- engage
1516
- });
1517
- const dispose = () => recognizer.dispose();
1518
- onCleanup(dispose);
1519
- return {
1520
- resize(size) {
1521
- const key = resizeKey(parseResize(overlay.getAttribute("data-resize") ?? ""));
1522
- io.commit({ [key]: size });
1523
- },
1524
- dispose,
1525
- [Symbol.dispose]: dispose
1526
- };
643
+ var gestures_exports = /* @__PURE__ */ __exportAll({
644
+ detent: () => detent,
645
+ nearest: () => nearest,
646
+ rubber: () => rubber,
647
+ snap: () => snap
648
+ });
649
+ /** iOS rubber-band curve: sub-linear overshoot, asymptotes to
650
+ * `dimension * constant` pull but never escape. */
651
+ function resist(overshoot, dimension, constant) {
652
+ return overshoot * dimension * constant / (dimension + constant * overshoot);
653
+ }
654
+ /** Elastic resistance past `[min, max]`. The true value stays in `Motion`, so
655
+ * release settles back cleanly. `dimension` = axis extent; `constant` = iOS
656
+ * tension (higher = looser). */
657
+ function rubber(min, max, dimension, constant = .55) {
658
+ return (x) => x < min ? min - resist(min - x, dimension, constant) : x > max ? max + resist(x - max, dimension, constant) : x;
659
+ }
660
+ /** The nearest detent to `value`. */
661
+ function nearest(value, points) {
662
+ return points.reduce((a, b) => Math.abs(b - value) < Math.abs(a - value) ? b : a);
663
+ }
664
+ /** Magnetic pull toward the nearest detent during a drag (0 = free, 1 = snap). */
665
+ function detent(points, strength = 0) {
666
+ return (x) => x + (nearest(x, points) - x) * strength;
667
+ }
668
+ /** Release target: project by velocity (`reach` ms of carry), then nearest detent. */
669
+ function snap(value, velocity, points, reach = 150) {
670
+ return nearest(value + velocity * reach, points);
1527
671
  }
1528
672
  //#endregion
1529
- //#region src/ui/overlay/resize.ts
1530
- /** Chain settle effects after the strategy's rest. */
1531
- function withEffects(base, effects) {
1532
- if (!effects.length) return base;
1533
- return {
1534
- bounds: base.bounds?.bind(base),
1535
- rest(ctx) {
1536
- let rested = base.rest(ctx);
1537
- if (rested === null) return null;
1538
- for (const e of effects) {
1539
- if (!e.settle) continue;
1540
- const s = e.settle(rested, ctx.velocity, [ctx.min, ctx.max]);
1541
- if (s === null) return null;
1542
- rested = s;
1543
- }
1544
- return rested;
1545
- }
1546
- };
1547
- }
1548
- function resize(target, space, ...effects) {
1549
- const io = createFrameIO(target, {
1550
- strategy: withEffects(space ?? freeResize(), effects),
1551
- dismissible: true,
1552
- velocityThreshold: .5
1553
- });
1554
- const buildSession = () => {
1555
- const parsed = parseResize(target.getAttribute("data-resize") ?? "");
1556
- const { snapshot, resizer } = io.engage();
1557
- if (parsed.block !== null && parsed.inline !== null) return resizeSession(snapshot, resizer, io, parsed.block, parsed.inline);
1558
- if (parsed.block !== null) return resizeHeightSession(snapshot, resizer, io, parsed.block);
1559
- if (parsed.inline !== null) return resizeWidthSession(snapshot, resizer, io, parsed.inline);
1560
- return null;
1561
- };
1562
- const attachments = [];
1563
- const attach = (handle) => {
1564
- const recognizer = createGestureRecognizer(handle, {
1565
- canEngage: () => true,
1566
- engage: () => buildSession()
1567
- });
1568
- attachments.push(recognizer.dispose);
1569
- return { dispose: recognizer.dispose };
1570
- };
1571
- const update = (size) => {
1572
- const parsed = parseResize(target.getAttribute("data-resize") ?? "");
1573
- const key = parsed.block !== null && parsed.inline === null ? "h" : "w";
1574
- io.commit({ [key]: size });
1575
- };
1576
- const dispose = () => {
1577
- for (const detach of attachments.splice(0)) detach();
1578
- };
1579
- onCleanup(dispose);
1580
- return {
1581
- update,
1582
- attach,
1583
- dispose,
1584
- [Symbol.dispose]: dispose
1585
- };
1586
- }
1587
- //#endregion
1588
- //#region src/ui/overlay/rubber.ts
1589
- /**
1590
- * Edge resistance — values past the bounds move at a fraction of the
1591
- * pointer (the iOS rubber band). Pure `resist()` under the hood; no
1592
- * channels involved, so it applies to anything a service moves.
1593
- */
1594
- function rubber() {
1595
- return { during: (value, [lo, hi]) => resist(value, lo, hi) };
1596
- }
673
+ //#region src/ui/overlay/motion.ts
674
+ var Motion = class {
675
+ #initial;
676
+ #time;
677
+ #value;
678
+ #delta;
679
+ #velocity;
680
+ constructor(value = 0) {
681
+ this.#initial = value;
682
+ this.#time = performance.now();
683
+ this.#value = signal(value);
684
+ this.#delta = signal(0);
685
+ this.#velocity = signal(0);
686
+ }
687
+ get value() {
688
+ return this.#value();
689
+ }
690
+ set value(value) {
691
+ const now = performance.now();
692
+ const prev = this.#value();
693
+ const dt = now - this.#time;
694
+ const delta = value - prev;
695
+ this.#delta(delta);
696
+ if (dt > 0) this.#velocity(delta / dt);
697
+ this.#value(value);
698
+ this.#time = now;
699
+ }
700
+ get delta() {
701
+ return this.#delta();
702
+ }
703
+ get velocity() {
704
+ return this.#velocity();
705
+ }
706
+ /** How far the value has moved from its initial (seed) — reactive.
707
+ * The displacement column: `value − initial`, starts at 0. */
708
+ get displacement() {
709
+ return this.#value() - this.#initial;
710
+ }
711
+ /** Nudge the value by a displacement — delta/velocity fall out of the setter. */
712
+ move(delta) {
713
+ this.value = this.#value() + delta;
714
+ }
715
+ abort(initial) {
716
+ if (initial !== void 0) this.#initial = initial;
717
+ this.#time = performance.now();
718
+ this.#value(this.#initial);
719
+ this.#delta(0);
720
+ this.#velocity(0);
721
+ }
722
+ };
1597
723
  //#endregion
1598
- export { PROJECTION_MS, anchor, areaToPlacement, clamp, closestDetent, confine, constraint, createOverlayGestures, detents, draggable, freeResize, resize, resolveConstraint, resolveVarPx, rubber };
724
+ export { ElementBox, gestures_exports as Gestures, Motion, MutableRegion, OverlayBox, PositionArea, WINDOW_BOX, WindowBox, anchor_length, inset, placeAxis };