@samline/drawer 2.0.4 → 2.0.6

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.
@@ -215,30 +215,38 @@ const nonTextInputTypes = new Set([
215
215
  'submit',
216
216
  'reset'
217
217
  ]);
218
- // The number of active usePreventScroll calls. Used to determine whether to revert back to the original page style/scroll position
219
- let preventScrollCount = 0;
220
- let restore;
218
+ const activePreventScrollLocks = new Set();
219
+ let activePreventScrollRestore = null;
220
+ function acquirePreventScrollLock(lockId) {
221
+ activePreventScrollLocks.add(lockId);
222
+ if (activePreventScrollLocks.size === 1 && isIOS()) {
223
+ activePreventScrollRestore = preventScrollMobileSafari();
224
+ }
225
+ }
226
+ function releasePreventScrollLock(lockId) {
227
+ activePreventScrollLocks.delete(lockId);
228
+ if (activePreventScrollLocks.size === 0) {
229
+ activePreventScrollRestore == null ? void 0 : activePreventScrollRestore();
230
+ activePreventScrollRestore = null;
231
+ }
232
+ }
221
233
  /**
222
234
  * Prevents scrolling on the document body on mount, and
223
235
  * restores it on unmount. Also ensures that content does not
224
236
  * shift due to the scrollbars disappearing.
225
237
  */ function usePreventScroll(options = {}) {
226
238
  let { isDisabled } = options;
239
+ const lockIdRef = React.useRef();
240
+ if (!lockIdRef.current) {
241
+ lockIdRef.current = Symbol('drawer-prevent-scroll-lock');
242
+ }
227
243
  useIsomorphicLayoutEffect(()=>{
228
244
  if (isDisabled) {
229
245
  return;
230
246
  }
231
- preventScrollCount++;
232
- if (preventScrollCount === 1) {
233
- if (isIOS()) {
234
- restore = preventScrollMobileSafari();
235
- }
236
- }
247
+ acquirePreventScrollLock(lockIdRef.current);
237
248
  return ()=>{
238
- preventScrollCount--;
239
- if (preventScrollCount === 0) {
240
- restore == null ? void 0 : restore();
241
- }
249
+ releasePreventScrollLock(lockIdRef.current);
242
250
  };
243
251
  }, [
244
252
  isDisabled
@@ -973,12 +981,23 @@ function useScaleBackground() {
973
981
  const { direction, isOpen, shouldScaleBackground, setBackgroundColorOnScale, noBodyStyles } = useDrawerContext();
974
982
  const timeoutIdRef = React__namespace.default.useRef(null);
975
983
  const initialBackgroundColor = React.useMemo(()=>document.body.style.backgroundColor, []);
984
+ React__namespace.default.useEffect(()=>{
985
+ return ()=>{
986
+ if (timeoutIdRef.current !== null) {
987
+ window.clearTimeout(timeoutIdRef.current);
988
+ timeoutIdRef.current = null;
989
+ }
990
+ };
991
+ }, []);
976
992
  function getScale() {
977
993
  return (window.innerWidth - WINDOW_TOP_OFFSET) / window.innerWidth;
978
994
  }
979
995
  React__namespace.default.useEffect(()=>{
980
996
  if (isOpen && shouldScaleBackground) {
981
- if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current);
997
+ if (timeoutIdRef.current !== null) {
998
+ clearTimeout(timeoutIdRef.current);
999
+ timeoutIdRef.current = null;
1000
+ }
982
1001
  const wrapper = document.querySelector('[data-drawer-wrapper]');
983
1002
  if (!wrapper) return;
984
1003
  chain(setBackgroundColorOnScale && !noBodyStyles ? assignStyle(document.body, {
@@ -1006,6 +1025,7 @@ function useScaleBackground() {
1006
1025
  } else {
1007
1026
  document.body.style.removeProperty('background');
1008
1027
  }
1028
+ timeoutIdRef.current = null;
1009
1029
  }, TRANSITIONS.DURATION * 1000);
1010
1030
  };
1011
1031
  }
@@ -1017,6 +1037,14 @@ function useScaleBackground() {
1017
1037
  }
1018
1038
 
1019
1039
  let previousBodyPosition = null;
1040
+ const activeBodyPositionLocks = new Set();
1041
+ let bodyPositionTimeoutId = null;
1042
+ function clearBodyPositionTimeout() {
1043
+ if (bodyPositionTimeoutId !== null) {
1044
+ window.clearTimeout(bodyPositionTimeoutId);
1045
+ bodyPositionTimeoutId = null;
1046
+ }
1047
+ }
1020
1048
  /**
1021
1049
  * This hook is necessary to prevent buggy behavior on iOS devices (need to test on Android).
1022
1050
  * I won't get into too much detail about what bugs it solves, but so far I've found that setting the body to `position: fixed` is the most reliable way to prevent those bugs.
@@ -1024,9 +1052,18 @@ let previousBodyPosition = null;
1024
1052
  */ function usePositionFixed({ isOpen, modal, nested, hasBeenOpened, preventScrollRestoration, noBodyStyles }) {
1025
1053
  const [activeUrl, setActiveUrl] = React__namespace.default.useState(()=>typeof window !== 'undefined' ? window.location.href : '');
1026
1054
  const scrollPos = React__namespace.default.useRef(0);
1055
+ const lockIdRef = React__namespace.default.useRef();
1056
+ if (!lockIdRef.current) {
1057
+ lockIdRef.current = Symbol('drawer-body-position-lock');
1058
+ }
1027
1059
  const setPositionFixed = React__namespace.default.useCallback(()=>{
1028
1060
  // All browsers on iOS will return true here.
1029
1061
  if (!isSafari()) return;
1062
+ const lockId = lockIdRef.current;
1063
+ if (activeBodyPositionLocks.has(lockId)) {
1064
+ return;
1065
+ }
1066
+ activeBodyPositionLocks.add(lockId);
1030
1067
  // If previousBodyPosition is already set, don't set it again.
1031
1068
  if (previousBodyPosition === null && isOpen && !noBodyStyles) {
1032
1069
  previousBodyPosition = {
@@ -1045,7 +1082,8 @@ let previousBodyPosition = null;
1045
1082
  right: '0px',
1046
1083
  height: 'auto'
1047
1084
  });
1048
- window.setTimeout(()=>window.requestAnimationFrame(()=>{
1085
+ clearBodyPositionTimeout();
1086
+ bodyPositionTimeoutId = window.setTimeout(()=>window.requestAnimationFrame(()=>{
1049
1087
  // Attempt to check if the bottom bar appeared due to the position change
1050
1088
  const bottomBarHeight = innerHeight - window.innerHeight;
1051
1089
  if (bottomBarHeight && scrollPos.current >= innerHeight) {
@@ -1055,12 +1093,19 @@ let previousBodyPosition = null;
1055
1093
  }), 300);
1056
1094
  }
1057
1095
  }, [
1058
- isOpen
1096
+ isOpen,
1097
+ noBodyStyles
1059
1098
  ]);
1060
1099
  const restorePositionSetting = React__namespace.default.useCallback(()=>{
1061
1100
  // All browsers on iOS will return true here.
1062
1101
  if (!isSafari()) return;
1102
+ const lockId = lockIdRef.current;
1103
+ activeBodyPositionLocks.delete(lockId);
1104
+ if (activeBodyPositionLocks.size > 0) {
1105
+ return;
1106
+ }
1063
1107
  if (previousBodyPosition !== null && !noBodyStyles) {
1108
+ clearBodyPositionTimeout();
1064
1109
  // Convert the position from "px" to Int
1065
1110
  const y = -parseInt(document.body.style.top, 10);
1066
1111
  const x = -parseInt(document.body.style.left, 10);
@@ -1076,7 +1121,9 @@ let previousBodyPosition = null;
1076
1121
  previousBodyPosition = null;
1077
1122
  }
1078
1123
  }, [
1079
- activeUrl
1124
+ activeUrl,
1125
+ noBodyStyles,
1126
+ setActiveUrl
1080
1127
  ]);
1081
1128
  React__namespace.default.useEffect(()=>{
1082
1129
  function onScroll() {
@@ -1426,8 +1473,15 @@ function getDragPermission({ targetTagName, hasNoDragAttribute, direction, timeS
1426
1473
  };
1427
1474
  }
1428
1475
 
1476
+ const useSafeLayoutEffect$1 = typeof window === 'undefined' ? React__namespace.default.useEffect : React__namespace.default.useLayoutEffect;
1429
1477
  function Root({ open: openProp, onOpenChange, children, onDrag: onDragProp, onRelease: onReleaseProp, snapPoints, shouldScaleBackground = false, setBackgroundColorOnScale = true, closeThreshold = CLOSE_THRESHOLD, scrollLockTimeout = SCROLL_LOCK_TIMEOUT, dismissible = true, handleOnly = false, fadeFromIndex = snapPoints && snapPoints.length - 1, activeSnapPoint: activeSnapPointProp, setActiveSnapPoint: setActiveSnapPointProp, fixed, modal = true, onClose, nested, noBodyStyles = false, direction = 'bottom', defaultOpen = false, disablePreventScroll = true, snapToSequentialPoint = false, preventScrollRestoration = false, repositionInputs = true, onAnimationEnd, container, autoFocus = false }) {
1430
1478
  var _drawerRef_current, _drawerRef_current1;
1479
+ const animationEndTimeoutRef = React__namespace.default.useRef(null);
1480
+ const nonModalPointerEventsRafRef = React__namespace.default.useRef(null);
1481
+ const shouldAnimateRafRef = React__namespace.default.useRef(null);
1482
+ const snapPointsResetTimeoutRef = React__namespace.default.useRef(null);
1483
+ const justReleasedTimeoutRef = React__namespace.default.useRef(null);
1484
+ const touchEndHandlerRef = React__namespace.default.useRef(null);
1431
1485
  const [isOpen = false, setIsOpen] = useControllableState({
1432
1486
  defaultProp: defaultOpen,
1433
1487
  prop: openProp,
@@ -1436,13 +1490,20 @@ function Root({ open: openProp, onOpenChange, children, onDrag: onDragProp, onRe
1436
1490
  if (!o && !nested) {
1437
1491
  restorePositionSetting();
1438
1492
  }
1439
- setTimeout(()=>{
1493
+ if (animationEndTimeoutRef.current !== null) {
1494
+ window.clearTimeout(animationEndTimeoutRef.current);
1495
+ }
1496
+ animationEndTimeoutRef.current = window.setTimeout(()=>{
1440
1497
  onAnimationEnd == null ? void 0 : onAnimationEnd(o);
1441
1498
  }, TRANSITIONS.DURATION * 1000);
1442
1499
  if (o && !modal) {
1443
1500
  if (typeof window !== 'undefined') {
1444
- window.requestAnimationFrame(()=>{
1501
+ if (nonModalPointerEventsRafRef.current !== null) {
1502
+ window.cancelAnimationFrame(nonModalPointerEventsRafRef.current);
1503
+ }
1504
+ nonModalPointerEventsRafRef.current = window.requestAnimationFrame(()=>{
1445
1505
  document.body.style.pointerEvents = 'auto';
1506
+ nonModalPointerEventsRafRef.current = null;
1446
1507
  });
1447
1508
  }
1448
1509
  }
@@ -1520,19 +1581,23 @@ function Root({ open: openProp, onOpenChange, children, onDrag: onDragProp, onRe
1520
1581
  noBodyStyles,
1521
1582
  autoFocus
1522
1583
  });
1523
- React__namespace.default.useEffect(()=>{
1584
+ useSafeLayoutEffect$1(()=>{
1524
1585
  var _drawerRef_current;
1525
1586
  if (!isOpen || !modal || autoFocus || typeof document === 'undefined') {
1526
1587
  return;
1527
1588
  }
1528
1589
  const activeElement = document.activeElement;
1529
- if (!(activeElement instanceof HTMLElement)) {
1590
+ if (!activeElement || activeElement === document.body) {
1530
1591
  return;
1531
1592
  }
1532
- if (((_drawerRef_current = drawerRef.current) == null ? void 0 : _drawerRef_current.contains(activeElement)) || activeElement.closest('[data-drawer]')) {
1593
+ const activeElementNode = activeElement;
1594
+ if (((_drawerRef_current = drawerRef.current) == null ? void 0 : _drawerRef_current.contains(activeElementNode)) || (activeElementNode.closest == null ? void 0 : activeElementNode.closest.call(activeElementNode, '[data-drawer]'))) {
1533
1595
  return;
1534
1596
  }
1535
- activeElement.blur();
1597
+ if (typeof activeElementNode.blur !== 'function') {
1598
+ return;
1599
+ }
1600
+ activeElementNode.blur();
1536
1601
  }, [
1537
1602
  autoFocus,
1538
1603
  isOpen,
@@ -1551,7 +1616,15 @@ function Root({ open: openProp, onOpenChange, children, onDrag: onDragProp, onRe
1551
1616
  dragStartTime.current = new Date();
1552
1617
  // iOS doesn't trigger mouseUp after scrolling so we need to listen to touched in order to disallow dragging
1553
1618
  if (isIOS()) {
1554
- window.addEventListener('touchend', ()=>isAllowedToDrag.current = false, {
1619
+ if (touchEndHandlerRef.current) {
1620
+ window.removeEventListener('touchend', touchEndHandlerRef.current);
1621
+ }
1622
+ const handleTouchEnd = ()=>{
1623
+ isAllowedToDrag.current = false;
1624
+ touchEndHandlerRef.current = null;
1625
+ };
1626
+ touchEndHandlerRef.current = handleTouchEnd;
1627
+ window.addEventListener('touchend', handleTouchEnd, {
1555
1628
  once: true
1556
1629
  });
1557
1630
  }
@@ -1668,9 +1741,15 @@ function Root({ open: openProp, onOpenChange, children, onDrag: onDragProp, onRe
1668
1741
  }
1669
1742
  }
1670
1743
  React__namespace.default.useEffect(()=>{
1671
- window.requestAnimationFrame(()=>{
1744
+ shouldAnimateRafRef.current = window.requestAnimationFrame(()=>{
1672
1745
  shouldAnimate.current = true;
1673
1746
  });
1747
+ return ()=>{
1748
+ if (shouldAnimateRafRef.current !== null) {
1749
+ window.cancelAnimationFrame(shouldAnimateRafRef.current);
1750
+ shouldAnimateRafRef.current = null;
1751
+ }
1752
+ };
1674
1753
  }, []);
1675
1754
  React__namespace.default.useEffect(()=>{
1676
1755
  var _window_visualViewport;
@@ -1720,10 +1799,14 @@ function Root({ open: openProp, onOpenChange, children, onDrag: onDragProp, onRe
1720
1799
  if (!fromWithin) {
1721
1800
  setIsOpen(false);
1722
1801
  }
1723
- setTimeout(()=>{
1802
+ if (snapPointsResetTimeoutRef.current !== null) {
1803
+ window.clearTimeout(snapPointsResetTimeoutRef.current);
1804
+ }
1805
+ snapPointsResetTimeoutRef.current = window.setTimeout(()=>{
1724
1806
  if (snapPoints) {
1725
1807
  setActiveSnapPoint(snapPoints[0]);
1726
1808
  }
1809
+ snapPointsResetTimeoutRef.current = null;
1727
1810
  }, TRANSITIONS.DURATION * 1000); // seconds to ms
1728
1811
  }
1729
1812
  function resetDrawer() {
@@ -1797,8 +1880,12 @@ function Root({ open: openProp, onOpenChange, children, onDrag: onDragProp, onRe
1797
1880
  if (releaseResult.shouldPreventFocus) {
1798
1881
  // `justReleased` is needed to prevent the drawer from focusing on an input when the drag ends, as it's not the intent most of the time.
1799
1882
  setJustReleased(true);
1800
- setTimeout(()=>{
1883
+ if (justReleasedTimeoutRef.current !== null) {
1884
+ window.clearTimeout(justReleasedTimeoutRef.current);
1885
+ }
1886
+ justReleasedTimeoutRef.current = window.setTimeout(()=>{
1801
1887
  setJustReleased(false);
1888
+ justReleasedTimeoutRef.current = null;
1802
1889
  }, 200);
1803
1890
  }
1804
1891
  if (releaseResult.action === 'close') {
@@ -1875,12 +1962,45 @@ function Root({ open: openProp, onOpenChange, children, onDrag: onDragProp, onRe
1875
1962
  });
1876
1963
  }
1877
1964
  }
1965
+ React__namespace.default.useEffect(()=>{
1966
+ return ()=>{
1967
+ if (animationEndTimeoutRef.current !== null) {
1968
+ window.clearTimeout(animationEndTimeoutRef.current);
1969
+ }
1970
+ if (nonModalPointerEventsRafRef.current !== null) {
1971
+ window.cancelAnimationFrame(nonModalPointerEventsRafRef.current);
1972
+ }
1973
+ if (snapPointsResetTimeoutRef.current !== null) {
1974
+ window.clearTimeout(snapPointsResetTimeoutRef.current);
1975
+ }
1976
+ if (justReleasedTimeoutRef.current !== null) {
1977
+ window.clearTimeout(justReleasedTimeoutRef.current);
1978
+ }
1979
+ if (nestedOpenChangeTimer.current) {
1980
+ window.clearTimeout(nestedOpenChangeTimer.current);
1981
+ }
1982
+ if (touchEndHandlerRef.current) {
1983
+ window.removeEventListener('touchend', touchEndHandlerRef.current);
1984
+ touchEndHandlerRef.current = null;
1985
+ }
1986
+ };
1987
+ }, []);
1878
1988
  React__namespace.default.useEffect(()=>{
1879
1989
  if (!modal) {
1880
1990
  // Need to do this manually unfortunately
1881
- window.requestAnimationFrame(()=>{
1991
+ if (nonModalPointerEventsRafRef.current !== null) {
1992
+ window.cancelAnimationFrame(nonModalPointerEventsRafRef.current);
1993
+ }
1994
+ nonModalPointerEventsRafRef.current = window.requestAnimationFrame(()=>{
1882
1995
  document.body.style.pointerEvents = 'auto';
1996
+ nonModalPointerEventsRafRef.current = null;
1883
1997
  });
1998
+ return ()=>{
1999
+ if (nonModalPointerEventsRafRef.current !== null) {
2000
+ window.cancelAnimationFrame(nonModalPointerEventsRafRef.current);
2001
+ nonModalPointerEventsRafRef.current = null;
2002
+ }
2003
+ };
1884
2004
  }
1885
2005
  }, [
1886
2006
  modal
@@ -1964,15 +2084,25 @@ const Content = /*#__PURE__*/ React__namespace.default.forwardRef(function({ onP
1964
2084
  const pointerStartRef = React__namespace.default.useRef(null);
1965
2085
  const lastKnownPointerEventRef = React__namespace.default.useRef(null);
1966
2086
  const wasBeyondThePointRef = React__namespace.default.useRef(false);
2087
+ const delayedSnapPointsRafRef = React__namespace.default.useRef(null);
1967
2088
  const hasSnapPoints = snapPoints && snapPoints.length > 0;
1968
2089
  useScaleBackground();
1969
2090
  React__namespace.default.useEffect(()=>{
1970
2091
  if (hasSnapPoints) {
1971
- window.requestAnimationFrame(()=>{
2092
+ delayedSnapPointsRafRef.current = window.requestAnimationFrame(()=>{
1972
2093
  setDelayedSnapPoints(true);
2094
+ delayedSnapPointsRafRef.current = null;
1973
2095
  });
1974
2096
  }
1975
- }, []);
2097
+ return ()=>{
2098
+ if (delayedSnapPointsRafRef.current !== null) {
2099
+ window.cancelAnimationFrame(delayedSnapPointsRafRef.current);
2100
+ delayedSnapPointsRafRef.current = null;
2101
+ }
2102
+ };
2103
+ }, [
2104
+ hasSnapPoints
2105
+ ]);
1976
2106
  function handleOnPointerUp(event) {
1977
2107
  pointerStartRef.current = null;
1978
2108
  wasBeyondThePointRef.current = false;
@@ -2073,6 +2203,7 @@ const DOUBLE_TAP_TIMEOUT = 120;
2073
2203
  const Handle = /*#__PURE__*/ React__namespace.default.forwardRef(function({ preventCycle = false, children, ...rest }, ref) {
2074
2204
  const { closeDrawer, isDragging, snapPoints, activeSnapPoint, setActiveSnapPoint, dismissible, handleOnly, isOpen, onPress, onDrag } = useDrawerContext();
2075
2205
  const closeTimeoutIdRef = React__namespace.default.useRef(null);
2206
+ const cycleTimeoutIdRef = React__namespace.default.useRef(null);
2076
2207
  const shouldCancelInteractionRef = React__namespace.default.useRef(false);
2077
2208
  function handleStartCycle() {
2078
2209
  // Stop if this is the second click of a double click
@@ -2080,7 +2211,7 @@ const Handle = /*#__PURE__*/ React__namespace.default.forwardRef(function({ prev
2080
2211
  handleCancelInteraction();
2081
2212
  return;
2082
2213
  }
2083
- window.setTimeout(()=>{
2214
+ cycleTimeoutIdRef.current = window.setTimeout(()=>{
2084
2215
  handleCycleSnapPoints();
2085
2216
  }, DOUBLE_TAP_TIMEOUT);
2086
2217
  }
@@ -2113,9 +2244,19 @@ const Handle = /*#__PURE__*/ React__namespace.default.forwardRef(function({ prev
2113
2244
  function handleCancelInteraction() {
2114
2245
  if (closeTimeoutIdRef.current) {
2115
2246
  window.clearTimeout(closeTimeoutIdRef.current);
2247
+ closeTimeoutIdRef.current = null;
2248
+ }
2249
+ if (cycleTimeoutIdRef.current) {
2250
+ window.clearTimeout(cycleTimeoutIdRef.current);
2251
+ cycleTimeoutIdRef.current = null;
2116
2252
  }
2117
2253
  shouldCancelInteractionRef.current = false;
2118
2254
  }
2255
+ React__namespace.default.useEffect(()=>{
2256
+ return ()=>{
2257
+ handleCancelInteraction();
2258
+ };
2259
+ }, []);
2119
2260
  return /*#__PURE__*/ React__namespace.default.createElement("div", {
2120
2261
  onClick: handleStartCycle,
2121
2262
  onPointerCancel: handleCancelInteraction,
@@ -2463,6 +2604,7 @@ function openAncestorChain(parentId) {
2463
2604
  openAncestorChain(nextParentId);
2464
2605
  }
2465
2606
  if (!parentRuntime.controller.getSnapshot().state.isOpen) {
2607
+ releaseHiddenFocusBeforeOpen(parentRuntime.options);
2466
2608
  parentRuntime.controller.setOpen(true);
2467
2609
  notifyOpenStateChange(parentRuntime, true);
2468
2610
  renderVanillaDrawer(parentRuntime.id);
@@ -2482,6 +2624,7 @@ function bindTriggerElement(runtime) {
2482
2624
  }
2483
2625
  const triggerElement = runtime.options.triggerElement;
2484
2626
  const handleClick = ()=>{
2627
+ releaseHiddenFocusBeforeOpen(runtime.options);
2485
2628
  runtime.controller.setOpen(true);
2486
2629
  renderVanillaDrawer(runtime.id);
2487
2630
  };
@@ -2517,6 +2660,16 @@ function notifyOpenStateChange(runtime, open) {
2517
2660
  function canUseDOM() {
2518
2661
  return typeof window !== 'undefined' && typeof document !== 'undefined';
2519
2662
  }
2663
+ function releaseHiddenFocusBeforeOpen(options) {
2664
+ if (!canUseDOM() || options.modal === false || options.autoFocus) {
2665
+ return;
2666
+ }
2667
+ const activeElement = document.activeElement;
2668
+ if (!activeElement || activeElement === document.body || typeof activeElement.blur !== 'function') {
2669
+ return;
2670
+ }
2671
+ activeElement.blur();
2672
+ }
2520
2673
  function getRuntimeDrawerElement(runtime) {
2521
2674
  if (!runtime.element) {
2522
2675
  return null;
@@ -2567,6 +2720,9 @@ function renderVanillaDrawer(id) {
2567
2720
  open: snapshot.state.isOpen,
2568
2721
  onOpenChange: (open)=>{
2569
2722
  const previousOpen = runtime.controller.getSnapshot().state.isOpen;
2723
+ if (open) {
2724
+ releaseHiddenFocusBeforeOpen(runtime.options);
2725
+ }
2570
2726
  runtime.controller.setOpen(open);
2571
2727
  if (previousOpen !== open) {
2572
2728
  notifyOpenStateChange(runtime, open);
@@ -2619,6 +2775,9 @@ function buildVanillaController(id) {
2619
2775
  open
2620
2776
  }).getSnapshot();
2621
2777
  }
2778
+ if (open) {
2779
+ releaseHiddenFocusBeforeOpen(runtime.options);
2780
+ }
2622
2781
  const previousOpen = runtime.controller.getSnapshot().state.isOpen;
2623
2782
  const snapshot = runtime.controller.setOpen(open);
2624
2783
  if (previousOpen !== open) {
@@ -2712,6 +2871,9 @@ function createDrawer(options = {}) {
2712
2871
  notifyOpenStateChange(existing, snapshot.state.isOpen);
2713
2872
  }
2714
2873
  }
2874
+ if (nextOptions.open && !previousOpen) {
2875
+ releaseHiddenFocusBeforeOpen(nextOptions);
2876
+ }
2715
2877
  renderVanillaDrawer(id);
2716
2878
  if (nextOptions.parentId && nextOptions.open) {
2717
2879
  openAncestorChain(nextOptions.parentId);