@ringcentral/spring-ui 1.11.2 → 1.13.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.
Files changed (241) hide show
  1. package/Accordion.js +90 -34
  2. package/AccordionHeader.js +72 -20
  3. package/Alert.js +120 -28
  4. package/Animation.js +2 -45
  5. package/Announcement.js +46 -35
  6. package/Autocomplete.js +1011 -580
  7. package/Avatar.js +134 -73
  8. package/AvatarGroup.js +50 -7
  9. package/Backdrop.js +45 -7
  10. package/Badge.js +77 -15
  11. package/Block.js +34 -12
  12. package/BlockHeader.js +55 -11
  13. package/BottomNavigation.js +181 -65
  14. package/BottomNavigationItem.js +93 -30
  15. package/Button.js +160 -24
  16. package/CallButton.js +53 -31
  17. package/Checkbox.js +88 -26
  18. package/Chip.js +126 -26
  19. package/CircularProgressIndicator.js +80 -8
  20. package/ClickAwayListener.js +4 -1
  21. package/ComponentsProps.js +1 -0
  22. package/DatePicker.js +611 -190
  23. package/DateUtilsContext.js +43 -25
  24. package/Dialer.js +576 -250
  25. package/Dialog.js +100 -45
  26. package/Divider.js +43 -7
  27. package/DragHandle.js +32 -6
  28. package/Drawer.js +117 -19
  29. package/Dropdown.js +154 -74
  30. package/EmptyState.js +63 -11
  31. package/FabButton.js +51 -17
  32. package/FocusTrap.js +109 -12
  33. package/FormField.js +106 -8
  34. package/FormLabel.js +81 -7
  35. package/Icon.js +1 -8
  36. package/IconButton.js +142 -26
  37. package/IconButtonGroup.js +35 -12
  38. package/InlineEditable.js +98 -44
  39. package/LinearProgressIndicator.js +73 -24
  40. package/Link.js +40 -11
  41. package/List.js +54 -9
  42. package/ListItem.js +152 -23
  43. package/ListItemText.js +44 -16
  44. package/ListNavigation.js +272 -20
  45. package/Menu.js +139 -23
  46. package/MenuActions.js +37 -12
  47. package/MenuDivider.js +34 -8
  48. package/MenuFooter.js +36 -12
  49. package/MenuHeader.js +54 -15
  50. package/MenuItem.js +402 -33
  51. package/MenuItemText.js +51 -8
  52. package/MenuList.js +87 -34
  53. package/Modal.js +345 -16
  54. package/NestedMenuContext.js +183 -11
  55. package/Option.js +32 -34
  56. package/Overlap.js +54 -7
  57. package/PageHeader.js +62 -24
  58. package/PickerTextField.js +156 -33
  59. package/Popover.js +262 -21
  60. package/Popper.js +130 -77
  61. package/PopperPaper.js +54 -8
  62. package/Portal.js +6 -1
  63. package/Radio.js +65 -21
  64. package/RadioGroup.js +56 -17
  65. package/Rating.js +223 -87
  66. package/Select.js +387 -41
  67. package/SingleFilter.js +148 -77
  68. package/Skeleton.js +68 -17
  69. package/Slider.js +168 -69
  70. package/Snackbar.js +122 -59
  71. package/SortableList.js +32 -12
  72. package/SortableListDndContext.js +69 -22
  73. package/SortableListItem.js +92 -47
  74. package/SortableVirtualizedList.js +52 -28
  75. package/SplitButton.js +124 -86
  76. package/Squircle.js +4 -7
  77. package/StatusIndicator.js +51 -9
  78. package/Step.js +63 -38
  79. package/StepButton.js +51 -23
  80. package/StepConnector.js +48 -8
  81. package/StepContent.js +68 -25
  82. package/StepIcon.js +90 -9
  83. package/StepLabel.js +90 -14
  84. package/Stepper.js +65 -23
  85. package/SubMenu.js +203 -99
  86. package/SwipeableDrawer.js +439 -194
  87. package/Switch.js +70 -23
  88. package/Tab.js +120 -57
  89. package/TabContext.js +59 -15
  90. package/TabPanel.js +40 -13
  91. package/Table.js +60 -17
  92. package/TableBody.js +42 -12
  93. package/TableCell.js +84 -50
  94. package/TableContainer.js +32 -10
  95. package/TableFooter.js +38 -10
  96. package/TableHead.js +49 -15
  97. package/TablePagination.js +153 -77
  98. package/TableRow.js +60 -21
  99. package/Tabs.js +593 -268
  100. package/Tag.js +51 -16
  101. package/Text.js +68 -37
  102. package/TextField.js +209 -29
  103. package/Textarea.js +51 -40
  104. package/TimePicker.js +490 -230
  105. package/Tooltip.js +48 -23
  106. package/Tray.js +237 -13
  107. package/TrayMenu.js +62 -41
  108. package/VirtualizedList.js +208 -109
  109. package/VirtualizedMenu.js +359 -29
  110. package/VisuallyHidden.js +29 -6
  111. package/chunks/1250.js +98 -0
  112. package/chunks/1294.js +126 -0
  113. package/chunks/2956.js +18 -0
  114. package/chunks/4476.js +460 -0
  115. package/chunks/4491.js +10 -0
  116. package/chunks/4535.js +364 -0
  117. package/chunks/5047.js +3 -0
  118. package/chunks/6335.js +24 -0
  119. package/chunks/7279.js +10 -0
  120. package/chunks/8011.js +8 -0
  121. package/chunks/8307.js +56 -0
  122. package/chunks/8467.js +8 -0
  123. package/chunks/8809.js +8 -0
  124. package/chunks/{RadioGroup.context.js → 9551.js} +12 -6
  125. package/chunks/987.js +8 -0
  126. package/index.css +2624 -2416
  127. package/index.js +245 -193
  128. package/internal/utils/index.d.ts +1 -0
  129. package/internal/utils/setRef.d.ts +2 -0
  130. package/package.json +8 -8
  131. package/src/Alert/Alert.type.d.ts +2 -1
  132. package/src/Animation/components/Fade/Fade.d.ts +4 -2
  133. package/src/Animation/components/Grow/Grow.d.ts +4 -2
  134. package/src/Animation/components/ShowHideEye/ShowHideEye.d.ts +1 -1
  135. package/src/Animation/components/SlideIn/SlideIn.d.ts +4 -2
  136. package/src/Animation/components/SlideIn/getSlideIn.d.ts +1 -1
  137. package/src/Animation/hooks/useMotionSafeTransition/useMotionSafeTransition.d.ts +2 -0
  138. package/src/Autocomplete/SuggestionList/SuggestionList.d.ts +1 -1
  139. package/src/Autocomplete/useAutocomplete.d.ts +18 -18
  140. package/src/Autocomplete/useAutocompleteTag.d.ts +18 -18
  141. package/src/Badge/Badge.d.ts +2 -1
  142. package/src/Button/Button.classes.d.ts +1 -1
  143. package/src/ComponentsProps.d.ts +111 -110
  144. package/src/DatePicker/DatePickerDataAttributes.d.ts +11 -0
  145. package/src/DatePicker/utils/DatePickerAriaLabelUtils.d.ts +2 -1
  146. package/src/DatePicker/utils/DatePickerUtils.d.ts +1 -1
  147. package/src/Dialer/DialDelete/DialDelete.d.ts +2 -2
  148. package/src/Dialer/DialPad/DialPad.d.ts +2 -2
  149. package/src/Dialer/Dialer.d.ts +4 -2
  150. package/src/Dropdown/Dropdown.type.d.ts +2 -2
  151. package/src/InlineEditable/InlineEditable.type.d.ts +2 -1
  152. package/src/ListNavigation/useListNavigation.d.ts +3 -3
  153. package/src/MenuActions/MenuActions.d.ts +2 -2
  154. package/src/MenuItem/MenuItem.type.d.ts +1 -0
  155. package/src/MenuItem/useMenuItem.d.ts +1 -1
  156. package/src/MenuItem/useVirtualizedMenuItem.d.ts +1 -1
  157. package/src/MenuList/MenuList.type.d.ts +1 -1
  158. package/src/MenuList/MenuListContext.d.ts +2 -3
  159. package/src/Modal/Modal.type.d.ts +1 -0
  160. package/src/NestedMenuContext/MenuOverlayGroupContext.d.ts +2 -2
  161. package/src/Popover/Popover.classes.d.ts +1 -1
  162. package/src/Popover/Popover.internal.d.ts +8 -0
  163. package/src/Popover/Popover.type.d.ts +1 -0
  164. package/src/Popper/Popper.type.d.ts +10 -7
  165. package/src/Rating/utils.d.ts +3 -2
  166. package/src/Select/Select.type.d.ts +7 -5
  167. package/src/Slider/Slider.d.ts +2 -2
  168. package/src/Slider/SliderThumb.d.ts +2 -1
  169. package/src/Squircle/Squircle.d.ts +1 -1
  170. package/src/StatusIndicator/StatusIndicator.d.ts +2 -1
  171. package/src/SubMenu/PopperMenu.type.d.ts +4 -1
  172. package/src/Table/Table.type.d.ts +3 -0
  173. package/src/Table/TableContext.d.ts +3 -0
  174. package/src/Table/TableRowContext.d.ts +7 -0
  175. package/src/Table/index.d.ts +1 -0
  176. package/src/TableCell/TableCell.classes.d.ts +1 -1
  177. package/src/TableFooter/TableFooter.classes.d.ts +1 -1
  178. package/src/TableFooter/TableFooter.type.d.ts +1 -0
  179. package/src/TableHead/TableHead.classes.d.ts +1 -1
  180. package/src/TableHead/TableHead.type.d.ts +1 -0
  181. package/src/TableRow/TableRow.type.d.ts +2 -0
  182. package/chunks/AccordionContext.js +0 -6
  183. package/chunks/Alert.js +0 -48
  184. package/chunks/AvatarGroup.js +0 -22
  185. package/chunks/Backdrop.js +0 -18
  186. package/chunks/Badge.js +0 -32
  187. package/chunks/BottomNavigationItem.js +0 -36
  188. package/chunks/Button.js +0 -88
  189. package/chunks/Chip.js +0 -73
  190. package/chunks/CircularProgressIndicator.js +0 -36
  191. package/chunks/Divider.js +0 -19
  192. package/chunks/DragHandle.js +0 -11
  193. package/chunks/Drawer.js +0 -32
  194. package/chunks/ExpandCollapseCaret.js +0 -24
  195. package/chunks/FormLabel.js +0 -27
  196. package/chunks/Icon.js +0 -25
  197. package/chunks/IconButtonLabel.js +0 -68
  198. package/chunks/List.js +0 -22
  199. package/chunks/ListItem.js +0 -83
  200. package/chunks/Menu.js +0 -61
  201. package/chunks/MenuDivider.js +0 -14
  202. package/chunks/MenuItemText.js +0 -17
  203. package/chunks/MenuList.js +0 -46
  204. package/chunks/Modal.js +0 -212
  205. package/chunks/Overlap.js +0 -25
  206. package/chunks/PickerTextField.js +0 -85
  207. package/chunks/Popover.js +0 -53
  208. package/chunks/PopperPaper.js +0 -15
  209. package/chunks/Select.js +0 -220
  210. package/chunks/ShowHideEye.js +0 -56
  211. package/chunks/SlideIn.js +0 -19
  212. package/chunks/SortableArrow.js +0 -73
  213. package/chunks/Squircle.js +0 -31
  214. package/chunks/StatusIndicator.js +0 -20
  215. package/chunks/StepConnector.js +0 -21
  216. package/chunks/StepIcon.js +0 -43
  217. package/chunks/StepLabel.js +0 -29
  218. package/chunks/StepperContext.js +0 -9
  219. package/chunks/TableContext.js +0 -6
  220. package/chunks/Tablelvl2Context.js +0 -6
  221. package/chunks/TabsContext.js +0 -6
  222. package/chunks/TextField.js +0 -110
  223. package/chunks/Tooltip.js +0 -217
  224. package/chunks/VirtualizedMenu.js +0 -164
  225. package/chunks/VisuallyHidden.js +0 -12
  226. package/chunks/getFade.js +0 -27
  227. package/chunks/getGrow.js +0 -70
  228. package/chunks/getSlideIn.js +0 -30
  229. package/chunks/hasContent.js +0 -3
  230. package/chunks/useAnimationFrame.js +0 -15
  231. package/chunks/useFocusTrapStack.js +0 -36
  232. package/chunks/useHelperTextId.js +0 -22
  233. package/chunks/useListNavigation.js +0 -199
  234. package/chunks/useMenuItemSelect.js +0 -273
  235. package/chunks/useMotionSafeTransition.js +0 -9
  236. package/chunks/useNestedMenuItem.js +0 -27
  237. package/chunks/useNestedParentMenu.js +0 -113
  238. package/chunks/usePrefersReducedMotion.js +0 -36
  239. package/chunks/useTooltipForceHide.js +0 -27
  240. package/chunks/useTrayNavigate.js +0 -150
  241. package/chunks/withTooltip.js +0 -15
