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