@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/index.cjs CHANGED
@@ -444,46 +444,198 @@ function notifyLinkedSheets(sheets, self) {
444
444
  }
445
445
  }
446
446
 
447
- // src/core/features/route.ts
448
- var closers = [];
449
- var handlerInstalled = false;
447
+ // src/core/features/history-coordinator.ts
448
+ var entries = [];
449
+ var subscribers = /* @__PURE__ */ new Set();
450
450
  var suppress = 0;
451
- var ensureHandler = () => {
452
- if (handlerInstalled || typeof window === "undefined") return;
453
- handlerInstalled = true;
454
- window.addEventListener("popstate", () => {
455
- if (suppress > 0) {
456
- suppress -= 1;
457
- return;
451
+ var internalDepth = 0;
452
+ var pendingRebrand = null;
453
+ var listenerInstalled = false;
454
+ var patchInstalled = false;
455
+ var origPushState = null;
456
+ var origReplaceState = null;
457
+ var patchedPushState = null;
458
+ var patchedReplaceState = null;
459
+ var hasWindow = () => typeof window !== "undefined";
460
+ var hasHistory = () => typeof history !== "undefined";
461
+ var runInternal = (fn) => {
462
+ internalDepth += 1;
463
+ try {
464
+ fn();
465
+ } finally {
466
+ internalDepth -= 1;
467
+ }
468
+ };
469
+ var notifyRouteChange = () => {
470
+ const snapshot = Array.from(subscribers);
471
+ for (const fn of snapshot) fn();
472
+ };
473
+ var ensureListener = () => {
474
+ if (listenerInstalled || !hasWindow()) return;
475
+ window.addEventListener("popstate", onPopState);
476
+ listenerInstalled = true;
477
+ };
478
+ var maybeTeardownListener = () => {
479
+ if (!listenerInstalled) return;
480
+ if (entries.length > 0 || subscribers.size > 0 || suppress > 0) return;
481
+ if (hasWindow()) window.removeEventListener("popstate", onPopState);
482
+ listenerInstalled = false;
483
+ };
484
+ var installPatch = () => {
485
+ if (patchInstalled || !hasHistory()) return;
486
+ origPushState = history.pushState;
487
+ origReplaceState = history.replaceState;
488
+ patchedPushState = function(...args) {
489
+ origPushState.apply(this, args);
490
+ if (internalDepth === 0) notifyRouteChange();
491
+ };
492
+ patchedReplaceState = function(...args) {
493
+ origReplaceState.apply(this, args);
494
+ if (internalDepth === 0) notifyRouteChange();
495
+ };
496
+ history.pushState = patchedPushState;
497
+ history.replaceState = patchedReplaceState;
498
+ patchInstalled = true;
499
+ };
500
+ var removePatch = () => {
501
+ if (!patchInstalled) return;
502
+ if (history.pushState === patchedPushState && origPushState) {
503
+ history.pushState = origPushState;
504
+ }
505
+ if (history.replaceState === patchedReplaceState && origReplaceState) {
506
+ history.replaceState = origReplaceState;
507
+ }
508
+ origPushState = null;
509
+ origReplaceState = null;
510
+ patchedPushState = null;
511
+ patchedReplaceState = null;
512
+ patchInstalled = false;
513
+ };
514
+ function onPopState() {
515
+ if (suppress > 0) {
516
+ suppress -= 1;
517
+ const rebrand = pendingRebrand;
518
+ pendingRebrand = null;
519
+ if (rebrand) rebrand();
520
+ maybeTeardownListener();
521
+ return;
522
+ }
523
+ if (entries.length === 0) {
524
+ notifyRouteChange();
525
+ maybeTeardownListener();
526
+ return;
527
+ }
528
+ let target = null;
529
+ let targetIdx = -1;
530
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
531
+ const entry = entries[i];
532
+ if (!entry.surface.isOpen()) continue;
533
+ if (!target) {
534
+ target = entry;
535
+ targetIdx = i;
458
536
  }
459
- let target = null;
460
- for (let i = closers.length - 1; i >= 0; i -= 1) {
461
- const c = closers[i];
462
- if (!c.isOpen()) continue;
463
- if (!target) target = c;
464
- if (c.isTop()) {
465
- target = c;
466
- break;
467
- }
537
+ if (entry.surface.isTop()) {
538
+ target = entry;
539
+ targetIdx = i;
540
+ break;
468
541
  }
469
- if (!target) return;
470
- target.consumed = true;
471
- const idx = closers.indexOf(target);
472
- if (idx !== -1) closers.splice(idx, 1);
473
- const settled = target;
474
- void Promise.resolve(settled.close()).then(() => {
475
- if (settled.isOpen()) settled.restore();
476
- });
542
+ }
543
+ if (!target) {
544
+ const removed = entries.pop();
545
+ if (removed) removed.live = false;
546
+ maybeTeardownListener();
547
+ return;
548
+ }
549
+ target.live = false;
550
+ entries.splice(targetIdx, 1);
551
+ const settled = target;
552
+ void Promise.resolve(settled.surface.close()).then(() => {
553
+ if (settled.surface.isOpen()) restore(settled);
477
554
  });
478
- };
479
- var programmaticBack = () => {
480
- suppress += 1;
481
- try {
482
- history.back();
483
- } catch {
484
- suppress = Math.max(0, suppress - 1);
555
+ maybeTeardownListener();
556
+ }
557
+ function restore(entry) {
558
+ if (!hasWindow() || !hasHistory()) return;
559
+ entry.live = true;
560
+ runInternal(() => {
561
+ try {
562
+ history.pushState(entry.surface.markerState(), "", entry.surface.url);
563
+ } catch {
564
+ entry.live = false;
565
+ }
566
+ });
567
+ if (entry.live) {
568
+ entries.push(entry);
569
+ ensureListener();
485
570
  }
486
- };
571
+ }
572
+ function pushBackMarker(surface) {
573
+ const entry = { surface, priorUrl: void 0, live: false };
574
+ if (!hasWindow() || !hasHistory()) {
575
+ return entry;
576
+ }
577
+ entry.priorUrl = surface.url !== void 0 ? location.href : void 0;
578
+ runInternal(() => {
579
+ try {
580
+ history.pushState(surface.markerState(), "", surface.url);
581
+ entry.live = true;
582
+ } catch {
583
+ entry.live = false;
584
+ }
585
+ });
586
+ if (entry.live) {
587
+ entries.push(entry);
588
+ ensureListener();
589
+ }
590
+ return entry;
591
+ }
592
+ function popBackMarker(handle) {
593
+ if (!hasWindow() || !hasHistory()) return;
594
+ const entry = handle;
595
+ const idx = entries.indexOf(entry);
596
+ if (idx === -1 || !entry.live) return;
597
+ const last = entries.length - 1;
598
+ entry.live = false;
599
+ entries.splice(idx, 1);
600
+ if (idx !== last) {
601
+ const rebrandSurface = entries[entries.length - 1].surface;
602
+ const rebrandUrl = entry.priorUrl;
603
+ pendingRebrand = () => {
604
+ runInternal(() => {
605
+ history.replaceState(
606
+ rebrandSurface.markerState(),
607
+ "",
608
+ rebrandUrl !== void 0 ? rebrandUrl : location.href
609
+ );
610
+ });
611
+ };
612
+ }
613
+ suppress += 1;
614
+ runInternal(() => {
615
+ try {
616
+ history.back();
617
+ } catch {
618
+ suppress = Math.max(0, suppress - 1);
619
+ pendingRebrand = null;
620
+ }
621
+ });
622
+ maybeTeardownListener();
623
+ }
624
+ function subscribeRouteChange(fn) {
625
+ if (!hasWindow() || !hasHistory()) return () => {
626
+ };
627
+ subscribers.add(fn);
628
+ if (subscribers.size === 1) installPatch();
629
+ ensureListener();
630
+ return () => {
631
+ if (!subscribers.has(fn)) return;
632
+ subscribers.delete(fn);
633
+ if (subscribers.size === 0) removePatch();
634
+ maybeTeardownListener();
635
+ };
636
+ }
637
+
638
+ // src/core/features/route.ts
487
639
  function installRoute(deps) {
488
640
  if (typeof window === "undefined") {
489
641
  return () => {
@@ -493,92 +645,39 @@ function installRoute(deps) {
493
645
  return () => {
494
646
  };
495
647
  }
496
- let pushed = false;
497
- let closer = null;
498
- const teardownEntry = () => {
499
- if (closer) {
500
- const idx = closers.indexOf(closer);
501
- if (idx !== -1) closers.splice(idx, 1);
502
- }
503
- const wasConsumed = closer?.consumed ?? false;
504
- closer = null;
505
- if (pushed && !wasConsumed) {
506
- pushed = false;
507
- programmaticBack();
508
- } else {
509
- pushed = false;
510
- }
511
- };
512
- const pushEntry = () => {
513
- try {
514
- if (deps.routedTo !== void 0) {
515
- history.pushState({ __bsRouted: deps.sheetId }, "", deps.routedTo);
516
- } else {
517
- history.pushState({ __bsSheet: deps.sheetId }, "");
518
- }
519
- pushed = true;
520
- } catch {
521
- pushed = false;
522
- }
523
- };
648
+ let handle = null;
524
649
  const onOpen = () => {
525
- if (closer) return;
526
- const self = {
650
+ if (handle) return;
651
+ handle = pushBackMarker({
527
652
  isOpen: () => !deps.isDestroyed() && deps.getSize() > 0,
528
- isTop: () => deps.isTopSheet(),
529
- close: () => deps.close(),
530
- restore: () => {
531
- if (deps.isDestroyed() || closer !== self) return;
532
- self.consumed = false;
533
- if (!closers.includes(self)) closers.push(self);
534
- pushEntry();
535
- },
536
- consumed: false
537
- };
538
- closer = self;
539
- ensureHandler();
540
- closers.push(self);
541
- pushEntry();
653
+ isTop: deps.isTopSheet,
654
+ close: deps.close,
655
+ markerState: () => deps.routedTo !== void 0 ? { __bsRouted: deps.sheetId } : { __bsSheet: deps.sheetId },
656
+ url: deps.routedTo
657
+ });
658
+ };
659
+ const onClose = () => {
660
+ if (!handle) return;
661
+ popBackMarker(handle);
662
+ handle = null;
542
663
  };
543
664
  const unsubscribeOpen = deps.on("open", onOpen);
544
- const unsubscribeClose = deps.on("close", teardownEntry);
665
+ const unsubscribeClose = deps.on("close", onClose);
545
666
  if (deps.getSize() > 0) onOpen();
546
667
  return () => {
547
668
  unsubscribeOpen();
548
669
  unsubscribeClose();
549
- teardownEntry();
670
+ if (handle) {
671
+ popBackMarker(handle);
672
+ handle = null;
673
+ }
550
674
  };
551
675
  }
552
676
  function installRouteChange(deps) {
553
- if (typeof window === "undefined" || typeof history === "undefined") {
554
- return () => {
555
- };
556
- }
557
- const onChange = () => {
677
+ return subscribeRouteChange(() => {
558
678
  if (deps.isDestroyed() || deps.getSize() <= 0) return;
559
679
  void deps.close();
560
- };
561
- const wrap = (key) => {
562
- const original = history[key];
563
- const patched = function(...args) {
564
- original.apply(this, args);
565
- onChange();
566
- };
567
- history[key] = patched;
568
- return () => {
569
- if (history[key] === patched) {
570
- history[key] = original;
571
- }
572
- };
573
- };
574
- window.addEventListener("popstate", onChange);
575
- const restorePush = wrap("pushState");
576
- const restoreReplace = wrap("replaceState");
577
- return () => {
578
- window.removeEventListener("popstate", onChange);
579
- restorePush();
580
- restoreReplace();
581
- };
680
+ });
582
681
  }
583
682
 
584
683
  // src/core/features/content-swipe.ts
@@ -2432,7 +2531,7 @@ var matchesRange = (def, progress) => {
2432
2531
  };
2433
2532
  function installScrimStages(deps, opts) {
2434
2533
  const doc = deps.host.ownerDocument;
2435
- const entries = opts.stages.map((def) => {
2534
+ const entries2 = opts.stages.map((def) => {
2436
2535
  const wrapper = doc.createElement("div");
2437
2536
  wrapper.className = "bs-scrim-stage";
2438
2537
  const ws = wrapper.style;
@@ -2453,9 +2552,9 @@ function installScrimStages(deps, opts) {
2453
2552
  const resolveActive = () => {
2454
2553
  const state = deps.getState();
2455
2554
  if (state.size === 0) {
2456
- return entries.find((e) => matchesId(e.def, state.activeId)) ?? null;
2555
+ return entries2.find((e) => matchesId(e.def, state.activeId)) ?? null;
2457
2556
  }
2458
- return entries.find((e) => matchesId(e.def, state.activeId)) ?? entries.find((e) => matchesRange(e.def, state.progress)) ?? null;
2557
+ return entries2.find((e) => matchesId(e.def, state.activeId)) ?? entries2.find((e) => matchesRange(e.def, state.progress)) ?? null;
2459
2558
  };
2460
2559
  const hide = (entry, animate) => {
2461
2560
  entry.inFlight?.cancel();
@@ -2504,13 +2603,13 @@ function installScrimStages(deps, opts) {
2504
2603
  deps.on("open", () => update(true)),
2505
2604
  deps.on("close", () => update(true))
2506
2605
  ];
2507
- if (entries.some((e) => e.def.forRange)) {
2606
+ if (entries2.some((e) => e.def.forRange)) {
2508
2607
  offs.push(deps.on("progress", () => update(true)));
2509
2608
  }
2510
2609
  update(false);
2511
2610
  return () => {
2512
2611
  offs.forEach((off) => off());
2513
- for (const entry of entries) {
2612
+ for (const entry of entries2) {
2514
2613
  entry.inFlight?.cancel();
2515
2614
  entry.inFlight = null;
2516
2615
  if (entry.wrapper.parentNode) {
@@ -2769,8 +2868,8 @@ var BottomSheetEngine = class {
2769
2868
  this.attach();
2770
2869
  this.registerInStack();
2771
2870
  if (this.snaps.getMaxAxisSize() === 0 && typeof IntersectionObserver !== "undefined" && typeof document !== "undefined") {
2772
- const io = new IntersectionObserver((entries) => {
2773
- for (const entry of entries) {
2871
+ const io = new IntersectionObserver((entries2) => {
2872
+ for (const entry of entries2) {
2774
2873
  if (!entry.isIntersecting) continue;
2775
2874
  if (this.destroyed) return;
2776
2875
  this.snaps.recompute();
@@ -3487,9 +3586,9 @@ var BottomSheetEngine = class {
3487
3586
  return;
3488
3587
  }
3489
3588
  if (this.emitBeforeSnap(target, previousId)) {
3490
- const restore = this.snaps.findById(previousId);
3491
- if (restore) {
3492
- void this.animation.animateTo(restore.size, 0);
3589
+ const restore2 = this.snaps.findById(previousId);
3590
+ if (restore2) {
3591
+ void this.animation.animateTo(restore2.size, 0);
3493
3592
  }
3494
3593
  return;
3495
3594
  }
@@ -3795,7 +3894,7 @@ var OverlayEngine = class {
3795
3894
  __publicField(this, "detachSwipe", null);
3796
3895
  __publicField(this, "respectReducedMotionEnabled");
3797
3896
  __publicField(this, "isTop", true);
3798
- __publicField(this, "backPushed", false);
3897
+ __publicField(this, "backMarker", null);
3799
3898
  __publicField(this, "movedToTarget", false);
3800
3899
  __publicField(this, "onOpenCb");
3801
3900
  __publicField(this, "onCloseCb");
@@ -3807,7 +3906,6 @@ var OverlayEngine = class {
3807
3906
  __publicField(this, "bus", createEventBus());
3808
3907
  __publicField(this, "detachEscape", null);
3809
3908
  __publicField(this, "detachBackdrop", null);
3810
- __publicField(this, "detachHardwareBack", null);
3811
3909
  __publicField(this, "releaseStackEntry", null);
3812
3910
  __publicField(this, "lifecycle");
3813
3911
  this.element = opts.element;
@@ -3961,7 +4059,10 @@ var OverlayEngine = class {
3961
4059
  this.isOpen_ = false;
3962
4060
  this.isAnimating_ = true;
3963
4061
  sheetStack.update();
3964
- this.popBackMarker(reason);
4062
+ if (reason !== "back" && this.backMarker) {
4063
+ popBackMarker(this.backMarker);
4064
+ }
4065
+ this.backMarker = null;
3965
4066
  this.element.setAttribute("data-state", "exiting");
3966
4067
  const dur = this.effectiveDuration();
3967
4068
  const transitionStr = `transform ${dur}ms ${this.exitEasing}, opacity ${dur}ms ease-in`;
@@ -4019,6 +4120,10 @@ var OverlayEngine = class {
4019
4120
  this.bus.clear();
4020
4121
  this.setWillChange(false);
4021
4122
  this.releaseInteractiveListeners();
4123
+ if (this.backMarker) {
4124
+ popBackMarker(this.backMarker);
4125
+ this.backMarker = null;
4126
+ }
4022
4127
  this.releaseStackEntry?.();
4023
4128
  this.releaseStackEntry = null;
4024
4129
  const hadOriginalParent = this.originalParent !== null;
@@ -4180,19 +4285,6 @@ var OverlayEngine = class {
4180
4285
  effectiveDuration() {
4181
4286
  return this.respectReducedMotionEnabled && prefersReducedMotion() ? 0 : this.duration;
4182
4287
  }
4183
- popBackMarker(reason) {
4184
- if (!this.backPushed) return;
4185
- this.backPushed = false;
4186
- if (reason === "back") return;
4187
- const state = history.state;
4188
- if (state && state.__bsOverlay === this.id) {
4189
- try {
4190
- history.back();
4191
- } catch {
4192
- return;
4193
- }
4194
- }
4195
- }
4196
4288
  resolvePeekPx(raw) {
4197
4289
  if (typeof raw === "number") return Math.max(0, raw);
4198
4290
  try {
@@ -4308,21 +4400,6 @@ var OverlayEngine = class {
4308
4400
  this.backdrop.addEventListener("click", handler);
4309
4401
  this.detachBackdrop = () => this.backdrop?.removeEventListener("click", handler);
4310
4402
  }
4311
- if (this.closeOnBackEnabled && typeof window !== "undefined" && !this.detachHardwareBack) {
4312
- try {
4313
- history.pushState({ __bsOverlay: this.id }, "");
4314
- this.backPushed = true;
4315
- } catch {
4316
- }
4317
- const onPop = () => {
4318
- if (this.isOpen_ && this.isTop) {
4319
- this.backPushed = false;
4320
- void this.close("back");
4321
- }
4322
- };
4323
- window.addEventListener("popstate", onPop);
4324
- this.detachHardwareBack = () => window.removeEventListener("popstate", onPop);
4325
- }
4326
4403
  if (this.closeOnOutsidePointerEnabled && !this.detachOutsidePointer) {
4327
4404
  const handler = (e) => {
4328
4405
  if (!this.isOpen_) return;
@@ -4335,14 +4412,20 @@ var OverlayEngine = class {
4335
4412
  document.addEventListener("pointerdown", handler, true);
4336
4413
  this.detachOutsidePointer = () => document.removeEventListener("pointerdown", handler, true);
4337
4414
  }
4415
+ if (this.closeOnBackEnabled && this.backMarker === null && typeof window !== "undefined") {
4416
+ this.backMarker = pushBackMarker({
4417
+ isOpen: () => this.isOpen_,
4418
+ isTop: () => this.isTop,
4419
+ close: () => this.close("back"),
4420
+ markerState: () => ({ __bsOverlay: this.id })
4421
+ });
4422
+ }
4338
4423
  }
4339
4424
  releaseInteractiveListeners() {
4340
4425
  this.detachEscape?.();
4341
4426
  this.detachEscape = null;
4342
4427
  this.detachBackdrop?.();
4343
4428
  this.detachBackdrop = null;
4344
- this.detachHardwareBack?.();
4345
- this.detachHardwareBack = null;
4346
4429
  this.detachSwipe?.();
4347
4430
  this.detachSwipe = null;
4348
4431
  this.detachOutsidePointer?.();