elements-kit 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -19
- package/dist/attributes.d.mts +1 -1
- package/dist/await.d.mts +6 -5
- package/dist/await.mjs +0 -36
- package/dist/{types-ncUK9rki.d.mts → children-s3nFjpLA.d.mts} +125 -227
- package/dist/custom-elements-CdpilMDu.d.mts +87 -0
- package/dist/custom-elements.d.mts +2 -2
- package/dist/{element-D3om9yA6.mjs → element-Di0PYFX1.mjs} +15 -38
- package/dist/for.d.mts +9 -4
- package/dist/for.mjs +1 -1
- package/dist/{fragment-ROPRBJQd.mjs → fragment-Bvs1sGu1.mjs} +3 -3
- package/dist/hydrate/index.mjs +1 -1
- package/dist/{hydrate-6Qx4n4Su.mjs → hydrate-CHomBRY9.mjs} +4 -4
- package/dist/integrations/astro-client.mjs +2 -2
- package/dist/integrations/astro-server.mjs +2 -2
- package/dist/integrations/astro-slots.d.mts +6 -5
- package/dist/integrations/astro-slots.mjs +9 -8
- package/dist/integrations/react.d.mts +1 -1
- package/dist/integrations/react.mjs +1 -1
- package/dist/jsx-runtime/index.d.mts +2 -2
- package/dist/jsx-runtime/index.mjs +2 -2
- package/dist/polyfill-BVNd6ogU.d.mts +9 -0
- package/dist/render.mjs +1 -1
- package/dist/server/index.mjs +1 -1
- package/dist/{server-iFSng50i.mjs → server-CTkyXTV6.mjs} +2 -2
- package/dist/signals/index.d.mts +1 -1
- package/dist/slot.d.mts +83 -2
- package/dist/slot.mjs +179 -2
- package/dist/ui/badge/badge.css +5 -5
- package/dist/ui/button/button.css +15 -15
- package/dist/ui/card/card.css +2 -2
- package/dist/ui/group/group.css +61 -0
- package/dist/ui/marketing/marketing.css +5 -3
- package/dist/ui/otp-input/index.d.mts +132 -0
- package/dist/ui/otp-input/index.mjs +328 -0
- package/dist/ui/otp-input/otp-input.css +157 -0
- package/dist/ui/overlay/index.css +52 -4
- package/dist/ui/overlay/index.d.mts +237 -63
- package/dist/ui/overlay/index.mjs +658 -66
- package/dist/ui/overlay/overlay.css +58 -2
- package/dist/ui/styles/index.css +664 -0
- package/dist/ui/styles/radius.css +7 -7
- package/dist/ui/styles/unset.css +212 -210
- package/dist/utilities/active-element.d.mts +1 -1
- package/dist/utilities/async.d.mts +1 -1
- package/dist/utilities/debounced.d.mts +1 -1
- package/dist/utilities/dom-lifecycle.bench.mjs +2 -2
- package/dist/utilities/element-rect.d.mts +1 -1
- package/dist/utilities/element-scroll.d.mts +1 -1
- package/dist/utilities/event-driven.d.mts +1 -1
- package/dist/utilities/event-listener.d.mts +1 -1
- package/dist/utilities/focus-within.d.mts +1 -1
- package/dist/utilities/hover.d.mts +1 -1
- package/dist/utilities/interval.d.mts +1 -1
- package/dist/utilities/location.d.mts +1 -1
- package/dist/utilities/media-devices.d.mts +1 -1
- package/dist/utilities/media-player.d.mts +1 -1
- package/dist/utilities/media-query.d.mts +1 -1
- package/dist/utilities/network.d.mts +1 -1
- package/dist/utilities/orientation.d.mts +1 -1
- package/dist/utilities/previous.d.mts +1 -1
- package/dist/utilities/promise.d.mts +1 -1
- package/dist/utilities/routing.d.mts +1 -1
- package/dist/utilities/search-params.d.mts +1 -1
- package/dist/utilities/storage.d.mts +1 -1
- package/dist/utilities/throttled.d.mts +1 -1
- package/dist/utilities/timeout.d.mts +1 -1
- package/dist/utilities/window-focus.d.mts +1 -1
- package/dist/utilities/window-size.d.mts +1 -1
- package/package.json +10 -1
- package/dist/custom-elements-DK5VUcWA.d.mts +0 -55
- package/dist/slot-BFWXeFRl.d.mts +0 -77
- package/dist/slot-Dvf30O0B.mjs +0 -138
- /package/dist/{attributes-3r7Diua4.d.mts → attributes-DILeh3-s.d.mts} +0 -0
- /package/dist/{scope-zbEG299F.mjs → scope-C1vELXqP.mjs} +0 -0
|
@@ -1,19 +1,46 @@
|
|
|
1
1
|
import { _ as onCleanup, u as effect } from "../../lib-DYypKhxk.mjs";
|
|
2
2
|
import "../../signals/index.mjs";
|
|
3
3
|
import { createElementRect } from "../../utilities/element-rect.mjs";
|
|
4
|
-
|
|
4
|
+
import { arrow, autoUpdate, computePosition, flip, offset, shift } from "@floating-ui/dom";
|
|
5
|
+
//#region src/ui/overlay/constraint.ts
|
|
6
|
+
/**
|
|
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).
|
|
11
|
+
*/
|
|
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]());
|
|
22
|
+
return rect;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
top: () => source.top,
|
|
26
|
+
left: () => source.left,
|
|
27
|
+
width: () => source.width,
|
|
28
|
+
height: () => source.height
|
|
29
|
+
};
|
|
30
|
+
}
|
|
5
31
|
/**
|
|
6
32
|
* Resolves a custom property holding a length to pixels. Plain `px` values
|
|
7
33
|
* parse directly; anything else (`svh` / `calc()` / fractions of the
|
|
8
|
-
* constraint) resolves natively by measuring a hidden probe.
|
|
34
|
+
* constraint) resolves natively by measuring a hidden probe. `fallback` is
|
|
35
|
+
* a CSS length expression used when the property is unset.
|
|
9
36
|
*/
|
|
10
|
-
function resolveVarPx(overlay, name, axis) {
|
|
37
|
+
function resolveVarPx(overlay, name, axis, fallback) {
|
|
11
38
|
const raw = getComputedStyle(overlay).getPropertyValue(name).trim();
|
|
12
39
|
if (/^-?\d+(\.\d+)?px$/.test(raw)) return parseFloat(raw);
|
|
13
40
|
const probe = document.createElement("div");
|
|
14
41
|
probe.style.position = "absolute";
|
|
15
42
|
probe.style.visibility = "hidden";
|
|
16
|
-
probe.style[axis] = `var(${name})`;
|
|
43
|
+
probe.style[axis] = fallback ? `var(${name}, ${fallback})` : `var(${name})`;
|
|
17
44
|
overlay.appendChild(probe);
|
|
18
45
|
const px = probe.getBoundingClientRect()[axis];
|
|
19
46
|
probe.remove();
|
|
@@ -29,14 +56,14 @@ function resolveConstraint(overlay) {
|
|
|
29
56
|
};
|
|
30
57
|
}
|
|
31
58
|
/**
|
|
32
|
-
* Confines an overlay to a
|
|
33
|
-
* `--overlay-constraint-*` variables.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* from those variables, so the overlay re-clamps when the rect changes.
|
|
59
|
+
* Confines an overlay to a {@link Region} by syncing it into the
|
|
60
|
+
* `--overlay-constraint-*` variables. Every location clamp and gesture
|
|
61
|
+
* bound derives from those variables, so the overlay re-clamps when the
|
|
62
|
+
* region changes.
|
|
37
63
|
*
|
|
38
|
-
* Caveat:
|
|
39
|
-
* without resizing (e.g. page scroll) does not
|
|
64
|
+
* Caveat: an element region observes size changes (`ResizeObserver`) — a
|
|
65
|
+
* container that moves without resizing (e.g. page scroll) does not
|
|
66
|
+
* retrigger the sync.
|
|
40
67
|
*
|
|
41
68
|
* Registers its cleanup with the current scope (`onCleanup`) and also
|
|
42
69
|
* returns it as `dispose` / `Symbol.dispose`; disposing removes the
|
|
@@ -44,24 +71,21 @@ function resolveConstraint(overlay) {
|
|
|
44
71
|
*
|
|
45
72
|
* @example
|
|
46
73
|
* ```ts
|
|
47
|
-
* import {
|
|
74
|
+
* import { constraint, confine } from "elements-kit/ui/overlay";
|
|
48
75
|
*
|
|
49
76
|
* const panel = document.querySelector("dialog.x-overlay")!;
|
|
50
|
-
*
|
|
77
|
+
* confine(panel, constraint(document.querySelector("main")!));
|
|
51
78
|
* ```
|
|
52
79
|
*/
|
|
53
|
-
function
|
|
54
|
-
const owned = source instanceof Element;
|
|
55
|
-
const rect = owned ? createElementRect(source) : source;
|
|
80
|
+
function confine(overlay, region) {
|
|
56
81
|
const stop = effect(() => {
|
|
57
|
-
overlay.style.setProperty("--overlay-constraint-top", `${
|
|
58
|
-
overlay.style.setProperty("--overlay-constraint-left", `${
|
|
59
|
-
overlay.style.setProperty("--overlay-constraint-width", `${
|
|
60
|
-
overlay.style.setProperty("--overlay-constraint-height", `${
|
|
82
|
+
overlay.style.setProperty("--overlay-constraint-top", `${region.top()}px`);
|
|
83
|
+
overlay.style.setProperty("--overlay-constraint-left", `${region.left()}px`);
|
|
84
|
+
overlay.style.setProperty("--overlay-constraint-width", `${region.width()}px`);
|
|
85
|
+
overlay.style.setProperty("--overlay-constraint-height", `${region.height()}px`);
|
|
61
86
|
});
|
|
62
87
|
const dispose = () => {
|
|
63
88
|
stop();
|
|
64
|
-
if (owned) rect[Symbol.dispose]();
|
|
65
89
|
overlay.style.removeProperty("--overlay-constraint-top");
|
|
66
90
|
overlay.style.removeProperty("--overlay-constraint-left");
|
|
67
91
|
overlay.style.removeProperty("--overlay-constraint-width");
|
|
@@ -74,11 +98,368 @@ function constrainOverlay(overlay, source) {
|
|
|
74
98
|
};
|
|
75
99
|
}
|
|
76
100
|
//#endregion
|
|
101
|
+
//#region src/ui/overlay/anchor.ts
|
|
102
|
+
/** The same compound gate as the index.css ANCHORED MODE block — both
|
|
103
|
+
* halves or neither, so the CSS and JS tiers can never disagree. */
|
|
104
|
+
const nativeAnchorSupport = () => typeof CSS !== "undefined" && CSS.supports("anchor-name: --x") && CSS.supports("position-area: block-end");
|
|
105
|
+
/** Measure whether the chain rule actually lands on the anchor's VISUAL
|
|
106
|
+
* box — `CSS.supports` is a parser check, and Firefox 151 passes it
|
|
107
|
+
* while resolving `anchor()` against the pre-transform layout box (a
|
|
108
|
+
* `translate`d trigger pins half a box off). The probe host is
|
|
109
|
+
* deliberately translated to catch exactly that. Inconclusive without a
|
|
110
|
+
* layout engine (tests) — then trust the declared support. */
|
|
111
|
+
let chainMeasured;
|
|
112
|
+
function probeChain() {
|
|
113
|
+
const host = document.createElement("div");
|
|
114
|
+
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";
|
|
115
|
+
const pin = document.createElement("div");
|
|
116
|
+
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)";
|
|
117
|
+
document.body.append(host, pin);
|
|
118
|
+
const hostRect = host.getBoundingClientRect();
|
|
119
|
+
const rect = pin.getBoundingClientRect();
|
|
120
|
+
host.remove();
|
|
121
|
+
pin.remove();
|
|
122
|
+
if (Math.abs(hostRect.top - 85) > 1) return true;
|
|
123
|
+
return Math.abs(rect.top - 85) < 1 && Math.abs(rect.left - 70) < 1 && Math.abs(rect.width - 40) < 1;
|
|
124
|
+
}
|
|
125
|
+
/** The follow pin needs more than the placement gate — the chain rule
|
|
126
|
+
* mirrors the followed box with `anchor()`/`anchor-size()`. A browser
|
|
127
|
+
* that places but can't chain falls back to the JS rect copy. */
|
|
128
|
+
const chainSupport = () => {
|
|
129
|
+
if (!nativeAnchorSupport() || !CSS.supports("top: anchor(top)") || !CSS.supports("width: anchor-size(width)")) return false;
|
|
130
|
+
chainMeasured ??= probeChain();
|
|
131
|
+
return chainMeasured;
|
|
132
|
+
};
|
|
133
|
+
/** While the Floating UI loop tracks, geometry writes must land
|
|
134
|
+
* instantly — but ONLY geometry. Enter/exit (opacity, scale) and
|
|
135
|
+
* close (display) keep transitioning, so `@starting-style` still plays. */
|
|
136
|
+
const TRACKING_TRANSITIONS = "opacity, scale, display";
|
|
137
|
+
let anchorNames = 0;
|
|
138
|
+
/**
|
|
139
|
+
* Maps a `position-area` value to the Floating UI placement. Spanning
|
|
140
|
+
* toward an edge leaves the box flush with the *opposite* edge, so
|
|
141
|
+
* `span-*-end` is a `-start` alignment (and vice versa) — Floating UI
|
|
142
|
+
* resolves `-start`/`-end` logically, so the span tokens need no dir
|
|
143
|
+
* check; only the physical inline sides do. Unknown values fall back to
|
|
144
|
+
* `bottom` (the CSS default, `block-end`).
|
|
145
|
+
*/
|
|
146
|
+
function areaToPlacement(area, rtl = false) {
|
|
147
|
+
const tokens = area.trim().split(/\s+/);
|
|
148
|
+
const main = tokens.find((t) => !t.startsWith("span-"));
|
|
149
|
+
const span = tokens.find((t) => t.startsWith("span-")) ?? "";
|
|
150
|
+
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" : ""}`;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Gives `overlay` its anchor element and returns it. The overlay follows
|
|
154
|
+
* the anchor for life; the anchor follows `follow` (element or rect)
|
|
155
|
+
* until something moves it. Pass the returned element to `draggable()`
|
|
156
|
+
* to make the composition tearable — dragging moves the anchor.
|
|
157
|
+
*
|
|
158
|
+
* `follow` may be a getter reading a signal — re-pinning on change is
|
|
159
|
+
* how a shared popover slides between nav triggers: the native chain
|
|
160
|
+
* glides there on the anchor element's CSS transition; the channel
|
|
161
|
+
* engine lets that one write animate.
|
|
162
|
+
*
|
|
163
|
+
* Registers all cleanup with the current scope (`onCleanup`): the anchor
|
|
164
|
+
* element, the wiring, and the stamped attributes are removed together.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* import { anchor, draggable, rubber } from "elements-kit/ui/overlay";
|
|
169
|
+
*
|
|
170
|
+
* const a = anchor(panel, trigger);
|
|
171
|
+
* draggable(a, undefined, rubber()).attach(panel);
|
|
172
|
+
*
|
|
173
|
+
* // shared nav popover — re-anchors (and glides) when the signal changes
|
|
174
|
+
* const active = signal(firstTrigger);
|
|
175
|
+
* anchor(menu, () => active(), { arrow: true });
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
function anchor(overlay, follow, opts = {}) {
|
|
179
|
+
const native = nativeAnchorSupport() && !opts.within && !opts.arrow;
|
|
180
|
+
let disposed = false;
|
|
181
|
+
const resolveFollow = () => (typeof follow === "function" ? follow() : follow) ?? void 0;
|
|
182
|
+
const el = document.createElement("span");
|
|
183
|
+
el.className = "x-overlay-anchor";
|
|
184
|
+
el.setAttribute("aria-hidden", "true");
|
|
185
|
+
if (!native) el.style.transitionProperty = "none";
|
|
186
|
+
document.body.append(el);
|
|
187
|
+
const proxyName = `--overlay-anchor-${anchorNames++}`;
|
|
188
|
+
const followName = `--overlay-follow-${anchorNames++}`;
|
|
189
|
+
const placeAtRect = (rect) => {
|
|
190
|
+
el.style.top = `${rect.top}px`;
|
|
191
|
+
el.style.left = `${rect.left}px`;
|
|
192
|
+
el.style.width = `${rect.width}px`;
|
|
193
|
+
el.style.height = `${rect.height}px`;
|
|
194
|
+
};
|
|
195
|
+
/** Pin the anchor element to a follow target (the `data-follow`
|
|
196
|
+
* contract — `draggable()` tears it on the first pointer-down).
|
|
197
|
+
* Re-pinning to another element keeps the SAME `position-anchor` name,
|
|
198
|
+
* so under the native chain the proxy's `anchor()` insets resolve to
|
|
199
|
+
* the new box and its CSS transition GLIDES there — the nav slide. */
|
|
200
|
+
let pinnedEl;
|
|
201
|
+
let hasPinned = false;
|
|
202
|
+
let stopFollowSync;
|
|
203
|
+
const releasePinMachinery = () => {
|
|
204
|
+
stopFollowSync?.();
|
|
205
|
+
stopFollowSync = void 0;
|
|
206
|
+
pinnedEl?.style.removeProperty("anchor-name");
|
|
207
|
+
pinnedEl = void 0;
|
|
208
|
+
};
|
|
209
|
+
const pin = (target) => {
|
|
210
|
+
const repositioning = hasPinned;
|
|
211
|
+
const flip = repositioning && target instanceof Element && chainSupport();
|
|
212
|
+
if (flip) placeAtRect(el.getBoundingClientRect());
|
|
213
|
+
releasePinMachinery();
|
|
214
|
+
if (!target) {
|
|
215
|
+
el.style.top = "50vh";
|
|
216
|
+
el.style.left = "50vw";
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
hasPinned = true;
|
|
220
|
+
if (!(target instanceof Element)) {
|
|
221
|
+
el.removeAttribute("data-follow");
|
|
222
|
+
placeAtRect(target);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
el.setAttribute("data-follow", "");
|
|
226
|
+
if (chainSupport()) {
|
|
227
|
+
target.style?.setProperty("anchor-name", followName);
|
|
228
|
+
pinnedEl = target;
|
|
229
|
+
el.style.setProperty("position-anchor", followName);
|
|
230
|
+
if (flip) el.offsetTop;
|
|
231
|
+
el.style.removeProperty("top");
|
|
232
|
+
el.style.removeProperty("left");
|
|
233
|
+
el.style.removeProperty("width");
|
|
234
|
+
el.style.removeProperty("height");
|
|
235
|
+
} else {
|
|
236
|
+
let first = !repositioning;
|
|
237
|
+
const sync = () => {
|
|
238
|
+
if (first) {
|
|
239
|
+
first = false;
|
|
240
|
+
const prev = el.style.transitionProperty;
|
|
241
|
+
el.style.transitionProperty = "none";
|
|
242
|
+
placeAtRect(target.getBoundingClientRect());
|
|
243
|
+
el.offsetTop;
|
|
244
|
+
if (prev) el.style.transitionProperty = prev;
|
|
245
|
+
else el.style.removeProperty("transition-property");
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
placeAtRect(target.getBoundingClientRect());
|
|
249
|
+
};
|
|
250
|
+
stopFollowSync = autoUpdate(target, el, sync);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const unpinCleanup = () => releasePinMachinery();
|
|
254
|
+
const followObserver = new MutationObserver(() => {
|
|
255
|
+
if (!el.hasAttribute("data-follow")) unpinCleanup();
|
|
256
|
+
});
|
|
257
|
+
followObserver.observe(el, {
|
|
258
|
+
attributes: true,
|
|
259
|
+
attributeFilter: ["data-follow"]
|
|
260
|
+
});
|
|
261
|
+
const stampPlacedHint = () => {
|
|
262
|
+
const style = getComputedStyle(overlay);
|
|
263
|
+
const hint = areaToPlacement(style.getPropertyValue("--overlay-area"), style.direction === "rtl");
|
|
264
|
+
overlay.setAttribute("data-placed", hint.split("-")[0]);
|
|
265
|
+
};
|
|
266
|
+
const isOpen = () => {
|
|
267
|
+
if (overlay.hasAttribute("open")) return true;
|
|
268
|
+
try {
|
|
269
|
+
return overlay.matches(":popover-open");
|
|
270
|
+
} catch {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
let haltLoop;
|
|
275
|
+
let engineDispose;
|
|
276
|
+
/** Channel engine only: reposition against a re-pinned target, letting
|
|
277
|
+
* that one write animate (the morph to the new anchor). */
|
|
278
|
+
let onRepoint;
|
|
279
|
+
if (native) {
|
|
280
|
+
overlay.setAttribute("data-anchor", "element");
|
|
281
|
+
el.style.setProperty("anchor-name", proxyName);
|
|
282
|
+
overlay.style.setProperty("position-anchor", proxyName);
|
|
283
|
+
engineDispose = () => {
|
|
284
|
+
const rect = overlay.getBoundingClientRect();
|
|
285
|
+
if (isOpen() && rect.width > 0) {
|
|
286
|
+
const c = resolveConstraint(overlay);
|
|
287
|
+
overlay.style.setProperty("--overlay-x", `${rect.left + rect.width / 2 - c.left}px`);
|
|
288
|
+
overlay.style.setProperty("--overlay-y", `${rect.top + rect.height / 2 - c.top}px`);
|
|
289
|
+
}
|
|
290
|
+
overlay.style.removeProperty("position-anchor");
|
|
291
|
+
overlay.removeAttribute("data-anchor");
|
|
292
|
+
};
|
|
293
|
+
} else {
|
|
294
|
+
let arrowEl = null;
|
|
295
|
+
let arrowOwned = false;
|
|
296
|
+
if (opts.arrow) {
|
|
297
|
+
arrowEl = overlay.querySelector(":scope > .x-overlay-arrow");
|
|
298
|
+
if (!arrowEl) {
|
|
299
|
+
arrowEl = document.createElement("span");
|
|
300
|
+
arrowEl.className = "x-overlay-arrow";
|
|
301
|
+
arrowEl.setAttribute("aria-hidden", "true");
|
|
302
|
+
overlay.append(arrowEl);
|
|
303
|
+
arrowOwned = true;
|
|
304
|
+
}
|
|
305
|
+
if (typeof opts.arrow === "number") overlay.style.setProperty("--overlay-arrow-size", `${opts.arrow}px`);
|
|
306
|
+
}
|
|
307
|
+
const boundaryRect = () => {
|
|
308
|
+
const w = opts.within;
|
|
309
|
+
if (!w) return void 0;
|
|
310
|
+
return {
|
|
311
|
+
x: w.left(),
|
|
312
|
+
y: w.top(),
|
|
313
|
+
width: w.width(),
|
|
314
|
+
height: w.height()
|
|
315
|
+
};
|
|
316
|
+
};
|
|
317
|
+
const update = async () => {
|
|
318
|
+
const style = getComputedStyle(overlay);
|
|
319
|
+
const placementHint = areaToPlacement(style.getPropertyValue("--overlay-area"), style.direction === "rtl");
|
|
320
|
+
const gapPx = resolveVarPx(overlay, "--overlay-gap", "width", "var(--space-2, 8px)");
|
|
321
|
+
const boundary = boundaryRect();
|
|
322
|
+
const middleware = [
|
|
323
|
+
offset(gapPx),
|
|
324
|
+
flip(boundary ? { boundary } : void 0),
|
|
325
|
+
shift(boundary ? { boundary } : void 0)
|
|
326
|
+
];
|
|
327
|
+
if (arrowEl) middleware.push(arrow({ element: arrowEl }));
|
|
328
|
+
const { x, y, placement, middlewareData } = await computePosition(el, overlay, {
|
|
329
|
+
strategy: "fixed",
|
|
330
|
+
placement: placementHint,
|
|
331
|
+
middleware
|
|
332
|
+
});
|
|
333
|
+
if (disposed) return;
|
|
334
|
+
const constraint = resolveConstraint(overlay);
|
|
335
|
+
overlay.style.setProperty("--overlay-x", `${x + overlay.offsetWidth / 2 - constraint.left}px`);
|
|
336
|
+
overlay.style.setProperty("--overlay-y", `${y + overlay.offsetHeight / 2 - constraint.top}px`);
|
|
337
|
+
if (placement) overlay.setAttribute("data-placed", placement.split("-")[0]);
|
|
338
|
+
const a = middlewareData.arrow;
|
|
339
|
+
if (a) {
|
|
340
|
+
if (a.x !== void 0) overlay.style.setProperty("--overlay-arrow-x", `${a.x}px`);
|
|
341
|
+
if (a.y !== void 0) overlay.style.setProperty("--overlay-arrow-y", `${a.y}px`);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
let stop;
|
|
345
|
+
let morphIn = isOpen();
|
|
346
|
+
const suppress = () => overlay.style.setProperty("transition-property", TRACKING_TRANSITIONS);
|
|
347
|
+
const release = () => overlay.style.removeProperty("transition-property");
|
|
348
|
+
const startLoop = () => {
|
|
349
|
+
if (stop) return;
|
|
350
|
+
const instant = !morphIn;
|
|
351
|
+
morphIn = false;
|
|
352
|
+
if (instant) suppress();
|
|
353
|
+
else if (!overlay.style.getPropertyValue("--overlay-x")) {
|
|
354
|
+
const rect = overlay.getBoundingClientRect();
|
|
355
|
+
const c = resolveConstraint(overlay);
|
|
356
|
+
overlay.style.setProperty("--overlay-x", `${rect.left + rect.width / 2 - c.left}px`);
|
|
357
|
+
overlay.style.setProperty("--overlay-y", `${rect.top + rect.height / 2 - c.top}px`);
|
|
358
|
+
}
|
|
359
|
+
let pending = instant;
|
|
360
|
+
stop = autoUpdate(el, overlay, () => update().then(() => {
|
|
361
|
+
if (pending && !disposed) {
|
|
362
|
+
pending = false;
|
|
363
|
+
release();
|
|
364
|
+
}
|
|
365
|
+
}));
|
|
366
|
+
};
|
|
367
|
+
haltLoop = () => {
|
|
368
|
+
stop?.();
|
|
369
|
+
stop = void 0;
|
|
370
|
+
release();
|
|
371
|
+
};
|
|
372
|
+
onRepoint = () => {
|
|
373
|
+
if (stop) update();
|
|
374
|
+
};
|
|
375
|
+
const sync = () => {
|
|
376
|
+
if (disposed) return;
|
|
377
|
+
if (isOpen()) startLoop();
|
|
378
|
+
else haltLoop?.();
|
|
379
|
+
};
|
|
380
|
+
const onDragMove = () => {
|
|
381
|
+
suppress();
|
|
382
|
+
update();
|
|
383
|
+
};
|
|
384
|
+
const onDragEnd = () => {
|
|
385
|
+
if (disposed || !stop) return;
|
|
386
|
+
release();
|
|
387
|
+
update();
|
|
388
|
+
};
|
|
389
|
+
overlay.addEventListener("toggle", sync);
|
|
390
|
+
overlay.addEventListener("close", sync);
|
|
391
|
+
el.addEventListener("dragmove", onDragMove);
|
|
392
|
+
el.addEventListener("dragend", onDragEnd);
|
|
393
|
+
const openObserver = new MutationObserver(sync);
|
|
394
|
+
openObserver.observe(overlay, {
|
|
395
|
+
attributes: true,
|
|
396
|
+
attributeFilter: ["open"]
|
|
397
|
+
});
|
|
398
|
+
sync();
|
|
399
|
+
engineDispose = () => {
|
|
400
|
+
haltLoop?.();
|
|
401
|
+
openObserver.disconnect();
|
|
402
|
+
overlay.removeEventListener("toggle", sync);
|
|
403
|
+
overlay.removeEventListener("close", sync);
|
|
404
|
+
el.removeEventListener("dragmove", onDragMove);
|
|
405
|
+
el.removeEventListener("dragend", onDragEnd);
|
|
406
|
+
overlay.style.removeProperty("--overlay-arrow-x");
|
|
407
|
+
overlay.style.removeProperty("--overlay-arrow-y");
|
|
408
|
+
if (typeof opts.arrow === "number") overlay.style.removeProperty("--overlay-arrow-size");
|
|
409
|
+
if (arrowOwned) arrowEl?.remove();
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
const repin = (event) => {
|
|
413
|
+
if (disposed) return;
|
|
414
|
+
const target = resolveFollow();
|
|
415
|
+
if (!target) return;
|
|
416
|
+
if ((event.newState === "open" || event.type !== "toggle" && isOpen()) && !el.hasAttribute("data-follow")) {
|
|
417
|
+
el.style.removeProperty("top");
|
|
418
|
+
el.style.removeProperty("left");
|
|
419
|
+
el.style.removeProperty("width");
|
|
420
|
+
el.style.removeProperty("height");
|
|
421
|
+
pin(target);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
overlay.addEventListener("toggle", repin);
|
|
425
|
+
const repinObserver = new MutationObserver((mutations) => {
|
|
426
|
+
if (mutations.some((m) => m.attributeName === "open")) repin(new Event("open"));
|
|
427
|
+
});
|
|
428
|
+
repinObserver.observe(overlay, {
|
|
429
|
+
attributes: true,
|
|
430
|
+
attributeFilter: ["open"]
|
|
431
|
+
});
|
|
432
|
+
stampPlacedHint();
|
|
433
|
+
let stopFollowEffect;
|
|
434
|
+
if (typeof follow === "function") {
|
|
435
|
+
let first = true;
|
|
436
|
+
stopFollowEffect = effect(() => {
|
|
437
|
+
pin(resolveFollow());
|
|
438
|
+
if (!first) onRepoint?.();
|
|
439
|
+
first = false;
|
|
440
|
+
});
|
|
441
|
+
} else pin(resolveFollow());
|
|
442
|
+
const dispose = () => {
|
|
443
|
+
disposed = true;
|
|
444
|
+
stopFollowEffect?.();
|
|
445
|
+
engineDispose?.();
|
|
446
|
+
unpinCleanup();
|
|
447
|
+
followObserver.disconnect();
|
|
448
|
+
repinObserver.disconnect();
|
|
449
|
+
overlay.removeEventListener("toggle", repin);
|
|
450
|
+
overlay.removeAttribute("data-placed");
|
|
451
|
+
el.remove();
|
|
452
|
+
};
|
|
453
|
+
onCleanup(dispose);
|
|
454
|
+
return el;
|
|
455
|
+
}
|
|
456
|
+
//#endregion
|
|
77
457
|
//#region src/ui/overlay/resize-strategy.ts
|
|
78
458
|
/**
|
|
79
459
|
* Resize strategies — pluggable policy for a resize drag's live bounds
|
|
80
460
|
* and resting size. Pure (no DOM); the gesture injects the context.
|
|
81
|
-
* Built-
|
|
461
|
+
* Built-in: `freeResize` (default). `detents()` (detents.ts) quantizes a
|
|
462
|
+
* constraint region and doubles as a strategy.
|
|
82
463
|
*/
|
|
83
464
|
/** How far (ms) a release velocity is projected when picking a rest. */
|
|
84
465
|
const PROJECTION_MS = 160;
|
|
@@ -117,19 +498,35 @@ function freeResize(opts) {
|
|
|
117
498
|
}
|
|
118
499
|
};
|
|
119
500
|
}
|
|
501
|
+
//#endregion
|
|
502
|
+
//#region src/ui/overlay/detents.ts
|
|
120
503
|
/**
|
|
121
|
-
*
|
|
122
|
-
* axis (number `0–1`) or a CSS length (string). Flick-aware
|
|
123
|
-
* past the smallest
|
|
504
|
+
* Quantizes a region: each stop is a fraction of the region along the
|
|
505
|
+
* axis (number `0–1`) or a CSS length (string, resize-only). Flick-aware
|
|
506
|
+
* on release; shrinking past the smallest stop dismisses (when the
|
|
507
|
+
* consumer allows dismissal).
|
|
508
|
+
*
|
|
509
|
+
* @example
|
|
510
|
+
* ```ts
|
|
511
|
+
* import { constraint, detents } from "elements-kit/ui/overlay";
|
|
512
|
+
*
|
|
513
|
+
* const d = detents(constraint(), [0.25, 0.6, 0.9]); // of the viewport
|
|
514
|
+
* ```
|
|
124
515
|
*/
|
|
125
|
-
function detents(
|
|
126
|
-
const resolved = (ctx) =>
|
|
516
|
+
function detents(region, stops) {
|
|
517
|
+
const resolved = (ctx) => stops.map((s) => clamp(ctx.resolve(s), ctx.min, ctx.max)).sort((a, b) => a - b);
|
|
127
518
|
return {
|
|
128
|
-
|
|
519
|
+
region,
|
|
520
|
+
stops,
|
|
521
|
+
positions(axis) {
|
|
522
|
+
const size = axis === "width" ? region.width() : region.height();
|
|
523
|
+
return stops.filter((s) => typeof s === "number").map((s) => s * size).sort((a, b) => a - b);
|
|
524
|
+
},
|
|
525
|
+
bounds(ctx) {
|
|
129
526
|
const s = resolved(ctx);
|
|
130
527
|
return [s[0] ?? ctx.min, s[s.length - 1] ?? ctx.max];
|
|
131
528
|
},
|
|
132
|
-
rest
|
|
529
|
+
rest(ctx) {
|
|
133
530
|
const s = resolved(ctx);
|
|
134
531
|
const i = closestDetent(ctx.size, s, ctx.velocity, ctx.dismissible, ctx.velocityThreshold);
|
|
135
532
|
return i === -1 ? null : s[i];
|
|
@@ -276,13 +673,177 @@ function slidePastRoom(resisted, max, sign) {
|
|
|
276
673
|
* `null` when docked (the CSS clamp already holds that edge). The single
|
|
277
674
|
* source of anchoring truth for block, inline, and corner resizes.
|
|
278
675
|
*/
|
|
279
|
-
function anchor(args) {
|
|
676
|
+
function anchor$1(args) {
|
|
280
677
|
const { axis, center0, constraint, anchorSign, startSize, size, docked } = args;
|
|
281
678
|
if (docked) return null;
|
|
282
679
|
const origin = axis === "width" ? constraint.left : constraint.top;
|
|
283
680
|
return (axis === "width" ? center0.x : center0.y) - origin + anchorSign * (size - startSize) / 2;
|
|
284
681
|
}
|
|
285
682
|
//#endregion
|
|
683
|
+
//#region src/ui/overlay/draggable.ts
|
|
684
|
+
const isSpace = (s) => "positions" in s;
|
|
685
|
+
function draggable(target, space, ...effects) {
|
|
686
|
+
const region = space && isSpace(space) ? space.region : space;
|
|
687
|
+
const boundsFor = (axis) => {
|
|
688
|
+
if (!region) return axis === "x" ? [0, window.innerWidth] : [0, window.innerHeight];
|
|
689
|
+
return axis === "x" ? [region.left(), region.left() + region.width()] : [region.top(), region.top() + region.height()];
|
|
690
|
+
};
|
|
691
|
+
const during = (value, axis) => {
|
|
692
|
+
const bounds = boundsFor(axis);
|
|
693
|
+
let v = value;
|
|
694
|
+
for (const e of effects) if (e.during) v = e.during(v, bounds);
|
|
695
|
+
return v;
|
|
696
|
+
};
|
|
697
|
+
const write = (x, y) => {
|
|
698
|
+
target.style.left = `${x}px`;
|
|
699
|
+
target.style.top = `${y}px`;
|
|
700
|
+
};
|
|
701
|
+
const emit = (type, detail) => target.dispatchEvent(new CustomEvent(type, {
|
|
702
|
+
detail,
|
|
703
|
+
bubbles: true
|
|
704
|
+
}));
|
|
705
|
+
/** Tear a `data-follow` pin: freeze the current rect inline (inline
|
|
706
|
+
* wins over the pin rule — no jump), then drop the pin machinery. */
|
|
707
|
+
const takeOver = () => {
|
|
708
|
+
const rect = target.getBoundingClientRect();
|
|
709
|
+
write(rect.left, rect.top);
|
|
710
|
+
if (target.hasAttribute("data-follow")) {
|
|
711
|
+
target.style.width = `${rect.width}px`;
|
|
712
|
+
target.style.height = `${rect.height}px`;
|
|
713
|
+
target.removeAttribute("data-follow");
|
|
714
|
+
target.style.removeProperty("position-anchor");
|
|
715
|
+
}
|
|
716
|
+
return {
|
|
717
|
+
left: rect.left,
|
|
718
|
+
top: rect.top
|
|
719
|
+
};
|
|
720
|
+
};
|
|
721
|
+
const settleAxis = (value, velocity, axis) => {
|
|
722
|
+
const bounds = boundsFor(axis);
|
|
723
|
+
let v = value;
|
|
724
|
+
for (const e of effects) {
|
|
725
|
+
if (!e.settle) continue;
|
|
726
|
+
const s = e.settle(v, velocity, bounds);
|
|
727
|
+
if (s === null) return null;
|
|
728
|
+
v = s;
|
|
729
|
+
}
|
|
730
|
+
if (space && isSpace(space)) {
|
|
731
|
+
const stops = space.positions(axis === "x" ? "width" : "height");
|
|
732
|
+
if (stops.length && region) {
|
|
733
|
+
const origin = axis === "x" ? region.left() : region.top();
|
|
734
|
+
const projected = v + velocity * 160;
|
|
735
|
+
let best = origin + stops[0];
|
|
736
|
+
for (const stop of stops) {
|
|
737
|
+
const candidate = origin + stop;
|
|
738
|
+
if (Math.abs(candidate - projected) < Math.abs(best - projected)) best = candidate;
|
|
739
|
+
}
|
|
740
|
+
v = best;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return v;
|
|
744
|
+
};
|
|
745
|
+
const attachments = [];
|
|
746
|
+
const attach = (handle) => {
|
|
747
|
+
let engaged = null;
|
|
748
|
+
const onDown = (event) => {
|
|
749
|
+
if (engaged || event.button !== 0) return;
|
|
750
|
+
if (event.target?.closest("button, a, label, input, select, textarea, [contenteditable]")) return;
|
|
751
|
+
engaged = {
|
|
752
|
+
start: takeOver(),
|
|
753
|
+
down: {
|
|
754
|
+
x: event.clientX,
|
|
755
|
+
y: event.clientY
|
|
756
|
+
},
|
|
757
|
+
tracker: {
|
|
758
|
+
prev: {
|
|
759
|
+
x: event.clientX,
|
|
760
|
+
y: event.clientY
|
|
761
|
+
},
|
|
762
|
+
lastTime: event.timeStamp,
|
|
763
|
+
velocity: {
|
|
764
|
+
x: 0,
|
|
765
|
+
y: 0
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
transition: target.style.transitionProperty
|
|
769
|
+
};
|
|
770
|
+
target.style.transitionProperty = "none";
|
|
771
|
+
handle.setPointerCapture(event.pointerId);
|
|
772
|
+
};
|
|
773
|
+
const onMove = (event) => {
|
|
774
|
+
if (!engaged) return;
|
|
775
|
+
const client = {
|
|
776
|
+
x: event.clientX,
|
|
777
|
+
y: event.clientY
|
|
778
|
+
};
|
|
779
|
+
engaged.tracker.velocity = updateVelocity(engaged.tracker, client, event.timeStamp);
|
|
780
|
+
engaged.tracker.prev = client;
|
|
781
|
+
engaged.tracker.lastTime = event.timeStamp;
|
|
782
|
+
const x = during(engaged.start.left + (client.x - engaged.down.x), "x");
|
|
783
|
+
const y = during(engaged.start.top + (client.y - engaged.down.y), "y");
|
|
784
|
+
write(x, y);
|
|
785
|
+
emit("dragmove", {
|
|
786
|
+
x,
|
|
787
|
+
y
|
|
788
|
+
});
|
|
789
|
+
};
|
|
790
|
+
const onUp = (event) => {
|
|
791
|
+
if (!engaged) return;
|
|
792
|
+
const { start, down, tracker, transition } = engaged;
|
|
793
|
+
engaged = null;
|
|
794
|
+
target.style.transitionProperty = transition;
|
|
795
|
+
const raw = {
|
|
796
|
+
x: start.left + (event.clientX - down.x),
|
|
797
|
+
y: start.top + (event.clientY - down.y)
|
|
798
|
+
};
|
|
799
|
+
const x = settleAxis(raw.x, tracker.velocity.x, "x");
|
|
800
|
+
const y = settleAxis(raw.y, tracker.velocity.y, "y");
|
|
801
|
+
const rest = x === null || y === null ? null : {
|
|
802
|
+
x,
|
|
803
|
+
y
|
|
804
|
+
};
|
|
805
|
+
if (rest) write(rest.x, rest.y);
|
|
806
|
+
emit("dragend", {
|
|
807
|
+
...raw,
|
|
808
|
+
velocity: tracker.velocity,
|
|
809
|
+
rest
|
|
810
|
+
});
|
|
811
|
+
};
|
|
812
|
+
handle.addEventListener("pointerdown", onDown);
|
|
813
|
+
handle.addEventListener("pointermove", onMove);
|
|
814
|
+
handle.addEventListener("pointerup", onUp);
|
|
815
|
+
handle.addEventListener("pointercancel", onUp);
|
|
816
|
+
const dispose = () => {
|
|
817
|
+
handle.removeEventListener("pointerdown", onDown);
|
|
818
|
+
handle.removeEventListener("pointermove", onMove);
|
|
819
|
+
handle.removeEventListener("pointerup", onUp);
|
|
820
|
+
handle.removeEventListener("pointercancel", onUp);
|
|
821
|
+
};
|
|
822
|
+
attachments.push(dispose);
|
|
823
|
+
return { dispose };
|
|
824
|
+
};
|
|
825
|
+
const update = (point) => {
|
|
826
|
+
takeOver();
|
|
827
|
+
const x = during(point.x, "x");
|
|
828
|
+
const y = during(point.y, "y");
|
|
829
|
+
write(x, y);
|
|
830
|
+
emit("dragmove", {
|
|
831
|
+
x,
|
|
832
|
+
y
|
|
833
|
+
});
|
|
834
|
+
};
|
|
835
|
+
const dispose = () => {
|
|
836
|
+
for (const detach of attachments.splice(0)) detach();
|
|
837
|
+
};
|
|
838
|
+
onCleanup(dispose);
|
|
839
|
+
return {
|
|
840
|
+
update,
|
|
841
|
+
attach,
|
|
842
|
+
dispose,
|
|
843
|
+
[Symbol.dispose]: dispose
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
//#endregion
|
|
286
847
|
//#region src/ui/overlay/move-session.ts
|
|
287
848
|
/** Move bounds on the box center — the same clamp the stylesheet applies to
|
|
288
849
|
* the persisted location point (center floor = half the box). */
|
|
@@ -636,7 +1197,7 @@ function resizeHeightSession(snap, resizer, io, side) {
|
|
|
636
1197
|
min: 0,
|
|
637
1198
|
max: hardMax
|
|
638
1199
|
};
|
|
639
|
-
const anchorAt = (size) => anchor({
|
|
1200
|
+
const anchorAt = (size) => anchor$1({
|
|
640
1201
|
axis: "height",
|
|
641
1202
|
center0: snap.center0,
|
|
642
1203
|
constraint: snap.constraint,
|
|
@@ -741,7 +1302,7 @@ function resizeSession(snap, resizer, io, block, inline) {
|
|
|
741
1302
|
const frameFor = (w, h) => ({
|
|
742
1303
|
w,
|
|
743
1304
|
h,
|
|
744
|
-
x: anchor({
|
|
1305
|
+
x: anchor$1({
|
|
745
1306
|
axis: "width",
|
|
746
1307
|
center0: snap.center0,
|
|
747
1308
|
constraint: snap.constraint,
|
|
@@ -750,7 +1311,7 @@ function resizeSession(snap, resizer, io, block, inline) {
|
|
|
750
1311
|
size: w,
|
|
751
1312
|
docked: false
|
|
752
1313
|
}),
|
|
753
|
-
y: anchor({
|
|
1314
|
+
y: anchor$1({
|
|
754
1315
|
axis: "height",
|
|
755
1316
|
center0: snap.center0,
|
|
756
1317
|
constraint: snap.constraint,
|
|
@@ -816,7 +1377,7 @@ function resizeWidthSession(snap, resizer, io, side) {
|
|
|
816
1377
|
min: 0,
|
|
817
1378
|
max: hardMax
|
|
818
1379
|
};
|
|
819
|
-
const anchorAt = (size) => anchor({
|
|
1380
|
+
const anchorAt = (size) => anchor$1({
|
|
820
1381
|
axis: "width",
|
|
821
1382
|
center0: snap.center0,
|
|
822
1383
|
constraint: snap.constraint,
|
|
@@ -904,10 +1465,10 @@ function selectSession(key, parsed, snapshot, resizer, move, io) {
|
|
|
904
1465
|
*
|
|
905
1466
|
* @example
|
|
906
1467
|
* ```ts
|
|
907
|
-
* import { createOverlayGestures, detents } from "elements-kit/ui/overlay";
|
|
1468
|
+
* import { constraint, createOverlayGestures, detents } from "elements-kit/ui/overlay";
|
|
908
1469
|
*
|
|
909
1470
|
* const el = document.querySelector("dialog.x-overlay")!;
|
|
910
|
-
* createOverlayGestures(el, { resize: detents([0.25, 0.6, 0.9]) });
|
|
1471
|
+
* createOverlayGestures(el, { resize: detents(constraint(), [0.25, 0.6, 0.9]) });
|
|
911
1472
|
* el.addEventListener("resizechange", (e) => console.log(e.detail));
|
|
912
1473
|
* ```
|
|
913
1474
|
*/
|
|
@@ -960,42 +1521,73 @@ function createOverlayGestures(overlay, options) {
|
|
|
960
1521
|
};
|
|
961
1522
|
}
|
|
962
1523
|
//#endregion
|
|
963
|
-
//#region src/ui/overlay/
|
|
964
|
-
/**
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
const
|
|
1524
|
+
//#region src/ui/overlay/resize.ts
|
|
1525
|
+
/** Chain settle effects after the strategy's rest. */
|
|
1526
|
+
function withEffects(base, effects) {
|
|
1527
|
+
if (!effects.length) return base;
|
|
1528
|
+
return {
|
|
1529
|
+
bounds: base.bounds?.bind(base),
|
|
1530
|
+
rest(ctx) {
|
|
1531
|
+
let rested = base.rest(ctx);
|
|
1532
|
+
if (rested === null) return null;
|
|
1533
|
+
for (const e of effects) {
|
|
1534
|
+
if (!e.settle) continue;
|
|
1535
|
+
const s = e.settle(rested, ctx.velocity, [ctx.min, ctx.max]);
|
|
1536
|
+
if (s === null) return null;
|
|
1537
|
+
rested = s;
|
|
1538
|
+
}
|
|
1539
|
+
return rested;
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
function resize(target, space, ...effects) {
|
|
1544
|
+
const io = createFrameIO(target, {
|
|
1545
|
+
strategy: withEffects(space ?? freeResize(), effects),
|
|
1546
|
+
dismissible: true,
|
|
1547
|
+
velocityThreshold: .5
|
|
1548
|
+
});
|
|
1549
|
+
const buildSession = () => {
|
|
1550
|
+
const parsed = parseResize(target.getAttribute("data-resize") ?? "");
|
|
1551
|
+
const { snapshot, resizer } = io.engage();
|
|
1552
|
+
if (parsed.block !== null && parsed.inline !== null) return resizeSession(snapshot, resizer, io, parsed.block, parsed.inline);
|
|
1553
|
+
if (parsed.block !== null) return resizeHeightSession(snapshot, resizer, io, parsed.block);
|
|
1554
|
+
if (parsed.inline !== null) return resizeWidthSession(snapshot, resizer, io, parsed.inline);
|
|
1555
|
+
return null;
|
|
1556
|
+
};
|
|
1557
|
+
const attachments = [];
|
|
1558
|
+
const attach = (handle) => {
|
|
1559
|
+
const recognizer = createGestureRecognizer(handle, {
|
|
1560
|
+
canEngage: () => true,
|
|
1561
|
+
engage: () => buildSession()
|
|
1562
|
+
});
|
|
1563
|
+
attachments.push(recognizer.dispose);
|
|
1564
|
+
return { dispose: recognizer.dispose };
|
|
1565
|
+
};
|
|
1566
|
+
const update = (size) => {
|
|
1567
|
+
const parsed = parseResize(target.getAttribute("data-resize") ?? "");
|
|
1568
|
+
const key = parsed.block !== null && parsed.inline === null ? "h" : "w";
|
|
1569
|
+
io.commit({ [key]: size });
|
|
1570
|
+
};
|
|
989
1571
|
const dispose = () => {
|
|
990
|
-
|
|
991
|
-
constraint?.dispose();
|
|
1572
|
+
for (const detach of attachments.splice(0)) detach();
|
|
992
1573
|
};
|
|
993
1574
|
onCleanup(dispose);
|
|
994
1575
|
return {
|
|
995
|
-
|
|
1576
|
+
update,
|
|
1577
|
+
attach,
|
|
996
1578
|
dispose,
|
|
997
1579
|
[Symbol.dispose]: dispose
|
|
998
1580
|
};
|
|
999
1581
|
}
|
|
1000
1582
|
//#endregion
|
|
1001
|
-
|
|
1583
|
+
//#region src/ui/overlay/rubber.ts
|
|
1584
|
+
/**
|
|
1585
|
+
* Edge resistance — values past the bounds move at a fraction of the
|
|
1586
|
+
* pointer (the iOS rubber band). Pure `resist()` under the hood; no
|
|
1587
|
+
* channels involved, so it applies to anything a service moves.
|
|
1588
|
+
*/
|
|
1589
|
+
function rubber() {
|
|
1590
|
+
return { during: (value, [lo, hi]) => resist(value, lo, hi) };
|
|
1591
|
+
}
|
|
1592
|
+
//#endregion
|
|
1593
|
+
export { PROJECTION_MS, anchor, areaToPlacement, clamp, closestDetent, confine, constraint, createOverlayGestures, detents, draggable, freeResize, resize, resolveConstraint, resolveVarPx, rubber };
|