@@ -1,35 +1,47 @@
1
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { useState, useCallback, useEffect, useLayoutEffect, useMemo, useRef, forwardRef, useImperativeHandle } from 'react';
3
- import { useWindowContext, useEventCallback, useClasses } from '@ringcentral/spring-base';
4
- import { useThemeProps } from '@ringcentral/spring-theme';
5
- import clsx from 'clsx';
6
- import { D as Drawer } from './chunks/Drawer.js';
7
- import { D as DragHandle } from './chunks/DragHandle.js';
8
- import { useMotionValue, animate, useTransform } from 'framer-motion';
9
- import { rubberbandIfOutOfBounds, useDrag } from '@use-gesture/react';
10
- import { u as useAnimationFrame } from './chunks/useAnimationFrame.js';
11
- import { ResizeObserver } from '@juggle/resize-observer';
12
- import './chunks/Icon.js';
13
- import './chunks/usePrefersReducedMotion.js';
14
- import './chunks/getSlideIn.js';
15
- import './chunks/Modal.js';
16
- import '@mui/base';
17
- import './chunks/getFade.js';
18
- import './chunks/useFocusTrapStack.js';
19
- import './chunks/Backdrop.js';
20
-
21
- var SwipeableDrawer$1 = {"sui-swipeable-drawer-header-drag":"sui-swipeable-drawer-header-drag"};
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
3
+ import { useClasses, useEventCallback, useWindowContext } from "@ringcentral/spring-base";
4
+ import { useThemeProps } from "@ringcentral/spring-theme";
5
+ import clsx from "clsx";
6
+ import { animate, useMotionValue, useTransform } from "framer-motion";
7
+ import { rubberbandIfOutOfBounds, useDrag } from "@use-gesture/react";
8
+ import { ResizeObserver } from "@juggle/resize-observer";
9
+ import { useAnimationFrame } from "./chunks/6335.js";
10
+ import { Drawer as Drawer_Drawer } from "./Drawer.js";
11
+ import { DragHandle as DragHandle_DragHandle } from "./DragHandle.js";
12
+
13
+
14
+
15
+
16
+
17
+
18
+ // extracted by css-extract-rspack-plugin
19
+ /* export default */ var SwipeableDrawer = ({"sui-swipeable-drawer-header-drag":"sui-swipeable-drawer-header-drag"});
20
+
21
+
22
+
23
+
24
+
25
+ // Keeps track of whether everything is good to go or not, in the most efficient way possible
22
26
 
