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