@surdeddd/bottom-sheet 0.9.1 → 0.11.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.
Files changed (50) hide show
  1. package/README.md +126 -83
  2. package/dist/{BottomSheetEngine-CBRcBwV-.d.ts → BottomSheetEngine-DRjCU73e.d.ts} +6 -5
  3. package/dist/{BottomSheetEngine-BcyruNP1.d.cts → BottomSheetEngine-DzsIwD28.d.cts} +6 -5
  4. package/dist/element.cjs +388 -222
  5. package/dist/element.d.cts +1 -1
  6. package/dist/element.d.ts +1 -1
  7. package/dist/element.iife.global.js +2 -2
  8. package/dist/element.js +388 -222
  9. package/dist/index.cjs +389 -266
  10. package/dist/index.d.cts +2 -2
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.js +389 -266
  13. package/dist/integrations/formik.d.cts +1 -1
  14. package/dist/integrations/formik.d.ts +1 -1
  15. package/dist/integrations/react-hook-form.d.cts +1 -1
  16. package/dist/integrations/react-hook-form.d.ts +1 -1
  17. package/dist/overlay.cjs +191 -58
  18. package/dist/overlay.d.cts +4 -3
  19. package/dist/overlay.d.ts +4 -3
  20. package/dist/overlay.js +191 -58
  21. package/dist/preact.d.cts +2 -1
  22. package/dist/preact.d.ts +2 -1
  23. package/dist/qwik.cjs +396 -211
  24. package/dist/qwik.d.cts +11 -2
  25. package/dist/qwik.d.ts +11 -2
  26. package/dist/qwik.js +396 -211
  27. package/dist/react.cjs +500 -332
  28. package/dist/react.d.cts +26 -4
  29. package/dist/react.d.ts +26 -4
  30. package/dist/react.js +500 -332
  31. package/dist/solid.cjs +362 -210
  32. package/dist/solid.d.cts +14 -4
  33. package/dist/solid.d.ts +14 -4
  34. package/dist/solid.js +362 -210
  35. package/dist/solid.source.jsx +362 -210
  36. package/dist/styles.css +17 -1
  37. package/dist/svelte-core.cjs +345 -208
  38. package/dist/svelte-core.js +345 -208
  39. package/dist/svelte-src/BottomSheet.svelte +6 -0
  40. package/dist/svelte.cjs +359 -220
  41. package/dist/svelte.d.cts +2 -0
  42. package/dist/svelte.d.ts +2 -0
  43. package/dist/svelte.js +359 -220
  44. package/dist/teleport-7uYNiEAT.d.cts +3 -0
  45. package/dist/teleport-7uYNiEAT.d.ts +3 -0
  46. package/dist/vue.cjs +353 -209
  47. package/dist/vue.d.cts +3 -1
  48. package/dist/vue.d.ts +3 -1
  49. package/dist/vue.js +353 -209
  50. package/package.json +11 -9
package/dist/qwik.js CHANGED
@@ -184,11 +184,18 @@ var resolveSnap = (point, mode, measureFit) => {
184
184
  return Math.max(0, el.getBoundingClientRect().width);
185
185
  };
186
186
 
187
+ // src/core/primitives/devWarn.ts
188
+ function devWarn(...args) {
189
+ if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
190
+ console.warn(...args);
191
+ }
192
+ }
193
+
187
194
  // src/core/primitives/snap-points.ts