23
27
  function useReady() {
24
28
  const [ready, setReady] = useState(false);
25
29
  const [readyMap, updateReadyMap] = useState({});
26
- const registerReady = useCallback((key) => {
27
- updateReadyMap((ready) => ({ ...ready, [key]: false }));
28
- return () => {
29
- updateReadyMap((ready) => ({ ...ready, [key]: true }));
30
+ const registerReady = useCallback((key)=>{
31
+ // Register the check we're gonna wait for until it's ready
32
+ updateReadyMap((ready)=>({
33
+ ...ready,
34
+ [key]: false
35
+ }));
36
+ return ()=>{
37
+ // Set it to ready
38
+ updateReadyMap((ready)=>({
39
+ ...ready,
40
+ [key]: true
41
+ }));
30
42
  };
31
43
  }, []);
32
- useEffect(() => {
44
+ useEffect(()=>{
33
45
  const states = Object.values(readyMap);
34
46
  if (states.length === 0) {
35
47
  return;
@@ -37,76 +49,106 @@ function useReady() {
37
49
  const isReady = states.every(Boolean);
38
50
  if (isReady) {
39
51
  setReady(true);
40
- }
41
- else {
52
+ } else {
42
53
  setReady(false);
43
54
  }
44
- }, [readyMap]);
45
- return { ready, registerReady };
55
+ }, [
56
+ readyMap
57
+ ]);
58
+ return {
59
+ ready,
60
+ registerReady
61
+ };
46
62
  }
47
63
 
64
+
65
+
66
+
67
+
68
+ // Hook for determining the size of an element using the Resize Observer API
69
+
70
+
71
+
48
72
  const observerOptions = {
49
- box: 'border-box',
73
+ // Respond to changes to padding, happens often on iOS when using env(safe-area-inset-bottom)
74
+ // And the user hides or shows the Safari browser toolbar
75
+ box: 'border-box'
50
76
  };
51
- function useElementSizeObserver(ref, { enabled, resizeSourceRef, }) {
77
+ /**
78
+ * Hook for determining the size of an element using the Resize Observer API.
79
+ *
80
+ * @param ref - A React ref to an element
81
+ * @param options - Configuration options
82
+ * @param options.enabled - Whether the observer is enabled
83
+ * @param options.resizeSourceRef - Optional ref to track resize source
84
+ */ function useElementSizeObserver(ref, { enabled, resizeSourceRef }) {
52
85
  const { requestAnimationFrame, cancelAnimationFrame } = useAnimationFrame();
53
86
  const [size, setSize] = useState(0);
54
- const handleResize = useCallback((entries) => {
87
+ const handleResize = useCallback((entries)=>{
88
+ // we only observe one element, so accessing the first entry here is fine
55
89
  const entry = entries?.[0];
56
90
  if (!entry) {
91
+ // Early return if no entry - can happen during observer disconnect
57
92
  return;
58
93
  }
59
94
  const el = entry.target;
60
95
  if (!el) {
96
+ // Early return if no target element
61
97
  return;
62
98
  }
99
+ // Prefer intrinsic content size to visible height so drag doesn't change the measured height.
100
+ // @juggle/resize-observer provides consistent borderBoxSize access across browsers
63
101
  const roBlock = entry?.borderBoxSize?.[0]?.blockSize ?? 0;
64
102
  const contentRectH = entry?.contentRect?.height ?? 0;
65
103
  const scrollH = el?.scrollHeight ?? 0;
66
104
  const offsetH = el?.offsetHeight ?? 0;
67
105
  const newSize = Math.max(scrollH, roBlock, contentRectH, offsetH);
68
106
  setSize(newSize);
107
+ // Update resize source if provided
69
108
  if (resizeSourceRef) {
70
109
  resizeSourceRef.current = 'element';
71
110
  }
72
- }, [resizeSourceRef]);
73
- useLayoutEffect(() => {
111
+ }, [
112
+ resizeSourceRef
113
+ ]);
114
+ useLayoutEffect(()=>{
74
115
  if (!enabled) {
75
116
  return;
76
117
  }
77
118
  let resizeObserver = null;
78
119
  let rafId = 0;
79
120
  let initialMeasurementRafId = 0;
80
- const startObserving = () => {
121
+ const startObserving = ()=>{
81
122
  const el = ref.current;
82
- if (!el || !el.isConnected)
83
- return;
123
+ if (!el || !el.isConnected) return;
124
+ // @juggle/resize-observer provides cross-browser compatibility and polyfills
84
125
  resizeObserver = new ResizeObserver(handleResize);
85
126
  resizeObserver.observe(el, observerOptions);
86
- initialMeasurementRafId = requestAnimationFrame(() => {
127
+ // Seed an initial measurement in case RO hasn't fired yet
128
+ initialMeasurementRafId = requestAnimationFrame(()=>{
87
129
  const initial = Math.max(el.scrollHeight, el.offsetHeight, el.getBoundingClientRect().height);
88
130
  if (initial > 0) {
89
131
  setSize(initial);
90
132
  }
133
+ // else keep waiting for RO to update on next ticks
91
134
  });
92
135
  };
93
- const waitForElement = () => {
136
+ // If the element is not yet mounted (e.g. Modal shouldRender=false), keep
137
+ // polling via rAF until it's available, then attach the observer.
138
+ const waitForElement = ()=>{
94
139
  if (ref.current) {
95
140
  startObserving();
96
- }
97
- else {
141
+ } else {
98
142
  rafId = requestAnimationFrame(waitForElement);
99
143
  }
100
144
  };
101
145
  if (ref.current) {
102
146
  startObserving();
103
- }
104
- else {
147
+ } else {
105
148
  rafId = requestAnimationFrame(waitForElement);
106
149
  }
107
- return () => {
108
- if (rafId)
109
- cancelAnimationFrame(rafId);
150
+ return ()=>{
151
+ if (rafId) cancelAnimationFrame(rafId);
110
152
  if (initialMeasurementRafId) {
111
153
  cancelAnimationFrame(initialMeasurementRafId);
112
154
  }
@@ -114,95 +156,138 @@ function useElementSizeObserver(ref, { enabled, resizeSourceRef, }) {
114
156
  resizeObserver.disconnect();
115
157
  }
116
158
  };
117
- }, [ref, handleResize, enabled, requestAnimationFrame, cancelAnimationFrame]);
159
+ }, [
160
+ ref,
161
+ handleResize,
162
+ enabled,
163
+ requestAnimationFrame,
164
+ cancelAnimationFrame
165
+ ]);
118
166
  return enabled ? size : 0;
119
167
  }
120
168
 
169
+ // Hook for measuring drawer dimensions
170
+
171
+
172
+
173
+
174
+ // Hook for tracking viewport height
121
175
  function useMaxHeight(registerReady) {
122
176
  const { externalWindow, globalThis: contextGlobal } = useWindowContext();
123
177
  const win = externalWindow ?? contextGlobal;
124
178
  const { requestAnimationFrame, cancelAnimationFrame } = useAnimationFrame();
125
- const setReady = useMemo(() => {
179
+ const setReady = useMemo(()=>{
126
180
  return registerReady ? registerReady('maxHeight') : undefined;
127
- }, [registerReady]);
128
- const [maxHeight, setMaxHeight] = useState(() => win.innerHeight ?? 0);
181
+ }, [
182
+ registerReady
183
+ ]);
184
+ const [maxHeight, setMaxHeight] = useState(()=>win.innerHeight ?? 0);
129
185
  const raf = useRef(0);
130
186
  const maxReadyCalledRef = useRef(false);
131
- useLayoutEffect(() => {
187
+ // Reset the ready flag when setReady changes (drawer reset)
188
+ useLayoutEffect(()=>{
132
189
  maxReadyCalledRef.current = false;
133
- }, [setReady]);
134
- useLayoutEffect(() => {
135
- const handleResize = () => {
190
+ }, [
191
+ setReady
192
+ ]);
193
+ useLayoutEffect(()=>{
194
+ const handleResize = ()=>{
136
195
  if (raf.current) {
196
+ // bail to throttle the amount of resize changes
137
197
  return;
138
198
  }
139
- raf.current = requestAnimationFrame(() => {
199
+ // throttle state changes using rAF from target window
200
+ raf.current = requestAnimationFrame(()=>{
201
+ // Check if cleanup already ran (raf.current would be 0)
140
202
  if (raf.current !== 0) {
141
- setMaxHeight(win.innerHeight);
203
+ setMaxHeight(win.innerHeight); // Safe to update
142
204
  raf.current = 0;
143
205
  }
144
206
  });
145
207
  };
146
208
  win.addEventListener('resize', handleResize);
147
209
  setMaxHeight(win.innerHeight);
210
+ // Call setReady exactly once for window height readiness
148
211
  if (setReady && !maxReadyCalledRef.current) {
149
212
  maxReadyCalledRef.current = true;
150
213
  setReady();
151
214
  }
152
- return () => {
215
+ return ()=>{
153
216
  win.removeEventListener('resize', handleResize);
154
217
  cancelAnimationFrame(raf.current);
155
218
  };
156
- }, [setReady, win, requestAnimationFrame, cancelAnimationFrame]);
219
+ }, [
220
+ setReady,
221
+ win,
222
+ requestAnimationFrame,
223
+ cancelAnimationFrame
224
+ ]);
157
225
  return maxHeight;
158
226
  }
159
- function usePreservedHeight(currentHeight) {
227
+ /**
228
+ * Custom hook to preserve last non-zero height values
229
+ */ function usePreservedHeight(currentHeight) {
160
230
  const lastHeightRef = useRef(0);
161
- useLayoutEffect(() => {
231
+ useLayoutEffect(()=>{
162
232
  if (currentHeight > 0) {
163
233
  lastHeightRef.current = currentHeight;
164
234
  }
165
- }, [currentHeight]);
235
+ }, [
236
+ currentHeight
237
+ ]);
166
238
  return currentHeight > 0 ? currentHeight : lastHeightRef.current;
167
239
  }
168
- function useDimensions({ contentRef, footerEnabled, footerRef, headerEnabled, headerRef, registerReady, resizeSourceRef, open, }) {
169
- const setReady = useMemo(() => registerReady('contentHeight'), [registerReady]);
240
+ function useDimensions({ contentRef, footerEnabled, footerRef, headerEnabled, headerRef, registerReady, resizeSourceRef, open }) {
241
+ const setReady = useMemo(()=>registerReady('contentHeight'), [
242
+ registerReady
243
+ ]);
244
+ // Do not re-register contentHeight readiness on disconnect; register once above and let
245
+ // the ResizeObserver drive readiness when size > 0. This avoids readiness loops.
246
+ // (Intentionally removed disconnection re-register logic)
170
247
  const maxHeight = useMaxHeight(registerReady);
248
+ // Measure all elements - only when drawer is open
171
249
  const headerHeight = useElementSizeObserver(headerRef, {
172
250
  enabled: open && headerEnabled,
173
- resizeSourceRef,
251
+ resizeSourceRef
174
252
  });
175
253
  const contentHeight = useElementSizeObserver(contentRef, {
176
254
  enabled: open,
177
- resizeSourceRef,
255
+ resizeSourceRef
178
256
  });
179
257
  const footerHeight = useElementSizeObserver(footerRef, {
180
258
  enabled: open && footerEnabled,
181
- resizeSourceRef,
259
+ resizeSourceRef
182
260
  });
261
+ // Preserve last non-zero heights to avoid collapsing to 0 when the
262
+ // modal/portal temporarily unmounts or RO reports 0 during transitions
183
263
  const effectiveContentHeight = usePreservedHeight(contentHeight);
184
264
  const effectiveHeaderHeight = usePreservedHeight(headerHeight);
185
265
  const effectiveFooterHeight = usePreservedHeight(footerHeight);
186
- useLayoutEffect(() => {
187
- if (!setReady)
188
- return;
266
+ // When content is measured (>0), mark ready for contentHeight
267
+ useLayoutEffect(()=>{
268
+ if (!setReady) return;
189
269
  const ok = contentHeight > 0;
190
270
  if (ok) {
191
271
  setReady();
192
272
  }
193
- }, [contentHeight, contentRef, setReady]);
273
+ }, [
274
+ contentHeight,
275
+ contentRef,
276
+ setReady
277
+ ]);
278
+ // Calculate available space for content, ensuring it's never negative
194
279
  const availableContentSpace = Math.max(0, maxHeight - effectiveHeaderHeight - effectiveFooterHeight);
195
- const minHeight = Math.min(availableContentSpace, effectiveContentHeight) +
196
- effectiveHeaderHeight +
197
- effectiveFooterHeight;
280
+ const minHeight = Math.min(availableContentSpace, effectiveContentHeight) + effectiveHeaderHeight + effectiveFooterHeight;
198
281
  return {
199
282
  maxHeight,
200
283
  minHeight,
201
284
  headerHeight,
202
- footerHeight: effectiveFooterHeight,
285
+ footerHeight: effectiveFooterHeight
203
286
  };
204
287
  }
205
288
 
289
+ // Hook for calculating snap points
290
+
206
291
  function roundAndCheckForNaN(value) {
207
292
  const rounded = Math.round(value);
208
293
  if (Number.isNaN(rounded)) {
@@ -210,12 +295,15 @@ function roundAndCheckForNaN(value) {
210
295
  }
211
296
  return rounded;
212
297
  }
298
+ // Clamp function with clear NaN handling
213
299
  function clamp(number, lower, upper) {
214
300
  number = +number;
215
301
  lower = +lower;
216
302
  upper = +upper;
303
+ // Handle NaN values with sensible defaults
217
304
  const safeLower = Number.isNaN(lower) ? 0 : lower;
218
305
  const safeUpper = Number.isNaN(upper) ? Number.POSITIVE_INFINITY : upper;
306
+ // Ensure lower <= upper to avoid invalid ranges
219
307
  const finalLower = Math.min(safeLower, safeUpper);
220
308
  const finalUpper = Math.max(safeLower, safeUpper);
221
309
  if (Number.isNaN(number)) {
@@ -224,20 +312,20 @@ function clamp(number, lower, upper) {
224
312
  return Math.min(Math.max(number, finalLower), finalUpper);
225
313
  }
226
314
  function processSnapPoints(snapPoints, maxHeight) {
315
+ // Validate maxHeight input
227
316
  if (!Number.isFinite(maxHeight) || maxHeight <= 0) {
228
317
  throw new Error(`Invalid maxHeight: ${maxHeight}. Must be a positive finite number.`);
229
318
  }
230
- const safeSnaps = []
231
- .concat(snapPoints)
232
- .map(roundAndCheckForNaN);
233
- const snapPointsDedupedSet = safeSnaps.reduce((acc, snapPoint) => {
319
+ // Match React Spring Bottom Sheet implementation exactly
320
+ const safeSnaps = [].concat(snapPoints).map(roundAndCheckForNaN);
321
+ const snapPointsDedupedSet = safeSnaps.reduce((acc, snapPoint)=>{
234
322
  acc.add(clamp(snapPoint, 0, maxHeight));
235
323
  return acc;
236
324
  }, new Set());
237
325
  const processedPoints = Array.from(snapPointsDedupedSet);
326
+ // Handle empty snap points array
238
327
  if (processedPoints.length === 0) {
239
- throw new Error('No valid snap points available. The snapPoints function must return at least one valid number.\n' +
240
- 'Example: snapPoints: ({ minHeight, maxHeight }) => [minHeight, maxHeight]');
328
+ throw new Error('No valid snap points available. The snapPoints function must return at least one valid number.\n' + 'Example: snapPoints: ({ minHeight, maxHeight }) => [minHeight, maxHeight]');
241
329
  }
242
330
  const minSnap = Math.min(...processedPoints);
243
331
  if (Number.isNaN(minSnap)) {
@@ -250,10 +338,10 @@ function processSnapPoints(snapPoints, maxHeight) {
250
338
  return {
251
339
  snapPoints: processedPoints,
252
340
  minSnap,
253
- maxSnap,
341
+ maxSnap
254
342
  };
255
343
  }
256
- function useSnapPoints({ contentRef, footerEnabled, footerRef, getSnapPoints, headerEnabled, headerRef, heightRef, lastSnapRef, ready, registerReady, resizeSourceRef, open, }) {
344
+ function useSnapPoints({ contentRef, footerEnabled, footerRef, getSnapPoints, headerEnabled, headerRef, heightRef, lastSnapRef, ready, registerReady, resizeSourceRef, open }) {
257
345
  const { maxHeight, minHeight, headerHeight, footerHeight } = useDimensions({
258
346
  contentRef,
259
347
  footerEnabled,
@@ -262,18 +350,19 @@ function useSnapPoints({ contentRef, footerEnabled, footerRef, getSnapPoints, he
262
350
  headerRef,
263
351
  registerReady,
264
352
  resizeSourceRef,
265
- open,
353
+ open
266
354
  });
267
- const { snapPoints, minSnap, maxSnap } = processSnapPoints(ready
268
- ? getSnapPoints({
269
- height: heightRef.current ?? 0,
270
- footerHeight,
271
- headerHeight,
272
- minHeight,
273
- maxHeight,
274
- })
275
- : [0], maxHeight);
355
+ const { snapPoints, minSnap, maxSnap } = processSnapPoints(ready ? getSnapPoints({
356
+ height: heightRef.current ?? 0,
357
+ footerHeight,
358
+ headerHeight,
359
+ minHeight,
360
+ maxHeight
361
+ }) : [
362
+ 0
363
+ ], maxHeight);
276
364
  function findSnap(numberOrCallback) {
365
+ // Defensive check: should never happen due to processSnapPoints validation
277
366
  if (snapPoints.length === 0) {
278
367
  throw new Error('Cannot find snap point: no snap points available');
279
368
  }
@@ -287,76 +376,106 @@ function useSnapPoints({ contentRef, footerEnabled, footerRef, getSnapPoints, he
287
376
  minHeight,
288
377
  maxHeight,
289
378
  snapPoints,
290
- lastSnap: lastSnapRef.current,
379
+ lastSnap: lastSnapRef.current
291
380
  });
292
- }
293
- catch (error) {
381
+ } catch (error) {
382
+ // If callback throws, fall back to current position or minSnap
294
383
  unsafeSearch = heightRef.current ?? minSnap;
295
384
  }
296
- }
297
- else {
385
+ } else {
298
386
  unsafeSearch = numberOrCallback;
299
387
  }
300
388
  let querySnap;
301
389
  try {
302
390
  querySnap = roundAndCheckForNaN(unsafeSearch);
303
- }
304
- catch (error) {
391
+ } catch (error) {
392
+ // If number is invalid (NaN, etc), fall back to current position or minSnap
305
393
  querySnap = heightRef.current ?? minSnap;
306
394
  }
307
- const isValidIndex = querySnap >= 0 &&
308
- querySnap < snapPoints.length &&
309
- Number.isInteger(querySnap);
395
+ // Support index-based selection: if querySnap is a small integer and within bounds, treat as index
396
+ const isValidIndex = querySnap >= 0 && querySnap < snapPoints.length && Number.isInteger(querySnap);
310
397
  if (isValidIndex) {
311
398
  return snapPoints[querySnap];
312
399
  }
313
- return snapPoints.reduce((prev, curr) => Math.abs(curr - querySnap) < Math.abs(prev - querySnap) ? curr : prev, minSnap);
400
+ // Otherwise, find closest snap point by value
401
+ return snapPoints.reduce((prev, curr)=>Math.abs(curr - querySnap) < Math.abs(prev - querySnap) ? curr : prev, minSnap);
314
402
  }
315
- return { minSnap, maxSnap, findSnap, maxHeight, snapPoints };
403
+ return {
404
+ minSnap,
405
+ maxSnap,
406
+ findSnap,
407
+ maxHeight,
408
+ snapPoints
409
+ };
316
410
  }
317
411
 
412
+ //implementation for Drawer gestures
413
+
414
+
415
+
416
+
417
+
418
+
419
+ // Standardized spring configuration
318
420
  const SPRING_CONFIG = {
319
421
  type: 'spring',
320
422
  stiffness: 600,
321
423
  damping: 50,
322
- mass: 1,
424
+ mass: 1
323
425
  };
426
+ // Rubberband effect constants
324
427
  const RUBBERBAND_RESISTANCE = 0.8;
325
428
  const SINGLE_SNAP_STRETCH_FACTOR = 1.2;
326
429
  const MULTI_SNAP_STRETCH_FACTOR = 1.8;
327
- const DISMISS_THRESHOLD = 50;
328
- const VELOCITY_THRESHOLD = 0.1;
329
- const PREDICTION_MULTIPLIER = 2;
330
- const DISMISS_POSITION_FACTOR = 2;
430
+ // Gesture behavior constants
431
+ const DISMISS_THRESHOLD = 50; // Minimum movement in pixels to trigger dismiss
432
+ const VELOCITY_THRESHOLD = 0.1; // Minimum velocity to apply to spring animation
433
+ const PREDICTION_MULTIPLIER = 2; // Multiplier for velocity-based position prediction
434
+ const DISMISS_POSITION_FACTOR = 2; // Divisor for calculating dismiss threshold (minSnap / 2)
435
+ // Default functions
331
436
  function defaultSnapPoints({ minHeight }) {
332
- return [minHeight];
437
+ // When no snap points are specified, just use content height
438
+ // This means the drawer shows content at natural size and can only dismiss
439
+ return [
440
+ minHeight
441
+ ];
333
442
  }
334
- function defaultInitialSnap({ snapPoints, lastSnap: _lastSnap, }) {
443
+ function defaultInitialSnap({ snapPoints, lastSnap: _lastSnap }) {
444
+ // Default behavior: choose the smallest snap point (usually content height)
445
+ // Users can override this in their initialSnap function if they want lastSnap behavior
335
446
  return Math.min(...snapPoints);
336
447
  }
337
448
  function useDrawerGestures(options) {
338
- const { anchor, open, onClose, snapPoints: getSnapPoints = defaultSnapPoints, initialSnap: getInitialSnap = defaultInitialSnap, contentRef, headerRef, footerRef, headerEnabled, footerEnabled, } = options;
449
+ const { anchor, open, onClose, snapPoints: getSnapPoints = defaultSnapPoints, initialSnap: getInitialSnap = defaultInitialSnap, contentRef, headerRef, footerRef, headerEnabled, footerEnabled } = options;
339
450
  const { externalWindow } = useWindowContext();
340
451
  const gestureWindow = externalWindow;
341
452
  const { ready, registerReady } = useReady();
453
+ // Height tracking refs
342
454
  const heightRef = useRef(0);
343
455
  const lastSnapRef = useRef(null);
344
456
  const resizeSourceRef = useRef('window');
457
+ // Animation cleanup ref to prevent memory leaks
345
458
  const animationRef = useRef(null);
346
- useLayoutEffect(() => {
347
- return () => {
459
+ // Cleanup animations on unmount to prevent memory leaks
460
+ useLayoutEffect(()=>{
461
+ return ()=>{
348
462
  animationRef.current?.stop();
349
463
  };
350
464
  }, []);
465
+ // Motion values
351
466
  const heightMotionValue = useMotionValue(0);
352
- useLayoutEffect(() => {
353
- if (open &&
354
- ready &&
355
- heightRef.current > 0 &&
356
- heightMotionValue.get() === 0) {
467
+ // We animate with spring only on release; during drag we set the value directly for immediate follow
468
+ // Ensure motion value starts with correct value when drawer opens
469
+ useLayoutEffect(()=>{
470
+ if (open && ready && heightRef.current > 0 && heightMotionValue.get() === 0) {
357
471
  heightMotionValue.set(heightRef.current);
358
472
  }
359
- }, [open, ready, heightMotionValue]);
473
+ }, [
474
+ open,
475
+ ready,
476
+ heightMotionValue
477
+ ]);
478
+ // Snap points calculation
360
479
  const { minSnap, maxSnap, findSnap, maxHeight } = useSnapPoints({
361
480
  contentRef: contentRef,
362
481
  footerEnabled,
@@ -369,33 +488,48 @@ function useDrawerGestures(options) {
369
488
  ready,
370
489
  registerReady,
371
490
  resizeSourceRef,
372
- open,
491
+ open
373
492
  });
374
- const defaultSnap = useMemo(() => {
375
- if (!ready)
376
- return 0;
493
+ // Calculate initial snap
494
+ const defaultSnap = useMemo(()=>{
495
+ if (!ready) return 0;
496
+ // If initial snap is a function, it receives state via findSnap
377
497
  return findSnap(getInitialSnap);
378
- }, [ready, getInitialSnap, findSnap]);
498
+ }, [
499
+ ready,
500
+ getInitialSnap,
501
+ findSnap
502
+ ]);
503
+ // Only set the initial height once per open cycle when content is present
379
504
  const didInitRef = useRef(false);
380
505
  const isContentPresent = !!contentRef?.current && contentRef.current.isConnected;
381
- useLayoutEffect(() => {
382
- if (!open || !ready)
383
- return;
384
- if (didInitRef.current)
385
- return;
506
+ useLayoutEffect(()=>{
507
+ if (!open || !ready) return;
508
+ if (didInitRef.current) return;
509
+ // Use the calculated defaultSnap (which may or may not include lastSnap based on user's initialSnap function)
386
510
  const safeInitial = defaultSnap > 0 ? defaultSnap : minSnap;
387
- if (!isContentPresent && safeInitial <= 0)
388
- return;
511
+ // If we don't have content present and no valid fallback height, wait
512
+ if (!isContentPresent && safeInitial <= 0) return;
513
+ // 1. Set to 0 immediately
389
514
  heightMotionValue.set(0);
515
+ // 2. Update heightRef
390
516
  heightRef.current = safeInitial;
517
+ // 3. Animate to target position
391
518
  animationRef.current = animate(heightMotionValue, safeInitial, SPRING_CONFIG);
392
519
  didInitRef.current = true;
393
- }, [open, ready, isContentPresent, defaultSnap, minSnap, heightMotionValue]);
394
- useLayoutEffect(() => {
395
- if (!open || !ready || !isContentPresent)
396
- return;
397
- if (!didInitRef.current)
398
- return;
520
+ }, [
521
+ open,
522
+ ready,
523
+ isContentPresent,
524
+ defaultSnap,
525
+ minSnap,
526
+ heightMotionValue
527
+ ]);
528
+ // Content resize:
529
+ useLayoutEffect(()=>{
530
+ if (!open || !ready || !isContentPresent) return;
531
+ if (!didInitRef.current) return; // Only after initial setup is complete
532
+ // Find closest snap point to current height (preserves user positioning)
399
533
  const currentHeight = heightRef.current ?? 0;
400
534
  if (currentHeight > 0) {
401
535
  const closestSnap = findSnap(currentHeight);
@@ -403,66 +537,82 @@ function useDrawerGestures(options) {
403
537
  heightRef.current = closestSnap;
404
538
  lastSnapRef.current = closestSnap;
405
539
  }
406
- }, [minSnap, maxSnap]);
407
- useLayoutEffect(() => {
540
+ }, [
541
+ minSnap,
542
+ maxSnap
543
+ ]); // Triggers when snap points change (content resize)
544
+ // Reset init flag when closing so next open can initialize again
545
+ useLayoutEffect(()=>{
408
546
  if (!open) {
409
547
  didInitRef.current = false;
548
+ // Preserve the current height as lastSnap for next open
410
549
  if (heightRef.current > 0) {
411
550
  lastSnapRef.current = heightRef.current;
412
551
  }
552
+ // Animate motion value to 0 on close
413
553
  animationRef.current = animate(heightMotionValue, 0, SPRING_CONFIG);
414
554
  }
415
- }, [open, heightMotionValue]);
416
- const handleDrag = useEventCallback(({ args: [{ closeOnTap = false } = {}] = [], cancel, down, last, memo = heightMotionValue.get(), movement: [_mx, _my], tap, velocity: [_, velocityY], event, }) => {
555
+ }, [
556
+ open,
557
+ heightMotionValue
558
+ ]);
559
+ // Drag handler
560
+ const handleDrag = useEventCallback(({ args: [{ closeOnTap = false } = {}] = [], cancel, down, last, memo = heightMotionValue.get(), movement: [_mx, _my], tap, velocity: [_, velocityY], event })=>{
417
561
  if (!ready) {
418
562
  cancel();
419
563
  return memo;
420
564
  }
565
+ // Handle close on tap (backdrop tap)
421
566
  if (closeOnTap && tap) {
422
567
  cancel();
568
+ // Use timeout to avoid tap events on backdrop triggering click events underneath
423
569
  onClose(event, 'gestureSwipe');
424
570
  return memo;
425
571
  }
572
+ // Filter out taps
426
573
  if (tap) {
427
574
  return memo;
428
575
  }
429
- const getDismissInfo = () => {
430
- switch (anchor) {
576
+ // dismissal logic for all anchors
577
+ const getDismissInfo = ()=>{
578
+ switch(anchor){
431
579
  case 'bottom':
432
580
  return {
433
581
  isDismissDirection: _my > 0,
434
582
  movement: _my,
435
- threshold: DISMISS_THRESHOLD,
583
+ threshold: DISMISS_THRESHOLD
436
584
  };
437
585
  case 'top':
438
586
  return {
439
587
  isDismissDirection: _my < 0,
440
588
  movement: Math.abs(_my),
441
- threshold: DISMISS_THRESHOLD,
589
+ threshold: DISMISS_THRESHOLD
442
590
  };
443
591
  case 'left':
444
592
  return {
445
593
  isDismissDirection: _mx < 0,
446
594
  movement: Math.abs(_mx),
447
- threshold: DISMISS_THRESHOLD,
595
+ threshold: DISMISS_THRESHOLD
448
596
  };
449
597
  case 'right':
450
598
  return {
451
599
  isDismissDirection: _mx > 0,
452
600
  movement: Math.abs(_mx),
453
- threshold: DISMISS_THRESHOLD,
601
+ threshold: DISMISS_THRESHOLD
454
602
  };
455
603
  default:
456
604
  return {
457
605
  isDismissDirection: false,
458
606
  movement: 0,
459
- threshold: DISMISS_THRESHOLD,
607
+ threshold: DISMISS_THRESHOLD
460
608
  };
461
609
  }
462
610
  };
463
611
  const { isDismissDirection, movement, threshold } = getDismissInfo();
612
+ // For horizontal drawers, only handle dismiss (no snap points)
464
613
  const isVertical = anchor === 'top' || anchor === 'bottom';
465
614
  if (!isVertical) {
615
+ // Check for dismiss condition (horizontal drawers only)
466
616
  if (!down && isDismissDirection && movement > threshold) {
467
617
  cancel();
468
618
  onClose(event, 'gestureSwipe');
@@ -474,6 +624,7 @@ function useDrawerGestures(options) {
474
624
  const rawY = memo + my;
475
625
  const predictedDistance = my * velocityY;
476
626
  const predictedY = Math.max(minSnap, Math.min(maxHeight, rawY + predictedDistance * PREDICTION_MULTIPLIER));
627
+ // Check for vertical dismissal - only when trying to go below minimum snap point
477
628
  if (!down && isDismissDirection) {
478
629
  const dismissThreshold = minSnap / DISMISS_POSITION_FACTOR;
479
630
  const currentPosition = rawY + predictedDistance;
@@ -486,88 +637,145 @@ function useDrawerGestures(options) {
486
637
  let newY;
487
638
  if (down) {
488
639
  if (minSnap === maxSnap) {
640
+ // Special case when min and max snap are the same (only one snap point)
489
641
  if (rawY < minSnap) {
490
- newY = rawY;
491
- }
492
- else {
642
+ newY = rawY; // Allow smooth dragging down (toward dismiss) with no resistance
643
+ } else {
644
+ // Strong resistance when dragging up beyond content
493
645
  newY = rubberbandIfOutOfBounds(rawY, minSnap, maxSnap * SINGLE_SNAP_STRETCH_FACTOR, RUBBERBAND_RESISTANCE);
494
646
  }
495
- }
496
- else {
647
+ } else {
648
+ // Multiple snap points: allow stretching beyond maxSnap for rubberband effect
497
649
  newY = rubberbandIfOutOfBounds(rawY, 0, maxSnap * MULTI_SNAP_STRETCH_FACTOR, RUBBERBAND_RESISTANCE);
498
650
  }
499
- }
500
- else {
651
+ } else {
652
+ // On release: use predicted position
501
653
  newY = predictedY;
502
654
  }
503
655
  if (last) {
656
+ // End of gesture: find closest snap point and animate to it
504
657
  const targetSnap = findSnap(newY);
658
+ // Animate to target snap point using spring
505
659
  animationRef.current = animate(heightMotionValue, targetSnap, {
506
660
  ...SPRING_CONFIG,
507
- velocity: Math.abs(velocityY) > VELOCITY_THRESHOLD ? velocityY : 0,
661
+ velocity: Math.abs(velocityY) > VELOCITY_THRESHOLD ? velocityY : 0
508
662
  });
509
663
  heightRef.current = targetSnap;
510
664
  lastSnapRef.current = targetSnap;
511
665
  return memo;
512
666
  }
667
+ // During drag: directly set height to follow finger with rubberband
513
668
  heightMotionValue.set(newY);
514
669
  return memo;
515
670
  });
671
+ // Gesture binding
516
672
  const isVertical = anchor === 'top' || anchor === 'bottom';
517
- const gestureConfig = useMemo(() => ({
518
- filterTaps: true,
519
- ...(gestureWindow && {
520
- window: gestureWindow,
521
- }),
522
- }), [gestureWindow]);
673
+ const gestureConfig = useMemo(()=>({
674
+ filterTaps: true,
675
+ ...gestureWindow && {
676
+ window: gestureWindow
677
+ }
678
+ }), [
679
+ gestureWindow
680
+ ]);
523
681
  const bind = useDrag(handleDrag, gestureConfig);
524
- const heightStyle = useTransform(heightMotionValue, (y) => {
682
+ // animate height for vertical drawers only
683
+ const heightStyle = useTransform(heightMotionValue, (y)=>{
684
+ // Ensure drawer never exceeds viewport bounds
525
685
  const clamped = Math.max(0, Math.min(y, maxHeight));
526
686
  return `${Math.round(clamped)}px`;
527
687
  });
528
- const snapTo = useCallback((numberOrCallback) => {
529
- if (!ready)
530
- return;
688
+ // Imperative snapTo function
689
+ const snapTo = useCallback((numberOrCallback)=>{
690
+ if (!ready) return;
531
691
  const targetSnap = findSnap(numberOrCallback);
692
+ // Animate to target snap point
532
693
  animationRef.current = animate(heightMotionValue, targetSnap, SPRING_CONFIG);
694
+ // Update refs
533
695
  heightRef.current = targetSnap;
534
696
  lastSnapRef.current = targetSnap;
535
- }, [ready, findSnap, heightMotionValue, heightRef, lastSnapRef]);
697
+ }, [
698
+ ready,
699
+ findSnap,
700
+ heightMotionValue,
701
+ heightRef,
702
+ lastSnapRef
703
+ ]);
536
704
  return {
537
705
  bind,
538
706
  motionProps: {
539
- style: ready && isVertical
540
- ? {
541
- height: heightStyle,
542
- }
543
- : {},
707
+ style: ready && isVertical ? {
708
+ height: heightStyle
709
+ } : {}
544
710
  },
545
711
  isGestureEnabled: ready,
546
712
  contentRef,
547
713
  headerRef,
548
714
  footerRef,
549
- snapTo,
715
+ snapTo
550
716
  };
551
717
  }
552
718
 
553
- const SwipeableDrawer = forwardRef((inProps, ref) => {
719
+
720
+
721
+
722
+
723
+
724
+
725
+
726
+
727
+
728
+ /**
729
+ * SwipeableDrawer Component - Gesture-enhanced drawer with swipe support
730
+ *
731
+ * Wraps the base Drawer component and adds touch/mouse gesture support for resizing and closing.
732
+ * Perfect for mobile bottom sheets and interactive panels.
733
+ *
734
+ * @example
735
+ * ```tsx
736
+ * bottom sheet with gestures
737
+ * <SwipeableDrawer
738
+ * open={open}
739
+ * onClose={onClose}
740
+ * anchor="bottom"
741
+ * gestureControlsProps={{
742
+ * snapPoints: ({ minHeight, maxHeight }) => [
743
+ * minHeight, // Content height
744
+ * maxHeight * 0.6, // 60% of screen
745
+ * maxHeight // Full screen
746
+ * ],
747
+ * initialSnap: 0 // Start at content height
748
+ * }}
749
+ * >
750
+ * <Content />
751
+ * </SwipeableDrawer>
752
+ * ```
753
+ */ const SwipeableDrawer_SwipeableDrawer = /*#__PURE__*/ forwardRef((inProps, ref)=>{
554
754
  const props = useThemeProps({
555
755
  props: inProps,
556
- name: 'SwipeableDrawer',
756
+ name: 'SwipeableDrawer'
557
757
  });
558
758
  const { anchor = 'bottom', bodyProps, classes, className, children, header, footer, open, onClose, variant = 'temporary', blocking = true, gestureControlsProps, ...rest } = props;
759
+ // Refs for gesture system
559
760
  const contentRef = useRef(null);
560
761
  const headerRef = useRef(null);
561
762
  const footerRef = useRef(null);
562
763
  const isVertical = anchor === 'top' || anchor === 'bottom';
563
- const headerEnabled = useMemo(() => !!header || isVertical, [header, isVertical]);
564
- const footerEnabled = useMemo(() => !!footer, [footer]);
764
+ // Stabilize header/footer enabled state to prevent measurement fluctuations
765
+ // Header is enabled if there's a header prop OR if there's a drag handle (for vertical drawers)
766
+ const headerEnabled = useMemo(()=>!!header || isVertical, [
767
+ header,
768
+ isVertical
769
+ ]);
770
+ const footerEnabled = useMemo(()=>!!footer, [
771
+ footer
772
+ ]);
565
773
  const cls = useClasses('swipeable-drawer', classes, className);
566
774
  const { action, ...gestureProps } = gestureControlsProps || {};
567
775
  const gestureControls = useDrawerGestures({
568
776
  anchor,
569
777
  open,
570
- onClose: (event, reason) => {
778
+ onClose: (event, reason)=>{
571
779
  onClose?.(event, reason);
572
780
  },
573
781
  contentRef,
@@ -575,33 +783,70 @@ const SwipeableDrawer = forwardRef((inProps, ref) => {
575
783
  footerRef,
576
784
  headerEnabled,
577
785
  footerEnabled,
578
- ...gestureProps,
786
+ ...gestureProps
579
787
  });
580
- useImperativeHandle(action, () => ({
581
- snapTo: gestureControls.snapTo,
582
- }), [gestureControls.snapTo]);
788
+ // Expose snapTo function through action ref
789
+ useImperativeHandle(action, ()=>({
790
+ snapTo: gestureControls.snapTo
791
+ }), [
792
+ gestureControls.snapTo
793
+ ]);
583
794
  const gestureBindProps = gestureControls.bind();
795
+ // Enhanced body props that include gesture motion
584
796
  const enhancedBodyProps = {
585
797
  ...bodyProps,
586
- ...(!isVertical ? gestureBindProps : {}),
798
+ // Apply gesture bindings for horizontal drawers (left/right)
799
+ // Vertical drawers (top/bottom) use motion values instead
800
+ ...!isVertical ? gestureBindProps : {},
587
801
  style: {
588
802
  ...bodyProps?.style,
589
- ...(isVertical && {
590
- ...gestureControls.motionProps.style,
591
- }),
592
- },
803
+ // Apply motion styles for vertical drawers (top/bottom)
804
+ ...isVertical && {
805
+ ...gestureControls.motionProps.style
806
+ }
807
+ }
593
808
  };
809
+ // Consolidate measurement refs (refs only)
594
810
  const measurementRefs = {
595
811
  header: headerRef,
596
812
  footer: footerRef,
597
- content: contentRef,
813
+ content: contentRef
598
814
  };
815
+ // Gesture props for header
599
816
  const headerGestureProps = {
600
817
  className: clsx(isVertical && cls('header-drag')),
601
- ...(isVertical && gestureBindProps),
818
+ ...isVertical && gestureBindProps
602
819
  };
603
- return (jsx(Drawer, { ref: ref, anchor: anchor, open: open, onClose: onClose, variant: variant, blocking: blocking, bodyProps: enhancedBodyProps, classes: classes, className: className, measurementRefs: measurementRefs, headerProps: headerGestureProps, header: isVertical ? (jsxs(Fragment, { children: [jsx(DragHandle, { anchor: anchor }), header] })) : (header), footer: footer, ...rest, children: children }));
820
+ return /*#__PURE__*/ jsx(Drawer_Drawer, {
821
+ ref: ref,
822
+ anchor: anchor,
823
+ open: open,
824
+ onClose: onClose,
825
+ variant: variant,
826
+ blocking: blocking,
827
+ bodyProps: enhancedBodyProps,
828
+ classes: classes,
829
+ className: className,
830
+ measurementRefs: measurementRefs,
831
+ headerProps: headerGestureProps,
832
+ header: isVertical ? /*#__PURE__*/ jsxs(Fragment, {
833
+ children: [
834
+ /*#__PURE__*/ jsx(DragHandle_DragHandle, {
835
+ anchor: anchor
836
+ }),
837
+ header
838
+ ]
839
+ }) : header,
840
+ footer: footer,
841
+ ...rest,
842
+ children: children
843
+ });
604
844
  });
605
- SwipeableDrawer.displayName = 'SuiSwipeableDrawer';
845
+ SwipeableDrawer_SwipeableDrawer.displayName = 'SuiSwipeableDrawer';
846
+
847
+
848
+
849
+
850
+
606
851
 
607
- export { SwipeableDrawer, SwipeableDrawer$1 as swipeableDrawerStyles };
852
+ export { SwipeableDrawer as swipeableDrawerStyles, SwipeableDrawer_SwipeableDrawer as SwipeableDrawer };