@surdeddd/bottom-sheet 0.1.0 → 0.3.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 +57 -21
- package/dist/BottomSheetEngine-CAymrH-P.d.cts +285 -0
- package/dist/BottomSheetEngine-DcqcuudN.d.ts +285 -0
- package/dist/element.cjs +775 -537
- package/dist/element.d.cts +9 -57
- package/dist/element.d.ts +9 -57
- package/dist/element.iife.global.js +2 -2
- package/dist/element.js +775 -537
- package/dist/index.cjs +760 -600
- package/dist/index.d.cts +18 -85
- package/dist/index.d.ts +18 -85
- package/dist/index.js +758 -601
- package/dist/integrations/formik.d.cts +2 -8
- package/dist/integrations/formik.d.ts +2 -8
- package/dist/integrations/react-hook-form.d.cts +2 -11
- package/dist/integrations/react-hook-form.d.ts +2 -11
- package/dist/overlay.cjs +168 -122
- package/dist/overlay.d.cts +14 -137
- package/dist/overlay.d.ts +14 -137
- package/dist/overlay.js +168 -122
- package/dist/preact.d.cts +2 -2
- package/dist/preact.d.ts +2 -2
- package/dist/preact.js +0 -1
- package/dist/qwik.cjs +667 -519
- package/dist/qwik.d.cts +3 -24
- package/dist/qwik.d.ts +3 -24
- package/dist/qwik.js +668 -520
- package/dist/react.cjs +873 -648
- package/dist/react.d.cts +34 -159
- package/dist/react.d.ts +34 -159
- package/dist/react.js +873 -648
- package/dist/solid.cjs +703 -560
- package/dist/solid.d.cts +264 -32
- package/dist/solid.d.ts +264 -32
- package/dist/solid.js +704 -561
- package/dist/solid.source.jsx +3215 -0
- package/dist/styles.css +37 -112
- package/dist/svelte-core.cjs +3149 -0
- package/dist/svelte-core.js +3147 -0
- package/dist/svelte-src/BottomSheet.svelte +210 -0
- package/dist/svelte-src/index.js +2 -0
- package/dist/svelte.cjs +718 -547
- package/dist/svelte.d.cts +100 -0
- package/dist/svelte.d.ts +15 -4
- package/dist/svelte.js +719 -548
- package/dist/themes/ios.css +0 -3
- package/dist/themes/material.css +0 -3
- package/dist/themes/vercel.css +0 -3
- package/dist/{transform-Brkf-wzo.d.cts → transform-jVfAikE9.d.cts} +0 -1
- package/dist/{transform-Brkf-wzo.d.ts → transform-jVfAikE9.d.ts} +0 -1
- package/dist/vue.cjs +677 -526
- package/dist/vue.d.cts +78 -0
- package/dist/vue.d.ts +12 -2
- package/dist/vue.js +678 -527
- package/package.json +105 -45
- package/dist/BottomSheetEngine-CvSVbo6c.d.cts +0 -704
- package/dist/BottomSheetEngine-DbO8rW_3.d.ts +0 -704
package/dist/overlay.cjs
CHANGED
|
@@ -22,6 +22,18 @@ var SheetStack = class {
|
|
|
22
22
|
this.entries.splice(idx, 1);
|
|
23
23
|
this.recompute();
|
|
24
24
|
}
|
|
25
|
+
promote(id) {
|
|
26
|
+
const idx = this.entries.findIndex((e) => e.id === id);
|
|
27
|
+
if (idx === -1) return;
|
|
28
|
+
if (idx !== this.entries.length - 1) {
|
|
29
|
+
const [entry] = this.entries.splice(idx, 1);
|
|
30
|
+
this.entries.push(entry);
|
|
31
|
+
}
|
|
32
|
+
this.recompute();
|
|
33
|
+
}
|
|
34
|
+
update() {
|
|
35
|
+
this.recompute();
|
|
36
|
+
}
|
|
25
37
|
size() {
|
|
26
38
|
return this.entries.length;
|
|
27
39
|
}
|
|
@@ -29,9 +41,24 @@ var SheetStack = class {
|
|
|
29
41
|
this.entries = [];
|
|
30
42
|
}
|
|
31
43
|
recompute() {
|
|
44
|
+
let top = null;
|
|
45
|
+
const open = [];
|
|
46
|
+
for (const entry of this.entries) {
|
|
47
|
+
if (!entry.isOpen || entry.isOpen()) {
|
|
48
|
+
top = entry;
|
|
49
|
+
open.push(entry);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!top && this.entries.length > 0) {
|
|
53
|
+
top = this.entries[this.entries.length - 1];
|
|
54
|
+
}
|
|
32
55
|
this.entries.forEach((entry, i) => {
|
|
33
56
|
entry.setZIndex(BASE_Z + i * STEP);
|
|
34
|
-
entry.setIsTop(
|
|
57
|
+
entry.setIsTop(entry === top);
|
|
58
|
+
if (entry.setDepth) {
|
|
59
|
+
const openIdx = open.indexOf(entry);
|
|
60
|
+
entry.setDepth(openIdx === -1 ? 0 : open.length - 1 - openIdx);
|
|
61
|
+
}
|
|
35
62
|
});
|
|
36
63
|
}
|
|
37
64
|
};
|
|
@@ -154,9 +181,13 @@ var isVisible = (el) => {
|
|
|
154
181
|
var focusables = (root) => Array.from(root.querySelectorAll(FOCUSABLE)).filter(
|
|
155
182
|
(el) => !el.hasAttribute("inert") && isVisible(el) && !el.matches("[aria-hidden='true']")
|
|
156
183
|
);
|
|
184
|
+
var trapStack = [];
|
|
157
185
|
var installFocusTrap = (container, options = {}) => {
|
|
158
186
|
if (typeof document === "undefined") return () => {
|
|
159
187
|
};
|
|
188
|
+
const token = {};
|
|
189
|
+
trapStack.push(token);
|
|
190
|
+
const isActive = () => trapStack[trapStack.length - 1] === token;
|
|
160
191
|
const previouslyFocused = document.activeElement;
|
|
161
192
|
const focusInitial = () => {
|
|
162
193
|
const initial = typeof options.initialFocus === "string" ? container.querySelector(options.initialFocus) : options.initialFocus ?? focusables(container)[0] ?? container;
|
|
@@ -164,6 +195,7 @@ var installFocusTrap = (container, options = {}) => {
|
|
|
164
195
|
};
|
|
165
196
|
focusInitial();
|
|
166
197
|
const handleKey = (e) => {
|
|
198
|
+
if (!isActive()) return;
|
|
167
199
|
if (e.key === "Escape" && options.onEscape) {
|
|
168
200
|
options.onEscape();
|
|
169
201
|
return;
|
|
@@ -186,6 +218,7 @@ var installFocusTrap = (container, options = {}) => {
|
|
|
186
218
|
}
|
|
187
219
|
};
|
|
188
220
|
const handleFocusIn = (e) => {
|
|
221
|
+
if (!isActive()) return;
|
|
189
222
|
const target = e.target;
|
|
190
223
|
if (target && !container.contains(target)) {
|
|
191
224
|
const list = focusables(container);
|
|
@@ -198,6 +231,8 @@ var installFocusTrap = (container, options = {}) => {
|
|
|
198
231
|
return () => {
|
|
199
232
|
if (released) return;
|
|
200
233
|
released = true;
|
|
234
|
+
const idx = trapStack.indexOf(token);
|
|
235
|
+
if (idx !== -1) trapStack.splice(idx, 1);
|
|
201
236
|
document.removeEventListener("keydown", handleKey, true);
|
|
202
237
|
document.removeEventListener("focusin", handleFocusIn, true);
|
|
203
238
|
if (previouslyFocused && document.contains(previouslyFocused)) {
|
|
@@ -253,11 +288,17 @@ function createInertSiblings(rootProvider) {
|
|
|
253
288
|
return {
|
|
254
289
|
apply() {
|
|
255
290
|
if (typeof document === "undefined") return;
|
|
256
|
-
|
|
257
|
-
|
|
291
|
+
let top = rootProvider();
|
|
292
|
+
while (top.parentElement && top.parentElement !== document.body) {
|
|
293
|
+
top = top.parentElement;
|
|
294
|
+
}
|
|
295
|
+
const parent = top.parentElement ?? document.body;
|
|
258
296
|
for (const child of Array.from(parent.children)) {
|
|
259
|
-
if (child ===
|
|
297
|
+
if (child === top) continue;
|
|
260
298
|
if (child.hasAttribute("inert")) continue;
|
|
299
|
+
if (child.classList.contains("bs-backdrop") || child.classList.contains("bs-screen") || child.classList.contains("bs-root")) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
261
302
|
child.setAttribute("inert", "");
|
|
262
303
|
tracked.push(child);
|
|
263
304
|
}
|
|
@@ -273,7 +314,6 @@ function createInertSiblings(rootProvider) {
|
|
|
273
314
|
var LifecycleController = class {
|
|
274
315
|
constructor(deps, opts) {
|
|
275
316
|
__publicField(this, "element");
|
|
276
|
-
__publicField(this, "closeFn");
|
|
277
317
|
__publicField(this, "focusTrapEnabled");
|
|
278
318
|
__publicField(this, "initialFocus");
|
|
279
319
|
__publicField(this, "closeOnEscape");
|
|
@@ -282,16 +322,11 @@ var LifecycleController = class {
|
|
|
282
322
|
__publicField(this, "inertSiblings");
|
|
283
323
|
__publicField(this, "shouldApplyInertSiblings");
|
|
284
324
|
__publicField(this, "returnFocus");
|
|
285
|
-
/** Toggle-state teardowns — fire on close, NOT in destroy-only stack. */
|
|
286
325
|
__publicField(this, "releaseFocusTrap", null);
|
|
287
326
|
__publicField(this, "releaseScrollLock", null);
|
|
288
|
-
/** True between install() and release(). Guards release()/destroy() from
|
|
289
|
-
* re-running side effects (notably `returnFocus.focus()`) on already-
|
|
290
|
-
* released or never-installed instances. */
|
|
291
327
|
__publicField(this, "installed", false);
|
|
292
328
|
__publicField(this, "destroyed", false);
|
|
293
329
|
this.element = deps.element;
|
|
294
|
-
this.closeFn = deps.close;
|
|
295
330
|
this.focusTrapEnabled = opts.focusTrap ?? false;
|
|
296
331
|
this.initialFocus = opts.initialFocus;
|
|
297
332
|
this.closeOnEscape = opts.closeOnEscape ?? true;
|
|
@@ -301,14 +336,9 @@ var LifecycleController = class {
|
|
|
301
336
|
this.returnFocus = opts.returnFocus;
|
|
302
337
|
this.inertSiblings = createInertSiblings(() => this.element);
|
|
303
338
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
* doesn't double-install. Sets `installed=true` BEFORE the first side
|
|
308
|
-
* effect so a partial throw (detached / shadow-DOM target) leaves the
|
|
309
|
-
* caller's `release()` rollback path armed to undo whatever did succeed.
|
|
310
|
-
* No-op when destroyed.
|
|
311
|
-
*/
|
|
339
|
+
get isInstalled() {
|
|
340
|
+
return this.installed;
|
|
341
|
+
}
|
|
312
342
|
install() {
|
|
313
343
|
if (this.destroyed) return;
|
|
314
344
|
this.installed = true;
|
|
@@ -317,30 +347,17 @@ var LifecycleController = class {
|
|
|
317
347
|
}
|
|
318
348
|
if (this.focusTrapEnabled && !this.releaseFocusTrap) {
|
|
319
349
|
this.releaseFocusTrap = installFocusTrap(this.element, {
|
|
320
|
-
initialFocus: this.initialFocus
|
|
321
|
-
onEscape: this.closeOnEscape ? () => void this.closeFn() : void 0
|
|
350
|
+
initialFocus: this.initialFocus
|
|
322
351
|
});
|
|
323
352
|
}
|
|
324
353
|
if (this.inertSiblingsEnabled && this.shouldApplyInertSiblings()) {
|
|
325
354
|
this.inertSiblings.apply();
|
|
326
355
|
}
|
|
327
356
|
}
|
|
328
|
-
/**
|
|
329
|
-
* Update the post-release focus target. Overlay re-binds this when the
|
|
330
|
-
* consumer changes `returnFocus` after construction; the new value applies
|
|
331
|
-
* on the next `release()`. No-op when destroyed.
|
|
332
|
-
*/
|
|
333
357
|
setReturnFocus(target) {
|
|
334
358
|
if (this.destroyed) return;
|
|
335
359
|
this.returnFocus = target;
|
|
336
360
|
}
|
|
337
|
-
/**
|
|
338
|
-
* Release all install-time teardowns. Short-circuits when not installed,
|
|
339
|
-
* so `close() → destroy()` (each routing through here) only restores
|
|
340
|
-
* focus once. The `installed` guard lets `inertSiblings.remove()` and
|
|
341
|
-
* the focus-trap/scroll-lock teardown nullings stay simple — they were
|
|
342
|
-
* already idempotent, but `returnFocus.focus()` was firing on every call.
|
|
343
|
-
*/
|
|
344
361
|
release() {
|
|
345
362
|
if (!this.installed) return;
|
|
346
363
|
this.installed = false;
|
|
@@ -354,18 +371,18 @@ var LifecycleController = class {
|
|
|
354
371
|
target?.focus?.();
|
|
355
372
|
}
|
|
356
373
|
}
|
|
357
|
-
/**
|
|
358
|
-
* Final teardown — releases any still-installed listeners and seals the
|
|
359
|
-
* controller. After destroy(), install() is a no-op and setReturnFocus()
|
|
360
|
-
* is rejected, so post-destroy mutations can't queue up a stale focus
|
|
361
|
-
* restore.
|
|
362
|
-
*/
|
|
363
374
|
destroy() {
|
|
364
375
|
this.release();
|
|
365
376
|
this.destroyed = true;
|
|
366
377
|
}
|
|
367
378
|
};
|
|
368
379
|
|
|
380
|
+
// src/core/animation/animation.ts
|
|
381
|
+
var prefersReducedMotion = () => {
|
|
382
|
+
if (typeof window === "undefined" || !window.matchMedia) return false;
|
|
383
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
384
|
+
};
|
|
385
|
+
|
|
369
386
|
// src/core/overlay.ts
|
|
370
387
|
var OVERLAY_PRESETS = Object.freeze({
|
|
371
388
|
sheet: Object.freeze({
|
|
@@ -431,8 +448,6 @@ var OverlayEngine = class {
|
|
|
431
448
|
__publicField(this, "exitEasing");
|
|
432
449
|
__publicField(this, "backdropOpacity");
|
|
433
450
|
__publicField(this, "backdropFilter");
|
|
434
|
-
// Definite-assignment: written via assignSwipeFromConfig in the
|
|
435
|
-
// constructor — TS can't follow the helper, mark the assertion explicit.
|
|
436
451
|
__publicField(this, "swipeToCloseEnabled");
|
|
437
452
|
__publicField(this, "swipeThreshold");
|
|
438
453
|
__publicField(this, "swipeVelocityThreshold");
|
|
@@ -447,18 +462,16 @@ var OverlayEngine = class {
|
|
|
447
462
|
__publicField(this, "closeOnOutsidePointerEnabled");
|
|
448
463
|
__publicField(this, "detachOutsidePointer", null);
|
|
449
464
|
__publicField(this, "detachSwipe", null);
|
|
465
|
+
__publicField(this, "respectReducedMotionEnabled");
|
|
466
|
+
__publicField(this, "isTop", true);
|
|
467
|
+
__publicField(this, "backPushed", false);
|
|
468
|
+
__publicField(this, "movedToTarget", false);
|
|
450
469
|
__publicField(this, "onOpenCb");
|
|
451
470
|
__publicField(this, "onCloseCb");
|
|
452
471
|
__publicField(this, "isOpen_", false);
|
|
453
472
|
__publicField(this, "isAnimating_", false);
|
|
454
473
|
__publicField(this, "destroyed", false);
|
|
455
|
-
// Tracks whether setOverlayChildren ever populated the panel — lets
|
|
456
|
-
// clearOverlayChildren skip the DOM mutation when nothing was injected
|
|
457
|
-
// (so the field also acts as a zero-allocation feature flag).
|
|
458
474
|
__publicField(this, "hasInjectedChildren", false);
|
|
459
|
-
// Bumped on every open/close. awaitTransition captures it and ignores
|
|
460
|
-
// transitionend/timeout fired by older cycles — prevents cross-cycle
|
|
461
|
-
// listener bleed during rapid toggling.
|
|
462
475
|
__publicField(this, "cycleNonce", 0);
|
|
463
476
|
__publicField(this, "bus", createEventBus());
|
|
464
477
|
__publicField(this, "detachEscape", null);
|
|
@@ -489,6 +502,7 @@ var OverlayEngine = class {
|
|
|
489
502
|
this.mountTarget = opts.mountTo;
|
|
490
503
|
this.peekRaw = opts.peek;
|
|
491
504
|
this.closeOnOutsidePointerEnabled = opts.closeOnOutsidePointer ?? false;
|
|
505
|
+
this.respectReducedMotionEnabled = opts.respectReducedMotion ?? true;
|
|
492
506
|
this.onOpenCb = opts.onOpen;
|
|
493
507
|
this.onCloseCb = opts.onClose;
|
|
494
508
|
if (this.peekRaw !== void 0 && this.edge !== "bottom") {
|
|
@@ -511,9 +525,6 @@ var OverlayEngine = class {
|
|
|
511
525
|
closeOnEscape: this.closeOnEscape,
|
|
512
526
|
lockBodyScroll: this.bodyScrollLockEnabled,
|
|
513
527
|
inertSiblings: this.inertSiblingsEnabled,
|
|
514
|
-
// Overlay-specific guard: only paint body siblings inert when the
|
|
515
|
-
// overlay element is actually a body descendant. Detached / shadow-
|
|
516
|
-
// DOM / portal overlays would otherwise lock unrelated trees.
|
|
517
528
|
shouldApplyInertSiblings: () => {
|
|
518
529
|
const bodyChildren = Array.from(document.body.children);
|
|
519
530
|
return bodyChildren.some(
|
|
@@ -539,15 +550,17 @@ var OverlayEngine = class {
|
|
|
539
550
|
this.emit("before-open", void 0);
|
|
540
551
|
this.isOpen_ = true;
|
|
541
552
|
this.isAnimating_ = true;
|
|
553
|
+
sheetStack.promote(this.id);
|
|
542
554
|
this.element.removeAttribute("hidden");
|
|
543
555
|
this.element.setAttribute("data-state", "entering");
|
|
544
556
|
if (this.focusTrapEnabled) {
|
|
545
557
|
this.element.setAttribute("aria-modal", "true");
|
|
546
558
|
}
|
|
547
|
-
const
|
|
559
|
+
const dur = this.effectiveDuration();
|
|
560
|
+
const transitionStr = `transform ${dur}ms ${this.enterEasing}, opacity ${dur}ms ease-out`;
|
|
548
561
|
this.element.style.transition = transitionStr;
|
|
549
562
|
if (this.backdrop) {
|
|
550
|
-
this.backdrop.style.transition = `opacity ${
|
|
563
|
+
this.backdrop.style.transition = `opacity ${dur}ms ease-out`;
|
|
551
564
|
}
|
|
552
565
|
const cycle = ++this.cycleNonce;
|
|
553
566
|
return new Promise((resolve) => {
|
|
@@ -559,7 +572,7 @@ var OverlayEngine = class {
|
|
|
559
572
|
this.element.style.opacity = "1";
|
|
560
573
|
if (this.backdrop) {
|
|
561
574
|
this.backdrop.style.opacity = String(this.backdropOpacity);
|
|
562
|
-
this.backdrop.style.pointerEvents = "auto";
|
|
575
|
+
this.backdrop.style.pointerEvents = this.backdropOpacity > 0 ? "auto" : "none";
|
|
563
576
|
if (this.backdropFilter) {
|
|
564
577
|
this.backdrop.style.backdropFilter = this.backdropFilter;
|
|
565
578
|
this.backdrop.style.webkitBackdropFilter = this.backdropFilter;
|
|
@@ -595,7 +608,10 @@ var OverlayEngine = class {
|
|
|
595
608
|
});
|
|
596
609
|
return resolve();
|
|
597
610
|
}
|
|
598
|
-
this.awaitTransition(
|
|
611
|
+
this.awaitTransition(
|
|
612
|
+
cycle,
|
|
613
|
+
this.enterAnimation === "fade" ? "opacity" : "transform"
|
|
614
|
+
).then(() => {
|
|
599
615
|
if (this.destroyed || cycle !== this.cycleNonce) return resolve();
|
|
600
616
|
this.isAnimating_ = false;
|
|
601
617
|
this.element.setAttribute("data-state", "open");
|
|
@@ -612,11 +628,14 @@ var OverlayEngine = class {
|
|
|
612
628
|
this.emit("before-close", void 0);
|
|
613
629
|
this.isOpen_ = false;
|
|
614
630
|
this.isAnimating_ = true;
|
|
631
|
+
sheetStack.update();
|
|
632
|
+
this.popBackMarker(reason);
|
|
615
633
|
this.element.setAttribute("data-state", "exiting");
|
|
616
|
-
const
|
|
634
|
+
const dur = this.effectiveDuration();
|
|
635
|
+
const transitionStr = `transform ${dur}ms ${this.exitEasing}, opacity ${dur}ms ease-in`;
|
|
617
636
|
this.element.style.transition = transitionStr;
|
|
618
637
|
if (this.backdrop) {
|
|
619
|
-
this.backdrop.style.transition = `opacity ${
|
|
638
|
+
this.backdrop.style.transition = `opacity ${dur}ms ease-in`;
|
|
620
639
|
}
|
|
621
640
|
const cycle = ++this.cycleNonce;
|
|
622
641
|
return new Promise((resolve) => {
|
|
@@ -627,7 +646,7 @@ var OverlayEngine = class {
|
|
|
627
646
|
this.exitAnimation,
|
|
628
647
|
this.peekPx
|
|
629
648
|
);
|
|
630
|
-
this.element.style.opacity = "0";
|
|
649
|
+
this.element.style.opacity = this.peekPx !== void 0 ? "1" : "0";
|
|
631
650
|
if (this.backdrop) {
|
|
632
651
|
this.backdrop.style.opacity = "0";
|
|
633
652
|
this.backdrop.style.pointerEvents = "none";
|
|
@@ -637,7 +656,10 @@ var OverlayEngine = class {
|
|
|
637
656
|
}
|
|
638
657
|
}
|
|
639
658
|
this.releaseInteractiveListeners();
|
|
640
|
-
this.awaitTransition(
|
|
659
|
+
this.awaitTransition(
|
|
660
|
+
cycle,
|
|
661
|
+
this.exitAnimation === "fade" ? "opacity" : "transform"
|
|
662
|
+
).then(() => {
|
|
641
663
|
if (this.destroyed || cycle !== this.cycleNonce) return resolve();
|
|
642
664
|
this.isAnimating_ = false;
|
|
643
665
|
if (this.peekPx === void 0) {
|
|
@@ -665,14 +687,12 @@ var OverlayEngine = class {
|
|
|
665
687
|
this.releaseInteractiveListeners();
|
|
666
688
|
this.releaseStackEntry?.();
|
|
667
689
|
this.releaseStackEntry = null;
|
|
690
|
+
const hadOriginalParent = this.originalParent !== null;
|
|
668
691
|
this.restoreMount();
|
|
692
|
+
if (this.movedToTarget && !hadOriginalParent && this.element.parentNode) {
|
|
693
|
+
this.element.parentNode.removeChild(this.element);
|
|
694
|
+
}
|
|
669
695
|
}
|
|
670
|
-
/**
|
|
671
|
-
* Runtime visual setter — opacity is applied immediately when open so a
|
|
672
|
-
* live-controls panel can preview the change without re-opening. When
|
|
673
|
-
* closed we only stash the field; next open() picks it up so we don't
|
|
674
|
-
* mutate hidden DOM (avoids paint thrash on detached panels).
|
|
675
|
-
*/
|
|
676
696
|
setBackdropOpacity(opacity) {
|
|
677
697
|
if (this.destroyed) return;
|
|
678
698
|
this.backdropOpacity = opacity;
|
|
@@ -680,11 +700,6 @@ var OverlayEngine = class {
|
|
|
680
700
|
this.backdrop.style.opacity = String(opacity);
|
|
681
701
|
}
|
|
682
702
|
}
|
|
683
|
-
/**
|
|
684
|
-
* Runtime visual setter for `backdrop-filter`. `undefined` clears the
|
|
685
|
-
* filter (mirrors the close() behaviour). Defers to next open() when the
|
|
686
|
-
* overlay is currently closed.
|
|
687
|
-
*/
|
|
688
703
|
setBackdropFilter(filter) {
|
|
689
704
|
if (this.destroyed) return;
|
|
690
705
|
this.backdropFilter = filter;
|
|
@@ -698,12 +713,6 @@ var OverlayEngine = class {
|
|
|
698
713
|
style.webkitBackdropFilter = "";
|
|
699
714
|
}
|
|
700
715
|
}
|
|
701
|
-
/**
|
|
702
|
-
* Toggle / reconfigure swipe-to-close at runtime. Re-resolves the flat
|
|
703
|
-
* threshold fields, then installs or tears down the pointer listener so
|
|
704
|
-
* the change applies to the CURRENT open cycle. Idempotent: safe to call
|
|
705
|
-
* with the same config repeatedly.
|
|
706
|
-
*/
|
|
707
716
|
setSwipeToClose(config) {
|
|
708
717
|
if (this.destroyed) return;
|
|
709
718
|
const wasEnabled = this.swipeToCloseEnabled;
|
|
@@ -716,7 +725,6 @@ var OverlayEngine = class {
|
|
|
716
725
|
this.detachSwipe = null;
|
|
717
726
|
}
|
|
718
727
|
}
|
|
719
|
-
/** Animation setters only stage the field — applied on next open/close. */
|
|
720
728
|
setEnterAnimation(animation) {
|
|
721
729
|
if (this.destroyed) return;
|
|
722
730
|
this.enterAnimation = animation;
|
|
@@ -725,23 +733,15 @@ var OverlayEngine = class {
|
|
|
725
733
|
if (this.destroyed) return;
|
|
726
734
|
this.exitAnimation = animation;
|
|
727
735
|
}
|
|
728
|
-
/** Updates the field used at next close() — no immediate side effect. */
|
|
729
736
|
setReturnFocus(target) {
|
|
730
737
|
if (this.destroyed) return;
|
|
731
738
|
this.lifecycle.setReturnFocus(target);
|
|
732
739
|
}
|
|
733
|
-
/**
|
|
734
|
-
* Batch update — preset first, then individual overrides. Applied as a
|
|
735
|
-
* single DOM-write phase when open so live-controls don't trigger
|
|
736
|
-
* cascading reflows. Safe to call when closed (defers to next open).
|
|
737
|
-
*/
|
|
738
740
|
setOverlay(opts) {
|
|
739
741
|
if (this.destroyed) return;
|
|
740
742
|
const preset = opts.preset ? OVERLAY_PRESETS[opts.preset] : void 0;
|
|
741
743
|
const merged = preset ? {
|
|
742
744
|
backdropOpacity: preset.backdropOpacity,
|
|
743
|
-
// Preset's `undefined` filter means "no filter" — surface as null
|
|
744
|
-
// so the merged step clears the field instead of inheriting old.
|
|
745
745
|
backdropFilter: preset.backdropFilter === void 0 ? null : preset.backdropFilter,
|
|
746
746
|
swipeToClose: preset.swipeToClose,
|
|
747
747
|
enterAnimation: preset.enterAnimation,
|
|
@@ -792,6 +792,36 @@ var OverlayEngine = class {
|
|
|
792
792
|
}
|
|
793
793
|
}
|
|
794
794
|
}
|
|
795
|
+
if (opts.duration !== void 0) this.duration = opts.duration;
|
|
796
|
+
if (opts.enterEasing !== void 0) this.enterEasing = opts.enterEasing;
|
|
797
|
+
if (opts.exitEasing !== void 0) this.exitEasing = opts.exitEasing;
|
|
798
|
+
if (opts.closeOnEscape !== void 0) {
|
|
799
|
+
this.closeOnEscape = opts.closeOnEscape;
|
|
800
|
+
if (!opts.closeOnEscape) {
|
|
801
|
+
this.detachEscape?.();
|
|
802
|
+
this.detachEscape = null;
|
|
803
|
+
} else if (this.isOpen_) {
|
|
804
|
+
this.installInteractiveListeners();
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (opts.closeOnBackdrop !== void 0) {
|
|
808
|
+
this.closeOnBackdropEnabled = opts.closeOnBackdrop;
|
|
809
|
+
if (!opts.closeOnBackdrop) {
|
|
810
|
+
this.detachBackdrop?.();
|
|
811
|
+
this.detachBackdrop = null;
|
|
812
|
+
} else if (this.isOpen_) {
|
|
813
|
+
this.installInteractiveListeners();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (opts.closeOnOutsidePointer !== void 0) {
|
|
817
|
+
this.closeOnOutsidePointerEnabled = opts.closeOnOutsidePointer;
|
|
818
|
+
if (!opts.closeOnOutsidePointer) {
|
|
819
|
+
this.detachOutsidePointer?.();
|
|
820
|
+
this.detachOutsidePointer = null;
|
|
821
|
+
} else if (this.isOpen_) {
|
|
822
|
+
this.installInteractiveListeners();
|
|
823
|
+
}
|
|
824
|
+
}
|
|
795
825
|
if (opts.children !== void 0) {
|
|
796
826
|
if (opts.children === null) {
|
|
797
827
|
this.clearOverlayChildren();
|
|
@@ -800,30 +830,17 @@ var OverlayEngine = class {
|
|
|
800
830
|
}
|
|
801
831
|
}
|
|
802
832
|
}
|
|
803
|
-
/**
|
|
804
|
-
* Runtime children injection — replace the panel's contents without
|
|
805
|
-
* unmounting the engine. `replaceChildren()` is a single atomic DOM op,
|
|
806
|
-
* so callers can swap content rapidly without intermediate flicker.
|
|
807
|
-
* Function form is resolved exactly ONCE per call (cheap lazy content).
|
|
808
|
-
*/
|
|
809
833
|
setOverlayChildren(children) {
|
|
810
834
|
if (this.destroyed) return;
|
|
811
835
|
const node = typeof children === "function" ? children() : children;
|
|
812
836
|
this.element.replaceChildren(node);
|
|
813
837
|
this.hasInjectedChildren = true;
|
|
814
838
|
}
|
|
815
|
-
/**
|
|
816
|
-
* Clear injected children. No-op when nothing was injected so consumers
|
|
817
|
-
* who never used setOverlayChildren can call this defensively without
|
|
818
|
-
* paying for a DOM mutation.
|
|
819
|
-
*/
|
|
820
839
|
clearOverlayChildren() {
|
|
821
840
|
if (this.destroyed || !this.hasInjectedChildren) return;
|
|
822
841
|
this.element.replaceChildren();
|
|
823
842
|
this.hasInjectedChildren = false;
|
|
824
843
|
}
|
|
825
|
-
// Shared swipe-config normalizer — used by constructor, setSwipeToClose,
|
|
826
|
-
// and setOverlay so the union/object shape is decoded in exactly one place.
|
|
827
844
|
assignSwipeFromConfig(swipe) {
|
|
828
845
|
if (typeof swipe === "object" && swipe !== null) {
|
|
829
846
|
this.swipeToCloseEnabled = swipe.enabled ?? true;
|
|
@@ -838,6 +855,22 @@ var OverlayEngine = class {
|
|
|
838
855
|
emit(event, payload) {
|
|
839
856
|
this.bus.emit(event, payload);
|
|
840
857
|
}
|
|
858
|
+
effectiveDuration() {
|
|
859
|
+
return this.respectReducedMotionEnabled && prefersReducedMotion() ? 0 : this.duration;
|
|
860
|
+
}
|
|
861
|
+
popBackMarker(reason) {
|
|
862
|
+
if (!this.backPushed) return;
|
|
863
|
+
this.backPushed = false;
|
|
864
|
+
if (reason === "back") return;
|
|
865
|
+
const state = history.state;
|
|
866
|
+
if (state && state.__bsOverlay === this.id) {
|
|
867
|
+
try {
|
|
868
|
+
history.back();
|
|
869
|
+
} catch {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
841
874
|
resolvePeekPx(raw) {
|
|
842
875
|
if (typeof raw === "number") return Math.max(0, raw);
|
|
843
876
|
try {
|
|
@@ -859,6 +892,7 @@ var OverlayEngine = class {
|
|
|
859
892
|
this.originalParent = this.element.parentNode;
|
|
860
893
|
this.originalNextSibling = this.element.nextSibling;
|
|
861
894
|
target.appendChild(this.element);
|
|
895
|
+
this.movedToTarget = true;
|
|
862
896
|
this.mounted = true;
|
|
863
897
|
}
|
|
864
898
|
restoreMount() {
|
|
@@ -892,7 +926,7 @@ var OverlayEngine = class {
|
|
|
892
926
|
this.backdrop.style.willChange = "opacity";
|
|
893
927
|
}
|
|
894
928
|
}
|
|
895
|
-
awaitTransition(cycle) {
|
|
929
|
+
awaitTransition(cycle, property = "transform") {
|
|
896
930
|
return new Promise((resolve) => {
|
|
897
931
|
let done = false;
|
|
898
932
|
const finish = () => {
|
|
@@ -907,10 +941,10 @@ var OverlayEngine = class {
|
|
|
907
941
|
finish();
|
|
908
942
|
return;
|
|
909
943
|
}
|
|
910
|
-
if (e.propertyName ===
|
|
944
|
+
if (e.target === this.element && e.propertyName === property) finish();
|
|
911
945
|
};
|
|
912
946
|
this.element.addEventListener("transitionend", onEnd);
|
|
913
|
-
const timer = window.setTimeout(finish, this.
|
|
947
|
+
const timer = window.setTimeout(finish, this.effectiveDuration() + 50);
|
|
914
948
|
});
|
|
915
949
|
}
|
|
916
950
|
installInteractiveListeners() {
|
|
@@ -920,7 +954,10 @@ var OverlayEngine = class {
|
|
|
920
954
|
}
|
|
921
955
|
if (this.closeOnEscape && !this.detachEscape) {
|
|
922
956
|
const handler = (e) => {
|
|
923
|
-
if (e.
|
|
957
|
+
if (e.defaultPrevented) return;
|
|
958
|
+
if (e.key === "Escape" && this.isOpen_ && this.isTop) {
|
|
959
|
+
void this.close("escape");
|
|
960
|
+
}
|
|
924
961
|
};
|
|
925
962
|
document.addEventListener("keydown", handler);
|
|
926
963
|
this.detachEscape = () => document.removeEventListener("keydown", handler);
|
|
@@ -933,10 +970,14 @@ var OverlayEngine = class {
|
|
|
933
970
|
if (this.closeOnBackEnabled && typeof window !== "undefined" && !this.detachHardwareBack) {
|
|
934
971
|
try {
|
|
935
972
|
history.pushState({ __bsOverlay: this.id }, "");
|
|
973
|
+
this.backPushed = true;
|
|
936
974
|
} catch {
|
|
937
975
|
}
|
|
938
976
|
const onPop = () => {
|
|
939
|
-
if (this.isOpen_
|
|
977
|
+
if (this.isOpen_ && this.isTop) {
|
|
978
|
+
this.backPushed = false;
|
|
979
|
+
void this.close("back");
|
|
980
|
+
}
|
|
940
981
|
};
|
|
941
982
|
window.addEventListener("popstate", onPop);
|
|
942
983
|
this.detachHardwareBack = () => window.removeEventListener("popstate", onPop);
|
|
@@ -967,55 +1008,57 @@ var OverlayEngine = class {
|
|
|
967
1008
|
this.detachOutsidePointer = null;
|
|
968
1009
|
this.lifecycle.release();
|
|
969
1010
|
}
|
|
970
|
-
/**
|
|
971
|
-
* Light pointer-driven dismiss. Tracks finger along the edge axis, follows
|
|
972
|
-
* with translate, dismisses if past `swipeThreshold` (fraction of panel
|
|
973
|
-
* size) or release velocity exceeds `swipeVelocityThreshold` (px/ms).
|
|
974
|
-
* Pure CSS-transform during drag — no rAF or physics required since the
|
|
975
|
-
* panel only moves one direction.
|
|
976
|
-
*/
|
|
977
1011
|
installSwipeToClose() {
|
|
978
1012
|
const el = this.element;
|
|
979
1013
|
const edge = this.edge;
|
|
980
1014
|
const isVertical = edge === "bottom" || edge === "top";
|
|
981
1015
|
const sign = edge === "bottom" || edge === "right" ? 1 : -1;
|
|
982
|
-
const
|
|
983
|
-
const velocityThreshold = this.swipeVelocityThreshold;
|
|
1016
|
+
const SLOP = 8;
|
|
984
1017
|
let startX = 0;
|
|
985
1018
|
let startY = 0;
|
|
986
1019
|
let startT = 0;
|
|
1020
|
+
let tracking = false;
|
|
987
1021
|
let dragging = false;
|
|
988
1022
|
let pointerId = null;
|
|
1023
|
+
const previousTouchAction = el.style.touchAction;
|
|
1024
|
+
el.style.touchAction = isVertical ? "pan-x" : "pan-y";
|
|
989
1025
|
const onDown = (e) => {
|
|
990
1026
|
if (e.button !== void 0 && e.button !== 0) return;
|
|
991
1027
|
pointerId = e.pointerId;
|
|
992
1028
|
startX = e.clientX;
|
|
993
1029
|
startY = e.clientY;
|
|
994
1030
|
startT = performance.now();
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
try {
|
|
998
|
-
el.setPointerCapture(e.pointerId);
|
|
999
|
-
} catch {
|
|
1000
|
-
}
|
|
1031
|
+
tracking = true;
|
|
1032
|
+
dragging = false;
|
|
1001
1033
|
};
|
|
1002
1034
|
const onMove = (e) => {
|
|
1003
|
-
if (!
|
|
1035
|
+
if (!tracking || e.pointerId !== pointerId) return;
|
|
1004
1036
|
const delta = isVertical ? e.clientY - startY : e.clientX - startX;
|
|
1037
|
+
if (!dragging) {
|
|
1038
|
+
if (Math.abs(delta) < SLOP) return;
|
|
1039
|
+
dragging = true;
|
|
1040
|
+
el.style.transition = "none";
|
|
1041
|
+
try {
|
|
1042
|
+
el.setPointerCapture(e.pointerId);
|
|
1043
|
+
} catch {
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1005
1046
|
const offset = Math.max(0, sign * delta);
|
|
1006
1047
|
el.style.transform = isVertical ? `translate3d(0, ${sign * offset}px, 0)` : `translate3d(${sign * offset}px, 0, 0)`;
|
|
1007
1048
|
};
|
|
1008
1049
|
const finish = (e) => {
|
|
1009
|
-
if (!
|
|
1010
|
-
|
|
1050
|
+
if (!tracking || e.pointerId !== pointerId) return;
|
|
1051
|
+
tracking = false;
|
|
1011
1052
|
pointerId = null;
|
|
1053
|
+
if (!dragging) return;
|
|
1054
|
+
dragging = false;
|
|
1012
1055
|
const dt = performance.now() - startT || 1;
|
|
1013
1056
|
const delta = isVertical ? e.clientY - startY : e.clientX - startX;
|
|
1014
1057
|
const offset = Math.max(0, sign * delta);
|
|
1015
1058
|
const velocity = sign * delta / dt;
|
|
1016
1059
|
const size = isVertical ? el.offsetHeight : el.offsetWidth;
|
|
1017
|
-
const past = offset > size *
|
|
1018
|
-
el.style.transition = `transform ${this.
|
|
1060
|
+
const past = offset > size * this.swipeThreshold || velocity > this.swipeVelocityThreshold;
|
|
1061
|
+
el.style.transition = `transform ${this.effectiveDuration()}ms ${this.exitEasing}`;
|
|
1019
1062
|
if (past) {
|
|
1020
1063
|
void this.close("swipe");
|
|
1021
1064
|
} else {
|
|
@@ -1027,6 +1070,7 @@ var OverlayEngine = class {
|
|
|
1027
1070
|
el.addEventListener("pointerup", finish);
|
|
1028
1071
|
el.addEventListener("pointercancel", finish);
|
|
1029
1072
|
return () => {
|
|
1073
|
+
el.style.touchAction = previousTouchAction;
|
|
1030
1074
|
el.removeEventListener("pointerdown", onDown);
|
|
1031
1075
|
el.removeEventListener("pointermove", onMove);
|
|
1032
1076
|
el.removeEventListener("pointerup", finish);
|
|
@@ -1041,10 +1085,12 @@ var OverlayEngine = class {
|
|
|
1041
1085
|
if (this.backdrop) this.backdrop.style.zIndex = String(z - 1);
|
|
1042
1086
|
},
|
|
1043
1087
|
setIsTop: (isTop) => {
|
|
1088
|
+
this.isTop = isTop;
|
|
1044
1089
|
if (this.backdrop) {
|
|
1045
1090
|
this.backdrop.style.display = isTop ? "" : "none";
|
|
1046
1091
|
}
|
|
1047
|
-
}
|
|
1092
|
+
},
|
|
1093
|
+
isOpen: () => this.isOpen_
|
|
1048
1094
|
});
|
|
1049
1095
|
}
|
|
1050
1096
|
};
|