188
195
  var resolveSnapList = (points, mode, measureFit) => points.map((p) => {
189
196
  const raw = resolveSnap(p.size, mode, measureFit);
190
197
  if (!Number.isFinite(raw) || raw < 0) {
191
- console.warn(
198
+ devWarn(
192
199
  `[BottomSheet] snap "${p.id}" resolved to invalid size (${String(p.size)} \u2192 ${raw}); clamped to 0.`
193
200
  );
194
201
  return { id: p.id, size: 0 };
@@ -200,7 +207,7 @@ var auditVhUsage = (points) => {
200
207
  for (const p of points) {
201
208
  if (typeof p.size !== "string") continue;
202
209
  if (!VH_NOT_DVH.test(p.size)) continue;
203
- console.warn(
210
+ devWarn(
204
211
  `[BottomSheet] snap "${p.id}" uses "vh" \u2014 prefer "dvh" on mobile (iOS Safari URL bar makes vh unstable). See https://web.dev/blog/viewport-units`
205
212
  );
206
213
  }
@@ -254,6 +261,26 @@ var findDragSettleTarget = (input) => {
254
261
  );
255
262
  };
256
263
 
264
+ // src/core/primitives/cancelable-emit.ts
265
+ function emitCancelable(emit, base, warnLabel) {
266
+ let cancelled = false;
267
+ let frozen = false;
268
+ emit({
269
+ ...base,
270
+ cancel: () => {
271
+ if (frozen) {
272
+ devWarn(
273
+ `[BottomSheet] ${warnLabel}.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener.`
274
+ );
275
+ return;
276
+ }
277
+ cancelled = true;
278
+ }
279
+ });
280
+ frozen = true;
281
+ return cancelled;
282
+ }
283
+
257
284
  // src/core/primitives/snap-resolver.ts
258
285
  var SnapResolver = class {
259
286
  constructor(raw, allowed, mode, measureFit, onMaxAxisSizeChange) {
@@ -398,7 +425,9 @@ function notifyLinkedSheets(sheets, self) {
398
425
  try {
399
426
  if (linked.state.size === 0) continue;
400
427
  const ids = linked.getAllowedIds();
401
- const target = ids.find((id) => id !== "closed");
428
+ const resolved = linked.getResolvedSnaps();
429
+ const sizeOf = (id) => resolved.find((s) => s.id === id)?.size ?? 0;
430
+ const target = ids.find((id) => sizeOf(id) > 0);
402
431
  if (target && target !== linked.state.activeId) {
403
432
  void linked.snapTo(target);
404
433
  }
@@ -410,46 +439,198 @@ function notifyLinkedSheets(sheets, self) {
410
439
  }
411
440
  }
412
441
 
413
- // src/core/features/route.ts
414
- var closers = [];
415
- var handlerInstalled = false;
442
+ // src/core/features/history-coordinator.ts
443
+ var entries = [];
444
+ var subscribers = /* @__PURE__ */ new Set();
416
445
  var suppress = 0;
417
- var ensureHandler = () => {
418
- if (handlerInstalled || typeof window === "undefined") return;
419
- handlerInstalled = true;
420
- window.addEventListener("popstate", () => {
421
- if (suppress > 0) {
422
- suppress -= 1;
423
- return;
446
+ var internalDepth = 0;
447
+ var pendingRebrand = null;
448
+ var listenerInstalled = false;
449
+ var patchInstalled = false;
450
+ var origPushState = null;
451
+ var origReplaceState = null;
452
+ var patchedPushState = null;
453
+ var patchedReplaceState = null;
454
+ var hasWindow = () => typeof window !== "undefined";
455
+ var hasHistory = () => typeof history !== "undefined";
456
+ var runInternal = (fn) => {
457
+ internalDepth += 1;
458
+ try {
459
+ fn();
460
+ } finally {
461
+ internalDepth -= 1;
462
+ }
463
+ };
464
+ var notifyRouteChange = () => {
465
+ const snapshot = Array.from(subscribers);
466
+ for (const fn of snapshot) fn();
467
+ };
468
+ var ensureListener = () => {
469
+ if (listenerInstalled || !hasWindow()) return;
470
+ window.addEventListener("popstate", onPopState);
471
+ listenerInstalled = true;
472
+ };
473
+ var maybeTeardownListener = () => {
474
+ if (!listenerInstalled) return;
475
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
476
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
477
+ listenerInstalled = false;
478
+ };
479
+ var installPatch = () => {
480
+ if (patchInstalled || !hasHistory()) return;
481
+ origPushState = history.pushState;
482
+ origReplaceState = history.replaceState;
483
+ patchedPushState = function(...args) {
484
+ origPushState.apply(this, args);
485
+ if (internalDepth === 0) notifyRouteChange();
486
+ };
487
+ patchedReplaceState = function(...args) {
488
+ origReplaceState.apply(this, args);
489
+ if (internalDepth === 0) notifyRouteChange();
490
+ };
491
+ history.pushState = patchedPushState;
492
+ history.replaceState = patchedReplaceState;
493
+ patchInstalled = true;
494
+ };
495
+ var removePatch = () => {
496
+ if (!patchInstalled) return;
497
+ if (history.pushState === patchedPushState && origPushState) {
498
+ history.pushState = origPushState;
499
+ }
500
+ if (history.replaceState === patchedReplaceState && origReplaceState) {
501
+ history.replaceState = origReplaceState;
502
+ }
503
+ origPushState = null;
504
+ origReplaceState = null;
505
+ patchedPushState = null;
506
+ patchedReplaceState = null;
507
+ patchInstalled = false;
508
+ };
509
+ function onPopState() {
510
+ if (suppress > 0) {
511
+ suppress -= 1;
512
+ const rebrand = pendingRebrand;
513
+ pendingRebrand = null;
514
+ if (rebrand) rebrand();
515
+ maybeTeardownListener();
516
+ return;
517
+ }
518
+ if (entries.length === 0) {
519
+ notifyRouteChange();
520
+ maybeTeardownListener();
521
+ return;
522
+ }
523
+ let target = null;
524
+ let targetIdx = -1;
525
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
526
+ const entry = entries[i];
527
+ if (!entry.surface.isOpen()) continue;
528
+ if (!target) {
529
+ target = entry;
530
+ targetIdx = i;
424
531
  }
425
- let target = null;
426
- for (let i = closers.length - 1; i >= 0; i -= 1) {
427
- const c = closers[i];
428
- if (!c.isOpen()) continue;
429
- if (!target) target = c;
430
- if (c.isTop()) {
431
- target = c;
432
- break;
433
- }
532
+ if (entry.surface.isTop()) {
533
+ target = entry;
534
+ targetIdx = i;
535
+ break;
434
536
  }
435
- if (!target) return;
436
- target.consumed = true;
437
- const idx = closers.indexOf(target);
438
- if (idx !== -1) closers.splice(idx, 1);
439
- const settled = target;
440
- void Promise.resolve(settled.close()).then(() => {
441
- if (settled.isOpen()) settled.restore();
442
- });
537
+ }
538
+ if (!target) {
539
+ const removed = entries.pop();
540
+ if (removed) removed.live = false;
541
+ maybeTeardownListener();
542
+ return;
543
+ }
544
+ target.live = false;
545
+ entries.splice(targetIdx, 1);
546
+ const settled = target;
547
+ void Promise.resolve(settled.surface.close()).then(() => {
548
+ if (settled.surface.isOpen()) restore(settled);
443
549
  });
444
- };
445
- var programmaticBack = () => {
446
- suppress += 1;
447
- try {
448
- history.back();
449
- } catch {
450
- suppress = Math.max(0, suppress - 1);
550
+ maybeTeardownListener();
551
+ }
552
+ function restore(entry) {
553
+ if (!hasWindow() || !hasHistory()) return;
554
+ entry.live = true;
555
+ runInternal(() => {
556
+ try {
557
+ history.pushState(entry.surface.markerState(), "", entry.surface.url);
558
+ } catch {
559
+ entry.live = false;
560
+ }
561
+ });
562
+ if (entry.live) {
563
+ entries.push(entry);
564
+ ensureListener();
451
565
  }
452
- };
566
+ }
567
+ function pushBackMarker(surface) {
568
+ const entry = { surface, priorUrl: void 0, live: false };
569
+ if (!hasWindow() || !hasHistory()) {
570
+ return entry;
571
+ }
572
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
573
+ runInternal(() => {
574
+ try {
575
+ history.pushState(surface.markerState(), "", surface.url);
576
+ entry.live = true;
577
+ } catch {
578
+ entry.live = false;
579
+ }
580
+ });
581
+ if (entry.live) {
582
+ entries.push(entry);
583
+ ensureListener();
584
+ }
585
+ return entry;
586
+ }
587
+ function popBackMarker(handle) {
588
+ if (!hasWindow() || !hasHistory()) return;
589
+ const entry = handle;
590
+ const idx = entries.indexOf(entry);
591
+ if (idx === -1 || !entry.live) return;
592
+ const last = entries.length - 1;
593
+ entry.live = false;
594
+ entries.splice(idx, 1);
595
+ if (idx !== last) {
596
+ const rebrandSurface = entries[entries.length - 1].surface;
597
+ const rebrandUrl = entry.priorUrl;
598
+ pendingRebrand = () => {
599
+ runInternal(() => {
600
+ history.replaceState(
601
+ rebrandSurface.markerState(),
602
+ "",
603
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
604
+ );
605
+ });
606
+ };
607
+ }
608
+ suppress += 1;
609
+ runInternal(() => {
610
+ try {
611
+ history.back();
612
+ } catch {
613
+ suppress = Math.max(0, suppress - 1);
614
+ pendingRebrand = null;
615
+ }
616
+ });
617
+ maybeTeardownListener();
618
+ }
619
+ function subscribeRouteChange(fn) {
620
+ if (!hasWindow() || !hasHistory()) return () => {
621
+ };
622
+ subscribers.add(fn);
623
+ if (subscribers.size === 1) installPatch();
624
+ ensureListener();
625
+ return () => {
626
+ if (!subscribers.has(fn)) return;
627
+ subscribers.delete(fn);
628
+ if (subscribers.size === 0) removePatch();
629
+ maybeTeardownListener();
630
+ };
631
+ }
632
+
633
+ // src/core/features/route.ts
453
634
  function installRoute(deps) {
454
635
  if (typeof window === "undefined") {
455
636
  return () => {
@@ -459,92 +640,39 @@ function installRoute(deps) {
459
640
  return () => {
460
641
  };
461
642
  }
462
- let pushed = false;
463
- let closer = null;
464
- const teardownEntry = () => {
465
- if (closer) {
466
- const idx = closers.indexOf(closer);
467
- if (idx !== -1) closers.splice(idx, 1);
468
- }
469
- const wasConsumed = closer?.consumed ?? false;
470
- closer = null;
471
- if (pushed && !wasConsumed) {
472
- pushed = false;
473
- programmaticBack();
474
- } else {
475
- pushed = false;
476
- }
477
- };
478
- const pushEntry = () => {
479
- try {
480
- if (deps.routedTo !== void 0) {
481
- history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
482
- } else {
483
- history.pushState({ __bsSheet: deps.sheetId }, "");
484
- }
485
- pushed = true;
486
- } catch {
487
- pushed = false;
488
- }
489
- };
643
+ let handle = null;
490
644
  const onOpen = () => {
491
- if (closer) return;
492
- const self = {
645
+ if (handle) return;
646
+ handle = pushBackMarker({
493
647
  isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
494
- isTop: () => deps.isTopSheet(),
495
- close: () => deps.close(),
496
- restore: () => {
497
- if (deps.isDestroyed() || closer !== self) return;
498
- self.consumed = false;
499
- if (!closers.includes(self)) closers.push(self);
500
- pushEntry();
501
- },
502
- consumed: false
503
- };
504
- closer = self;
505
- ensureHandler();
506
- closers.push(self);
507
- pushEntry();
648
+ isTop: deps.isTopSheet,
649
+ close: deps.close,
650
+ markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
651
+ url: deps.routedTo
652
+ });
653
+ };
654
+ const onClose = () => {
655
+ if (!handle) return;
656
+ popBackMarker(handle);
657
+ handle = null;
508
658
  };
509
659
  const unsubscribeOpen = deps.on("open", onOpen);
510
- const unsubscribeClose = deps.on("close", teardownEntry);
660
+ const unsubscribeClose = deps.on("close", onClose);
511
661
  if (deps.getSize() > 0) onOpen();
512
662
  return () => {
513
663
  unsubscribeOpen();
514
664
  unsubscribeClose();
515
- teardownEntry();
665
+ if (handle) {
666
+ popBackMarker(handle);
667
+ handle = null;
668
+ }
516
669
  };
517
670
  }
518
671
  function installRouteChange(deps) {
519
- if (typeof window === "undefined" || typeof history === "undefined") {
520
- return () => {
521
- };
522
- }
523
- const onChange = () => {
672
+ return subscribeRouteChange(() => {
524
673
  if (deps.isDestroyed() || deps.getSize() <= 0) return;
525
674
  void deps.close();
526
- };
527
- const wrap = (key) => {
528
- const original = history[key];
529
- const patched = function(...args) {
530
- original.apply(this, args);
531
- onChange();
532
- };
533
- history[key] = patched;
534
- return () => {
535
- if (history[key] === patched) {
536
- history[key] = original;
537
- }
538
- };
539
- };
540
- window.addEventListener("popstate", onChange);
541
- const restorePush = wrap("pushState");
542
- const restoreReplace = wrap("replaceState");
543
- return () => {
544
- window.removeEventListener("popstate", onChange);
545
- restorePush();
546
- restoreReplace();
547
- };
675
+ });
548
676
  }
549
677
 
550
678
  // src/core/features/content-swipe.ts
@@ -615,11 +743,15 @@ function installVisualViewport(deps) {
615
743
  if (clamp < deps.getMaxAxisSize()) {
616
744
  deps.setMaxAxisSize(clamp);
617
745
  }
746
+ const wasAnimating = deps.isAnimating?.() ?? false;
747
+ deps.cancelInFlight();
748
+ deps.newCycle();
618
749
  const max = deps.getMaxAxisSize();
619
750
  deps.setSize(Math.min(deps.getSize(), max));
620
751
  const current = deps.resolveActiveSnap();
621
752
  if (current && !deps.isDragging()) {
622
753
  deps.applySize(Math.min(current.size, max));
754
+ if (wasAnimating) deps.resyncAfterCancel?.();
623
755
  } else {
624
756
  deps.applySize(deps.getSize());
625
757
  }
@@ -760,6 +892,54 @@ function installFitObserver(deps) {
760
892
  };
761
893
  }
762
894
 
895
+ // src/core/features/max-height-controller.ts
896
+ function createMaxHeightController(deps) {
897
+ let maxHeightCap;
898
+ let maxHeightRaw;
899
+ const resolveCap = () => {
900
+ const raw = maxHeightRaw;
901
+ if (raw === void 0) {
902
+ maxHeightCap = void 0;
903
+ return;
904
+ }
905
+ if (typeof raw === "number") {
906
+ maxHeightCap = raw;
907
+ return;
908
+ }
909
+ if (typeof window === "undefined") return;
910
+ const measured = resolveSnap(raw, deps.mode);
911
+ maxHeightCap = measured > 0 ? measured : void 0;
912
+ };
913
+ const clampTo = () => {
914
+ if (maxHeightCap === void 0) return;
915
+ if (deps.getMaxAxisSize() > maxHeightCap) {
916
+ deps.setMaxAxisSize(maxHeightCap);
917
+ }
918
+ };
919
+ return {
920
+ setRadius(r) {
921
+ deps.element.style.setProperty(
922
+ "--bs-radius",
923
+ typeof r === "number" ? `${r}px` : r
924
+ );
925
+ },
926
+ setMaxHeight(h) {
927
+ const value = typeof h === "number" ? `${h}px` : h;
928
+ const axis = layoutAxis(deps.mode);
929
+ deps.element.style.setProperty(
930
+ axis === "height" ? "max-height" : "max-width",
931
+ value
932
+ );
933
+ maxHeightRaw = h;
934
+ resolveCap();
935
+ deps.recompute();
936
+ },
937
+ resolveCap,
938
+ clampTo,
939
+ getCap: () => maxHeightCap
940
+ };
941
+ }
942
+
763
943
  // src/core/features/fit-measurement.ts
764
944
  var measureSheetNatural = (element, content, vertical) => {
765
945
  if (!content) {
@@ -891,13 +1071,6 @@ var WriteSentinel = class {
891
1071
  }
892
1072
  };
893
1073
 
894
- // src/core/primitives/devWarn.ts
895
- function devWarn(...args) {
896
- if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && typeof console !== "undefined") {
897
- console.warn(...args);
898
- }
899
- }
900
-
901
1074
  // src/core/primitives/overlay-position.ts
902
1075
  function resolveSheetAnchoredStyle(mode, position, inset) {
903
1076
  const sizeOffset = `calc(var(--bs-size, 0px) + ${inset})`;
@@ -2352,7 +2525,7 @@ var matchesRange = (def, progress) => {
2352
2525
  };
2353
2526
  function installScrimStages(deps, opts) {
2354
2527
  const doc = deps.host.ownerDocument;
2355
- const entries = opts.stages.map((def) => {
2528
+ const entries2 = opts.stages.map((def) => {
2356
2529
  const wrapper = doc.createElement("div");
2357
2530
  wrapper.className = "bs-scrim-stage";
2358
2531
  const ws = wrapper.style;
@@ -2373,9 +2546,9 @@ function installScrimStages(deps, opts) {
2373
2546
  const resolveActive = () => {
2374
2547
  const state = deps.getState();
2375
2548
  if (state.size === 0) {
2376
- return entries.find((e) => matchesId(e.def, state.activeId)) ?? null;
2549
+ return entries2.find((e) => matchesId(e.def, state.activeId)) ?? null;
2377
2550
  }
2378
- return entries.find((e) => matchesId(e.def, state.activeId)) ?? entries.find((e) => matchesRange(e.def, state.progress)) ?? null;
2551
+ return entries2.find((e) => matchesId(e.def, state.activeId)) ?? entries2.find((e) => matchesRange(e.def, state.progress)) ?? null;
2379
2552
  };
2380
2553
  const hide = (entry, animate) => {
2381
2554
  entry.inFlight?.cancel();
@@ -2424,13 +2597,13 @@ function installScrimStages(deps, opts) {
2424
2597
  deps.on("open", () => update(true)),
2425
2598
  deps.on("close", () => update(true))
2426
2599
  ];
2427
- if (entries.some((e) => e.def.forRange)) {
2600
+ if (entries2.some((e) => e.def.forRange)) {
2428
2601
  offs.push(deps.on("progress", () => update(true)));
2429
2602
  }
2430
2603
  update(false);
2431
2604
  return () => {
2432
2605
  offs.forEach((off) => off());
2433
- for (const entry of entries) {
2606
+ for (const entry of entries2) {
2434
2607
  entry.inFlight?.cancel();
2435
2608
  entry.inFlight = null;
2436
2609
  if (entry.wrapper.parentNode) {
@@ -2543,8 +2716,7 @@ var BottomSheetEngine = class {
2543
2716
  __publicField(this, "disableCloseFlag");
2544
2717
  __publicField(this, "disableDragFlag");
2545
2718
  __publicField(this, "closeOnRouteChange");
2546
- __publicField(this, "maxHeightCap");
2547
- __publicField(this, "maxHeightRaw");
2719
+ __publicField(this, "maxHeight");
2548
2720
  __publicField(this, "snapPointsRaw");
2549
2721
  __publicField(this, "snaps");
2550
2722
  __publicField(this, "activeId");
@@ -2655,11 +2827,20 @@ var BottomSheetEngine = class {
2655
2827
  auditVhUsage(this.snapPointsRaw);
2656
2828
  this.auditDuplicateSnapIds(this.snapPointsRaw);
2657
2829
  this.rootEl = this.element.closest(".bs-root");
2830
+ this.maxHeight = createMaxHeightController({
2831
+ element: this.element,
2832
+ mode: this.mode,
2833
+ getMaxAxisSize: () => this.snaps.getMaxAxisSize(),
2834
+ setMaxAxisSize: (size) => {
2835
+ this.snaps.setMaxAxisSize(size);
2836
+ },
2837
+ recompute: () => this.recompute()
2838
+ });
2658
2839
  this.fitDeps = {
2659
2840
  element: this.element,
2660
2841
  scrollContainer: this.scrollContainer,
2661
2842
  mode: this.mode,
2662
- getMaxHeightCap: () => this.maxHeightCap
2843
+ getMaxHeightCap: () => this.maxHeight.getCap()
2663
2844
  };
2664
2845
  this.snaps = new SnapResolver(
2665
2846
  this.snapPointsRaw,
@@ -2681,8 +2862,8 @@ var BottomSheetEngine = class {
2681
2862
  this.attach();
2682
2863
  this.registerInStack();
2683
2864
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
2684
- const io = new IntersectionObserver((entries) => {
2685
- for (const entry of entries) {
2865
+ const io = new IntersectionObserver((entries2) => {
2866
+ for (const entry of entries2) {
2686
2867
  if (!entry.isIntersecting) continue;
2687
2868
  if (this.destroyed) return;
2688
2869
  this.snaps.recompute();
@@ -2770,7 +2951,7 @@ var BottomSheetEngine = class {
2770
2951
  }
2771
2952
  use(plugin) {
2772
2953
  if (this.destroyed) {
2773
- console.warn(
2954
+ devWarn(
2774
2955
  `[BottomSheet] use("${plugin.name}") called on destroyed engine \u2014 ignored.`
2775
2956
  );
2776
2957
  return this;
@@ -2812,6 +2993,9 @@ var BottomSheetEngine = class {
2812
2993
  getAllowedIds() {
2813
2994
  return this.snaps.getAllowedIds().slice();
2814
2995
  }
2996
+ getResolvedSnaps() {
2997
+ return this.snaps.getResolvedSnaps();
2998
+ }
2815
2999
  async snapTo(id, velocityOrOpts = 0, _skipBeforeSnap = false) {
2816
3000
  if (this.destroyed) return;
2817
3001
  const opts = typeof velocityOrOpts === "object" ? velocityOrOpts : { velocity: velocityOrOpts };
@@ -2820,11 +3004,11 @@ var BottomSheetEngine = class {
2820
3004
  if (externalSignal?.aborted) return;
2821
3005
  const target = this.snaps.findById(id);
2822
3006
  if (!target) {
2823
- console.warn(`[BottomSheet] unknown snap id: ${id}`);
3007
+ devWarn(`[BottomSheet] unknown snap id: ${id}`);
2824
3008
  return;
2825
3009
  }
2826
3010
  if (!this.snaps.getAllowedIds().includes(id)) {
2827
- console.warn(`[BottomSheet] snap "${id}" is not in allowed list`);
3011
+ devWarn(`[BottomSheet] snap "${id}" is not in allowed list`);
2828
3012
  return;
2829
3013
  }
2830
3014
  if (!_skipBeforeSnap && this.emitBeforeSnap(target, this.activeId)) {
@@ -2956,42 +3140,11 @@ var BottomSheetEngine = class {
2956
3140
  }
2957
3141
  setRadius(r) {
2958
3142
  if (this.destroyed) return;
2959
- this.element.style.setProperty(
2960
- "--bs-radius",
2961
- typeof r === "number" ? `${r}px` : r
2962
- );
3143
+ this.maxHeight.setRadius(r);
2963
3144
  }
2964
3145
  setMaxHeight(h) {
2965
3146
  if (this.destroyed) return;
2966
- const value = typeof h === "number" ? `${h}px` : h;
2967
- const axis = layoutAxis(this.mode);
2968
- this.element.style.setProperty(
2969
- axis === "height" ? "max-height" : "max-width",
2970
- value
2971
- );
2972
- this.maxHeightRaw = h;
2973
- this.resolveMaxHeightCap();
2974
- this.recompute();
2975
- }
2976
- resolveMaxHeightCap() {
2977
- const raw = this.maxHeightRaw;
2978
- if (raw === void 0) {
2979
- this.maxHeightCap = void 0;
2980
- return;
2981
- }
2982
- if (typeof raw === "number") {
2983
- this.maxHeightCap = raw;
2984
- return;
2985
- }
2986
- if (typeof window === "undefined") return;
2987
- const measured = resolveSnap(raw, this.mode);
2988
- this.maxHeightCap = measured > 0 ? measured : void 0;
2989
- }
2990
- clampToMaxHeight() {
2991
- if (this.maxHeightCap === void 0) return;
2992
- if (this.snaps.getMaxAxisSize() > this.maxHeightCap) {
2993
- this.snaps.setMaxAxisSize(this.maxHeightCap);
2994
- }
3147
+ this.maxHeight.setMaxHeight(h);
2995
3148
  }
2996
3149
  setAllowed(ids, snap) {
2997
3150
  if (this.destroyed) return;
@@ -3079,7 +3232,7 @@ var BottomSheetEngine = class {
3079
3232
  };
3080
3233
  const host = this.screenComponent?.parentElement;
3081
3234
  if (!host) {
3082
- console.warn(
3235
+ devWarn(
3083
3236
  "[BottomSheet] setScrimStages: no scrim element mounted \u2014 pass `scrim` to the engine first."
3084
3237
  );
3085
3238
  return () => {
@@ -3129,9 +3282,9 @@ var BottomSheetEngine = class {
3129
3282
  }
3130
3283
  recompute() {
3131
3284
  if (this.destroyed) return;
3132
- this.resolveMaxHeightCap();
3285
+ this.maxHeight.resolveCap();
3133
3286
  this.snaps.recompute();
3134
- this.clampToMaxHeight();
3287
+ this.maxHeight.clampTo();
3135
3288
  this.scrim.invalidateOpacityCache();
3136
3289
  if (this.gesture?.isDragging) return;
3137
3290
  const current = this.snaps.findById(this.activeId);
@@ -3181,42 +3334,18 @@ var BottomSheetEngine = class {
3181
3334
  return this.currentAbort.signal;
3182
3335
  }
3183
3336
  emitBeforeSnap(target, previousId) {
3184
- let cancelled = false;
3185
- let frozen = false;
3186
- this.emit("before-snap", {
3187
- id: target.id,
3188
- size: target.size,
3189
- previousId,
3190
- cancel: () => {
3191
- if (frozen) {
3192
- console.warn(
3193
- "[BottomSheet] before-snap.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
3194
- );
3195
- return;
3196
- }
3197
- cancelled = true;
3198
- }
3199
- });
3200
- frozen = true;
3201
- return cancelled;
3337
+ return emitCancelable(
3338
+ (payload) => this.emit("before-snap", payload),
3339
+ { id: target.id, size: target.size, previousId },
3340
+ "before-snap"
3341
+ );
3202
3342
  }
3203
3343
  emitBeforeClose(reason) {
3204
- let cancelled = false;
3205
- let frozen = false;
3206
- this.emit("before-close", {
3207
- reason,
3208
- cancel: () => {
3209
- if (frozen) {
3210
- console.warn(
3211
- "[BottomSheet] before-close.cancel() called asynchronously \u2014 ignored. cancel() must be invoked synchronously inside the listener."
3212
- );
3213
- return;
3214
- }
3215
- cancelled = true;
3216
- }
3217
- });
3218
- frozen = true;
3219
- return cancelled;
3344
+ return emitCancelable(
3345
+ (payload) => this.emit("before-close", payload),
3346
+ { reason },
3347
+ "before-close"
3348
+ );
3220
3349
  }
3221
3350
  applyAutoAriaLabelledBy() {
3222
3351
  if (typeof document === "undefined") return;
@@ -3231,16 +3360,16 @@ var BottomSheetEngine = class {
3231
3360
  this.element.setAttribute("aria-labelledby", titled.id);
3232
3361
  }
3233
3362
  recomputeSnaps() {
3234
- this.resolveMaxHeightCap();
3363
+ this.maxHeight.resolveCap();
3235
3364
  this.snaps.setRaw(this.snapPointsRaw);
3236
- this.clampToMaxHeight();
3365
+ this.maxHeight.clampTo();
3237
3366
  this.scrim.invalidateOpacityCache();
3238
3367
  }
3239
3368
  auditDuplicateSnapIds(points) {
3240
3369
  const seen = /* @__PURE__ */ new Set();
3241
3370
  for (const p of points) {
3242
3371
  if (seen.has(p.id)) {
3243
- console.warn(
3372
+ devWarn(
3244
3373
  `[BottomSheet] duplicate snap id "${p.id}" \u2014 only the first occurrence is reachable.`
3245
3374
  );
3246
3375
  }
@@ -3407,7 +3536,15 @@ var BottomSheetEngine = class {
3407
3536
  setSize: (size) => {
3408
3537
  this.size = size;
3409
3538
  },
3410
- applySize: (size) => this.applySize(size)
3539
+ applySize: (size) => this.applySize(size),
3540
+ cancelInFlight: () => {
3541
+ this.animation.cancel();
3542
+ },
3543
+ newCycle: () => {
3544
+ this.newCycle();
3545
+ },
3546
+ isAnimating: () => this.animation.isAnimating,
3547
+ resyncAfterCancel: () => this.resyncAfterResize()
3411
3548
  }));
3412
3549
  }
3413
3550
  this.teardowns.add(installSliderKeyboard({
@@ -3443,9 +3580,9 @@ var BottomSheetEngine = class {
3443
3580
  return;
3444
3581
  }
3445
3582
  if (this.emitBeforeSnap(target, previousId)) {
3446
- const restore = this.snaps.findById(previousId);
3447
- if (restore) {
3448
- void this.animation.animateTo(restore.size, 0);
3583
+ const restore2 = this.snaps.findById(previousId);
3584
+ if (restore2) {
3585
+ void this.animation.animateTo(restore2.size, 0);
3449
3586
  }
3450
3587
  return;
3451
3588
  }
@@ -3568,6 +3705,38 @@ var BottomSheetEngine = class {
3568
3705
  sheetStack.update();
3569
3706
  }
3570
3707
  };
3708
+
3709
+ // src/core/features/teleport.ts
3710
+ var resolveTeleportTarget = (target) => {
3711
+ if (!target) return null;
3712
+ if (typeof target !== "string") return target;
3713
+ if (typeof document === "undefined") return null;
3714
+ if (target === "body") return document.body;
3715
+ return document.querySelector(target);
3716
+ };
3717
+ var teleportElements = (els, target) => {
3718
+ const records = [];
3719
+ for (const el of els) {
3720
+ if (!el || el.parentNode === target) continue;
3721
+ records.push({ el, parent: el.parentNode, next: el.nextSibling });
3722
+ target.appendChild(el);
3723
+ }
3724
+ return () => {
3725
+ for (let i = records.length - 1; i >= 0; i--) {
3726
+ const { el, parent, next } = records[i];
3727
+ if (parent && parent.isConnected) {
3728
+ const before = next && next.parentNode === parent ? next : null;
3729
+ try {
3730
+ parent.insertBefore(el, before);
3731
+ } catch {
3732
+ el.parentNode?.removeChild(el);
3733
+ }
3734
+ } else {
3735
+ el.parentNode?.removeChild(el);
3736
+ }
3737
+ }
3738
+ };
3739
+ };
3571
3740
  var BottomSheet = component$((props) => {
3572
3741
  const sheetRef = useSignal();
3573
3742
  const handleRef = useSignal();
@@ -3590,7 +3759,7 @@ var BottomSheet = component$((props) => {
3590
3759
  useVisibleTask$(({ cleanup }) => {
3591
3760
  if (!sheetRef.value) return;
3592
3761
  if (!handleRef.value || !contentRef.value) {
3593
- console.warn(
3762
+ devWarn(
3594
3763
  "[BottomSheet/qwik] handle or content ref missing at attach time \u2014 drag and content-swipe disabled. Make sure ref bindings render before useVisibleTask$ fires."
3595
3764
  );
3596
3765
  }
@@ -3623,6 +3792,11 @@ var BottomSheet = component$((props) => {
3623
3792
  };
3624
3793
  const engine = new BottomSheetEngine(engineOpts);
3625
3794
  engineStore.engine = noSerialize(engine);
3795
+ const teleportTarget = props.teleport === false ? null : resolveTeleportTarget(props.teleportTo);
3796
+ const restoreTeleport = teleportTarget ? teleportElements(
3797
+ [backdropRef.value, scrimRef.value, sheetRef.value],
3798
+ teleportTarget
3799
+ ) : null;
3626
3800
  const sync = () => {
3627
3801
  const s = engine.state;
3628
3802
  state.size = s.size;
@@ -3637,8 +3811,18 @@ var BottomSheet = component$((props) => {
3637
3811
  sync();
3638
3812
  props.onSnap$?.(payload.id);
3639
3813
  });
3640
- engine.on("dragstart", sync);
3641
- engine.on("dragend", sync);
3814
+ engine.on("open", (payload) => props.onOpen$?.(payload.id));
3815
+ engine.on("close", () => props.onClose$?.());
3816
+ engine.on("opened", (payload) => props.onOpened$?.(payload.id));
3817
+ engine.on("closed", () => props.onClosed$?.());
3818
+ engine.on("dragstart", (payload) => {
3819
+ sync();
3820
+ props.onDragStart$?.(payload);
3821
+ });
3822
+ engine.on("dragend", (payload) => {
3823
+ sync();
3824
+ props.onDragEnd$?.(payload);
3825
+ });
3642
3826
  let onBackdropClick;
3643
3827
  if (props.backdrop !== false && props.closeOnBackdrop !== false && backdropRef.value) {
3644
3828
  onBackdropClick = () => {
@@ -3682,6 +3866,7 @@ var BottomSheet = component$((props) => {
3682
3866
  if (onBackdropClick && backdropRef.value) {
3683
3867
  backdropRef.value.removeEventListener("click", onBackdropClick);
3684
3868
  }
3869
+ restoreTeleport?.();
3685
3870
  engineStore.engine = void 0;
3686
3871
  engine.destroy();
3687
3872
  });