@surdeddd/bottom-sheet 0.10.0 → 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.
package/dist/react.cjs CHANGED
@@ -448,46 +448,198 @@ function notifyLinkedSheets(sheets, self) {
448
448
  }
449
449
  }
450
450
 
451
- // src/core/features/route.ts
452
- var closers = [];
453
- var handlerInstalled = false;
451
+ // src/core/features/history-coordinator.ts
452
+ var entries = [];
453
+ var subscribers = /* @__PURE__ */ new Set();
454
454
  var suppress = 0;
455
- var ensureHandler = () => {
456
- if (handlerInstalled || typeof window === "undefined") return;
457
- handlerInstalled = true;
458
- window.addEventListener("popstate", () => {
459
- if (suppress > 0) {
460
- suppress -= 1;
461
- return;
455
+ var internalDepth = 0;
456
+ var pendingRebrand = null;
457
+ var listenerInstalled = false;
458
+ var patchInstalled = false;
459
+ var origPushState = null;
460
+ var origReplaceState = null;
461
+ var patchedPushState = null;
462
+ var patchedReplaceState = null;
463
+ var hasWindow = () => typeof window !== "undefined";
464
+ var hasHistory = () => typeof history !== "undefined";
465
+ var runInternal = (fn) => {
466
+ internalDepth += 1;
467
+ try {
468
+ fn();
469
+ } finally {
470
+ internalDepth -= 1;
471
+ }
472
+ };
473
+ var notifyRouteChange = () => {
474
+ const snapshot = Array.from(subscribers);
475
+ for (const fn of snapshot) fn();
476
+ };
477
+ var ensureListener = () => {
478
+ if (listenerInstalled || !hasWindow()) return;
479
+ window.addEventListener("popstate", onPopState);
480
+ listenerInstalled = true;
481
+ };
482
+ var maybeTeardownListener = () => {
483
+ if (!listenerInstalled) return;
484
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
485
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
486
+ listenerInstalled = false;
487
+ };
488
+ var installPatch = () => {
489
+ if (patchInstalled || !hasHistory()) return;
490
+ origPushState = history.pushState;
491
+ origReplaceState = history.replaceState;
492
+ patchedPushState = function(...args) {
493
+ origPushState.apply(this, args);
494
+ if (internalDepth === 0) notifyRouteChange();
495
+ };
496
+ patchedReplaceState = function(...args) {
497
+ origReplaceState.apply(this, args);
498
+ if (internalDepth === 0) notifyRouteChange();
499
+ };
500
+ history.pushState = patchedPushState;
501
+ history.replaceState = patchedReplaceState;
502
+ patchInstalled = true;
503
+ };
504
+ var removePatch = () => {
505
+ if (!patchInstalled) return;
506
+ if (history.pushState === patchedPushState && origPushState) {
507
+ history.pushState = origPushState;
508
+ }
509
+ if (history.replaceState === patchedReplaceState && origReplaceState) {
510
+ history.replaceState = origReplaceState;
511
+ }
512
+ origPushState = null;
513
+ origReplaceState = null;
514
+ patchedPushState = null;
515
+ patchedReplaceState = null;
516
+ patchInstalled = false;
517
+ };
518
+ function onPopState() {
519
+ if (suppress > 0) {
520
+ suppress -= 1;
521
+ const rebrand = pendingRebrand;
522
+ pendingRebrand = null;
523
+ if (rebrand) rebrand();
524
+ maybeTeardownListener();
525
+ return;
526
+ }
527
+ if (entries.length === 0) {
528
+ notifyRouteChange();
529
+ maybeTeardownListener();
530
+ return;
531
+ }
532
+ let target = null;
533
+ let targetIdx = -1;
534
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
535
+ const entry = entries[i];
536
+ if (!entry.surface.isOpen()) continue;
537
+ if (!target) {
538
+ target = entry;
539
+ targetIdx = i;
462
540
  }
463
- let target = null;
464
- for (let i = closers.length - 1; i >= 0; i -= 1) {
465
- const c = closers[i];
466
- if (!c.isOpen()) continue;
467
- if (!target) target = c;
468
- if (c.isTop()) {
469
- target = c;
470
- break;
471
- }
541
+ if (entry.surface.isTop()) {
542
+ target = entry;
543
+ targetIdx = i;
544
+ break;
472
545
  }
473
- if (!target) return;
474
- target.consumed = true;
475
- const idx = closers.indexOf(target);
476
- if (idx !== -1) closers.splice(idx, 1);
477
- const settled = target;
478
- void Promise.resolve(settled.close()).then(() => {
479
- if (settled.isOpen()) settled.restore();
480
- });
546
+ }
547
+ if (!target) {
548
+ const removed = entries.pop();
549
+ if (removed) removed.live = false;
550
+ maybeTeardownListener();
551
+ return;
552
+ }
553
+ target.live = false;
554
+ entries.splice(targetIdx, 1);
555
+ const settled = target;
556
+ void Promise.resolve(settled.surface.close()).then(() => {
557
+ if (settled.surface.isOpen()) restore(settled);
481
558
  });
482
- };
483
- var programmaticBack = () => {
484
- suppress += 1;
485
- try {
486
- history.back();
487
- } catch {
488
- suppress = Math.max(0, suppress - 1);
559
+ maybeTeardownListener();
560
+ }
561
+ function restore(entry) {
562
+ if (!hasWindow() || !hasHistory()) return;
563
+ entry.live = true;
564
+ runInternal(() => {
565
+ try {
566
+ history.pushState(entry.surface.markerState(), "", entry.surface.url);
567
+ } catch {
568
+ entry.live = false;
569
+ }
570
+ });
571
+ if (entry.live) {
572
+ entries.push(entry);
573
+ ensureListener();
489
574
  }
490
- };
575
+ }
576
+ function pushBackMarker(surface) {
577
+ const entry = { surface, priorUrl: void 0, live: false };
578
+ if (!hasWindow() || !hasHistory()) {
579
+ return entry;
580
+ }
581
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
582
+ runInternal(() => {
583
+ try {
584
+ history.pushState(surface.markerState(), "", surface.url);
585
+ entry.live = true;
586
+ } catch {
587
+ entry.live = false;
588
+ }
589
+ });
590
+ if (entry.live) {
591
+ entries.push(entry);
592
+ ensureListener();
593
+ }
594
+ return entry;
595
+ }
596
+ function popBackMarker(handle) {
597
+ if (!hasWindow() || !hasHistory()) return;
598
+ const entry = handle;
599
+ const idx = entries.indexOf(entry);
600
+ if (idx === -1 || !entry.live) return;
601
+ const last = entries.length - 1;
602
+ entry.live = false;
603
+ entries.splice(idx, 1);
604
+ if (idx !== last) {
605
+ const rebrandSurface = entries[entries.length - 1].surface;
606
+ const rebrandUrl = entry.priorUrl;
607
+ pendingRebrand = () => {
608
+ runInternal(() => {
609
+ history.replaceState(
610
+ rebrandSurface.markerState(),
611
+ "",
612
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
613
+ );
614
+ });
615
+ };
616
+ }
617
+ suppress += 1;
618
+ runInternal(() => {
619
+ try {
620
+ history.back();
621
+ } catch {
622
+ suppress = Math.max(0, suppress - 1);
623
+ pendingRebrand = null;
624
+ }
625
+ });
626
+ maybeTeardownListener();
627
+ }
628
+ function subscribeRouteChange(fn) {
629
+ if (!hasWindow() || !hasHistory()) return () => {
630
+ };
631
+ subscribers.add(fn);
632
+ if (subscribers.size === 1) installPatch();
633
+ ensureListener();
634
+ return () => {
635
+ if (!subscribers.has(fn)) return;
636
+ subscribers.delete(fn);
637
+ if (subscribers.size === 0) removePatch();
638
+ maybeTeardownListener();
639
+ };
640
+ }
641
+
642
+ // src/core/features/route.ts
491
643
  function installRoute(deps) {
492
644
  if (typeof window === "undefined") {
493
645
  return () => {
@@ -497,92 +649,39 @@ function installRoute(deps) {
497
649
  return () => {
498
650
  };
499
651
  }
500
- let pushed = false;
501
- let closer = null;
502
- const teardownEntry = () => {
503
- if (closer) {
504
- const idx = closers.indexOf(closer);
505
- if (idx !== -1) closers.splice(idx, 1);
506
- }
507
- const wasConsumed = closer?.consumed ?? false;
508
- closer = null;
509
- if (pushed && !wasConsumed) {
510
- pushed = false;
511
- programmaticBack();
512
- } else {
513
- pushed = false;
514
- }
515
- };
516
- const pushEntry = () => {
517
- try {
518
- if (deps.routedTo !== void 0) {
519
- history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
520
- } else {
521
- history.pushState({ __bsSheet: deps.sheetId }, "");
522
- }
523
- pushed = true;
524
- } catch {
525
- pushed = false;
526
- }
527
- };
652
+ let handle = null;
528
653
  const onOpen = () => {
529
- if (closer) return;
530
- const self = {
654
+ if (handle) return;
655
+ handle = pushBackMarker({
531
656
  isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
532
- isTop: () => deps.isTopSheet(),
533
- close: () => deps.close(),
534
- restore: () => {
535
- if (deps.isDestroyed() || closer !== self) return;
536
- self.consumed = false;
537
- if (!closers.includes(self)) closers.push(self);
538
- pushEntry();
539
- },
540
- consumed: false
541
- };
542
- closer = self;
543
- ensureHandler();
544
- closers.push(self);
545
- pushEntry();
657
+ isTop: deps.isTopSheet,
658
+ close: deps.close,
659
+ markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
660
+ url: deps.routedTo
661
+ });
662
+ };
663
+ const onClose = () => {
664
+ if (!handle) return;
665
+ popBackMarker(handle);
666
+ handle = null;
546
667
  };
547
668
  const unsubscribeOpen = deps.on("open", onOpen);
548
- const unsubscribeClose = deps.on("close", teardownEntry);
669
+ const unsubscribeClose = deps.on("close", onClose);
549
670
  if (deps.getSize() > 0) onOpen();
550
671
  return () => {
551
672
  unsubscribeOpen();
552
673
  unsubscribeClose();
553
- teardownEntry();
674
+ if (handle) {
675
+ popBackMarker(handle);
676
+ handle = null;
677
+ }
554
678
  };
555
679
  }
556
680
  function installRouteChange(deps) {
557
- if (typeof window === "undefined" || typeof history === "undefined") {
558
- return () => {
559
- };
560
- }
561
- const onChange = () => {
681
+ return subscribeRouteChange(() => {
562
682
  if (deps.isDestroyed() || deps.getSize() <= 0) return;
563
683
  void deps.close();
564
- };
565
- const wrap = (key) => {
566
- const original = history[key];
567
- const patched = function(...args) {
568
- original.apply(this, args);
569
- onChange();
570
- };
571
- history[key] = patched;
572
- return () => {
573
- if (history[key] === patched) {
574
- history[key] = original;
575
- }
576
- };
577
- };
578
- window.addEventListener("popstate", onChange);
579
- const restorePush = wrap("pushState");
580
- const restoreReplace = wrap("replaceState");
581
- return () => {
582
- window.removeEventListener("popstate", onChange);
583
- restorePush();
584
- restoreReplace();
585
- };
684
+ });
586
685
  }
587
686
 
588
687
  // src/core/features/content-swipe.ts
@@ -2435,7 +2534,7 @@ var matchesRange = (def, progress) => {
2435
2534
  };
2436
2535
  function installScrimStages(deps, opts) {
2437
2536
  const doc = deps.host.ownerDocument;
2438
- const entries = opts.stages.map((def) => {
2537
+ const entries2 = opts.stages.map((def) => {
2439
2538
  const wrapper = doc.createElement("div");
2440
2539
  wrapper.className = "bs-scrim-stage";
2441
2540
  const ws = wrapper.style;
@@ -2456,9 +2555,9 @@ function installScrimStages(deps, opts) {
2456
2555
  const resolveActive = () => {
2457
2556
  const state = deps.getState();
2458
2557
  if (state.size === 0) {
2459
- return entries.find((e) => matchesId(e.def, state.activeId)) ?? null;
2558
+ return entries2.find((e) => matchesId(e.def, state.activeId)) ?? null;
2460
2559
  }
2461
- return entries.find((e) => matchesId(e.def, state.activeId)) ?? entries.find((e) => matchesRange(e.def, state.progress)) ?? null;
2560
+ return entries2.find((e) => matchesId(e.def, state.activeId)) ?? entries2.find((e) => matchesRange(e.def, state.progress)) ?? null;
2462
2561
  };
2463
2562
  const hide = (entry, animate) => {
2464
2563
  entry.inFlight?.cancel();
@@ -2507,13 +2606,13 @@ function installScrimStages(deps, opts) {
2507
2606
  deps.on("open", () => update(true)),
2508
2607
  deps.on("close", () => update(true))
2509
2608
  ];
2510
- if (entries.some((e) => e.def.forRange)) {
2609
+ if (entries2.some((e) => e.def.forRange)) {
2511
2610
  offs.push(deps.on("progress", () => update(true)));
2512
2611
  }
2513
2612
  update(false);
2514
2613
  return () => {
2515
2614
  offs.forEach((off) => off());
2516
- for (const entry of entries) {
2615
+ for (const entry of entries2) {
2517
2616
  entry.inFlight?.cancel();
2518
2617
  entry.inFlight = null;
2519
2618
  if (entry.wrapper.parentNode) {
@@ -2772,8 +2871,8 @@ var BottomSheetEngine = class {
2772
2871
  this.attach();
2773
2872
  this.registerInStack();
2774
2873
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
2775
- const io = new IntersectionObserver((entries) => {
2776
- for (const entry of entries) {
2874
+ const io = new IntersectionObserver((entries2) => {
2875
+ for (const entry of entries2) {
2777
2876
  if (!entry.isIntersecting) continue;
2778
2877
  if (this.destroyed) return;
2779
2878
  this.snaps.recompute();
@@ -3490,9 +3589,9 @@ var BottomSheetEngine = class {
3490
3589
  return;
3491
3590
  }
3492
3591
  if (this.emitBeforeSnap(target, previousId)) {
3493
- const restore = this.snaps.findById(previousId);
3494
- if (restore) {
3495
- void this.animation.animateTo(restore.size, 0);
3592
+ const restore2 = this.snaps.findById(previousId);
3593
+ if (restore2) {
3594
+ void this.animation.animateTo(restore2.size, 0);
3496
3595
  }
3497
3596
  return;
3498
3597
  }
@@ -4329,7 +4428,7 @@ var OverlayEngine = class {
4329
4428
  __publicField(this, "detachSwipe", null);
4330
4429
  __publicField(this, "respectReducedMotionEnabled");
4331
4430
  __publicField(this, "isTop", true);
4332
- __publicField(this, "backPushed", false);
4431
+ __publicField(this, "backMarker", null);
4333
4432
  __publicField(this, "movedToTarget", false);
4334
4433
  __publicField(this, "onOpenCb");
4335
4434
  __publicField(this, "onCloseCb");
@@ -4341,7 +4440,6 @@ var OverlayEngine = class {
4341
4440
  __publicField(this, "bus", createEventBus());
4342
4441
  __publicField(this, "detachEscape", null);
4343
4442
  __publicField(this, "detachBackdrop", null);
4344
- __publicField(this, "detachHardwareBack", null);
4345
4443
  __publicField(this, "releaseStackEntry", null);
4346
4444
  __publicField(this, "lifecycle");
4347
4445
  this.element = opts.element;
@@ -4495,7 +4593,10 @@ var OverlayEngine = class {
4495
4593
  this.isOpen_ = false;
4496
4594
  this.isAnimating_ = true;
4497
4595
  sheetStack.update();
4498
- this.popBackMarker(reason);
4596
+ if (reason !== "back" && this.backMarker) {
4597
+ popBackMarker(this.backMarker);
4598
+ }
4599
+ this.backMarker = null;
4499
4600
  this.element.setAttribute("data-state", "exiting");
4500
4601
  const dur = this.effectiveDuration();
4501
4602
  const transitionStr = `transform ${dur}ms ${this.exitEasing}, opacity ${dur}ms ease-in`;
@@ -4553,6 +4654,10 @@ var OverlayEngine = class {
4553
4654
  this.bus.clear();
4554
4655
  this.setWillChange(false);
4555
4656
  this.releaseInteractiveListeners();
4657
+ if (this.backMarker) {
4658
+ popBackMarker(this.backMarker);
4659
+ this.backMarker = null;
4660
+ }
4556
4661
  this.releaseStackEntry?.();
4557
4662
  this.releaseStackEntry = null;
4558
4663
  const hadOriginalParent = this.originalParent !== null;
@@ -4714,19 +4819,6 @@ var OverlayEngine = class {
4714
4819
  effectiveDuration() {
4715
4820
  return this.respectReducedMotionEnabled && prefersReducedMotion() ? 0 : this.duration;
4716
4821
  }
4717
- popBackMarker(reason) {
4718
- if (!this.backPushed) return;
4719
- this.backPushed = false;
4720
- if (reason === "back") return;
4721
- const state = history.state;
4722
- if (state && state.__bsOverlay === this.id) {
4723
- try {
4724
- history.back();
4725
- } catch {
4726
- return;
4727
- }
4728
- }
4729
- }
4730
4822
  resolvePeekPx(raw) {
4731
4823
  if (typeof raw === "number") return Math.max(0, raw);
4732
4824
  try {
@@ -4842,21 +4934,6 @@ var OverlayEngine = class {
4842
4934
  this.backdrop.addEventListener("click", handler);
4843
4935
  this.detachBackdrop = () => this.backdrop?.removeEventListener("click", handler);
4844
4936
  }
4845
- if (this.closeOnBackEnabled && typeof window !== "undefined" && !this.detachHardwareBack) {
4846
- try {
4847
- history.pushState({ __bsOverlay: this.id }, "");
4848
- this.backPushed = true;
4849
- } catch {
4850
- }
4851
- const onPop = () => {
4852
- if (this.isOpen_ && this.isTop) {
4853
- this.backPushed = false;
4854
- void this.close("back");
4855
- }
4856
- };
4857
- window.addEventListener("popstate", onPop);
4858
- this.detachHardwareBack = () => window.removeEventListener("popstate", onPop);
4859
- }
4860
4937
  if (this.closeOnOutsidePointerEnabled && !this.detachOutsidePointer) {
4861
4938
  const handler = (e) => {
4862
4939
  if (!this.isOpen_) return;
@@ -4869,14 +4946,20 @@ var OverlayEngine = class {
4869
4946
  document.addEventListener("pointerdown", handler, true);
4870
4947
  this.detachOutsidePointer = () => document.removeEventListener("pointerdown", handler, true);
4871
4948
  }
4949
+ if (this.closeOnBackEnabled && this.backMarker === null && typeof window !== "undefined") {
4950
+ this.backMarker = pushBackMarker({
4951
+ isOpen: () => this.isOpen_,
4952
+ isTop: () => this.isTop,
4953
+ close: () => this.close("back"),
4954
+ markerState: () => ({ __bsOverlay: this.id })
4955
+ });
4956
+ }
4872
4957
  }
4873
4958
  releaseInteractiveListeners() {
4874
4959
  this.detachEscape?.();
4875
4960
  this.detachEscape = null;
4876
4961
  this.detachBackdrop?.();
4877
4962
  this.detachBackdrop = null;
4878
- this.detachHardwareBack?.();
4879
- this.detachHardwareBack = null;
4880
4963
  this.detachSwipe?.();
4881
4964
  this.detachSwipe = null;
4882
4965
  this.detachOutsidePointer?.();