@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/overlay.cjs CHANGED
@@ -529,6 +529,146 @@ function installOverlaySwipe(deps) {
529
529
  };
530
530
  }
531
531
 
532
+ // src/core/features/history-coordinator.ts
533
+ var entries = [];
534
+ var subscribers = /* @__PURE__ */ new Set();
535
+ var suppress = 0;
536
+ var pendingRebrand = null;
537
+ var listenerInstalled = false;
538
+ var hasWindow = () => typeof window !== "undefined";
539
+ var hasHistory = () => typeof history !== "undefined";
540
+ var runInternal = (fn) => {
541
+ try {
542
+ fn();
543
+ } finally {
544
+ }
545
+ };
546
+ var notifyRouteChange = () => {
547
+ const snapshot = Array.from(subscribers);
548
+ for (const fn of snapshot) fn();
549
+ };
550
+ var ensureListener = () => {
551
+ if (listenerInstalled || !hasWindow()) return;
552
+ window.addEventListener("popstate", onPopState);
553
+ listenerInstalled = true;
554
+ };
555
+ var maybeTeardownListener = () => {
556
+ if (!listenerInstalled) return;
557
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
558
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
559
+ listenerInstalled = false;
560
+ };
561
+ function onPopState() {
562
+ if (suppress > 0) {
563
+ suppress -= 1;
564
+ const rebrand = pendingRebrand;
565
+ pendingRebrand = null;
566
+ if (rebrand) rebrand();
567
+ maybeTeardownListener();
568
+ return;
569
+ }
570
+ if (entries.length === 0) {
571
+ notifyRouteChange();
572
+ maybeTeardownListener();
573
+ return;
574
+ }
575
+ let target = null;
576
+ let targetIdx = -1;
577
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
578
+ const entry = entries[i];
579
+ if (!entry.surface.isOpen()) continue;
580
+ if (!target) {
581
+ target = entry;
582
+ targetIdx = i;
583
+ }
584
+ if (entry.surface.isTop()) {
585
+ target = entry;
586
+ targetIdx = i;
587
+ break;
588
+ }
589
+ }
590
+ if (!target) {
591
+ const removed = entries.pop();
592
+ if (removed) removed.live = false;
593
+ maybeTeardownListener();
594
+ return;
595
+ }
596
+ target.live = false;
597
+ entries.splice(targetIdx, 1);
598
+ const settled = target;
599
+ void Promise.resolve(settled.surface.close()).then(() => {
600
+ if (settled.surface.isOpen()) restore(settled);
601
+ });
602
+ maybeTeardownListener();
603
+ }
604
+ function restore(entry) {
605
+ if (!hasWindow() || !hasHistory()) return;
606
+ entry.live = true;
607
+ runInternal(() => {
608
+ try {
609
+ history.pushState(entry.surface.markerState(), "", entry.surface.url);
610
+ } catch {
611
+ entry.live = false;
612
+ }
613
+ });
614
+ if (entry.live) {
615
+ entries.push(entry);
616
+ ensureListener();
617
+ }
618
+ }
619
+ function pushBackMarker(surface) {
620
+ const entry = { surface, priorUrl: void 0, live: false };
621
+ if (!hasWindow() || !hasHistory()) {
622
+ return entry;
623
+ }
624
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
625
+ runInternal(() => {
626
+ try {
627
+ history.pushState(surface.markerState(), "", surface.url);
628
+ entry.live = true;
629
+ } catch {
630
+ entry.live = false;
631
+ }
632
+ });
633
+ if (entry.live) {
634
+ entries.push(entry);
635
+ ensureListener();
636
+ }
637
+ return entry;
638
+ }
639
+ function popBackMarker(handle) {
640
+ if (!hasWindow() || !hasHistory()) return;
641
+ const entry = handle;
642
+ const idx = entries.indexOf(entry);
643
+ if (idx === -1 || !entry.live) return;
644
+ const last = entries.length - 1;
645
+ entry.live = false;
646
+ entries.splice(idx, 1);
647
+ if (idx !== last) {
648
+ const rebrandSurface = entries[entries.length - 1].surface;
649
+ const rebrandUrl = entry.priorUrl;
650
+ pendingRebrand = () => {
651
+ runInternal(() => {
652
+ history.replaceState(
653
+ rebrandSurface.markerState(),
654
+ "",
655
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
656
+ );
657
+ });
658
+ };
659
+ }
660
+ suppress += 1;
661
+ runInternal(() => {
662
+ try {
663
+ history.back();
664
+ } catch {
665
+ suppress = Math.max(0, suppress - 1);
666
+ pendingRebrand = null;
667
+ }
668
+ });
669
+ maybeTeardownListener();
670
+ }
671
+
532
672
  // src/core/overlay-options.ts
533
673
  var OVERLAY_PRESETS = Object.freeze({
534
674
  sheet: Object.freeze({
@@ -596,7 +736,7 @@ var OverlayEngine = class {
596
736
  __publicField(this, "detachSwipe", null);
597
737
  __publicField(this, "respectReducedMotionEnabled");
598
738
  __publicField(this, "isTop", true);
599
- __publicField(this, "backPushed", false);
739
+ __publicField(this, "backMarker", null);
600
740
  __publicField(this, "movedToTarget", false);
601
741
  __publicField(this, "onOpenCb");
602
742
  __publicField(this, "onCloseCb");
@@ -608,7 +748,6 @@ var OverlayEngine = class {
608
748
  __publicField(this, "bus", createEventBus());
609
749
  __publicField(this, "detachEscape", null);
610
750
  __publicField(this, "detachBackdrop", null);
611
- __publicField(this, "detachHardwareBack", null);
612
751
  __publicField(this, "releaseStackEntry", null);
613
752
  __publicField(this, "lifecycle");
614
753
  this.element = opts.element;
@@ -762,7 +901,10 @@ var OverlayEngine = class {
762
901
  this.isOpen_ = false;
763
902
  this.isAnimating_ = true;
764
903
  sheetStack.update();
765
- this.popBackMarker(reason);
904
+ if (reason !== "back" && this.backMarker) {
905
+ popBackMarker(this.backMarker);
906
+ }
907
+ this.backMarker = null;
766
908
  this.element.setAttribute("data-state", "exiting");
767
909
  const dur = this.effectiveDuration();
768
910
  const transitionStr = `transform ${dur}ms ${this.exitEasing}, opacity ${dur}ms ease-in`;
@@ -820,6 +962,10 @@ var OverlayEngine = class {
820
962
  this.bus.clear();
821
963
  this.setWillChange(false);
822
964
  this.releaseInteractiveListeners();
965
+ if (this.backMarker) {
966
+ popBackMarker(this.backMarker);
967
+ this.backMarker = null;
968
+ }
823
969
  this.releaseStackEntry?.();
824
970
  this.releaseStackEntry = null;
825
971
  const hadOriginalParent = this.originalParent !== null;
@@ -981,19 +1127,6 @@ var OverlayEngine = class {
981
1127
  effectiveDuration() {
982
1128
  return this.respectReducedMotionEnabled && prefersReducedMotion() ? 0 : this.duration;
983
1129
  }
984
- popBackMarker(reason) {
985
- if (!this.backPushed) return;
986
- this.backPushed = false;
987
- if (reason === "back") return;
988
- const state = history.state;
989
- if (state && state.__bsOverlay === this.id) {
990
- try {
991
- history.back();
992
- } catch {
993
- return;
994
- }
995
- }
996
- }
997
1130
  resolvePeekPx(raw) {
998
1131
  if (typeof raw === "number") return Math.max(0, raw);
999
1132
  try {
@@ -1109,21 +1242,6 @@ var OverlayEngine = class {
1109
1242
  this.backdrop.addEventListener("click", handler);
1110
1243
  this.detachBackdrop = () => this.backdrop?.removeEventListener("click", handler);
1111
1244
  }
1112
- if (this.closeOnBackEnabled && typeof window !== "undefined" && !this.detachHardwareBack) {
1113
- try {
1114
- history.pushState({ __bsOverlay: this.id }, "");
1115
- this.backPushed = true;
1116
- } catch {
1117
- }
1118
- const onPop = () => {
1119
- if (this.isOpen_ && this.isTop) {
1120
- this.backPushed = false;
1121
- void this.close("back");
1122
- }
1123
- };
1124
- window.addEventListener("popstate", onPop);
1125
- this.detachHardwareBack = () => window.removeEventListener("popstate", onPop);
1126
- }
1127
1245
  if (this.closeOnOutsidePointerEnabled && !this.detachOutsidePointer) {
1128
1246
  const handler = (e) => {
1129
1247
  if (!this.isOpen_) return;
@@ -1136,14 +1254,20 @@ var OverlayEngine = class {
1136
1254
  document.addEventListener("pointerdown", handler, true);
1137
1255
  this.detachOutsidePointer = () => document.removeEventListener("pointerdown", handler, true);
1138
1256
  }
1257
+ if (this.closeOnBackEnabled && this.backMarker === null && typeof window !== "undefined") {
1258
+ this.backMarker = pushBackMarker({
1259
+ isOpen: () => this.isOpen_,
1260
+ isTop: () => this.isTop,
1261
+ close: () => this.close("back"),
1262
+ markerState: () => ({ __bsOverlay: this.id })
1263
+ });
1264
+ }
1139
1265
  }
1140
1266
  releaseInteractiveListeners() {
1141
1267
  this.detachEscape?.();
1142
1268
  this.detachEscape = null;
1143
1269
  this.detachBackdrop?.();
1144
1270
  this.detachBackdrop = null;
1145
- this.detachHardwareBack?.();
1146
- this.detachHardwareBack = null;
1147
1271
  this.detachSwipe?.();
1148
1272
  this.detachSwipe = null;
1149
1273
  this.detachOutsidePointer?.();
@@ -110,7 +110,7 @@ declare class OverlayEngine {
110
110
  private detachSwipe;
111
111
  private respectReducedMotionEnabled;
112
112
  private isTop;
113
- private backPushed;
113
+ private backMarker;
114
114
  private movedToTarget;
115
115
  private onOpenCb?;
116
116
  private onCloseCb?;
@@ -122,7 +122,6 @@ declare class OverlayEngine {
122
122
  private bus;
123
123
  private detachEscape;
124
124
  private detachBackdrop;
125
- private detachHardwareBack;
126
125
  private releaseStackEntry;
127
126
  private lifecycle;
128
127
  constructor(opts: OverlayOptions);
@@ -144,7 +143,6 @@ declare class OverlayEngine {
144
143
  private assignSwipeFromConfig;
145
144
  private emit;
146
145
  private effectiveDuration;
147
- private popBackMarker;
148
146
  private resolvePeekPx;
149
147
  private maybeMount;
150
148
  private restoreMount;
package/dist/overlay.d.ts CHANGED
@@ -110,7 +110,7 @@ declare class OverlayEngine {
110
110
  private detachSwipe;
111
111
  private respectReducedMotionEnabled;
112
112
  private isTop;
113
- private backPushed;
113
+ private backMarker;
114
114
  private movedToTarget;
115
115
  private onOpenCb?;
116
116
  private onCloseCb?;
@@ -122,7 +122,6 @@ declare class OverlayEngine {
122
122
  private bus;
123
123
  private detachEscape;
124
124
  private detachBackdrop;
125
- private detachHardwareBack;
126
125
  private releaseStackEntry;
127
126
  private lifecycle;
128
127
  constructor(opts: OverlayOptions);
@@ -144,7 +143,6 @@ declare class OverlayEngine {
144
143
  private assignSwipeFromConfig;
145
144
  private emit;
146
145
  private effectiveDuration;
147
- private popBackMarker;
148
146
  private resolvePeekPx;
149
147
  private maybeMount;
150
148
  private restoreMount;
package/dist/overlay.js CHANGED
@@ -527,6 +527,146 @@ function installOverlaySwipe(deps) {
527
527
  };
528
528
  }
529
529
 
530
+ // src/core/features/history-coordinator.ts
531
+ var entries = [];
532
+ var subscribers = /* @__PURE__ */ new Set();
533
+ var suppress = 0;
534
+ var pendingRebrand = null;
535
+ var listenerInstalled = false;
536
+ var hasWindow = () => typeof window !== "undefined";
537
+ var hasHistory = () => typeof history !== "undefined";
538
+ var runInternal = (fn) => {
539
+ try {
540
+ fn();
541
+ } finally {
542
+ }
543
+ };
544
+ var notifyRouteChange = () => {
545
+ const snapshot = Array.from(subscribers);
546
+ for (const fn of snapshot) fn();
547
+ };
548
+ var ensureListener = () => {
549
+ if (listenerInstalled || !hasWindow()) return;
550
+ window.addEventListener("popstate", onPopState);
551
+ listenerInstalled = true;
552
+ };
553
+ var maybeTeardownListener = () => {
554
+ if (!listenerInstalled) return;
555
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
556
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
557
+ listenerInstalled = false;
558
+ };
559
+ function onPopState() {
560
+ if (suppress > 0) {
561
+ suppress -= 1;
562
+ const rebrand = pendingRebrand;
563
+ pendingRebrand = null;
564
+ if (rebrand) rebrand();
565
+ maybeTeardownListener();
566
+ return;
567
+ }
568
+ if (entries.length === 0) {
569
+ notifyRouteChange();
570
+ maybeTeardownListener();
571
+ return;
572
+ }
573
+ let target = null;
574
+ let targetIdx = -1;
575
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
576
+ const entry = entries[i];
577
+ if (!entry.surface.isOpen()) continue;
578
+ if (!target) {
579
+ target = entry;
580
+ targetIdx = i;
581
+ }
582
+ if (entry.surface.isTop()) {
583
+ target = entry;
584
+ targetIdx = i;
585
+ break;
586
+ }
587
+ }
588
+ if (!target) {
589
+ const removed = entries.pop();
590
+ if (removed) removed.live = false;
591
+ maybeTeardownListener();
592
+ return;
593
+ }
594
+ target.live = false;
595
+ entries.splice(targetIdx, 1);
596
+ const settled = target;
597
+ void Promise.resolve(settled.surface.close()).then(() => {
598
+ if (settled.surface.isOpen()) restore(settled);
599
+ });
600
+ maybeTeardownListener();
601
+ }
602
+ function restore(entry) {
603
+ if (!hasWindow() || !hasHistory()) return;
604
+ entry.live = true;
605
+ runInternal(() => {
606
+ try {
607
+ history.pushState(entry.surface.markerState(), "", entry.surface.url);
608
+ } catch {
609
+ entry.live = false;
610
+ }
611
+ });
612
+ if (entry.live) {
613
+ entries.push(entry);
614
+ ensureListener();
615
+ }
616
+ }
617
+ function pushBackMarker(surface) {
618
+ const entry = { surface, priorUrl: void 0, live: false };
619
+ if (!hasWindow() || !hasHistory()) {
620
+ return entry;
621
+ }
622
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
623
+ runInternal(() => {
624
+ try {
625
+ history.pushState(surface.markerState(), "", surface.url);
626
+ entry.live = true;
627
+ } catch {
628
+ entry.live = false;
629
+ }
630
+ });
631
+ if (entry.live) {
632
+ entries.push(entry);
633
+ ensureListener();
634
+ }
635
+ return entry;
636
+ }
637
+ function popBackMarker(handle) {
638
+ if (!hasWindow() || !hasHistory()) return;
639
+ const entry = handle;
640
+ const idx = entries.indexOf(entry);
641
+ if (idx === -1 || !entry.live) return;
642
+ const last = entries.length - 1;
643
+ entry.live = false;
644
+ entries.splice(idx, 1);
645
+ if (idx !== last) {
646
+ const rebrandSurface = entries[entries.length - 1].surface;
647
+ const rebrandUrl = entry.priorUrl;
648
+ pendingRebrand = () => {
649
+ runInternal(() => {
650
+ history.replaceState(
651
+ rebrandSurface.markerState(),
652
+ "",
653
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
654
+ );
655
+ });
656
+ };
657
+ }
658
+ suppress += 1;
659
+ runInternal(() => {
660
+ try {
661
+ history.back();
662
+ } catch {
663
+ suppress = Math.max(0, suppress - 1);
664
+ pendingRebrand = null;
665
+ }
666
+ });
667
+ maybeTeardownListener();
668
+ }
669
+
530
670
  // src/core/overlay-options.ts
531
671
  var OVERLAY_PRESETS = Object.freeze({
532
672
  sheet: Object.freeze({
@@ -594,7 +734,7 @@ var OverlayEngine = class {
594
734
  __publicField(this, "detachSwipe", null);
595
735
  __publicField(this, "respectReducedMotionEnabled");
596
736
  __publicField(this, "isTop", true);
597
- __publicField(this, "backPushed", false);
737
+ __publicField(this, "backMarker", null);
598
738
  __publicField(this, "movedToTarget", false);
599
739
  __publicField(this, "onOpenCb");
600
740
  __publicField(this, "onCloseCb");
@@ -606,7 +746,6 @@ var OverlayEngine = class {
606
746
  __publicField(this, "bus", createEventBus());
607
747
  __publicField(this, "detachEscape", null);
608
748
  __publicField(this, "detachBackdrop", null);
609
- __publicField(this, "detachHardwareBack", null);
610
749
  __publicField(this, "releaseStackEntry", null);
611
750
  __publicField(this, "lifecycle");
612
751
  this.element = opts.element;
@@ -760,7 +899,10 @@ var OverlayEngine = class {
760
899
  this.isOpen_ = false;
761
900
  this.isAnimating_ = true;
762
901
  sheetStack.update();
763
- this.popBackMarker(reason);
902
+ if (reason !== "back" && this.backMarker) {
903
+ popBackMarker(this.backMarker);
904
+ }
905
+ this.backMarker = null;
764
906
  this.element.setAttribute("data-state", "exiting");
765
907
  const dur = this.effectiveDuration();
766
908
  const transitionStr = `transform ${dur}ms ${this.exitEasing}, opacity ${dur}ms ease-in`;
@@ -818,6 +960,10 @@ var OverlayEngine = class {
818
960
  this.bus.clear();
819
961
  this.setWillChange(false);
820
962
  this.releaseInteractiveListeners();
963
+ if (this.backMarker) {
964
+ popBackMarker(this.backMarker);
965
+ this.backMarker = null;
966
+ }
821
967
  this.releaseStackEntry?.();
822
968
  this.releaseStackEntry = null;
823
969
  const hadOriginalParent = this.originalParent !== null;
@@ -979,19 +1125,6 @@ var OverlayEngine = class {
979
1125
  effectiveDuration() {
980
1126
  return this.respectReducedMotionEnabled && prefersReducedMotion() ? 0 : this.duration;
981
1127
  }
982
- popBackMarker(reason) {
983
- if (!this.backPushed) return;
984
- this.backPushed = false;
985
- if (reason === "back") return;
986
- const state = history.state;
987
- if (state && state.__bsOverlay === this.id) {
988
- try {
989
- history.back();
990
- } catch {
991
- return;
992
- }
993
- }
994
- }
995
1128
  resolvePeekPx(raw) {
996
1129
  if (typeof raw === "number") return Math.max(0, raw);
997
1130
  try {
@@ -1107,21 +1240,6 @@ var OverlayEngine = class {
1107
1240
  this.backdrop.addEventListener("click", handler);
1108
1241
  this.detachBackdrop = () => this.backdrop?.removeEventListener("click", handler);
1109
1242
  }
1110
- if (this.closeOnBackEnabled && typeof window !== "undefined" && !this.detachHardwareBack) {
1111
- try {
1112
- history.pushState({ __bsOverlay: this.id }, "");
1113
- this.backPushed = true;
1114
- } catch {
1115
- }
1116
- const onPop = () => {
1117
- if (this.isOpen_ && this.isTop) {
1118
- this.backPushed = false;
1119
- void this.close("back");
1120
- }
1121
- };
1122
- window.addEventListener("popstate", onPop);
1123
- this.detachHardwareBack = () => window.removeEventListener("popstate", onPop);
1124
- }
1125
1243
  if (this.closeOnOutsidePointerEnabled && !this.detachOutsidePointer) {
1126
1244
  const handler = (e) => {
1127
1245
  if (!this.isOpen_) return;
@@ -1134,14 +1252,20 @@ var OverlayEngine = class {
1134
1252
  document.addEventListener("pointerdown", handler, true);
1135
1253
  this.detachOutsidePointer = () => document.removeEventListener("pointerdown", handler, true);
1136
1254
  }
1255
+ if (this.closeOnBackEnabled && this.backMarker === null && typeof window !== "undefined") {
1256
+ this.backMarker = pushBackMarker({
1257
+ isOpen: () => this.isOpen_,
1258
+ isTop: () => this.isTop,
1259
+ close: () => this.close("back"),
1260
+ markerState: () => ({ __bsOverlay: this.id })
1261
+ });
1262
+ }
1137
1263
  }
1138
1264
  releaseInteractiveListeners() {
1139
1265
  this.detachEscape?.();
1140
1266
  this.detachEscape = null;
1141
1267
  this.detachBackdrop?.();
1142
1268
  this.detachBackdrop = null;
1143
- this.detachHardwareBack?.();
1144
- this.detachHardwareBack = null;
1145
1269
  this.detachSwipe?.();
1146
1270
  this.detachSwipe = null;
1147
1271
  this.detachOutsidePointer?.();