@yahoo/uds-mobile 2.10.0 → 2.12.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 (197) hide show
  1. package/README.md +22 -24
  2. package/dist/bin/fixtures/dist/index.mjs +0 -10
  3. package/dist/bin/modes/dist/index.mjs +1 -0
  4. package/dist/bin/palette/dist/index.mjs +1 -0
  5. package/dist/components/AndroidBackHandler.js.map +1 -1
  6. package/dist/components/Avatar.cjs +1 -1
  7. package/dist/components/Avatar.d.cts +1 -1
  8. package/dist/components/Avatar.d.ts +1 -1
  9. package/dist/components/Avatar.js +1 -1
  10. package/dist/components/Avatar.js.map +1 -1
  11. package/dist/components/Badge.cjs +1 -1
  12. package/dist/components/Badge.d.cts +1 -1
  13. package/dist/components/Badge.d.ts +1 -1
  14. package/dist/components/Badge.js +1 -1
  15. package/dist/components/Badge.js.map +1 -1
  16. package/dist/components/Banner/Banner.js.map +1 -1
  17. package/dist/components/Banner/BannerContent.js.map +1 -1
  18. package/dist/components/Banner/BannerDescription.js.map +1 -1
  19. package/dist/components/Banner/BannerTitle.js.map +1 -1
  20. package/dist/components/Banner/utils.js.map +1 -1
  21. package/dist/components/BlurTarget.cjs +2 -1
  22. package/dist/components/BlurTarget.d.cts +2 -1
  23. package/dist/components/BlurTarget.d.cts.map +1 -1
  24. package/dist/components/BlurTarget.d.ts +2 -1
  25. package/dist/components/BlurTarget.d.ts.map +1 -1
  26. package/dist/components/BlurTarget.js +2 -1
  27. package/dist/components/BlurTarget.js.map +1 -1
  28. package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
  29. package/dist/components/BottomSheet/BottomSheetContent.js.map +1 -1
  30. package/dist/components/BottomSheet/BottomSheetDismiss.js.map +1 -1
  31. package/dist/components/BottomSheet/BottomSheetHandle.js.map +1 -1
  32. package/dist/components/BottomSheet/BottomSheetHeader.js.map +1 -1
  33. package/dist/components/BottomSheet/BottomSheetInternalProvider.js.map +1 -1
  34. package/dist/components/BottomSheet/BottomSheetProvider.js.map +1 -1
  35. package/dist/components/BottomSheet/BottomSheetTrigger.js.map +1 -1
  36. package/dist/components/BottomSheet/useBottomSheetDrag.js.map +1 -1
  37. package/dist/components/BottomSheet/useBottomSheetScroll.js.map +1 -1
  38. package/dist/components/BottomSheet/useBottomSheetSnapModel.js.map +1 -1
  39. package/dist/components/BottomSheet/useBottomSheetStore.js.map +1 -1
  40. package/dist/components/BottomSheet/useExpansionMargins.js.map +1 -1
  41. package/dist/components/BottomSheet/useKeyboardAvoidance.js.map +1 -1
  42. package/dist/components/BottomSheet/utils.js.map +1 -1
  43. package/dist/components/Box.cjs +1 -1
  44. package/dist/components/Box.d.cts +1 -1
  45. package/dist/components/Box.d.ts +1 -1
  46. package/dist/components/Box.js +1 -1
  47. package/dist/components/Box.js.map +1 -1
  48. package/dist/components/Button.cjs +1 -1
  49. package/dist/components/Button.d.cts +1 -1
  50. package/dist/components/Button.d.ts +1 -1
  51. package/dist/components/Button.js +1 -1
  52. package/dist/components/Button.js.map +1 -1
  53. package/dist/components/Checkbox.cjs +1 -1
  54. package/dist/components/Checkbox.d.cts +1 -1
  55. package/dist/components/Checkbox.d.ts +1 -1
  56. package/dist/components/Checkbox.js +1 -1
  57. package/dist/components/Checkbox.js.map +1 -1
  58. package/dist/components/Chip.cjs +1 -1
  59. package/dist/components/Chip.d.cts +1 -1
  60. package/dist/components/Chip.d.ts +1 -1
  61. package/dist/components/Chip.js +1 -1
  62. package/dist/components/Chip.js.map +1 -1
  63. package/dist/components/Divider/Divider.cjs +103 -0
  64. package/dist/components/Divider/Divider.d.cts +50 -0
  65. package/dist/components/Divider/Divider.d.cts.map +1 -0
  66. package/dist/components/Divider/Divider.d.ts +50 -0
  67. package/dist/components/Divider/Divider.d.ts.map +1 -0
  68. package/dist/components/Divider/Divider.js +103 -0
  69. package/dist/components/Divider/Divider.js.map +1 -0
  70. package/dist/components/Divider/DividerLabel.cjs +37 -0
  71. package/dist/components/Divider/DividerLabel.d.cts +18 -0
  72. package/dist/components/Divider/DividerLabel.d.cts.map +1 -0
  73. package/dist/components/Divider/DividerLabel.d.ts +18 -0
  74. package/dist/components/Divider/DividerLabel.d.ts.map +1 -0
  75. package/dist/components/Divider/DividerLabel.js +37 -0
  76. package/dist/components/Divider/DividerLabel.js.map +1 -0
  77. package/dist/components/Divider/DividerLine.cjs +62 -0
  78. package/dist/components/Divider/DividerLine.d.cts +19 -0
  79. package/dist/components/Divider/DividerLine.d.cts.map +1 -0
  80. package/dist/components/Divider/DividerLine.d.ts +19 -0
  81. package/dist/components/Divider/DividerLine.d.ts.map +1 -0
  82. package/dist/components/Divider/DividerLine.js +62 -0
  83. package/dist/components/Divider/DividerLine.js.map +1 -0
  84. package/dist/components/Divider/index.cjs +8 -0
  85. package/dist/components/Divider/index.d.cts +6 -0
  86. package/dist/components/Divider/index.d.ts +6 -0
  87. package/dist/components/Divider/index.js +5 -0
  88. package/dist/components/Divider/types.cjs +1 -0
  89. package/dist/components/Divider/types.d.cts +35 -0
  90. package/dist/components/Divider/types.d.cts.map +1 -0
  91. package/dist/components/Divider/types.d.ts +35 -0
  92. package/dist/components/Divider/types.d.ts.map +1 -0
  93. package/dist/components/Divider/types.js +1 -0
  94. package/dist/components/Divider/utils.cjs +33 -0
  95. package/dist/components/Divider/utils.d.cts +12 -0
  96. package/dist/components/Divider/utils.d.cts.map +1 -0
  97. package/dist/components/Divider/utils.d.ts +12 -0
  98. package/dist/components/Divider/utils.d.ts.map +1 -0
  99. package/dist/components/Divider/utils.js +31 -0
  100. package/dist/components/Divider/utils.js.map +1 -0
  101. package/dist/components/HStack.cjs +1 -1
  102. package/dist/components/HStack.d.cts +1 -1
  103. package/dist/components/HStack.d.ts +1 -1
  104. package/dist/components/HStack.js +1 -1
  105. package/dist/components/HStack.js.map +1 -1
  106. package/dist/components/Icon.cjs +1 -1
  107. package/dist/components/Icon.d.cts +2 -2
  108. package/dist/components/Icon.d.ts +2 -2
  109. package/dist/components/Icon.js +1 -1
  110. package/dist/components/Icon.js.map +1 -1
  111. package/dist/components/IconButton.cjs +1 -1
  112. package/dist/components/IconButton.d.cts +1 -1
  113. package/dist/components/IconButton.d.ts +1 -1
  114. package/dist/components/IconButton.js +1 -1
  115. package/dist/components/IconButton.js.map +1 -1
  116. package/dist/components/IconSlot.cjs +1 -1
  117. package/dist/components/IconSlot.d.cts +1 -1
  118. package/dist/components/IconSlot.d.ts +1 -1
  119. package/dist/components/IconSlot.js +1 -1
  120. package/dist/components/IconSlot.js.map +1 -1
  121. package/dist/components/Image.cjs +1 -1
  122. package/dist/components/Image.d.cts +1 -1
  123. package/dist/components/Image.d.ts +1 -1
  124. package/dist/components/Image.js +1 -1
  125. package/dist/components/Image.js.map +1 -1
  126. package/dist/components/Input.cjs +1 -1
  127. package/dist/components/Input.d.cts +1 -1
  128. package/dist/components/Input.d.ts +1 -1
  129. package/dist/components/Input.js +1 -1
  130. package/dist/components/Input.js.map +1 -1
  131. package/dist/components/Link.cjs +1 -1
  132. package/dist/components/Link.d.cts +1 -1
  133. package/dist/components/Link.d.ts +1 -1
  134. package/dist/components/Link.js +1 -1
  135. package/dist/components/Link.js.map +1 -1
  136. package/dist/components/Pressable.cjs +1 -1
  137. package/dist/components/Pressable.d.cts +1 -1
  138. package/dist/components/Pressable.d.ts +1 -1
  139. package/dist/components/Pressable.js +1 -1
  140. package/dist/components/Pressable.js.map +1 -1
  141. package/dist/components/Radio.cjs +1 -1
  142. package/dist/components/Radio.d.cts +1 -1
  143. package/dist/components/Radio.d.ts +1 -1
  144. package/dist/components/Radio.js +1 -1
  145. package/dist/components/Radio.js.map +1 -1
  146. package/dist/components/Screen.cjs +1 -1
  147. package/dist/components/Screen.d.cts +1 -1
  148. package/dist/components/Screen.d.ts +1 -1
  149. package/dist/components/Screen.js +1 -1
  150. package/dist/components/Screen.js.map +1 -1
  151. package/dist/components/Scrim.js.map +1 -1
  152. package/dist/components/Switch.cjs +1 -1
  153. package/dist/components/Switch.d.cts +1 -1
  154. package/dist/components/Switch.d.ts +1 -1
  155. package/dist/components/Switch.js +1 -1
  156. package/dist/components/Switch.js.map +1 -1
  157. package/dist/components/Tabs/Tab.js.map +1 -1
  158. package/dist/components/Tabs/TabList.js.map +1 -1
  159. package/dist/components/Tabs/TabPanel.js.map +1 -1
  160. package/dist/components/Tabs/Tabs.js.map +1 -1
  161. package/dist/components/Tabs/tabTheme.js.map +1 -1
  162. package/dist/components/Tabs/tabsContexts.js.map +1 -1
  163. package/dist/components/Text.cjs +1 -1
  164. package/dist/components/Text.d.cts +1 -1
  165. package/dist/components/Text.d.ts +1 -1
  166. package/dist/components/Text.js +1 -1
  167. package/dist/components/Text.js.map +1 -1
  168. package/dist/components/UDSProvider.js.map +1 -1
  169. package/dist/components/VStack.cjs +1 -1
  170. package/dist/components/VStack.d.cts +1 -1
  171. package/dist/components/VStack.d.ts +1 -1
  172. package/dist/components/VStack.js +1 -1
  173. package/dist/components/VStack.js.map +1 -1
  174. package/dist/jest/mocks/icons.js.map +1 -1
  175. package/dist/jest/mocks/react-native.js.map +1 -1
  176. package/dist/jest/mocks/reanimated.js.map +1 -1
  177. package/dist/jest/mocks/styles.cjs +15 -0
  178. package/dist/jest/mocks/styles.d.cts +3 -2
  179. package/dist/jest/mocks/styles.d.cts.map +1 -1
  180. package/dist/jest/mocks/styles.d.ts +3 -2
  181. package/dist/jest/mocks/styles.d.ts.map +1 -1
  182. package/dist/jest/mocks/styles.js +15 -1
  183. package/dist/jest/mocks/styles.js.map +1 -1
  184. package/dist/jest/mocks/svg.js.map +1 -1
  185. package/dist/jest/mocks/unistyles.js.map +1 -1
  186. package/dist/jest/setup.js.map +1 -1
  187. package/dist/portal.js.map +1 -1
  188. package/dist/types/dist/index.d.cts +12 -2
  189. package/dist/types/dist/index.d.cts.map +1 -1
  190. package/dist/types/dist/index.d.ts +12 -2
  191. package/dist/types/dist/index.d.ts.map +1 -1
  192. package/fonts/uds-icons.ttf +0 -0
  193. package/generated/styles.cjs +19 -13
  194. package/generated/styles.d.ts +12 -0
  195. package/generated/styles.mjs +19 -13
  196. package/generated/unistyles.d.ts +9 -9
  197. package/package.json +11 -1
@@ -1 +1 @@
1
- {"version":3,"file":"BottomSheetDismiss.js","names":[],"sources":["../../../src/components/BottomSheet/BottomSheetDismiss.tsx"],"sourcesContent":["import type { ReactElement } from 'react';\nimport { cloneElement, memo, useCallback } from 'react';\n\nimport { useBottomSheetContext } from './BottomSheetProvider';\n\ninterface Props {\n render: ReactElement<{ onPress?: () => void }>;\n}\n\n/**\n * Clones the `render` element and injects an `onPress` handler that closes\n * the nearest bottom sheet.\n *\n * Uses the `render` prop pattern to avoid nested pressable wrappers.\n *\n * @example\n * ```tsx\n * <BottomSheetDismiss render={<Button>Close</Button>} />\n * ```\n */\n// eslint-disable-next-line react/prop-types -- TypeScript validates props via Props interface\nconst BottomSheetDismiss = memo(function BottomSheetDismiss({ render }: Props) {\n const controller = useBottomSheetContext();\n\n if (!controller) {\n throw new Error('BottomSheetDismiss must be used inside a BottomSheetProvider.');\n }\n\n const handlePress = useCallback(() => {\n controller.close();\n }, [controller]);\n\n return cloneElement(render, {\n onPress: handlePress,\n });\n});\n\nexport { BottomSheetDismiss };\n"],"mappings":";;;;;;;;;;;;;;;AAqBA,MAAM,qBAAqB,KAAK,SAAS,mBAAmB,EAAE,UAAiB;CAC7E,MAAM,aAAa,uBAAuB;AAE1C,KAAI,CAAC,WACH,OAAM,IAAI,MAAM,gEAAgE;AAOlF,QAAO,aAAa,QAAQ,EAC1B,SALkB,kBAAkB;AACpC,aAAW,OAAO;IACjB,CAAC,WAAW,CAGO,EACrB,CAAC;EACF"}
1
+ {"version":3,"file":"BottomSheetDismiss.js","names":[],"sources":["../../../src/components/BottomSheet/BottomSheetDismiss.tsx"],"sourcesContent":["import type { ReactElement } from 'react';\nimport { cloneElement, memo, useCallback } from 'react';\n\nimport { useBottomSheetContext } from './BottomSheetProvider';\n\ninterface Props {\n render: ReactElement<{ onPress?: () => void }>;\n}\n\n/**\n * Clones the `render` element and injects an `onPress` handler that closes\n * the nearest bottom sheet.\n *\n * Uses the `render` prop pattern to avoid nested pressable wrappers.\n *\n * @example\n * ```tsx\n * <BottomSheetDismiss render={<Button>Close</Button>} />\n * ```\n */\n// eslint-disable-next-line react/prop-types -- TypeScript validates props via Props interface\nconst BottomSheetDismiss = memo(function BottomSheetDismiss({ render }: Props) {\n const controller = useBottomSheetContext();\n\n if (!controller) {\n throw new Error('BottomSheetDismiss must be used inside a BottomSheetProvider.');\n }\n\n const handlePress = useCallback(() => {\n controller.close();\n }, [controller]);\n\n return cloneElement(render, {\n onPress: handlePress,\n });\n});\n\nexport { BottomSheetDismiss };\n"],"mappings":";;;;;;;;;;;;;;;AAqBA,MAAM,qBAAqB,KAAK,SAAS,mBAAmB,EAAE,UAAiB;CAC7E,MAAM,aAAa,uBAAuB;CAE1C,IAAI,CAAC,YACH,MAAM,IAAI,MAAM,gEAAgE;CAOlF,OAAO,aAAa,QAAQ,EAC1B,SALkB,kBAAkB;EACpC,WAAW,OAAO;IACjB,CAAC,WAAW,CAGO,EACrB,CAAC;EACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"BottomSheetHandle.js","names":["RNStyleSheet"],"sources":["../../../src/components/BottomSheet/BottomSheetHandle.tsx"],"sourcesContent":["import { memo, useCallback } from 'react';\nimport { StyleSheet as RNStyleSheet, View } from 'react-native';\n\nimport { bottomSheetStyles } from '../../../generated/styles';\nimport { useBottomSheetInternalContext } from './BottomSheetInternalProvider';\nimport { useBottomSheetContext } from './BottomSheetProvider';\n\n/**\n * Visual drag-affordance bar displayed at the top of the sheet.\n *\n * Accessible as `role=\"adjustable\"` with increment (expand) and decrement\n * (collapse / dismiss) actions that map to snap-point navigation.\n */\nconst BottomSheetHandle = memo(function BottomSheetHandle() {\n bottomSheetStyles.useVariants({});\n const handleTokens = RNStyleSheet.flatten(bottomSheetStyles.handleIndicator) as {\n backgroundColor?: string;\n };\n\n const internalCtx = useBottomSheetInternalContext();\n const controller = useBottomSheetContext();\n\n const handleAccessibilityAction = useCallback(\n (event: { nativeEvent: { actionName: string } }) => {\n if (!internalCtx || !controller) {\n return;\n }\n\n const { activeSnapIndex, snapCount } = internalCtx;\n const current = activeSnapIndex.value;\n\n if (event.nativeEvent.actionName === 'increment' && current < snapCount - 1) {\n // Expand to next snap.\n // We can't call animateToSnap directly from here — use controller-level API\n // or set activeSnapIndex. For now, this is a best-effort accessibility hook.\n activeSnapIndex.value = current + 1;\n } else if (event.nativeEvent.actionName === 'decrement') {\n if (current > 0) {\n activeSnapIndex.value = current - 1;\n } else {\n // At lowest snap — dismiss.\n controller.close();\n }\n }\n },\n [internalCtx, controller],\n );\n\n return (\n <View\n style={[internalStyles.handle, { backgroundColor: handleTokens.backgroundColor }]}\n accessible\n accessibilityRole=\"adjustable\"\n accessibilityLabel=\"Sheet handle\"\n accessibilityHint=\"Swipe up to expand, swipe down to collapse\"\n accessibilityActions={[\n { name: 'increment', label: 'Expand' },\n { name: 'decrement', label: 'Collapse' },\n ]}\n onAccessibilityAction={handleAccessibilityAction}\n />\n );\n});\n\nconst internalStyles = RNStyleSheet.create({\n handle: {\n alignSelf: 'center',\n width: 36,\n height: 4,\n borderRadius: 2,\n marginBottom: 8,\n },\n});\n\nexport { BottomSheetHandle };\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAM,oBAAoB,KAAK,SAAS,oBAAoB;AAC1D,mBAAkB,YAAY,EAAE,CAAC;CACjC,MAAM,eAAeA,WAAa,QAAQ,kBAAkB,gBAAgB;CAI5E,MAAM,cAAc,+BAA+B;CACnD,MAAM,aAAa,uBAAuB;CAE1C,MAAM,4BAA4B,aAC/B,UAAmD;AAClD,MAAI,CAAC,eAAe,CAAC,WACnB;EAGF,MAAM,EAAE,iBAAiB,cAAc;EACvC,MAAM,UAAU,gBAAgB;AAEhC,MAAI,MAAM,YAAY,eAAe,eAAe,UAAU,YAAY,EAIxE,iBAAgB,QAAQ,UAAU;WACzB,MAAM,YAAY,eAAe,YAC1C,KAAI,UAAU,EACZ,iBAAgB,QAAQ,UAAU;MAGlC,YAAW,OAAO;IAIxB,CAAC,aAAa,WAAW,CAC1B;AAED,QACE,oBAAC,MAAD;EACE,OAAO,CAAC,eAAe,QAAQ,EAAE,iBAAiB,aAAa,iBAAiB,CAAC;EACjF,YAAA;EACA,mBAAkB;EAClB,oBAAmB;EACnB,mBAAkB;EAClB,sBAAsB,CACpB;GAAE,MAAM;GAAa,OAAO;GAAU,EACtC;GAAE,MAAM;GAAa,OAAO;GAAY,CACzC;EACD,uBAAuB;EACvB,CAAA;EAEJ;AAEF,MAAM,iBAAiBA,WAAa,OAAO,EACzC,QAAQ;CACN,WAAW;CACX,OAAO;CACP,QAAQ;CACR,cAAc;CACd,cAAc;CACf,EACF,CAAC"}
1
+ {"version":3,"file":"BottomSheetHandle.js","names":["RNStyleSheet"],"sources":["../../../src/components/BottomSheet/BottomSheetHandle.tsx"],"sourcesContent":["import { memo, useCallback } from 'react';\nimport { StyleSheet as RNStyleSheet, View } from 'react-native';\n\nimport { bottomSheetStyles } from '../../../generated/styles';\nimport { useBottomSheetInternalContext } from './BottomSheetInternalProvider';\nimport { useBottomSheetContext } from './BottomSheetProvider';\n\n/**\n * Visual drag-affordance bar displayed at the top of the sheet.\n *\n * Accessible as `role=\"adjustable\"` with increment (expand) and decrement\n * (collapse / dismiss) actions that map to snap-point navigation.\n */\nconst BottomSheetHandle = memo(function BottomSheetHandle() {\n bottomSheetStyles.useVariants({});\n const handleTokens = RNStyleSheet.flatten(bottomSheetStyles.handleIndicator) as {\n backgroundColor?: string;\n };\n\n const internalCtx = useBottomSheetInternalContext();\n const controller = useBottomSheetContext();\n\n const handleAccessibilityAction = useCallback(\n (event: { nativeEvent: { actionName: string } }) => {\n if (!internalCtx || !controller) {\n return;\n }\n\n const { activeSnapIndex, snapCount } = internalCtx;\n const current = activeSnapIndex.value;\n\n if (event.nativeEvent.actionName === 'increment' && current < snapCount - 1) {\n // Expand to next snap.\n // We can't call animateToSnap directly from here — use controller-level API\n // or set activeSnapIndex. For now, this is a best-effort accessibility hook.\n activeSnapIndex.value = current + 1;\n } else if (event.nativeEvent.actionName === 'decrement') {\n if (current > 0) {\n activeSnapIndex.value = current - 1;\n } else {\n // At lowest snap — dismiss.\n controller.close();\n }\n }\n },\n [internalCtx, controller],\n );\n\n return (\n <View\n style={[internalStyles.handle, { backgroundColor: handleTokens.backgroundColor }]}\n accessible\n accessibilityRole=\"adjustable\"\n accessibilityLabel=\"Sheet handle\"\n accessibilityHint=\"Swipe up to expand, swipe down to collapse\"\n accessibilityActions={[\n { name: 'increment', label: 'Expand' },\n { name: 'decrement', label: 'Collapse' },\n ]}\n onAccessibilityAction={handleAccessibilityAction}\n />\n );\n});\n\nconst internalStyles = RNStyleSheet.create({\n handle: {\n alignSelf: 'center',\n width: 36,\n height: 4,\n borderRadius: 2,\n marginBottom: 8,\n },\n});\n\nexport { BottomSheetHandle };\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAM,oBAAoB,KAAK,SAAS,oBAAoB;CAC1D,kBAAkB,YAAY,EAAE,CAAC;CACjC,MAAM,eAAeA,WAAa,QAAQ,kBAAkB,gBAAgB;CAI5E,MAAM,cAAc,+BAA+B;CACnD,MAAM,aAAa,uBAAuB;CAE1C,MAAM,4BAA4B,aAC/B,UAAmD;EAClD,IAAI,CAAC,eAAe,CAAC,YACnB;EAGF,MAAM,EAAE,iBAAiB,cAAc;EACvC,MAAM,UAAU,gBAAgB;EAEhC,IAAI,MAAM,YAAY,eAAe,eAAe,UAAU,YAAY,GAIxE,gBAAgB,QAAQ,UAAU;OAC7B,IAAI,MAAM,YAAY,eAAe,aAC1C,IAAI,UAAU,GACZ,gBAAgB,QAAQ,UAAU;OAGlC,WAAW,OAAO;IAIxB,CAAC,aAAa,WAAW,CAC1B;CAED,OACE,oBAAC,MAAD;EACE,OAAO,CAAC,eAAe,QAAQ,EAAE,iBAAiB,aAAa,iBAAiB,CAAC;EACjF,YAAA;EACA,mBAAkB;EAClB,oBAAmB;EACnB,mBAAkB;EAClB,sBAAsB,CACpB;GAAE,MAAM;GAAa,OAAO;GAAU,EACtC;GAAE,MAAM;GAAa,OAAO;GAAY,CACzC;EACD,uBAAuB;EACvB,CAAA;EAEJ;AAEF,MAAM,iBAAiBA,WAAa,OAAO,EACzC,QAAQ;CACN,WAAW;CACX,OAAO;CACP,QAAQ;CACR,cAAc;CACd,cAAc;CACf,EACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"BottomSheetHeader.js","names":["Text"],"sources":["../../../src/components/BottomSheet/BottomSheetHeader.tsx"],"sourcesContent":["import { memo } from 'react';\nimport { StyleSheet, View } from 'react-native';\n\nimport { bottomSheetStyles } from '../../../generated/styles';\nimport { Text } from '../Text';\nimport type { BottomSheetHeaderProps, BottomSheetHeaderTokens } from './types';\n\n/** Width of the leading/trailing action slots in pixels. */\nconst SLOT_WIDTH = 40;\n\n/**\n * Three-column header for the bottom sheet.\n *\n * Layout: `[40px start slot] [1fr center title] [40px end slot]`.\n * Empty slots render as invisible spacers to keep the title centered.\n * Typography is read from the configurator's `bottomSheetStyles.header` tokens.\n */\nconst BottomSheetHeader = memo(function BottomSheetHeader({\n children,\n start,\n end,\n}: BottomSheetHeaderProps) {\n const headerTokens = StyleSheet.flatten(bottomSheetStyles.header) as BottomSheetHeaderTokens;\n\n return (\n <View style={internalStyles.container}>\n <View style={internalStyles.slot}>{start}</View>\n <View style={internalStyles.title}>\n <Text\n style={{\n fontFamily: headerTokens.fontFamily,\n fontSize: headerTokens.fontSize,\n letterSpacing: headerTokens.letterSpacing,\n lineHeight: headerTokens.lineHeight,\n color: headerTokens.color,\n }}\n numberOfLines={1}\n >\n {children}\n </Text>\n </View>\n <View style={internalStyles.slot}>{end}</View>\n </View>\n );\n});\n\nconst internalStyles = StyleSheet.create({\n container: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n },\n slot: {\n width: SLOT_WIDTH,\n alignItems: 'center',\n justifyContent: 'center',\n },\n title: {\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n },\n});\n\nexport { BottomSheetHeader };\n"],"mappings":";;;;;;;;AAQA,MAAM,aAAa;;;;;;;;AASnB,MAAM,oBAAoB,KAAK,SAAS,kBAAkB,EACxD,UACA,OACA,OACyB;CACzB,MAAM,eAAe,WAAW,QAAQ,kBAAkB,OAAO;AAEjE,QACE,qBAAC,MAAD;EAAM,OAAO,eAAe;YAA5B;GACE,oBAAC,MAAD;IAAM,OAAO,eAAe;cAAO;IAAa,CAAA;GAChD,oBAAC,MAAD;IAAM,OAAO,eAAe;cAC1B,oBAACA,QAAD;KACE,OAAO;MACL,YAAY,aAAa;MACzB,UAAU,aAAa;MACvB,eAAe,aAAa;MAC5B,YAAY,aAAa;MACzB,OAAO,aAAa;MACrB;KACD,eAAe;KAEd;KACI,CAAA;IACF,CAAA;GACP,oBAAC,MAAD;IAAM,OAAO,eAAe;cAAO;IAAW,CAAA;GACzC;;EAET;AAEF,MAAM,iBAAiB,WAAW,OAAO;CACvC,WAAW;EACT,eAAe;EACf,YAAY;EACZ,gBAAgB;EACjB;CACD,MAAM;EACJ,OAAO;EACP,YAAY;EACZ,gBAAgB;EACjB;CACD,OAAO;EACL,MAAM;EACN,YAAY;EACZ,gBAAgB;EACjB;CACF,CAAC"}
1
+ {"version":3,"file":"BottomSheetHeader.js","names":["Text"],"sources":["../../../src/components/BottomSheet/BottomSheetHeader.tsx"],"sourcesContent":["import { memo } from 'react';\nimport { StyleSheet, View } from 'react-native';\n\nimport { bottomSheetStyles } from '../../../generated/styles';\nimport { Text } from '../Text';\nimport type { BottomSheetHeaderProps, BottomSheetHeaderTokens } from './types';\n\n/** Width of the leading/trailing action slots in pixels. */\nconst SLOT_WIDTH = 40;\n\n/**\n * Three-column header for the bottom sheet.\n *\n * Layout: `[40px start slot] [1fr center title] [40px end slot]`.\n * Empty slots render as invisible spacers to keep the title centered.\n * Typography is read from the configurator's `bottomSheetStyles.header` tokens.\n */\nconst BottomSheetHeader = memo(function BottomSheetHeader({\n children,\n start,\n end,\n}: BottomSheetHeaderProps) {\n const headerTokens = StyleSheet.flatten(bottomSheetStyles.header) as BottomSheetHeaderTokens;\n\n return (\n <View style={internalStyles.container}>\n <View style={internalStyles.slot}>{start}</View>\n <View style={internalStyles.title}>\n <Text\n style={{\n fontFamily: headerTokens.fontFamily,\n fontSize: headerTokens.fontSize,\n letterSpacing: headerTokens.letterSpacing,\n lineHeight: headerTokens.lineHeight,\n color: headerTokens.color,\n }}\n numberOfLines={1}\n >\n {children}\n </Text>\n </View>\n <View style={internalStyles.slot}>{end}</View>\n </View>\n );\n});\n\nconst internalStyles = StyleSheet.create({\n container: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n },\n slot: {\n width: SLOT_WIDTH,\n alignItems: 'center',\n justifyContent: 'center',\n },\n title: {\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n },\n});\n\nexport { BottomSheetHeader };\n"],"mappings":";;;;;;;;AAQA,MAAM,aAAa;;;;;;;;AASnB,MAAM,oBAAoB,KAAK,SAAS,kBAAkB,EACxD,UACA,OACA,OACyB;CACzB,MAAM,eAAe,WAAW,QAAQ,kBAAkB,OAAO;CAEjE,OACE,qBAAC,MAAD;EAAM,OAAO,eAAe;YAA5B;GACE,oBAAC,MAAD;IAAM,OAAO,eAAe;cAAO;IAAa,CAAA;GAChD,oBAAC,MAAD;IAAM,OAAO,eAAe;cAC1B,oBAACA,QAAD;KACE,OAAO;MACL,YAAY,aAAa;MACzB,UAAU,aAAa;MACvB,eAAe,aAAa;MAC5B,YAAY,aAAa;MACzB,OAAO,aAAa;MACrB;KACD,eAAe;KAEd;KACI,CAAA;IACF,CAAA;GACP,oBAAC,MAAD;IAAM,OAAO,eAAe;cAAO;IAAW,CAAA;GACzC;;EAET;AAEF,MAAM,iBAAiB,WAAW,OAAO;CACvC,WAAW;EACT,eAAe;EACf,YAAY;EACZ,gBAAgB;EACjB;CACD,MAAM;EACJ,OAAO;EACP,YAAY;EACZ,gBAAgB;EACjB;CACD,OAAO;EACL,MAAM;EACN,YAAY;EACZ,gBAAgB;EACjB;CACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"BottomSheetInternalProvider.js","names":[],"sources":["../../../src/components/BottomSheet/BottomSheetInternalProvider.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { createContext, useContext } from 'react';\n\nimport type { BottomSheetInternalContextValue } from './types';\n\nconst BottomSheetInternalContext = createContext<BottomSheetInternalContextValue | null>(null);\n\ninterface Props {\n children: ReactNode;\n value: BottomSheetInternalContextValue;\n}\n\nfunction BottomSheetInternalProvider({ children, value }: Props) {\n return (\n <BottomSheetInternalContext.Provider value={value}>\n {children}\n </BottomSheetInternalContext.Provider>\n );\n}\n\n/**\n * Reads the internal scroll/gesture coordination context.\n * Returns `null` when rendered outside a `<BottomSheet>`.\n * @internal\n */\nfunction useBottomSheetInternalContext(): BottomSheetInternalContextValue | null {\n return useContext(BottomSheetInternalContext);\n}\n\nexport { BottomSheetInternalProvider, useBottomSheetInternalContext };\n"],"mappings":";;;;AAKA,MAAM,6BAA6B,cAAsD,KAAK;AAO9F,SAAS,4BAA4B,EAAE,UAAU,SAAgB;AAC/D,QACE,oBAAC,2BAA2B,UAA5B;EAA4C;EACzC;EACmC,CAAA;;;;;;;AAS1C,SAAS,gCAAwE;AAC/E,QAAO,WAAW,2BAA2B"}
1
+ {"version":3,"file":"BottomSheetInternalProvider.js","names":[],"sources":["../../../src/components/BottomSheet/BottomSheetInternalProvider.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { createContext, useContext } from 'react';\n\nimport type { BottomSheetInternalContextValue } from './types';\n\nconst BottomSheetInternalContext = createContext<BottomSheetInternalContextValue | null>(null);\n\ninterface Props {\n children: ReactNode;\n value: BottomSheetInternalContextValue;\n}\n\nfunction BottomSheetInternalProvider({ children, value }: Props) {\n return (\n <BottomSheetInternalContext.Provider value={value}>\n {children}\n </BottomSheetInternalContext.Provider>\n );\n}\n\n/**\n * Reads the internal scroll/gesture coordination context.\n * Returns `null` when rendered outside a `<BottomSheet>`.\n * @internal\n */\nfunction useBottomSheetInternalContext(): BottomSheetInternalContextValue | null {\n return useContext(BottomSheetInternalContext);\n}\n\nexport { BottomSheetInternalProvider, useBottomSheetInternalContext };\n"],"mappings":";;;;AAKA,MAAM,6BAA6B,cAAsD,KAAK;AAO9F,SAAS,4BAA4B,EAAE,UAAU,SAAgB;CAC/D,OACE,oBAAC,2BAA2B,UAA5B;EAA4C;EACzC;EACmC,CAAA;;;;;;;AAS1C,SAAS,gCAAwE;CAC/E,OAAO,WAAW,2BAA2B"}
@@ -1 +1 @@
1
- {"version":3,"file":"BottomSheetProvider.js","names":[],"sources":["../../../src/components/BottomSheet/BottomSheetProvider.tsx"],"sourcesContent":["import { createContext, useContext } from 'react';\n\nimport type { BottomSheetController, BottomSheetProviderProps } from './types';\nimport { useBottomSheetStore } from './useBottomSheetStore';\n\nconst BottomSheetContext = createContext<BottomSheetController | null>(null);\n\n/**\n * Provides a BottomSheetController to all descendants via React context.\n *\n * Accepts an optional `controller` prop. If omitted, creates an internal store.\n * All descendants (BottomSheet, BottomSheetTrigger, BottomSheetDismiss) resolve\n * the same store from this context.\n */\nfunction BottomSheetProvider({ children, controller }: BottomSheetProviderProps) {\n const fallbackStore = useBottomSheetStore();\n const store = controller ?? fallbackStore;\n\n return <BottomSheetContext.Provider value={store}>{children}</BottomSheetContext.Provider>;\n}\n\nBottomSheetProvider.displayName = 'BottomSheetProvider';\n\n/** @internal — read the controller from context. Returns null if no provider. */\nfunction useBottomSheetContext(): BottomSheetController | null {\n return useContext(BottomSheetContext);\n}\n\nexport { BottomSheetProvider, useBottomSheetContext };\n"],"mappings":";;;;;AAKA,MAAM,qBAAqB,cAA4C,KAAK;;;;;;;;AAS5E,SAAS,oBAAoB,EAAE,UAAU,cAAwC;CAC/E,MAAM,gBAAgB,qBAAqB;CAC3C,MAAM,QAAQ,cAAc;AAE5B,QAAO,oBAAC,mBAAmB,UAApB;EAA6B,OAAO;EAAQ;EAAuC,CAAA;;AAG5F,oBAAoB,cAAc;;AAGlC,SAAS,wBAAsD;AAC7D,QAAO,WAAW,mBAAmB"}
1
+ {"version":3,"file":"BottomSheetProvider.js","names":[],"sources":["../../../src/components/BottomSheet/BottomSheetProvider.tsx"],"sourcesContent":["import { createContext, useContext } from 'react';\n\nimport type { BottomSheetController, BottomSheetProviderProps } from './types';\nimport { useBottomSheetStore } from './useBottomSheetStore';\n\nconst BottomSheetContext = createContext<BottomSheetController | null>(null);\n\n/**\n * Provides a BottomSheetController to all descendants via React context.\n *\n * Accepts an optional `controller` prop. If omitted, creates an internal store.\n * All descendants (BottomSheet, BottomSheetTrigger, BottomSheetDismiss) resolve\n * the same store from this context.\n */\nfunction BottomSheetProvider({ children, controller }: BottomSheetProviderProps) {\n const fallbackStore = useBottomSheetStore();\n const store = controller ?? fallbackStore;\n\n return <BottomSheetContext.Provider value={store}>{children}</BottomSheetContext.Provider>;\n}\n\nBottomSheetProvider.displayName = 'BottomSheetProvider';\n\n/** @internal — read the controller from context. Returns null if no provider. */\nfunction useBottomSheetContext(): BottomSheetController | null {\n return useContext(BottomSheetContext);\n}\n\nexport { BottomSheetProvider, useBottomSheetContext };\n"],"mappings":";;;;;AAKA,MAAM,qBAAqB,cAA4C,KAAK;;;;;;;;AAS5E,SAAS,oBAAoB,EAAE,UAAU,cAAwC;CAC/E,MAAM,gBAAgB,qBAAqB;CAC3C,MAAM,QAAQ,cAAc;CAE5B,OAAO,oBAAC,mBAAmB,UAApB;EAA6B,OAAO;EAAQ;EAAuC,CAAA;;AAG5F,oBAAoB,cAAc;;AAGlC,SAAS,wBAAsD;CAC7D,OAAO,WAAW,mBAAmB"}
@@ -1 +1 @@
1
- {"version":3,"file":"BottomSheetTrigger.js","names":[],"sources":["../../../src/components/BottomSheet/BottomSheetTrigger.tsx"],"sourcesContent":["import type { ReactElement } from 'react';\nimport { cloneElement, memo, useCallback } from 'react';\n\nimport { useBottomSheetContext } from './BottomSheetProvider';\n\ninterface Props {\n render: ReactElement<{ onPress?: () => void }>;\n}\n\n/**\n * Clones the `render` element and injects an `onPress` handler that opens\n * the nearest bottom sheet.\n *\n * Uses the `render` prop pattern to avoid nested pressable wrappers.\n *\n * @example\n * ```tsx\n * <BottomSheetTrigger render={<Button>Open Sheet</Button>} />\n * ```\n */\n// eslint-disable-next-line react/prop-types -- TypeScript validates props via Props interface\nconst BottomSheetTrigger = memo(function BottomSheetTrigger({ render }: Props) {\n const controller = useBottomSheetContext();\n\n if (!controller) {\n throw new Error('BottomSheetTrigger must be used inside a BottomSheetProvider.');\n }\n\n const handlePress = useCallback(() => {\n controller.open();\n }, [controller]);\n\n return cloneElement(render, {\n onPress: handlePress,\n });\n});\n\nexport { BottomSheetTrigger };\n"],"mappings":";;;;;;;;;;;;;;;AAqBA,MAAM,qBAAqB,KAAK,SAAS,mBAAmB,EAAE,UAAiB;CAC7E,MAAM,aAAa,uBAAuB;AAE1C,KAAI,CAAC,WACH,OAAM,IAAI,MAAM,gEAAgE;AAOlF,QAAO,aAAa,QAAQ,EAC1B,SALkB,kBAAkB;AACpC,aAAW,MAAM;IAChB,CAAC,WAAW,CAGO,EACrB,CAAC;EACF"}
1
+ {"version":3,"file":"BottomSheetTrigger.js","names":[],"sources":["../../../src/components/BottomSheet/BottomSheetTrigger.tsx"],"sourcesContent":["import type { ReactElement } from 'react';\nimport { cloneElement, memo, useCallback } from 'react';\n\nimport { useBottomSheetContext } from './BottomSheetProvider';\n\ninterface Props {\n render: ReactElement<{ onPress?: () => void }>;\n}\n\n/**\n * Clones the `render` element and injects an `onPress` handler that opens\n * the nearest bottom sheet.\n *\n * Uses the `render` prop pattern to avoid nested pressable wrappers.\n *\n * @example\n * ```tsx\n * <BottomSheetTrigger render={<Button>Open Sheet</Button>} />\n * ```\n */\n// eslint-disable-next-line react/prop-types -- TypeScript validates props via Props interface\nconst BottomSheetTrigger = memo(function BottomSheetTrigger({ render }: Props) {\n const controller = useBottomSheetContext();\n\n if (!controller) {\n throw new Error('BottomSheetTrigger must be used inside a BottomSheetProvider.');\n }\n\n const handlePress = useCallback(() => {\n controller.open();\n }, [controller]);\n\n return cloneElement(render, {\n onPress: handlePress,\n });\n});\n\nexport { BottomSheetTrigger };\n"],"mappings":";;;;;;;;;;;;;;;AAqBA,MAAM,qBAAqB,KAAK,SAAS,mBAAmB,EAAE,UAAiB;CAC7E,MAAM,aAAa,uBAAuB;CAE1C,IAAI,CAAC,YACH,MAAM,IAAI,MAAM,gEAAgE;CAOlF,OAAO,aAAa,QAAQ,EAC1B,SALkB,kBAAkB;EACpC,WAAW,MAAM;IAChB,CAAC,WAAW,CAGO,EACrB,CAAC;EACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useBottomSheetDrag.js","names":[],"sources":["../../../src/components/BottomSheet/useBottomSheetDrag.ts"],"sourcesContent":["import { useCallback, useRef } from 'react';\nimport { Platform } from 'react-native';\nimport type { GestureType } from 'react-native-gesture-handler';\nimport { Gesture } from 'react-native-gesture-handler';\nimport type { SharedValue } from 'react-native-reanimated';\nimport { runOnJS, useSharedValue } from 'react-native-reanimated';\n\nimport type { BottomSheetHeight } from './types';\nimport { clamp, clampIndex, resolveHeightToPx } from './utils';\n\n/**\n * Velocity threshold (px/s) above which a downward fling dismisses the sheet.\n */\nconst DRAG_DISMISS_VELOCITY = 500;\n\n/**\n * Duration (ms) after opening during which drag is blocked to prevent\n * accidentally stealing scroll intent.\n */\nconst OPEN_LOCK_TIMEOUT_MS = 250;\n\n/**\n * Horizontal movement (px) that causes the pan gesture to fail,\n * handing control to horizontal scrolling or other gestures.\n */\nconst HORIZONTAL_FAIL_THRESHOLD = 20;\n\n/**\n * Minimum vertical movement (px) required before deciding whether to\n * activate the pan gesture or let the scroll view handle the touch.\n */\nconst VERTICAL_ACTIVATION_THRESHOLD = 10;\n\n/**\n * Approximate height (px) of the header area (paddingTop + handle + header row).\n * Touches above this threshold always activate the pan for sheet collapse.\n * Touches below it may defer to the scroll view on Android.\n */\nconst HEADER_AREA_HEIGHT = 64;\n\n/** How much drag past max snap is visible (0–1). Lower = more resistance. */\nconst RUBBER_BAND_FACTOR = 0.2;\n\n/** Max enableOverstretch distance (px) past the max snap point. */\nconst MAX_OVERSTRETCH_PX = 30;\n\ninterface UseBottomSheetDragParams {\n enableDrag: boolean;\n dismissible: boolean;\n enableOverstretch: boolean;\n resolvedSnapPoints: BottomSheetHeight[];\n containerHeight: SharedValue<number>;\n bottomInsetPx: number;\n translateY: SharedValue<number>;\n activeSnapIndex: SharedValue<number>;\n isAnimating: SharedValue<boolean>;\n animateToSnapWorklet: (index: number) => void;\n animateToCloseWorklet: () => void;\n onDismiss: () => void;\n /** Scroll lock — written here, read by BottomSheetContent. */\n scrollLocked: SharedValue<boolean>;\n /** Current content scroll offset (UI-thread shared value). */\n scrollOffsetY: SharedValue<number>;\n /** Compute translateY for a given snap index (worklet). Provided by the snap model. */\n getTranslateYForSnap: (index: number) => number;\n}\n\ninterface UseBottomSheetDragResult {\n /** The pan gesture to attach to GestureDetector. */\n panGesture: GestureType;\n /** Ref to the pan gesture — for simultaneousWithExternalGesture in scroll. */\n panGestureRef: React.RefObject<GestureType | undefined>;\n /** Shared value — set to Date.now() when sheet opens, for fresh-open lock. */\n openTimeSV: SharedValue<number>;\n}\n\n/**\n * Builds the pan gesture that drives sheet dragging.\n *\n * Handles:\n * - Drag-to-snap (nearest or velocity-based)\n * - Drag-to-dismiss (past lowest snap threshold or fast fling)\n * - Fresh-open lock (prevents accidental drag right after opening)\n * - Scroll coordination (gates between sheet drag and content scroll)\n *\n * Scroll is only unlocked at the max snap point. At non-max snaps, dragging\n * anywhere on the sheet expands or collapses it. At max snap, content is\n * scrollable; dragging down from scroll-top collapses the sheet.\n *\n * All gesture callbacks run on the UI thread via Reanimated worklets.\n */\nfunction useBottomSheetDrag({\n enableDrag,\n dismissible,\n enableOverstretch,\n resolvedSnapPoints,\n containerHeight,\n bottomInsetPx,\n translateY,\n activeSnapIndex,\n isAnimating,\n animateToSnapWorklet,\n animateToCloseWorklet,\n onDismiss,\n scrollLocked,\n scrollOffsetY,\n getTranslateYForSnap,\n}: UseBottomSheetDragParams): UseBottomSheetDragResult {\n // Shared values for drag state (UI thread).\n const isDragActive = useSharedValue(false);\n const touchStartX = useSharedValue(0);\n const touchStartY = useSharedValue(0);\n const dragStartTranslateY = useSharedValue(0);\n const dragStartIndex = useSharedValue(0);\n const openTimeSV = useSharedValue(0);\n\n // Ref for the pan gesture — passed to BottomSheetContent for gesture composition.\n const panGestureRef = useRef<GestureType | undefined>(undefined);\n\n const snapCount = resolvedSnapPoints.length;\n const maxSnapIndex = snapCount - 1;\n\n /** Compute all snap translateY positions — worklet. */\n const getSnapTranslateYs = useCallback((): number[] => {\n 'worklet';\n const result: number[] = [];\n for (let i = 0; i < snapCount; i++) {\n result.push(getTranslateYForSnap(i));\n }\n return result;\n }, [snapCount, getTranslateYForSnap]);\n\n /** Dismiss threshold: 25% of the lowest snap height. */\n const getDismissThreshold = useCallback((): number => {\n 'worklet';\n if (snapCount === 0) {\n return 50;\n }\n const lowestSnapHeight = resolveHeightToPx(resolvedSnapPoints[0]!, containerHeight.value);\n return lowestSnapHeight * 0.25;\n }, [resolvedSnapPoints, snapCount, containerHeight]);\n\n const handleDismissJS = useCallback(() => {\n onDismiss();\n }, [onDismiss]);\n\n const panGesture = Gesture.Pan()\n .withRef(panGestureRef)\n .manualActivation(true)\n .onTouchesDown((e, _stateManager) => {\n 'worklet';\n const touch = e.allTouches[0];\n if (touch) {\n touchStartX.value = touch.absoluteX;\n touchStartY.value = touch.absoluteY;\n }\n })\n .onTouchesMove((e, stateManager) => {\n 'worklet';\n const touch = e.allTouches[0];\n if (!touch) {\n return;\n }\n\n const dx = touch.absoluteX - touchStartX.value;\n const dy = touch.absoluteY - touchStartY.value;\n\n // Fail on horizontal movement (replaces failOffsetX).\n if (Math.abs(dx) > HORIZONTAL_FAIL_THRESHOLD) {\n stateManager.fail();\n return;\n }\n\n // Wait for sufficient vertical movement (replaces activeOffsetY).\n if (Math.abs(dy) < VERTICAL_ACTIVATION_THRESHOLD) {\n return;\n }\n\n // Fresh-open lock.\n const now = Date.now();\n if (openTimeSV.value > 0 && now - openTimeSV.value < OPEN_LOCK_TIMEOUT_MS) {\n stateManager.fail();\n return;\n }\n\n // Don't activate during animation.\n if (isAnimating.value) {\n stateManager.fail();\n return;\n }\n\n const isAtMaxSnap = activeSnapIndex.value === maxSnapIndex;\n\n if (isAtMaxSnap) {\n if (dy < 0) {\n if (!enableOverstretch) {\n // No enableOverstretch — let the scroll view handle all upward drags.\n stateManager.fail();\n return;\n }\n // Header/handle: allow rubber-band enableOverstretch.\n // Content area: defer to scroll view (always on Android,\n // only when scrolled on iOS).\n const localY = touchStartY.value - translateY.value;\n const isInContentArea = localY > HEADER_AREA_HEIGHT;\n if (isInContentArea && (Platform.OS === 'android' || scrollOffsetY.value > 1)) {\n stateManager.fail();\n return;\n }\n } else {\n // Dragging down on Android: defer to scroll view in content\n // area when scrolled. Header drags always collapse.\n if (Platform.OS === 'android' && scrollOffsetY.value > 1) {\n const localY = touchStartY.value - translateY.value;\n if (localY > HEADER_AREA_HEIGHT) {\n stateManager.fail();\n return;\n }\n }\n }\n }\n\n stateManager.activate();\n })\n .onStart(() => {\n 'worklet';\n isDragActive.value = true;\n dragStartTranslateY.value = translateY.value;\n dragStartIndex.value = activeSnapIndex.value;\n scrollLocked.value = true;\n })\n .onUpdate((event) => {\n 'worklet';\n if (!isDragActive.value) {\n return;\n }\n\n const maxSnapTranslateY = snapCount > 0 ? getTranslateYForSnap(snapCount - 1) : 0;\n const closedTranslateY = containerHeight.value + bottomInsetPx;\n\n const nextY = dragStartTranslateY.value + event.translationY;\n\n if (enableOverstretch && nextY < maxSnapTranslateY) {\n // Rubber-band enableOverstretch past max snap.\n const overflow = maxSnapTranslateY - nextY;\n const dampedOverflow = Math.min(overflow * RUBBER_BAND_FACTOR, MAX_OVERSTRETCH_PX);\n translateY.value = maxSnapTranslateY - dampedOverflow;\n } else {\n translateY.value = clamp(nextY, maxSnapTranslateY, closedTranslateY);\n }\n })\n .onEnd((event) => {\n 'worklet';\n if (!isDragActive.value) {\n return;\n }\n isDragActive.value = false;\n\n const currentY = translateY.value;\n const startIndex = dragStartIndex.value;\n const snapTranslateYs = getSnapTranslateYs();\n\n // Compute drag metrics.\n const startSnapY = snapTranslateYs[startIndex] ?? containerHeight.value;\n const draggedDistance = dragStartTranslateY.value - currentY;\n const draggedDown = draggedDistance < 0;\n const draggedUp = draggedDistance > 0;\n const velocityPxPerSec = Math.abs(event.velocityY);\n\n // 1. Dismiss check: at lowest snap, dragged down, meets threshold.\n const isAtLowestSnap = startIndex === 0;\n const dismissThreshold = getDismissThreshold();\n const draggedDownAmount = currentY - startSnapY;\n\n if (\n dismissible &&\n isAtLowestSnap &&\n draggedDown &&\n (velocityPxPerSec >= DRAG_DISMISS_VELOCITY || draggedDownAmount >= dismissThreshold)\n ) {\n scrollLocked.value = true;\n animateToCloseWorklet();\n runOnJS(handleDismissJS)();\n return;\n }\n\n // 2. Velocity snap: fast enough → move one snap in drag direction.\n if (velocityPxPerSec >= DRAG_DISMISS_VELOCITY && (draggedUp || draggedDown)) {\n const direction = draggedUp ? 1 : -1;\n const nextIndex = clampIndex(startIndex + direction, snapCount);\n scrollLocked.value = nextIndex !== maxSnapIndex;\n animateToSnapWorklet(nextIndex);\n return;\n }\n\n // 3. Direction-aware snap: if the user dragged past 25% of the distance\n // to the next snap in their drag direction, move to that snap.\n // Otherwise snap back to the starting snap.\n const nextIndexInDirection = draggedUp\n ? Math.min(startIndex + 1, maxSnapIndex)\n : Math.max(startIndex - 1, 0);\n const nextSnapY = snapTranslateYs[nextIndexInDirection] ?? startSnapY;\n const distBetweenSnaps = Math.abs(nextSnapY - startSnapY);\n const dragDist = Math.abs(currentY - startSnapY);\n\n const targetIndex =\n distBetweenSnaps > 0 && dragDist > distBetweenSnaps * 0.25\n ? nextIndexInDirection\n : startIndex;\n\n scrollLocked.value = targetIndex !== maxSnapIndex;\n animateToSnapWorklet(targetIndex);\n })\n .onFinalize(() => {\n 'worklet';\n if (isDragActive.value) {\n isDragActive.value = false;\n }\n })\n .enabled(enableDrag);\n\n return {\n panGesture,\n panGestureRef,\n openTimeSV,\n };\n}\n\nexport { useBottomSheetDrag };\n"],"mappings":";;;;;;;;;;AAaA,MAAM,wBAAwB;;;;;AAM9B,MAAM,uBAAuB;;;;;AAM7B,MAAM,4BAA4B;;;;;AAMlC,MAAM,gCAAgC;;;;;;AAOtC,MAAM,qBAAqB;;AAG3B,MAAM,qBAAqB;;AAG3B,MAAM,qBAAqB;;;;;;;;;;;;;;;;AA+C3B,SAAS,mBAAmB,EAC1B,YACA,aACA,mBACA,oBACA,iBACA,eACA,YACA,iBACA,aACA,sBACA,uBACA,WACA,cACA,eACA,wBACqD;CAErD,MAAM,eAAe,eAAe,MAAM;CAC1C,MAAM,cAAc,eAAe,EAAE;CACrC,MAAM,cAAc,eAAe,EAAE;CACrC,MAAM,sBAAsB,eAAe,EAAE;CAC7C,MAAM,iBAAiB,eAAe,EAAE;CACxC,MAAM,aAAa,eAAe,EAAE;CAGpC,MAAM,gBAAgB,OAAgC,KAAA,EAAU;CAEhE,MAAM,YAAY,mBAAmB;CACrC,MAAM,eAAe,YAAY;;CAGjC,MAAM,qBAAqB,kBAA4B;AACrD;EACA,MAAM,SAAmB,EAAE;AAC3B,OAAK,IAAI,IAAI,GAAG,IAAI,WAAW,IAC7B,QAAO,KAAK,qBAAqB,EAAE,CAAC;AAEtC,SAAO;IACN,CAAC,WAAW,qBAAqB,CAAC;;CAGrC,MAAM,sBAAsB,kBAA0B;AACpD;AACA,MAAI,cAAc,EAChB,QAAO;AAGT,SADyB,kBAAkB,mBAAmB,IAAK,gBAAgB,MAC5D,GAAG;IACzB;EAAC;EAAoB;EAAW;EAAgB,CAAC;CAEpD,MAAM,kBAAkB,kBAAkB;AACxC,aAAW;IACV,CAAC,UAAU,CAAC;AAiLf,QAAO;EACL,YAhLiB,QAAQ,KAAK,CAC7B,QAAQ,cAAc,CACtB,iBAAiB,KAAK,CACtB,eAAe,GAAG,kBAAkB;AACnC;GACA,MAAM,QAAQ,EAAE,WAAW;AAC3B,OAAI,OAAO;AACT,gBAAY,QAAQ,MAAM;AAC1B,gBAAY,QAAQ,MAAM;;IAE5B,CACD,eAAe,GAAG,iBAAiB;AAClC;GACA,MAAM,QAAQ,EAAE,WAAW;AAC3B,OAAI,CAAC,MACH;GAGF,MAAM,KAAK,MAAM,YAAY,YAAY;GACzC,MAAM,KAAK,MAAM,YAAY,YAAY;AAGzC,OAAI,KAAK,IAAI,GAAG,GAAG,2BAA2B;AAC5C,iBAAa,MAAM;AACnB;;AAIF,OAAI,KAAK,IAAI,GAAG,GAAG,8BACjB;GAIF,MAAM,MAAM,KAAK,KAAK;AACtB,OAAI,WAAW,QAAQ,KAAK,MAAM,WAAW,QAAQ,sBAAsB;AACzE,iBAAa,MAAM;AACnB;;AAIF,OAAI,YAAY,OAAO;AACrB,iBAAa,MAAM;AACnB;;AAKF,OAFoB,gBAAgB,UAAU;QAGxC,KAAK,GAAG;AACV,SAAI,CAAC,mBAAmB;AAEtB,mBAAa,MAAM;AACnB;;AAOF,SAFe,YAAY,QAAQ,WAAW,QACb,uBACT,SAAS,OAAO,aAAa,cAAc,QAAQ,IAAI;AAC7E,mBAAa,MAAM;AACnB;;eAKE,SAAS,OAAO,aAAa,cAAc,QAAQ;SACtC,YAAY,QAAQ,WAAW,QACjC,oBAAoB;AAC/B,mBAAa,MAAM;AACnB;;;;AAMR,gBAAa,UAAU;IACvB,CACD,cAAc;AACb;AACA,gBAAa,QAAQ;AACrB,uBAAoB,QAAQ,WAAW;AACvC,kBAAe,QAAQ,gBAAgB;AACvC,gBAAa,QAAQ;IACrB,CACD,UAAU,UAAU;AACnB;AACA,OAAI,CAAC,aAAa,MAChB;GAGF,MAAM,oBAAoB,YAAY,IAAI,qBAAqB,YAAY,EAAE,GAAG;GAChF,MAAM,mBAAmB,gBAAgB,QAAQ;GAEjD,MAAM,QAAQ,oBAAoB,QAAQ,MAAM;AAEhD,OAAI,qBAAqB,QAAQ,mBAAmB;IAElD,MAAM,WAAW,oBAAoB;AAErC,eAAW,QAAQ,oBADI,KAAK,IAAI,WAAW,oBAAoB,mBACV;SAErD,YAAW,QAAQ,MAAM,OAAO,mBAAmB,iBAAiB;IAEtE,CACD,OAAO,UAAU;AAChB;AACA,OAAI,CAAC,aAAa,MAChB;AAEF,gBAAa,QAAQ;GAErB,MAAM,WAAW,WAAW;GAC5B,MAAM,aAAa,eAAe;GAClC,MAAM,kBAAkB,oBAAoB;GAG5C,MAAM,aAAa,gBAAgB,eAAe,gBAAgB;GAClE,MAAM,kBAAkB,oBAAoB,QAAQ;GACpD,MAAM,cAAc,kBAAkB;GACtC,MAAM,YAAY,kBAAkB;GACpC,MAAM,mBAAmB,KAAK,IAAI,MAAM,UAAU;GAGlD,MAAM,iBAAiB,eAAe;GACtC,MAAM,mBAAmB,qBAAqB;GAC9C,MAAM,oBAAoB,WAAW;AAErC,OACE,eACA,kBACA,gBACC,oBAAoB,yBAAyB,qBAAqB,mBACnE;AACA,iBAAa,QAAQ;AACrB,2BAAuB;AACvB,YAAQ,gBAAgB,EAAE;AAC1B;;AAIF,OAAI,oBAAoB,0BAA0B,aAAa,cAAc;IAE3E,MAAM,YAAY,WAAW,cADX,YAAY,IAAI,KACmB,UAAU;AAC/D,iBAAa,QAAQ,cAAc;AACnC,yBAAqB,UAAU;AAC/B;;GAMF,MAAM,uBAAuB,YACzB,KAAK,IAAI,aAAa,GAAG,aAAa,GACtC,KAAK,IAAI,aAAa,GAAG,EAAE;GAC/B,MAAM,YAAY,gBAAgB,yBAAyB;GAC3D,MAAM,mBAAmB,KAAK,IAAI,YAAY,WAAW;GACzD,MAAM,WAAW,KAAK,IAAI,WAAW,WAAW;GAEhD,MAAM,cACJ,mBAAmB,KAAK,WAAW,mBAAmB,MAClD,uBACA;AAEN,gBAAa,QAAQ,gBAAgB;AACrC,wBAAqB,YAAY;IACjC,CACD,iBAAiB;AAChB;AACA,OAAI,aAAa,MACf,cAAa,QAAQ;IAEvB,CACD,QAAQ,WAGC;EACV;EACA;EACD"}
1
+ {"version":3,"file":"useBottomSheetDrag.js","names":[],"sources":["../../../src/components/BottomSheet/useBottomSheetDrag.ts"],"sourcesContent":["import { useCallback, useRef } from 'react';\nimport { Platform } from 'react-native';\nimport type { GestureType } from 'react-native-gesture-handler';\nimport { Gesture } from 'react-native-gesture-handler';\nimport type { SharedValue } from 'react-native-reanimated';\nimport { runOnJS, useSharedValue } from 'react-native-reanimated';\n\nimport type { BottomSheetHeight } from './types';\nimport { clamp, clampIndex, resolveHeightToPx } from './utils';\n\n/**\n * Velocity threshold (px/s) above which a downward fling dismisses the sheet.\n */\nconst DRAG_DISMISS_VELOCITY = 500;\n\n/**\n * Duration (ms) after opening during which drag is blocked to prevent\n * accidentally stealing scroll intent.\n */\nconst OPEN_LOCK_TIMEOUT_MS = 250;\n\n/**\n * Horizontal movement (px) that causes the pan gesture to fail,\n * handing control to horizontal scrolling or other gestures.\n */\nconst HORIZONTAL_FAIL_THRESHOLD = 20;\n\n/**\n * Minimum vertical movement (px) required before deciding whether to\n * activate the pan gesture or let the scroll view handle the touch.\n */\nconst VERTICAL_ACTIVATION_THRESHOLD = 10;\n\n/**\n * Approximate height (px) of the header area (paddingTop + handle + header row).\n * Touches above this threshold always activate the pan for sheet collapse.\n * Touches below it may defer to the scroll view on Android.\n */\nconst HEADER_AREA_HEIGHT = 64;\n\n/** How much drag past max snap is visible (0–1). Lower = more resistance. */\nconst RUBBER_BAND_FACTOR = 0.2;\n\n/** Max enableOverstretch distance (px) past the max snap point. */\nconst MAX_OVERSTRETCH_PX = 30;\n\ninterface UseBottomSheetDragParams {\n enableDrag: boolean;\n dismissible: boolean;\n enableOverstretch: boolean;\n resolvedSnapPoints: BottomSheetHeight[];\n containerHeight: SharedValue<number>;\n bottomInsetPx: number;\n translateY: SharedValue<number>;\n activeSnapIndex: SharedValue<number>;\n isAnimating: SharedValue<boolean>;\n animateToSnapWorklet: (index: number) => void;\n animateToCloseWorklet: () => void;\n onDismiss: () => void;\n /** Scroll lock — written here, read by BottomSheetContent. */\n scrollLocked: SharedValue<boolean>;\n /** Current content scroll offset (UI-thread shared value). */\n scrollOffsetY: SharedValue<number>;\n /** Compute translateY for a given snap index (worklet). Provided by the snap model. */\n getTranslateYForSnap: (index: number) => number;\n}\n\ninterface UseBottomSheetDragResult {\n /** The pan gesture to attach to GestureDetector. */\n panGesture: GestureType;\n /** Ref to the pan gesture — for simultaneousWithExternalGesture in scroll. */\n panGestureRef: React.RefObject<GestureType | undefined>;\n /** Shared value — set to Date.now() when sheet opens, for fresh-open lock. */\n openTimeSV: SharedValue<number>;\n}\n\n/**\n * Builds the pan gesture that drives sheet dragging.\n *\n * Handles:\n * - Drag-to-snap (nearest or velocity-based)\n * - Drag-to-dismiss (past lowest snap threshold or fast fling)\n * - Fresh-open lock (prevents accidental drag right after opening)\n * - Scroll coordination (gates between sheet drag and content scroll)\n *\n * Scroll is only unlocked at the max snap point. At non-max snaps, dragging\n * anywhere on the sheet expands or collapses it. At max snap, content is\n * scrollable; dragging down from scroll-top collapses the sheet.\n *\n * All gesture callbacks run on the UI thread via Reanimated worklets.\n */\nfunction useBottomSheetDrag({\n enableDrag,\n dismissible,\n enableOverstretch,\n resolvedSnapPoints,\n containerHeight,\n bottomInsetPx,\n translateY,\n activeSnapIndex,\n isAnimating,\n animateToSnapWorklet,\n animateToCloseWorklet,\n onDismiss,\n scrollLocked,\n scrollOffsetY,\n getTranslateYForSnap,\n}: UseBottomSheetDragParams): UseBottomSheetDragResult {\n // Shared values for drag state (UI thread).\n const isDragActive = useSharedValue(false);\n const touchStartX = useSharedValue(0);\n const touchStartY = useSharedValue(0);\n const dragStartTranslateY = useSharedValue(0);\n const dragStartIndex = useSharedValue(0);\n const openTimeSV = useSharedValue(0);\n\n // Ref for the pan gesture — passed to BottomSheetContent for gesture composition.\n const panGestureRef = useRef<GestureType | undefined>(undefined);\n\n const snapCount = resolvedSnapPoints.length;\n const maxSnapIndex = snapCount - 1;\n\n /** Compute all snap translateY positions — worklet. */\n const getSnapTranslateYs = useCallback((): number[] => {\n 'worklet';\n const result: number[] = [];\n for (let i = 0; i < snapCount; i++) {\n result.push(getTranslateYForSnap(i));\n }\n return result;\n }, [snapCount, getTranslateYForSnap]);\n\n /** Dismiss threshold: 25% of the lowest snap height. */\n const getDismissThreshold = useCallback((): number => {\n 'worklet';\n if (snapCount === 0) {\n return 50;\n }\n const lowestSnapHeight = resolveHeightToPx(resolvedSnapPoints[0]!, containerHeight.value);\n return lowestSnapHeight * 0.25;\n }, [resolvedSnapPoints, snapCount, containerHeight]);\n\n const handleDismissJS = useCallback(() => {\n onDismiss();\n }, [onDismiss]);\n\n const panGesture = Gesture.Pan()\n .withRef(panGestureRef)\n .manualActivation(true)\n .onTouchesDown((e, _stateManager) => {\n 'worklet';\n const touch = e.allTouches[0];\n if (touch) {\n touchStartX.value = touch.absoluteX;\n touchStartY.value = touch.absoluteY;\n }\n })\n .onTouchesMove((e, stateManager) => {\n 'worklet';\n const touch = e.allTouches[0];\n if (!touch) {\n return;\n }\n\n const dx = touch.absoluteX - touchStartX.value;\n const dy = touch.absoluteY - touchStartY.value;\n\n // Fail on horizontal movement (replaces failOffsetX).\n if (Math.abs(dx) > HORIZONTAL_FAIL_THRESHOLD) {\n stateManager.fail();\n return;\n }\n\n // Wait for sufficient vertical movement (replaces activeOffsetY).\n if (Math.abs(dy) < VERTICAL_ACTIVATION_THRESHOLD) {\n return;\n }\n\n // Fresh-open lock.\n const now = Date.now();\n if (openTimeSV.value > 0 && now - openTimeSV.value < OPEN_LOCK_TIMEOUT_MS) {\n stateManager.fail();\n return;\n }\n\n // Don't activate during animation.\n if (isAnimating.value) {\n stateManager.fail();\n return;\n }\n\n const isAtMaxSnap = activeSnapIndex.value === maxSnapIndex;\n\n if (isAtMaxSnap) {\n if (dy < 0) {\n if (!enableOverstretch) {\n // No enableOverstretch — let the scroll view handle all upward drags.\n stateManager.fail();\n return;\n }\n // Header/handle: allow rubber-band enableOverstretch.\n // Content area: defer to scroll view (always on Android,\n // only when scrolled on iOS).\n const localY = touchStartY.value - translateY.value;\n const isInContentArea = localY > HEADER_AREA_HEIGHT;\n if (isInContentArea && (Platform.OS === 'android' || scrollOffsetY.value > 1)) {\n stateManager.fail();\n return;\n }\n } else {\n // Dragging down on Android: defer to scroll view in content\n // area when scrolled. Header drags always collapse.\n if (Platform.OS === 'android' && scrollOffsetY.value > 1) {\n const localY = touchStartY.value - translateY.value;\n if (localY > HEADER_AREA_HEIGHT) {\n stateManager.fail();\n return;\n }\n }\n }\n }\n\n stateManager.activate();\n })\n .onStart(() => {\n 'worklet';\n isDragActive.value = true;\n dragStartTranslateY.value = translateY.value;\n dragStartIndex.value = activeSnapIndex.value;\n scrollLocked.value = true;\n })\n .onUpdate((event) => {\n 'worklet';\n if (!isDragActive.value) {\n return;\n }\n\n const maxSnapTranslateY = snapCount > 0 ? getTranslateYForSnap(snapCount - 1) : 0;\n const closedTranslateY = containerHeight.value + bottomInsetPx;\n\n const nextY = dragStartTranslateY.value + event.translationY;\n\n if (enableOverstretch && nextY < maxSnapTranslateY) {\n // Rubber-band enableOverstretch past max snap.\n const overflow = maxSnapTranslateY - nextY;\n const dampedOverflow = Math.min(overflow * RUBBER_BAND_FACTOR, MAX_OVERSTRETCH_PX);\n translateY.value = maxSnapTranslateY - dampedOverflow;\n } else {\n translateY.value = clamp(nextY, maxSnapTranslateY, closedTranslateY);\n }\n })\n .onEnd((event) => {\n 'worklet';\n if (!isDragActive.value) {\n return;\n }\n isDragActive.value = false;\n\n const currentY = translateY.value;\n const startIndex = dragStartIndex.value;\n const snapTranslateYs = getSnapTranslateYs();\n\n // Compute drag metrics.\n const startSnapY = snapTranslateYs[startIndex] ?? containerHeight.value;\n const draggedDistance = dragStartTranslateY.value - currentY;\n const draggedDown = draggedDistance < 0;\n const draggedUp = draggedDistance > 0;\n const velocityPxPerSec = Math.abs(event.velocityY);\n\n // 1. Dismiss check: at lowest snap, dragged down, meets threshold.\n const isAtLowestSnap = startIndex === 0;\n const dismissThreshold = getDismissThreshold();\n const draggedDownAmount = currentY - startSnapY;\n\n if (\n dismissible &&\n isAtLowestSnap &&\n draggedDown &&\n (velocityPxPerSec >= DRAG_DISMISS_VELOCITY || draggedDownAmount >= dismissThreshold)\n ) {\n scrollLocked.value = true;\n animateToCloseWorklet();\n runOnJS(handleDismissJS)();\n return;\n }\n\n // 2. Velocity snap: fast enough → move one snap in drag direction.\n if (velocityPxPerSec >= DRAG_DISMISS_VELOCITY && (draggedUp || draggedDown)) {\n const direction = draggedUp ? 1 : -1;\n const nextIndex = clampIndex(startIndex + direction, snapCount);\n scrollLocked.value = nextIndex !== maxSnapIndex;\n animateToSnapWorklet(nextIndex);\n return;\n }\n\n // 3. Direction-aware snap: if the user dragged past 25% of the distance\n // to the next snap in their drag direction, move to that snap.\n // Otherwise snap back to the starting snap.\n const nextIndexInDirection = draggedUp\n ? Math.min(startIndex + 1, maxSnapIndex)\n : Math.max(startIndex - 1, 0);\n const nextSnapY = snapTranslateYs[nextIndexInDirection] ?? startSnapY;\n const distBetweenSnaps = Math.abs(nextSnapY - startSnapY);\n const dragDist = Math.abs(currentY - startSnapY);\n\n const targetIndex =\n distBetweenSnaps > 0 && dragDist > distBetweenSnaps * 0.25\n ? nextIndexInDirection\n : startIndex;\n\n scrollLocked.value = targetIndex !== maxSnapIndex;\n animateToSnapWorklet(targetIndex);\n })\n .onFinalize(() => {\n 'worklet';\n if (isDragActive.value) {\n isDragActive.value = false;\n }\n })\n .enabled(enableDrag);\n\n return {\n panGesture,\n panGestureRef,\n openTimeSV,\n };\n}\n\nexport { useBottomSheetDrag };\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useBottomSheetScroll.js","names":[],"sources":["../../../src/components/BottomSheet/useBottomSheetScroll.ts"],"sourcesContent":["import { Gesture } from 'react-native-gesture-handler';\nimport type Animated from 'react-native-reanimated';\nimport {\n useAnimatedProps,\n useAnimatedRef,\n useAnimatedScrollHandler,\n} from 'react-native-reanimated';\n\nimport { useBottomSheetInternalContext } from './BottomSheetInternalProvider';\n\n/**\n * Low-level hook for consumers who need a custom scrollable (e.g. `FlatList`,\n * `SectionList`) instead of `<BottomSheetContent>`.\n *\n * Returns all the props needed to wire a scrollable into the sheet's\n * scroll-lock coordination (scrollEnabled toggle, offset tracking, gesture composition).\n *\n * Must be rendered inside a `<BottomSheet>`.\n *\n * @throws If used outside a `<BottomSheet>` component tree.\n *\n * @returns `{ scrollHandler, scrollableRef, nativeGesture, animatedProps }`\n *\n * @example\n * ```tsx\n * const { scrollHandler, scrollableRef, nativeGesture, animatedProps } = useBottomSheetScroll();\n *\n * <GestureDetector gesture={nativeGesture}>\n * <Animated.FlatList\n * ref={scrollableRef}\n * onScroll={scrollHandler}\n * scrollEventThrottle={16}\n * animatedProps={animatedProps}\n * bounces={false}\n * overScrollMode=\"never\"\n * {...otherProps}\n * />\n * </GestureDetector>\n * ```\n */\nfunction useBottomSheetScroll() {\n const internalCtx = useBottomSheetInternalContext();\n const scrollableRef = useAnimatedRef<Animated.ScrollView>();\n\n const animatedProps = useAnimatedProps(() => ({\n scrollEnabled: !internalCtx?.scrollLocked.value,\n decelerationRate: internalCtx?.scrollLocked.value ? 0 : 0.998,\n }));\n\n const scrollHandler = useAnimatedScrollHandler({\n onScroll: (event) => {\n if (!internalCtx) {\n return;\n }\n internalCtx.scrollOffsetY.value = event.contentOffset.y;\n },\n });\n\n if (!internalCtx) {\n throw new Error('useBottomSheetScroll must be used inside a BottomSheet component.');\n }\n\n const { panGestureRef } = internalCtx;\n\n const nativeGesture = Gesture.Native().simultaneousWithExternalGesture(panGestureRef);\n\n return {\n scrollHandler,\n scrollableRef,\n nativeGesture,\n animatedProps,\n };\n}\n\nexport { useBottomSheetScroll };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,SAAS,uBAAuB;CAC9B,MAAM,cAAc,+BAA+B;CACnD,MAAM,gBAAgB,gBAAqC;CAE3D,MAAM,gBAAgB,wBAAwB;EAC5C,eAAe,CAAC,aAAa,aAAa;EAC1C,kBAAkB,aAAa,aAAa,QAAQ,IAAI;EACzD,EAAE;CAEH,MAAM,gBAAgB,yBAAyB,EAC7C,WAAW,UAAU;AACnB,MAAI,CAAC,YACH;AAEF,cAAY,cAAc,QAAQ,MAAM,cAAc;IAEzD,CAAC;AAEF,KAAI,CAAC,YACH,OAAM,IAAI,MAAM,oEAAoE;CAGtF,MAAM,EAAE,kBAAkB;AAI1B,QAAO;EACL;EACA;EACA,eALoB,QAAQ,QAAQ,CAAC,gCAAgC,cAKxD;EACb;EACD"}
1
+ {"version":3,"file":"useBottomSheetScroll.js","names":[],"sources":["../../../src/components/BottomSheet/useBottomSheetScroll.ts"],"sourcesContent":["import { Gesture } from 'react-native-gesture-handler';\nimport type Animated from 'react-native-reanimated';\nimport {\n useAnimatedProps,\n useAnimatedRef,\n useAnimatedScrollHandler,\n} from 'react-native-reanimated';\n\nimport { useBottomSheetInternalContext } from './BottomSheetInternalProvider';\n\n/**\n * Low-level hook for consumers who need a custom scrollable (e.g. `FlatList`,\n * `SectionList`) instead of `<BottomSheetContent>`.\n *\n * Returns all the props needed to wire a scrollable into the sheet's\n * scroll-lock coordination (scrollEnabled toggle, offset tracking, gesture composition).\n *\n * Must be rendered inside a `<BottomSheet>`.\n *\n * @throws If used outside a `<BottomSheet>` component tree.\n *\n * @returns `{ scrollHandler, scrollableRef, nativeGesture, animatedProps }`\n *\n * @example\n * ```tsx\n * const { scrollHandler, scrollableRef, nativeGesture, animatedProps } = useBottomSheetScroll();\n *\n * <GestureDetector gesture={nativeGesture}>\n * <Animated.FlatList\n * ref={scrollableRef}\n * onScroll={scrollHandler}\n * scrollEventThrottle={16}\n * animatedProps={animatedProps}\n * bounces={false}\n * overScrollMode=\"never\"\n * {...otherProps}\n * />\n * </GestureDetector>\n * ```\n */\nfunction useBottomSheetScroll() {\n const internalCtx = useBottomSheetInternalContext();\n const scrollableRef = useAnimatedRef<Animated.ScrollView>();\n\n const animatedProps = useAnimatedProps(() => ({\n scrollEnabled: !internalCtx?.scrollLocked.value,\n decelerationRate: internalCtx?.scrollLocked.value ? 0 : 0.998,\n }));\n\n const scrollHandler = useAnimatedScrollHandler({\n onScroll: (event) => {\n if (!internalCtx) {\n return;\n }\n internalCtx.scrollOffsetY.value = event.contentOffset.y;\n },\n });\n\n if (!internalCtx) {\n throw new Error('useBottomSheetScroll must be used inside a BottomSheet component.');\n }\n\n const { panGestureRef } = internalCtx;\n\n const nativeGesture = Gesture.Native().simultaneousWithExternalGesture(panGestureRef);\n\n return {\n scrollHandler,\n scrollableRef,\n nativeGesture,\n animatedProps,\n };\n}\n\nexport { useBottomSheetScroll };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,SAAS,uBAAuB;CAC9B,MAAM,cAAc,+BAA+B;CACnD,MAAM,gBAAgB,gBAAqC;CAE3D,MAAM,gBAAgB,wBAAwB;EAC5C,eAAe,CAAC,aAAa,aAAa;EAC1C,kBAAkB,aAAa,aAAa,QAAQ,IAAI;EACzD,EAAE;CAEH,MAAM,gBAAgB,yBAAyB,EAC7C,WAAW,UAAU;EACnB,IAAI,CAAC,aACH;EAEF,YAAY,cAAc,QAAQ,MAAM,cAAc;IAEzD,CAAC;CAEF,IAAI,CAAC,aACH,MAAM,IAAI,MAAM,oEAAoE;CAGtF,MAAM,EAAE,kBAAkB;CAI1B,OAAO;EACL;EACA;EACA,eALoB,QAAQ,QAAQ,CAAC,gCAAgC,cAKxD;EACb;EACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"useBottomSheetSnapModel.js","names":[],"sources":["../../../src/components/BottomSheet/useBottomSheetSnapModel.ts"],"sourcesContent":["import { useCallback, useMemo } from 'react';\nimport type { SharedValue } from 'react-native-reanimated';\nimport { runOnJS, useSharedValue, withSpring } from 'react-native-reanimated';\n\nimport { MOTION_CONFIG } from '../../motion';\nimport type { BottomSheetHeight } from './types';\nimport { clampIndex, DEFAULT_SNAP_POINT, resolveHeightToPx } from './utils';\n\n/**\n * Spring configuration for sheet snap/close animations.\n * Uses the UDS `smooth['3']` motion preset.\n */\nconst SHEET_SPRING_CONFIG = {\n damping: MOTION_CONFIG.smooth['3'].damping,\n stiffness: MOTION_CONFIG.smooth['3'].stiffness,\n mass: 1,\n} as const;\n\n/** @internal */\ninterface UseBottomSheetSnapModelParams {\n snapPointsProp?: BottomSheetHeight[];\n height?: BottomSheetHeight;\n defaultSnapPointIndex: number;\n snapPointIndexProp?: number;\n onSnapPointChange?: (index: number, height: BottomSheetHeight) => void;\n containerHeight: SharedValue<number>;\n /** Bottom inset in px (margin + safe area). */\n bottomInsetPx: number;\n /** Top safe-area inset in px — limits maximum sheet height. */\n topInsetPx: number;\n /** Horizontal margin in px (currently informational, not used in snap math). */\n sideInsetPx: number;\n /** Whether margins collapse at the max snap point. */\n fullWidthAtMaxSnap: boolean;\n /** Bottom inset at max snap when fullWidthAtMaxSnap (safe area only, no margin). */\n expandedBottomInsetPx: number;\n}\n\ninterface UseBottomSheetSnapModelResult {\n /** The resolved snap points array. */\n resolvedSnapPoints: BottomSheetHeight[];\n /** Animated translateY position of the sheet. */\n translateY: SharedValue<number>;\n /** Current active snap index. */\n activeSnapIndex: SharedValue<number>;\n /** Whether the sheet is currently animating. */\n isAnimating: SharedValue<boolean>;\n /** Compute translateY for a given snap index (worklet). */\n getTranslateYForSnap: (index: number) => number;\n /** Compute the closed translateY position (worklet). */\n getClosedTranslateY: () => number;\n /** Animate to a specific snap index. */\n animateToSnap: (index: number) => void;\n /** Animate to the closed position. Optional callback fires on JS thread when complete. */\n animateToClose: (onComplete?: () => void) => void;\n /** Animate to a specific snap index from within a worklet. */\n animateToSnapWorklet: (index: number) => void;\n /** Animate to the closed position from within a worklet. */\n animateToCloseWorklet: () => void;\n}\n\n/**\n * Manages the snap-point position model for the bottom sheet.\n *\n * Resolves snap point strings to pixel positions, provides spring-animated\n * `animateToSnap` / `animateToClose` functions for both JS and UI threads,\n * and exposes shared values for `translateY`, `activeSnapIndex`, and `isAnimating`.\n *\n * Position model: `translateY = containerHeight - snapHeightPx - bottomInsetPx`\n */\nfunction useBottomSheetSnapModel({\n snapPointsProp,\n height,\n defaultSnapPointIndex,\n snapPointIndexProp,\n onSnapPointChange,\n containerHeight,\n bottomInsetPx,\n topInsetPx,\n fullWidthAtMaxSnap,\n expandedBottomInsetPx,\n}: UseBottomSheetSnapModelParams): UseBottomSheetSnapModelResult {\n const resolvedSnapPoints = useMemo<BottomSheetHeight[]>(() => {\n if (snapPointsProp?.length) {\n return snapPointsProp;\n }\n if (height !== undefined) {\n return [height];\n }\n return [DEFAULT_SNAP_POINT];\n }, [height, snapPointsProp]);\n\n // Start off-screen (will be set to closed position on first layout).\n const translateY = useSharedValue(9999);\n const activeSnapIndex = useSharedValue(\n clampIndex(snapPointIndexProp ?? defaultSnapPointIndex, resolvedSnapPoints.length),\n );\n const isAnimating = useSharedValue(false);\n\n /**\n * Compute translateY for a snap index.\n * Position model: translateY = containerHeight - snapHeightPx - bottomInsetPx\n */\n const getTranslateYForSnap = useCallback(\n (index: number): number => {\n 'worklet';\n const clamped = clampIndex(index, resolvedSnapPoints.length);\n const sp = resolvedSnapPoints[clamped]!;\n const heightPx = resolveHeightToPx(sp, containerHeight.value);\n // At max snap with fullWidthAtMaxSnap, margins collapse so use reduced bottom inset.\n const isExpandedMaxSnap = fullWidthAtMaxSnap && clamped === resolvedSnapPoints.length - 1;\n const effectiveBottomInset = isExpandedMaxSnap ? expandedBottomInsetPx : bottomInsetPx;\n const maxHeight = containerHeight.value - effectiveBottomInset - topInsetPx;\n const clampedHeight = Math.max(0, Math.min(heightPx, maxHeight));\n return containerHeight.value - clampedHeight - effectiveBottomInset;\n },\n [\n resolvedSnapPoints,\n containerHeight,\n bottomInsetPx,\n topInsetPx,\n fullWidthAtMaxSnap,\n expandedBottomInsetPx,\n ],\n );\n\n /** Compute the closed (off-screen) translateY. */\n const getClosedTranslateY = useCallback((): number => {\n 'worklet';\n return containerHeight.value + bottomInsetPx;\n }, [containerHeight, bottomInsetPx]);\n\n /** Emit snap point change callback on the JS thread. */\n const emitSnapChange = useCallback(\n (index: number) => {\n onSnapPointChange?.(index, resolvedSnapPoints[index]!);\n },\n [onSnapPointChange, resolvedSnapPoints],\n );\n\n /** Animate to snap index — call from JS thread. */\n const animateToSnap = useCallback(\n (index: number) => {\n const clamped = clampIndex(index, resolvedSnapPoints.length);\n const targetY = getTranslateYForSnap(clamped);\n\n isAnimating.value = true;\n activeSnapIndex.value = clamped;\n translateY.value = withSpring(targetY, SHEET_SPRING_CONFIG, (finished) => {\n 'worklet';\n if (finished) {\n isAnimating.value = false;\n }\n });\n\n emitSnapChange(clamped);\n },\n [\n resolvedSnapPoints,\n getTranslateYForSnap,\n isAnimating,\n activeSnapIndex,\n translateY,\n emitSnapChange,\n ],\n );\n\n /** Animate to closed position — call from JS thread. */\n const animateToClose = useCallback(\n (onComplete?: () => void) => {\n const targetY = getClosedTranslateY();\n isAnimating.value = true;\n translateY.value = withSpring(targetY, SHEET_SPRING_CONFIG, (finished) => {\n 'worklet';\n if (finished) {\n isAnimating.value = false;\n if (onComplete) {\n runOnJS(onComplete)();\n }\n }\n });\n },\n [getClosedTranslateY, isAnimating, translateY],\n );\n\n /** Animate to snap — safe to call from a worklet (onEnd handler). */\n const animateToSnapWorklet = useCallback(\n (index: number) => {\n 'worklet';\n const clamped = clampIndex(index, resolvedSnapPoints.length);\n const targetY = getTranslateYForSnap(clamped);\n\n isAnimating.value = true;\n activeSnapIndex.value = clamped;\n translateY.value = withSpring(targetY, SHEET_SPRING_CONFIG, (finished) => {\n if (finished) {\n isAnimating.value = false;\n }\n });\n\n runOnJS(emitSnapChange)(clamped);\n },\n [\n resolvedSnapPoints,\n getTranslateYForSnap,\n isAnimating,\n activeSnapIndex,\n translateY,\n emitSnapChange,\n ],\n );\n\n /** Animate to closed — safe to call from a worklet. */\n const animateToCloseWorklet = useCallback(() => {\n 'worklet';\n const targetY = getClosedTranslateY();\n isAnimating.value = true;\n translateY.value = withSpring(targetY, SHEET_SPRING_CONFIG, (finished) => {\n if (finished) {\n isAnimating.value = false;\n }\n });\n }, [getClosedTranslateY, isAnimating, translateY]);\n\n return {\n resolvedSnapPoints,\n translateY,\n activeSnapIndex,\n isAnimating,\n getTranslateYForSnap,\n getClosedTranslateY,\n animateToSnap,\n animateToClose,\n animateToSnapWorklet,\n animateToCloseWorklet,\n };\n}\n\nexport { SHEET_SPRING_CONFIG, useBottomSheetSnapModel };\n"],"mappings":";;;;;;;;;;AAYA,MAAM,sBAAsB;CAC1B,SAAS,cAAc,OAAO,KAAK;CACnC,WAAW,cAAc,OAAO,KAAK;CACrC,MAAM;CACP;;;;;;;;;;AAsDD,SAAS,wBAAwB,EAC/B,gBACA,QACA,uBACA,oBACA,mBACA,iBACA,eACA,YACA,oBACA,yBAC+D;CAC/D,MAAM,qBAAqB,cAAmC;AAC5D,MAAI,gBAAgB,OAClB,QAAO;AAET,MAAI,WAAW,KAAA,EACb,QAAO,CAAC,OAAO;AAEjB,SAAO,CAAA,MAAoB;IAC1B,CAAC,QAAQ,eAAe,CAAC;CAG5B,MAAM,aAAa,eAAe,KAAK;CACvC,MAAM,kBAAkB,eACtB,WAAW,sBAAsB,uBAAuB,mBAAmB,OAAO,CACnF;CACD,MAAM,cAAc,eAAe,MAAM;;;;;CAMzC,MAAM,uBAAuB,aAC1B,UAA0B;AACzB;EACA,MAAM,UAAU,WAAW,OAAO,mBAAmB,OAAO;EAC5D,MAAM,KAAK,mBAAmB;EAC9B,MAAM,WAAW,kBAAkB,IAAI,gBAAgB,MAAM;EAG7D,MAAM,uBADoB,sBAAsB,YAAY,mBAAmB,SAAS,IACvC,wBAAwB;EACzE,MAAM,YAAY,gBAAgB,QAAQ,uBAAuB;EACjE,MAAM,gBAAgB,KAAK,IAAI,GAAG,KAAK,IAAI,UAAU,UAAU,CAAC;AAChE,SAAO,gBAAgB,QAAQ,gBAAgB;IAEjD;EACE;EACA;EACA;EACA;EACA;EACA;EACD,CACF;;CAGD,MAAM,sBAAsB,kBAA0B;AACpD;AACA,SAAO,gBAAgB,QAAQ;IAC9B,CAAC,iBAAiB,cAAc,CAAC;;CAGpC,MAAM,iBAAiB,aACpB,UAAkB;AACjB,sBAAoB,OAAO,mBAAmB,OAAQ;IAExD,CAAC,mBAAmB,mBAAmB,CACxC;AAsFD,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA,eA1FoB,aACnB,UAAkB;GACjB,MAAM,UAAU,WAAW,OAAO,mBAAmB,OAAO;GAC5D,MAAM,UAAU,qBAAqB,QAAQ;AAE7C,eAAY,QAAQ;AACpB,mBAAgB,QAAQ;AACxB,cAAW,QAAQ,WAAW,SAAS,sBAAsB,aAAa;AACxE;AACA,QAAI,SACF,aAAY,QAAQ;KAEtB;AAEF,kBAAe,QAAQ;KAEzB;GACE;GACA;GACA;GACA;GACA;GACA;GACD,CAmEY;EACb,gBAhEqB,aACpB,eAA4B;GAC3B,MAAM,UAAU,qBAAqB;AACrC,eAAY,QAAQ;AACpB,cAAW,QAAQ,WAAW,SAAS,sBAAsB,aAAa;AACxE;AACA,QAAI,UAAU;AACZ,iBAAY,QAAQ;AACpB,SAAI,WACF,SAAQ,WAAW,EAAE;;KAGzB;KAEJ;GAAC;GAAqB;GAAa;GAAW,CAkDhC;EACd,sBA/C2B,aAC1B,UAAkB;AACjB;GACA,MAAM,UAAU,WAAW,OAAO,mBAAmB,OAAO;GAC5D,MAAM,UAAU,qBAAqB,QAAQ;AAE7C,eAAY,QAAQ;AACpB,mBAAgB,QAAQ;AACxB,cAAW,QAAQ,WAAW,SAAS,sBAAsB,aAAa;AACxE,QAAI,SACF,aAAY,QAAQ;KAEtB;AAEF,WAAQ,eAAe,CAAC,QAAQ;KAElC;GACE;GACA;GACA;GACA;GACA;GACA;GACD,CAwBmB;EACpB,uBArB4B,kBAAkB;AAC9C;GACA,MAAM,UAAU,qBAAqB;AACrC,eAAY,QAAQ;AACpB,cAAW,QAAQ,WAAW,SAAS,sBAAsB,aAAa;AACxE,QAAI,SACF,aAAY,QAAQ;KAEtB;KACD;GAAC;GAAqB;GAAa;GAAW,CAY1B;EACtB"}
1
+ {"version":3,"file":"useBottomSheetSnapModel.js","names":[],"sources":["../../../src/components/BottomSheet/useBottomSheetSnapModel.ts"],"sourcesContent":["import { useCallback, useMemo } from 'react';\nimport type { SharedValue } from 'react-native-reanimated';\nimport { runOnJS, useSharedValue, withSpring } from 'react-native-reanimated';\n\nimport { MOTION_CONFIG } from '../../motion';\nimport type { BottomSheetHeight } from './types';\nimport { clampIndex, DEFAULT_SNAP_POINT, resolveHeightToPx } from './utils';\n\n/**\n * Spring configuration for sheet snap/close animations.\n * Uses the UDS `smooth['3']` motion preset.\n */\nconst SHEET_SPRING_CONFIG = {\n damping: MOTION_CONFIG.smooth['3'].damping,\n stiffness: MOTION_CONFIG.smooth['3'].stiffness,\n mass: 1,\n} as const;\n\n/** @internal */\ninterface UseBottomSheetSnapModelParams {\n snapPointsProp?: BottomSheetHeight[];\n height?: BottomSheetHeight;\n defaultSnapPointIndex: number;\n snapPointIndexProp?: number;\n onSnapPointChange?: (index: number, height: BottomSheetHeight) => void;\n containerHeight: SharedValue<number>;\n /** Bottom inset in px (margin + safe area). */\n bottomInsetPx: number;\n /** Top safe-area inset in px — limits maximum sheet height. */\n topInsetPx: number;\n /** Horizontal margin in px (currently informational, not used in snap math). */\n sideInsetPx: number;\n /** Whether margins collapse at the max snap point. */\n fullWidthAtMaxSnap: boolean;\n /** Bottom inset at max snap when fullWidthAtMaxSnap (safe area only, no margin). */\n expandedBottomInsetPx: number;\n}\n\ninterface UseBottomSheetSnapModelResult {\n /** The resolved snap points array. */\n resolvedSnapPoints: BottomSheetHeight[];\n /** Animated translateY position of the sheet. */\n translateY: SharedValue<number>;\n /** Current active snap index. */\n activeSnapIndex: SharedValue<number>;\n /** Whether the sheet is currently animating. */\n isAnimating: SharedValue<boolean>;\n /** Compute translateY for a given snap index (worklet). */\n getTranslateYForSnap: (index: number) => number;\n /** Compute the closed translateY position (worklet). */\n getClosedTranslateY: () => number;\n /** Animate to a specific snap index. */\n animateToSnap: (index: number) => void;\n /** Animate to the closed position. Optional callback fires on JS thread when complete. */\n animateToClose: (onComplete?: () => void) => void;\n /** Animate to a specific snap index from within a worklet. */\n animateToSnapWorklet: (index: number) => void;\n /** Animate to the closed position from within a worklet. */\n animateToCloseWorklet: () => void;\n}\n\n/**\n * Manages the snap-point position model for the bottom sheet.\n *\n * Resolves snap point strings to pixel positions, provides spring-animated\n * `animateToSnap` / `animateToClose` functions for both JS and UI threads,\n * and exposes shared values for `translateY`, `activeSnapIndex`, and `isAnimating`.\n *\n * Position model: `translateY = containerHeight - snapHeightPx - bottomInsetPx`\n */\nfunction useBottomSheetSnapModel({\n snapPointsProp,\n height,\n defaultSnapPointIndex,\n snapPointIndexProp,\n onSnapPointChange,\n containerHeight,\n bottomInsetPx,\n topInsetPx,\n fullWidthAtMaxSnap,\n expandedBottomInsetPx,\n}: UseBottomSheetSnapModelParams): UseBottomSheetSnapModelResult {\n const resolvedSnapPoints = useMemo<BottomSheetHeight[]>(() => {\n if (snapPointsProp?.length) {\n return snapPointsProp;\n }\n if (height !== undefined) {\n return [height];\n }\n return [DEFAULT_SNAP_POINT];\n }, [height, snapPointsProp]);\n\n // Start off-screen (will be set to closed position on first layout).\n const translateY = useSharedValue(9999);\n const activeSnapIndex = useSharedValue(\n clampIndex(snapPointIndexProp ?? defaultSnapPointIndex, resolvedSnapPoints.length),\n );\n const isAnimating = useSharedValue(false);\n\n /**\n * Compute translateY for a snap index.\n * Position model: translateY = containerHeight - snapHeightPx - bottomInsetPx\n */\n const getTranslateYForSnap = useCallback(\n (index: number): number => {\n 'worklet';\n const clamped = clampIndex(index, resolvedSnapPoints.length);\n const sp = resolvedSnapPoints[clamped]!;\n const heightPx = resolveHeightToPx(sp, containerHeight.value);\n // At max snap with fullWidthAtMaxSnap, margins collapse so use reduced bottom inset.\n const isExpandedMaxSnap = fullWidthAtMaxSnap && clamped === resolvedSnapPoints.length - 1;\n const effectiveBottomInset = isExpandedMaxSnap ? expandedBottomInsetPx : bottomInsetPx;\n const maxHeight = containerHeight.value - effectiveBottomInset - topInsetPx;\n const clampedHeight = Math.max(0, Math.min(heightPx, maxHeight));\n return containerHeight.value - clampedHeight - effectiveBottomInset;\n },\n [\n resolvedSnapPoints,\n containerHeight,\n bottomInsetPx,\n topInsetPx,\n fullWidthAtMaxSnap,\n expandedBottomInsetPx,\n ],\n );\n\n /** Compute the closed (off-screen) translateY. */\n const getClosedTranslateY = useCallback((): number => {\n 'worklet';\n return containerHeight.value + bottomInsetPx;\n }, [containerHeight, bottomInsetPx]);\n\n /** Emit snap point change callback on the JS thread. */\n const emitSnapChange = useCallback(\n (index: number) => {\n onSnapPointChange?.(index, resolvedSnapPoints[index]!);\n },\n [onSnapPointChange, resolvedSnapPoints],\n );\n\n /** Animate to snap index — call from JS thread. */\n const animateToSnap = useCallback(\n (index: number) => {\n const clamped = clampIndex(index, resolvedSnapPoints.length);\n const targetY = getTranslateYForSnap(clamped);\n\n isAnimating.value = true;\n activeSnapIndex.value = clamped;\n translateY.value = withSpring(targetY, SHEET_SPRING_CONFIG, (finished) => {\n 'worklet';\n if (finished) {\n isAnimating.value = false;\n }\n });\n\n emitSnapChange(clamped);\n },\n [\n resolvedSnapPoints,\n getTranslateYForSnap,\n isAnimating,\n activeSnapIndex,\n translateY,\n emitSnapChange,\n ],\n );\n\n /** Animate to closed position — call from JS thread. */\n const animateToClose = useCallback(\n (onComplete?: () => void) => {\n const targetY = getClosedTranslateY();\n isAnimating.value = true;\n translateY.value = withSpring(targetY, SHEET_SPRING_CONFIG, (finished) => {\n 'worklet';\n if (finished) {\n isAnimating.value = false;\n if (onComplete) {\n runOnJS(onComplete)();\n }\n }\n });\n },\n [getClosedTranslateY, isAnimating, translateY],\n );\n\n /** Animate to snap — safe to call from a worklet (onEnd handler). */\n const animateToSnapWorklet = useCallback(\n (index: number) => {\n 'worklet';\n const clamped = clampIndex(index, resolvedSnapPoints.length);\n const targetY = getTranslateYForSnap(clamped);\n\n isAnimating.value = true;\n activeSnapIndex.value = clamped;\n translateY.value = withSpring(targetY, SHEET_SPRING_CONFIG, (finished) => {\n if (finished) {\n isAnimating.value = false;\n }\n });\n\n runOnJS(emitSnapChange)(clamped);\n },\n [\n resolvedSnapPoints,\n getTranslateYForSnap,\n isAnimating,\n activeSnapIndex,\n translateY,\n emitSnapChange,\n ],\n );\n\n /** Animate to closed — safe to call from a worklet. */\n const animateToCloseWorklet = useCallback(() => {\n 'worklet';\n const targetY = getClosedTranslateY();\n isAnimating.value = true;\n translateY.value = withSpring(targetY, SHEET_SPRING_CONFIG, (finished) => {\n if (finished) {\n isAnimating.value = false;\n }\n });\n }, [getClosedTranslateY, isAnimating, translateY]);\n\n return {\n resolvedSnapPoints,\n translateY,\n activeSnapIndex,\n isAnimating,\n getTranslateYForSnap,\n getClosedTranslateY,\n animateToSnap,\n animateToClose,\n animateToSnapWorklet,\n animateToCloseWorklet,\n };\n}\n\nexport { SHEET_SPRING_CONFIG, useBottomSheetSnapModel };\n"],"mappings":";;;;;;;;;;AAYA,MAAM,sBAAsB;CAC1B,SAAS,cAAc,OAAO,KAAK;CACnC,WAAW,cAAc,OAAO,KAAK;CACrC,MAAM;CACP;;;;;;;;;;AAsDD,SAAS,wBAAwB,EAC/B,gBACA,QACA,uBACA,oBACA,mBACA,iBACA,eACA,YACA,oBACA,yBAC+D;CAC/D,MAAM,qBAAqB,cAAmC;EAC5D,IAAI,gBAAgB,QAClB,OAAO;EAET,IAAI,WAAW,KAAA,GACb,OAAO,CAAC,OAAO;EAEjB,OAAO,CAAA,MAAoB;IAC1B,CAAC,QAAQ,eAAe,CAAC;CAG5B,MAAM,aAAa,eAAe,KAAK;CACvC,MAAM,kBAAkB,eACtB,WAAW,sBAAsB,uBAAuB,mBAAmB,OAAO,CACnF;CACD,MAAM,cAAc,eAAe,MAAM;;;;;CAMzC,MAAM,uBAAuB,aAC1B,UAA0B;AACzB;EACA,MAAM,UAAU,WAAW,OAAO,mBAAmB,OAAO;EAC5D,MAAM,KAAK,mBAAmB;EAC9B,MAAM,WAAW,kBAAkB,IAAI,gBAAgB,MAAM;EAG7D,MAAM,uBADoB,sBAAsB,YAAY,mBAAmB,SAAS,IACvC,wBAAwB;EACzE,MAAM,YAAY,gBAAgB,QAAQ,uBAAuB;EACjE,MAAM,gBAAgB,KAAK,IAAI,GAAG,KAAK,IAAI,UAAU,UAAU,CAAC;EAChE,OAAO,gBAAgB,QAAQ,gBAAgB;IAEjD;EACE;EACA;EACA;EACA;EACA;EACA;EACD,CACF;;CAGD,MAAM,sBAAsB,kBAA0B;AACpD;EACA,OAAO,gBAAgB,QAAQ;IAC9B,CAAC,iBAAiB,cAAc,CAAC;;CAGpC,MAAM,iBAAiB,aACpB,UAAkB;EACjB,oBAAoB,OAAO,mBAAmB,OAAQ;IAExD,CAAC,mBAAmB,mBAAmB,CACxC;CAsFD,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA,eA1FoB,aACnB,UAAkB;GACjB,MAAM,UAAU,WAAW,OAAO,mBAAmB,OAAO;GAC5D,MAAM,UAAU,qBAAqB,QAAQ;GAE7C,YAAY,QAAQ;GACpB,gBAAgB,QAAQ;GACxB,WAAW,QAAQ,WAAW,SAAS,sBAAsB,aAAa;AACxE;IACA,IAAI,UACF,YAAY,QAAQ;KAEtB;GAEF,eAAe,QAAQ;KAEzB;GACE;GACA;GACA;GACA;GACA;GACA;GACD,CAmEY;EACb,gBAhEqB,aACpB,eAA4B;GAC3B,MAAM,UAAU,qBAAqB;GACrC,YAAY,QAAQ;GACpB,WAAW,QAAQ,WAAW,SAAS,sBAAsB,aAAa;AACxE;IACA,IAAI,UAAU;KACZ,YAAY,QAAQ;KACpB,IAAI,YACF,QAAQ,WAAW,EAAE;;KAGzB;KAEJ;GAAC;GAAqB;GAAa;GAAW,CAkDhC;EACd,sBA/C2B,aAC1B,UAAkB;AACjB;GACA,MAAM,UAAU,WAAW,OAAO,mBAAmB,OAAO;GAC5D,MAAM,UAAU,qBAAqB,QAAQ;GAE7C,YAAY,QAAQ;GACpB,gBAAgB,QAAQ;GACxB,WAAW,QAAQ,WAAW,SAAS,sBAAsB,aAAa;IACxE,IAAI,UACF,YAAY,QAAQ;KAEtB;GAEF,QAAQ,eAAe,CAAC,QAAQ;KAElC;GACE;GACA;GACA;GACA;GACA;GACA;GACD,CAwBmB;EACpB,uBArB4B,kBAAkB;AAC9C;GACA,MAAM,UAAU,qBAAqB;GACrC,YAAY,QAAQ;GACpB,WAAW,QAAQ,WAAW,SAAS,sBAAsB,aAAa;IACxE,IAAI,UACF,YAAY,QAAQ;KAEtB;KACD;GAAC;GAAqB;GAAa;GAAW,CAY1B;EACtB"}
@@ -1 +1 @@
1
- {"version":3,"file":"useBottomSheetStore.js","names":[],"sources":["../../../src/components/BottomSheet/useBottomSheetStore.ts"],"sourcesContent":["import { useCallback, useEffect, useState, useSyncExternalStore } from 'react';\n\nimport type { BottomSheetController, UseBottomSheetStoreProps } from './types';\n\ntype Listener = () => void;\n\n/**\n * Creates a lightweight external store for bottom-sheet open/close state.\n * Uses the `subscribe` / `getSnapshot` pattern for {@link useSyncExternalStore}.\n *\n * @param initialOpen - Initial open state.\n * @returns Store with `subscribe`, `getSnapshot`, and `setOpen`.\n * @internal\n */\nfunction createBottomSheetStore(initialOpen: boolean) {\n let isOpen = initialOpen;\n const listeners = new Set<Listener>();\n\n function subscribe(listener: Listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n }\n\n function getSnapshot() {\n return isOpen;\n }\n\n function setOpen(next: boolean) {\n if (next === isOpen) {\n return;\n }\n isOpen = next;\n for (const listener of listeners) {\n listener();\n }\n }\n\n return { subscribe, getSnapshot, setOpen };\n}\n\n/** @internal */\nconst BOTTOM_SHEET_INTERNAL_STORE_KEY = '__bottomSheetInternal';\n\ntype InternalStore = ReturnType<typeof createBottomSheetStore>;\n\ntype BottomSheetControllerWithInternal = BottomSheetController & {\n [BOTTOM_SHEET_INTERNAL_STORE_KEY]: InternalStore;\n};\n\n/** @internal — retrieve the internal store from a controller. */\nfunction getBottomSheetInternal(controller: BottomSheetController): InternalStore {\n const internal = (controller as BottomSheetControllerWithInternal)[\n BOTTOM_SHEET_INTERNAL_STORE_KEY\n ];\n if (!internal) {\n throw new Error('Invalid BottomSheet controller. Use `useBottomSheetStore()` to create one.');\n }\n return internal;\n}\n\n/**\n * Creates a {@link BottomSheetController} that manages open/close state.\n *\n * Supports both uncontrolled (`defaultOpen`) and controlled (`open`) modes.\n * The returned controller can be passed to `<BottomSheetProvider controller={...}>`\n * or `<BottomSheet controller={...}>`.\n *\n * @param props - Optional `defaultOpen` and/or controlled `open` value.\n * @returns A stable {@link BottomSheetController} with `open()`, `close()`, and `isOpen`.\n *\n * @example\n * ```tsx\n * const store = useBottomSheetStore();\n * // ...\n * <Button onPress={() => store.open()}>Open</Button>\n * ```\n */\nfunction useBottomSheetStore({\n defaultOpen = false,\n open: openProp,\n}: UseBottomSheetStoreProps = {}): BottomSheetController {\n // Use useState with lazy initializer to create the store once.\n const [store] = useState(() => createBottomSheetStore(openProp ?? defaultOpen));\n\n // Sync controlled `open` prop into store via effect.\n useEffect(() => {\n if (openProp !== undefined) {\n store.setOpen(openProp);\n }\n }, [openProp, store]);\n\n const isOpen = useSyncExternalStore(store.subscribe, store.getSnapshot);\n\n const open = useCallback(() => store.setOpen(true), [store]);\n const close = useCallback(() => store.setOpen(false), [store]);\n\n return {\n open,\n close,\n isOpen,\n [BOTTOM_SHEET_INTERNAL_STORE_KEY]: store,\n } as BottomSheetController;\n}\n\nexport { getBottomSheetInternal, useBottomSheetStore };\n"],"mappings":";;;;;;;;;;;AAcA,SAAS,uBAAuB,aAAsB;CACpD,IAAI,SAAS;CACb,MAAM,4BAAY,IAAI,KAAe;CAErC,SAAS,UAAU,UAAoB;AACrC,YAAU,IAAI,SAAS;AACvB,eAAa;AACX,aAAU,OAAO,SAAS;;;CAI9B,SAAS,cAAc;AACrB,SAAO;;CAGT,SAAS,QAAQ,MAAe;AAC9B,MAAI,SAAS,OACX;AAEF,WAAS;AACT,OAAK,MAAM,YAAY,UACrB,WAAU;;AAId,QAAO;EAAE;EAAW;EAAa;EAAS;;;AAI5C,MAAM,kCAAkC;;AASxC,SAAS,uBAAuB,YAAkD;CAChF,MAAM,WAAY,WAChB;AAEF,KAAI,CAAC,SACH,OAAM,IAAI,MAAM,6EAA6E;AAE/F,QAAO;;;;;;;;;;;;;;;;;;;AAoBT,SAAS,oBAAoB,EAC3B,cAAc,OACd,MAAM,aACsB,EAAE,EAAyB;CAEvD,MAAM,CAAC,SAAS,eAAe,uBAAuB,YAAY,YAAY,CAAC;AAG/E,iBAAgB;AACd,MAAI,aAAa,KAAA,EACf,OAAM,QAAQ,SAAS;IAExB,CAAC,UAAU,MAAM,CAAC;CAErB,MAAM,SAAS,qBAAqB,MAAM,WAAW,MAAM,YAAY;AAKvE,QAAO;EACL,MAJW,kBAAkB,MAAM,QAAQ,KAAK,EAAE,CAAC,MAAM,CAIrD;EACJ,OAJY,kBAAkB,MAAM,QAAQ,MAAM,EAAE,CAAC,MAAM,CAItD;EACL;GACC,kCAAkC;EACpC"}
1
+ {"version":3,"file":"useBottomSheetStore.js","names":[],"sources":["../../../src/components/BottomSheet/useBottomSheetStore.ts"],"sourcesContent":["import { useCallback, useEffect, useState, useSyncExternalStore } from 'react';\n\nimport type { BottomSheetController, UseBottomSheetStoreProps } from './types';\n\ntype Listener = () => void;\n\n/**\n * Creates a lightweight external store for bottom-sheet open/close state.\n * Uses the `subscribe` / `getSnapshot` pattern for {@link useSyncExternalStore}.\n *\n * @param initialOpen - Initial open state.\n * @returns Store with `subscribe`, `getSnapshot`, and `setOpen`.\n * @internal\n */\nfunction createBottomSheetStore(initialOpen: boolean) {\n let isOpen = initialOpen;\n const listeners = new Set<Listener>();\n\n function subscribe(listener: Listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n }\n\n function getSnapshot() {\n return isOpen;\n }\n\n function setOpen(next: boolean) {\n if (next === isOpen) {\n return;\n }\n isOpen = next;\n for (const listener of listeners) {\n listener();\n }\n }\n\n return { subscribe, getSnapshot, setOpen };\n}\n\n/** @internal */\nconst BOTTOM_SHEET_INTERNAL_STORE_KEY = '__bottomSheetInternal';\n\ntype InternalStore = ReturnType<typeof createBottomSheetStore>;\n\ntype BottomSheetControllerWithInternal = BottomSheetController & {\n [BOTTOM_SHEET_INTERNAL_STORE_KEY]: InternalStore;\n};\n\n/** @internal — retrieve the internal store from a controller. */\nfunction getBottomSheetInternal(controller: BottomSheetController): InternalStore {\n const internal = (controller as BottomSheetControllerWithInternal)[\n BOTTOM_SHEET_INTERNAL_STORE_KEY\n ];\n if (!internal) {\n throw new Error('Invalid BottomSheet controller. Use `useBottomSheetStore()` to create one.');\n }\n return internal;\n}\n\n/**\n * Creates a {@link BottomSheetController} that manages open/close state.\n *\n * Supports both uncontrolled (`defaultOpen`) and controlled (`open`) modes.\n * The returned controller can be passed to `<BottomSheetProvider controller={...}>`\n * or `<BottomSheet controller={...}>`.\n *\n * @param props - Optional `defaultOpen` and/or controlled `open` value.\n * @returns A stable {@link BottomSheetController} with `open()`, `close()`, and `isOpen`.\n *\n * @example\n * ```tsx\n * const store = useBottomSheetStore();\n * // ...\n * <Button onPress={() => store.open()}>Open</Button>\n * ```\n */\nfunction useBottomSheetStore({\n defaultOpen = false,\n open: openProp,\n}: UseBottomSheetStoreProps = {}): BottomSheetController {\n // Use useState with lazy initializer to create the store once.\n const [store] = useState(() => createBottomSheetStore(openProp ?? defaultOpen));\n\n // Sync controlled `open` prop into store via effect.\n useEffect(() => {\n if (openProp !== undefined) {\n store.setOpen(openProp);\n }\n }, [openProp, store]);\n\n const isOpen = useSyncExternalStore(store.subscribe, store.getSnapshot);\n\n const open = useCallback(() => store.setOpen(true), [store]);\n const close = useCallback(() => store.setOpen(false), [store]);\n\n return {\n open,\n close,\n isOpen,\n [BOTTOM_SHEET_INTERNAL_STORE_KEY]: store,\n } as BottomSheetController;\n}\n\nexport { getBottomSheetInternal, useBottomSheetStore };\n"],"mappings":";;;;;;;;;;;AAcA,SAAS,uBAAuB,aAAsB;CACpD,IAAI,SAAS;CACb,MAAM,4BAAY,IAAI,KAAe;CAErC,SAAS,UAAU,UAAoB;EACrC,UAAU,IAAI,SAAS;EACvB,aAAa;GACX,UAAU,OAAO,SAAS;;;CAI9B,SAAS,cAAc;EACrB,OAAO;;CAGT,SAAS,QAAQ,MAAe;EAC9B,IAAI,SAAS,QACX;EAEF,SAAS;EACT,KAAK,MAAM,YAAY,WACrB,UAAU;;CAId,OAAO;EAAE;EAAW;EAAa;EAAS;;;AAI5C,MAAM,kCAAkC;;AASxC,SAAS,uBAAuB,YAAkD;CAChF,MAAM,WAAY,WAChB;CAEF,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,6EAA6E;CAE/F,OAAO;;;;;;;;;;;;;;;;;;;AAoBT,SAAS,oBAAoB,EAC3B,cAAc,OACd,MAAM,aACsB,EAAE,EAAyB;CAEvD,MAAM,CAAC,SAAS,eAAe,uBAAuB,YAAY,YAAY,CAAC;CAG/E,gBAAgB;EACd,IAAI,aAAa,KAAA,GACf,MAAM,QAAQ,SAAS;IAExB,CAAC,UAAU,MAAM,CAAC;CAErB,MAAM,SAAS,qBAAqB,MAAM,WAAW,MAAM,YAAY;CAKvE,OAAO;EACL,MAJW,kBAAkB,MAAM,QAAQ,KAAK,EAAE,CAAC,MAAM,CAIrD;EACJ,OAJY,kBAAkB,MAAM,QAAQ,MAAM,EAAE,CAAC,MAAM,CAItD;EACL;GACC,kCAAkC;EACpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useExpansionMargins.js","names":[],"sources":["../../../src/components/BottomSheet/useExpansionMargins.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport type { SharedValue } from 'react-native-reanimated';\nimport { interpolate, useAnimatedStyle } from 'react-native-reanimated';\n\nimport type { BottomSheetHeight } from './types';\nimport { clampIndex, resolveHeightToPx } from './utils';\n\n/** @internal */\ninterface UseExpansionMarginsParams {\n /** Whether `fullWidthAtMaxSnap` is enabled on the sheet. */\n enabled: boolean;\n /** Base horizontal margin from configurator tokens (px). */\n baseMarginHorizontal: number;\n /** Base bottom margin from configurator tokens (px). */\n baseMarginBottom: number;\n /** Resolved snap points array. */\n resolvedSnapPoints: BottomSheetHeight[];\n /** Container height (UI-thread shared value). */\n containerHeight: SharedValue<number>;\n /** Bottom inset in px (margin + safe area). */\n bottomInsetPx: number;\n /** Bottom inset at max snap when expanded (safe area only, no margin). */\n expandedBottomInsetPx: number;\n /** The sheet's animated translateY shared value. */\n translateY: SharedValue<number>;\n}\n\n/**\n * Animates sheet margins from their base values to `0` as the sheet expands\n * from the second-highest snap point to the max snap point.\n *\n * Also returns the interpolation thresholds (`maxSnapTranslateY` and\n * `thresholdTranslateY`) so the caller can animate other properties\n * (e.g. the height constraint's effective bottom inset) in sync.\n *\n * @returns An animated margin style and the expansion interpolation thresholds.\n */\nfunction useExpansionMargins({\n enabled,\n baseMarginHorizontal,\n baseMarginBottom,\n resolvedSnapPoints,\n containerHeight,\n bottomInsetPx,\n expandedBottomInsetPx,\n translateY,\n}: UseExpansionMarginsParams) {\n // Compute threshold and max snap translateY positions.\n // Max snap uses expandedBottomInsetPx (margins collapsed), threshold uses bottomInsetPx.\n const { maxSnapTranslateY, thresholdTranslateY } = useMemo(() => {\n if (!enabled || resolvedSnapPoints.length === 0) {\n return { maxSnapTranslateY: 0, thresholdTranslateY: 0 };\n }\n\n const h = containerHeight.value;\n const maxIdx = resolvedSnapPoints.length - 1;\n const maxHeight = resolveHeightToPx(resolvedSnapPoints[maxIdx]!, h);\n const clampedMax = Math.min(maxHeight, h - expandedBottomInsetPx);\n const maxTransY = h - clampedMax - expandedBottomInsetPx;\n\n // Threshold is the second-highest snap, or the max snap if only one exists.\n const thresholdIdx = clampIndex(maxIdx - 1, resolvedSnapPoints.length);\n const thresholdHeight = resolveHeightToPx(resolvedSnapPoints[thresholdIdx]!, h);\n const clampedThreshold = Math.min(thresholdHeight, h - bottomInsetPx);\n const thresholdTransY = h - clampedThreshold - bottomInsetPx;\n\n return { maxSnapTranslateY: maxTransY, thresholdTranslateY: thresholdTransY };\n }, [enabled, resolvedSnapPoints, containerHeight.value, bottomInsetPx, expandedBottomInsetPx]);\n\n const animatedMarginStyle = useAnimatedStyle(() => {\n if (!enabled) {\n return {};\n }\n\n // When translateY moves from thresholdTranslateY to maxSnapTranslateY\n // (sheet expanding), margins go from base values to 0.\n const mh = interpolate(\n translateY.value,\n [maxSnapTranslateY, thresholdTranslateY],\n [0, baseMarginHorizontal],\n 'clamp',\n );\n const mb = interpolate(\n translateY.value,\n [maxSnapTranslateY, thresholdTranslateY],\n [0, baseMarginBottom],\n 'clamp',\n );\n\n return {\n marginLeft: mh,\n marginRight: mh,\n marginBottom: mb,\n };\n });\n\n return { animatedMarginStyle, maxSnapTranslateY, thresholdTranslateY };\n}\n\nexport { useExpansionMargins };\n"],"mappings":";;;;;;;;;;;;;;;AAqCA,SAAS,oBAAoB,EAC3B,SACA,sBACA,kBACA,oBACA,iBACA,eACA,uBACA,cAC4B;CAG5B,MAAM,EAAE,mBAAmB,wBAAwB,cAAc;AAC/D,MAAI,CAAC,WAAW,mBAAmB,WAAW,EAC5C,QAAO;GAAE,mBAAmB;GAAG,qBAAqB;GAAG;EAGzD,MAAM,IAAI,gBAAgB;EAC1B,MAAM,SAAS,mBAAmB,SAAS;EAC3C,MAAM,YAAY,kBAAkB,mBAAmB,SAAU,EAAE;EAEnE,MAAM,YAAY,IADC,KAAK,IAAI,WAAW,IAAI,sBACX,GAAG;EAInC,MAAM,kBAAkB,kBAAkB,mBADrB,WAAW,SAAS,GAAG,mBAAmB,OACU,GAAI,EAAE;AAI/E,SAAO;GAAE,mBAAmB;GAAW,qBAFf,IADC,KAAK,IAAI,iBAAiB,IAAI,cACX,GAAG;GAE8B;IAC5E;EAAC;EAAS;EAAoB,gBAAgB;EAAO;EAAe;EAAsB,CAAC;AA6B9F,QAAO;EAAE,qBA3BmB,uBAAuB;AACjD,OAAI,CAAC,QACH,QAAO,EAAE;GAKX,MAAM,KAAK,YACT,WAAW,OACX,CAAC,mBAAmB,oBAAoB,EACxC,CAAC,GAAG,qBAAqB,EACzB,QACD;AAQD,UAAO;IACL,YAAY;IACZ,aAAa;IACb,cAVS,YACT,WAAW,OACX,CAAC,mBAAmB,oBAAoB,EACxC,CAAC,GAAG,iBAAiB,EACrB,QAMgB;IACjB;IAGyB;EAAE;EAAmB;EAAqB"}
1
+ {"version":3,"file":"useExpansionMargins.js","names":[],"sources":["../../../src/components/BottomSheet/useExpansionMargins.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport type { SharedValue } from 'react-native-reanimated';\nimport { interpolate, useAnimatedStyle } from 'react-native-reanimated';\n\nimport type { BottomSheetHeight } from './types';\nimport { clampIndex, resolveHeightToPx } from './utils';\n\n/** @internal */\ninterface UseExpansionMarginsParams {\n /** Whether `fullWidthAtMaxSnap` is enabled on the sheet. */\n enabled: boolean;\n /** Base horizontal margin from configurator tokens (px). */\n baseMarginHorizontal: number;\n /** Base bottom margin from configurator tokens (px). */\n baseMarginBottom: number;\n /** Resolved snap points array. */\n resolvedSnapPoints: BottomSheetHeight[];\n /** Container height (UI-thread shared value). */\n containerHeight: SharedValue<number>;\n /** Bottom inset in px (margin + safe area). */\n bottomInsetPx: number;\n /** Bottom inset at max snap when expanded (safe area only, no margin). */\n expandedBottomInsetPx: number;\n /** The sheet's animated translateY shared value. */\n translateY: SharedValue<number>;\n}\n\n/**\n * Animates sheet margins from their base values to `0` as the sheet expands\n * from the second-highest snap point to the max snap point.\n *\n * Also returns the interpolation thresholds (`maxSnapTranslateY` and\n * `thresholdTranslateY`) so the caller can animate other properties\n * (e.g. the height constraint's effective bottom inset) in sync.\n *\n * @returns An animated margin style and the expansion interpolation thresholds.\n */\nfunction useExpansionMargins({\n enabled,\n baseMarginHorizontal,\n baseMarginBottom,\n resolvedSnapPoints,\n containerHeight,\n bottomInsetPx,\n expandedBottomInsetPx,\n translateY,\n}: UseExpansionMarginsParams) {\n // Compute threshold and max snap translateY positions.\n // Max snap uses expandedBottomInsetPx (margins collapsed), threshold uses bottomInsetPx.\n const { maxSnapTranslateY, thresholdTranslateY } = useMemo(() => {\n if (!enabled || resolvedSnapPoints.length === 0) {\n return { maxSnapTranslateY: 0, thresholdTranslateY: 0 };\n }\n\n const h = containerHeight.value;\n const maxIdx = resolvedSnapPoints.length - 1;\n const maxHeight = resolveHeightToPx(resolvedSnapPoints[maxIdx]!, h);\n const clampedMax = Math.min(maxHeight, h - expandedBottomInsetPx);\n const maxTransY = h - clampedMax - expandedBottomInsetPx;\n\n // Threshold is the second-highest snap, or the max snap if only one exists.\n const thresholdIdx = clampIndex(maxIdx - 1, resolvedSnapPoints.length);\n const thresholdHeight = resolveHeightToPx(resolvedSnapPoints[thresholdIdx]!, h);\n const clampedThreshold = Math.min(thresholdHeight, h - bottomInsetPx);\n const thresholdTransY = h - clampedThreshold - bottomInsetPx;\n\n return { maxSnapTranslateY: maxTransY, thresholdTranslateY: thresholdTransY };\n }, [enabled, resolvedSnapPoints, containerHeight.value, bottomInsetPx, expandedBottomInsetPx]);\n\n const animatedMarginStyle = useAnimatedStyle(() => {\n if (!enabled) {\n return {};\n }\n\n // When translateY moves from thresholdTranslateY to maxSnapTranslateY\n // (sheet expanding), margins go from base values to 0.\n const mh = interpolate(\n translateY.value,\n [maxSnapTranslateY, thresholdTranslateY],\n [0, baseMarginHorizontal],\n 'clamp',\n );\n const mb = interpolate(\n translateY.value,\n [maxSnapTranslateY, thresholdTranslateY],\n [0, baseMarginBottom],\n 'clamp',\n );\n\n return {\n marginLeft: mh,\n marginRight: mh,\n marginBottom: mb,\n };\n });\n\n return { animatedMarginStyle, maxSnapTranslateY, thresholdTranslateY };\n}\n\nexport { useExpansionMargins };\n"],"mappings":";;;;;;;;;;;;;;;AAqCA,SAAS,oBAAoB,EAC3B,SACA,sBACA,kBACA,oBACA,iBACA,eACA,uBACA,cAC4B;CAG5B,MAAM,EAAE,mBAAmB,wBAAwB,cAAc;EAC/D,IAAI,CAAC,WAAW,mBAAmB,WAAW,GAC5C,OAAO;GAAE,mBAAmB;GAAG,qBAAqB;GAAG;EAGzD,MAAM,IAAI,gBAAgB;EAC1B,MAAM,SAAS,mBAAmB,SAAS;EAC3C,MAAM,YAAY,kBAAkB,mBAAmB,SAAU,EAAE;EAEnE,MAAM,YAAY,IADC,KAAK,IAAI,WAAW,IAAI,sBACX,GAAG;EAInC,MAAM,kBAAkB,kBAAkB,mBADrB,WAAW,SAAS,GAAG,mBAAmB,OACU,GAAI,EAAE;EAI/E,OAAO;GAAE,mBAAmB;GAAW,qBAFf,IADC,KAAK,IAAI,iBAAiB,IAAI,cACX,GAAG;GAE8B;IAC5E;EAAC;EAAS;EAAoB,gBAAgB;EAAO;EAAe;EAAsB,CAAC;CA6B9F,OAAO;EAAE,qBA3BmB,uBAAuB;GACjD,IAAI,CAAC,SACH,OAAO,EAAE;GAKX,MAAM,KAAK,YACT,WAAW,OACX,CAAC,mBAAmB,oBAAoB,EACxC,CAAC,GAAG,qBAAqB,EACzB,QACD;GAQD,OAAO;IACL,YAAY;IACZ,aAAa;IACb,cAVS,YACT,WAAW,OACX,CAAC,mBAAmB,oBAAoB,EACxC,CAAC,GAAG,iBAAiB,EACrB,QAMgB;IACjB;IAGyB;EAAE;EAAmB;EAAqB"}
@@ -1 +1 @@
1
- {"version":3,"file":"useKeyboardAvoidance.js","names":[],"sources":["../../../src/components/BottomSheet/useKeyboardAvoidance.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { Keyboard, Platform } from 'react-native';\nimport type { SharedValue } from 'react-native-reanimated';\nimport { withTiming } from 'react-native-reanimated';\n\n/** Duration (ms) for the keyboard offset animation. */\nconst KEYBOARD_ANIMATION_DURATION = 250;\n\n/** @internal */\ninterface UseKeyboardAvoidanceParams {\n /** Whether the sheet is currently open. */\n isOpen: boolean;\n /** The sheet's animated translateY shared value. */\n translateY: SharedValue<number>;\n /** Whether the sheet is currently animating (snap/close spring). */\n isAnimating: SharedValue<boolean>;\n}\n\n/**\n * Offsets the sheet's `translateY` when the software keyboard opens or closes.\n *\n * - **iOS**: Listens to `keyboardWillShow` / `keyboardWillHide` for smoother,\n * synchronous animation with the keyboard.\n * - **Android**: Listens to `keyboardDidShow` / `keyboardDidHide` (will-events\n * are not available on Android).\n *\n * Captures `translateY` before the offset and restores it when the keyboard hides.\n */\nfunction useKeyboardAvoidance({ isOpen, translateY, isAnimating }: UseKeyboardAvoidanceParams) {\n useEffect(() => {\n if (!isOpen) {\n return;\n }\n\n // Store the translateY before keyboard offset so we can restore it.\n let preKeyboardTranslateY: number | null = null;\n\n const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';\n const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';\n\n const showSub = Keyboard.addListener(showEvent, (event) => {\n const keyboardHeight = event.endCoordinates.height;\n if (keyboardHeight <= 0) {\n return;\n }\n\n // Capture current position before offsetting.\n if (preKeyboardTranslateY === null) {\n preKeyboardTranslateY = translateY.value;\n }\n\n // Clear isAnimating so a cancelled snap/close spring doesn't leave\n // drag permanently blocked. The keyboard animation takes over translateY.\n isAnimating.value = false;\n\n // Move sheet up by keyboard height.\n const target = preKeyboardTranslateY - keyboardHeight;\n // Clamp to 0 so the sheet doesn't go above the screen.\n const clamped = Math.max(0, target);\n translateY.value = withTiming(clamped, { duration: KEYBOARD_ANIMATION_DURATION });\n });\n\n const hideSub = Keyboard.addListener(hideEvent, () => {\n if (preKeyboardTranslateY !== null) {\n translateY.value = withTiming(preKeyboardTranslateY, {\n duration: KEYBOARD_ANIMATION_DURATION,\n });\n preKeyboardTranslateY = null;\n }\n });\n\n return () => {\n showSub.remove();\n hideSub.remove();\n };\n }, [isOpen, translateY, isAnimating]);\n}\n\nexport { useKeyboardAvoidance };\n"],"mappings":";;;;;;AAMA,MAAM,8BAA8B;;;;;;;;;;;AAsBpC,SAAS,qBAAqB,EAAE,QAAQ,YAAY,eAA2C;AAC7F,iBAAgB;AACd,MAAI,CAAC,OACH;EAIF,IAAI,wBAAuC;EAE3C,MAAM,YAAY,SAAS,OAAO,QAAQ,qBAAqB;EAC/D,MAAM,YAAY,SAAS,OAAO,QAAQ,qBAAqB;EAE/D,MAAM,UAAU,SAAS,YAAY,YAAY,UAAU;GACzD,MAAM,iBAAiB,MAAM,eAAe;AAC5C,OAAI,kBAAkB,EACpB;AAIF,OAAI,0BAA0B,KAC5B,yBAAwB,WAAW;AAKrC,eAAY,QAAQ;GAGpB,MAAM,SAAS,wBAAwB;AAGvC,cAAW,QAAQ,WADH,KAAK,IAAI,GAAG,OACS,EAAE,EAAE,UAAU,6BAA6B,CAAC;IACjF;EAEF,MAAM,UAAU,SAAS,YAAY,iBAAiB;AACpD,OAAI,0BAA0B,MAAM;AAClC,eAAW,QAAQ,WAAW,uBAAuB,EACnD,UAAU,6BACX,CAAC;AACF,4BAAwB;;IAE1B;AAEF,eAAa;AACX,WAAQ,QAAQ;AAChB,WAAQ,QAAQ;;IAEjB;EAAC;EAAQ;EAAY;EAAY,CAAC"}
1
+ {"version":3,"file":"useKeyboardAvoidance.js","names":[],"sources":["../../../src/components/BottomSheet/useKeyboardAvoidance.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { Keyboard, Platform } from 'react-native';\nimport type { SharedValue } from 'react-native-reanimated';\nimport { withTiming } from 'react-native-reanimated';\n\n/** Duration (ms) for the keyboard offset animation. */\nconst KEYBOARD_ANIMATION_DURATION = 250;\n\n/** @internal */\ninterface UseKeyboardAvoidanceParams {\n /** Whether the sheet is currently open. */\n isOpen: boolean;\n /** The sheet's animated translateY shared value. */\n translateY: SharedValue<number>;\n /** Whether the sheet is currently animating (snap/close spring). */\n isAnimating: SharedValue<boolean>;\n}\n\n/**\n * Offsets the sheet's `translateY` when the software keyboard opens or closes.\n *\n * - **iOS**: Listens to `keyboardWillShow` / `keyboardWillHide` for smoother,\n * synchronous animation with the keyboard.\n * - **Android**: Listens to `keyboardDidShow` / `keyboardDidHide` (will-events\n * are not available on Android).\n *\n * Captures `translateY` before the offset and restores it when the keyboard hides.\n */\nfunction useKeyboardAvoidance({ isOpen, translateY, isAnimating }: UseKeyboardAvoidanceParams) {\n useEffect(() => {\n if (!isOpen) {\n return;\n }\n\n // Store the translateY before keyboard offset so we can restore it.\n let preKeyboardTranslateY: number | null = null;\n\n const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';\n const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';\n\n const showSub = Keyboard.addListener(showEvent, (event) => {\n const keyboardHeight = event.endCoordinates.height;\n if (keyboardHeight <= 0) {\n return;\n }\n\n // Capture current position before offsetting.\n if (preKeyboardTranslateY === null) {\n preKeyboardTranslateY = translateY.value;\n }\n\n // Clear isAnimating so a cancelled snap/close spring doesn't leave\n // drag permanently blocked. The keyboard animation takes over translateY.\n isAnimating.value = false;\n\n // Move sheet up by keyboard height.\n const target = preKeyboardTranslateY - keyboardHeight;\n // Clamp to 0 so the sheet doesn't go above the screen.\n const clamped = Math.max(0, target);\n translateY.value = withTiming(clamped, { duration: KEYBOARD_ANIMATION_DURATION });\n });\n\n const hideSub = Keyboard.addListener(hideEvent, () => {\n if (preKeyboardTranslateY !== null) {\n translateY.value = withTiming(preKeyboardTranslateY, {\n duration: KEYBOARD_ANIMATION_DURATION,\n });\n preKeyboardTranslateY = null;\n }\n });\n\n return () => {\n showSub.remove();\n hideSub.remove();\n };\n }, [isOpen, translateY, isAnimating]);\n}\n\nexport { useKeyboardAvoidance };\n"],"mappings":";;;;;;AAMA,MAAM,8BAA8B;;;;;;;;;;;AAsBpC,SAAS,qBAAqB,EAAE,QAAQ,YAAY,eAA2C;CAC7F,gBAAgB;EACd,IAAI,CAAC,QACH;EAIF,IAAI,wBAAuC;EAE3C,MAAM,YAAY,SAAS,OAAO,QAAQ,qBAAqB;EAC/D,MAAM,YAAY,SAAS,OAAO,QAAQ,qBAAqB;EAE/D,MAAM,UAAU,SAAS,YAAY,YAAY,UAAU;GACzD,MAAM,iBAAiB,MAAM,eAAe;GAC5C,IAAI,kBAAkB,GACpB;GAIF,IAAI,0BAA0B,MAC5B,wBAAwB,WAAW;GAKrC,YAAY,QAAQ;GAGpB,MAAM,SAAS,wBAAwB;GAGvC,WAAW,QAAQ,WADH,KAAK,IAAI,GAAG,OACS,EAAE,EAAE,UAAU,6BAA6B,CAAC;IACjF;EAEF,MAAM,UAAU,SAAS,YAAY,iBAAiB;GACpD,IAAI,0BAA0B,MAAM;IAClC,WAAW,QAAQ,WAAW,uBAAuB,EACnD,UAAU,6BACX,CAAC;IACF,wBAAwB;;IAE1B;EAEF,aAAa;GACX,QAAQ,QAAQ;GAChB,QAAQ,QAAQ;;IAEjB;EAAC;EAAQ;EAAY;EAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../../src/components/BottomSheet/utils.ts"],"sourcesContent":["import type { BottomSheetHeight } from './types';\n\n/** Default snap point used when neither `snapPoints` nor `height` is specified. */\nconst DEFAULT_SNAP_POINT: BottomSheetHeight = '40%';\n\n/**\n * Resolves a {@link BottomSheetHeight} string to a pixel value.\n *\n * @param height - A percentage (`'50%'`) or pixel (`'300px'`) string.\n * @param containerHeightPx - The container height in pixels (used for `%` values).\n * @returns The resolved height in pixels, or `0` if the format is invalid.\n */\nfunction resolveHeightToPx(height: BottomSheetHeight, containerHeightPx: number): number {\n 'worklet';\n const trimmed = (height as string).trim();\n\n if (trimmed.endsWith('%')) {\n const pct = Number.parseFloat(trimmed.slice(0, -1));\n if (Number.isFinite(pct)) {\n return (pct / 100) * containerHeightPx;\n }\n }\n\n if (trimmed.endsWith('px')) {\n const px = Number.parseFloat(trimmed.slice(0, -2));\n if (Number.isFinite(px)) {\n return px;\n }\n }\n\n return 0;\n}\n\n/**\n * Clamps an index into the valid range `[0, length - 1]`.\n *\n * @param index - The index to clamp.\n * @param length - The length of the array.\n * @returns The clamped index.\n */\nfunction clampIndex(index: number, length: number): number {\n 'worklet';\n if (length <= 0) {\n return 0;\n }\n return Math.max(0, Math.min(index, length - 1));\n}\n\n/**\n * Clamps a numeric value to `[min, max]`.\n *\n * @param value - The value to clamp.\n * @param min - Lower bound.\n * @param max - Upper bound.\n * @returns The clamped value.\n */\nfunction clamp(value: number, min: number, max: number): number {\n 'worklet';\n return Math.max(min, Math.min(max, value));\n}\n\n/**\n * Resolves an array of snap points into pixel heights and translateY positions.\n *\n * Position model: `translateY = containerHeight - snapHeightPx - bottomInsetPx`\n *\n * @param snapPoints - Array of snap point height strings.\n * @param containerHeightPx - Container height in pixels.\n * @param bottomInsetPx - Bottom inset (margin + safe area) in pixels.\n * @param maxHeightPx - Maximum allowed sheet height in pixels.\n * @returns Object containing `heightsPx`, `translateYs`, and `closedTranslateY`.\n */\nfunction resolveSnapPositions(\n snapPoints: BottomSheetHeight[],\n containerHeightPx: number,\n bottomInsetPx: number,\n maxHeightPx: number,\n) {\n 'worklet';\n const heightsPx = snapPoints.map((sp) =>\n clamp(resolveHeightToPx(sp, containerHeightPx), 0, maxHeightPx),\n );\n const translateYs = heightsPx.map((h) => containerHeightPx - h - bottomInsetPx);\n const closedTranslateY = containerHeightPx + bottomInsetPx;\n\n return { heightsPx, translateYs, closedTranslateY };\n}\n\nexport { clamp, clampIndex, DEFAULT_SNAP_POINT, resolveHeightToPx, resolveSnapPositions };\n"],"mappings":";;;AAGA,MAAM,qBAAwC;;;;;;;;AAS9C,SAAS,kBAAkB,QAA2B,mBAAmC;AACvF;CACA,MAAM,UAAW,OAAkB,MAAM;AAEzC,KAAI,QAAQ,SAAS,IAAI,EAAE;EACzB,MAAM,MAAM,OAAO,WAAW,QAAQ,MAAM,GAAG,GAAG,CAAC;AACnD,MAAI,OAAO,SAAS,IAAI,CACtB,QAAQ,MAAM,MAAO;;AAIzB,KAAI,QAAQ,SAAS,KAAK,EAAE;EAC1B,MAAM,KAAK,OAAO,WAAW,QAAQ,MAAM,GAAG,GAAG,CAAC;AAClD,MAAI,OAAO,SAAS,GAAG,CACrB,QAAO;;AAIX,QAAO;;;;;;;;;AAUT,SAAS,WAAW,OAAe,QAAwB;AACzD;AACA,KAAI,UAAU,EACZ,QAAO;AAET,QAAO,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,SAAS,EAAE,CAAC;;;;;;;;;;AAWjD,SAAS,MAAM,OAAe,KAAa,KAAqB;AAC9D;AACA,QAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,MAAM,CAAC;;;;;;;;;;;;;AAc5C,SAAS,qBACP,YACA,mBACA,eACA,aACA;AACA;CACA,MAAM,YAAY,WAAW,KAAK,OAChC,MAAM,kBAAkB,IAAI,kBAAkB,EAAE,GAAG,YAAY,CAChE;AAID,QAAO;EAAE;EAAW,aAHA,UAAU,KAAK,MAAM,oBAAoB,IAAI,cAGlC;EAAE,kBAFR,oBAAoB;EAEM"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../src/components/BottomSheet/utils.ts"],"sourcesContent":["import type { BottomSheetHeight } from './types';\n\n/** Default snap point used when neither `snapPoints` nor `height` is specified. */\nconst DEFAULT_SNAP_POINT: BottomSheetHeight = '40%';\n\n/**\n * Resolves a {@link BottomSheetHeight} string to a pixel value.\n *\n * @param height - A percentage (`'50%'`) or pixel (`'300px'`) string.\n * @param containerHeightPx - The container height in pixels (used for `%` values).\n * @returns The resolved height in pixels, or `0` if the format is invalid.\n */\nfunction resolveHeightToPx(height: BottomSheetHeight, containerHeightPx: number): number {\n 'worklet';\n const trimmed = (height as string).trim();\n\n if (trimmed.endsWith('%')) {\n const pct = Number.parseFloat(trimmed.slice(0, -1));\n if (Number.isFinite(pct)) {\n return (pct / 100) * containerHeightPx;\n }\n }\n\n if (trimmed.endsWith('px')) {\n const px = Number.parseFloat(trimmed.slice(0, -2));\n if (Number.isFinite(px)) {\n return px;\n }\n }\n\n return 0;\n}\n\n/**\n * Clamps an index into the valid range `[0, length - 1]`.\n *\n * @param index - The index to clamp.\n * @param length - The length of the array.\n * @returns The clamped index.\n */\nfunction clampIndex(index: number, length: number): number {\n 'worklet';\n if (length <= 0) {\n return 0;\n }\n return Math.max(0, Math.min(index, length - 1));\n}\n\n/**\n * Clamps a numeric value to `[min, max]`.\n *\n * @param value - The value to clamp.\n * @param min - Lower bound.\n * @param max - Upper bound.\n * @returns The clamped value.\n */\nfunction clamp(value: number, min: number, max: number): number {\n 'worklet';\n return Math.max(min, Math.min(max, value));\n}\n\n/**\n * Resolves an array of snap points into pixel heights and translateY positions.\n *\n * Position model: `translateY = containerHeight - snapHeightPx - bottomInsetPx`\n *\n * @param snapPoints - Array of snap point height strings.\n * @param containerHeightPx - Container height in pixels.\n * @param bottomInsetPx - Bottom inset (margin + safe area) in pixels.\n * @param maxHeightPx - Maximum allowed sheet height in pixels.\n * @returns Object containing `heightsPx`, `translateYs`, and `closedTranslateY`.\n */\nfunction resolveSnapPositions(\n snapPoints: BottomSheetHeight[],\n containerHeightPx: number,\n bottomInsetPx: number,\n maxHeightPx: number,\n) {\n 'worklet';\n const heightsPx = snapPoints.map((sp) =>\n clamp(resolveHeightToPx(sp, containerHeightPx), 0, maxHeightPx),\n );\n const translateYs = heightsPx.map((h) => containerHeightPx - h - bottomInsetPx);\n const closedTranslateY = containerHeightPx + bottomInsetPx;\n\n return { heightsPx, translateYs, closedTranslateY };\n}\n\nexport { clamp, clampIndex, DEFAULT_SNAP_POINT, resolveHeightToPx, resolveSnapPositions };\n"],"mappings":";;;AAGA,MAAM,qBAAwC;;;;;;;;AAS9C,SAAS,kBAAkB,QAA2B,mBAAmC;AACvF;CACA,MAAM,UAAW,OAAkB,MAAM;CAEzC,IAAI,QAAQ,SAAS,IAAI,EAAE;EACzB,MAAM,MAAM,OAAO,WAAW,QAAQ,MAAM,GAAG,GAAG,CAAC;EACnD,IAAI,OAAO,SAAS,IAAI,EACtB,OAAQ,MAAM,MAAO;;CAIzB,IAAI,QAAQ,SAAS,KAAK,EAAE;EAC1B,MAAM,KAAK,OAAO,WAAW,QAAQ,MAAM,GAAG,GAAG,CAAC;EAClD,IAAI,OAAO,SAAS,GAAG,EACrB,OAAO;;CAIX,OAAO;;;;;;;;;AAUT,SAAS,WAAW,OAAe,QAAwB;AACzD;CACA,IAAI,UAAU,GACZ,OAAO;CAET,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,OAAO,SAAS,EAAE,CAAC;;;;;;;;;;AAWjD,SAAS,MAAM,OAAe,KAAa,KAAqB;AAC9D;CACA,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,MAAM,CAAC;;;;;;;;;;;;;AAc5C,SAAS,qBACP,YACA,mBACA,eACA,aACA;AACA;CACA,MAAM,YAAY,WAAW,KAAK,OAChC,MAAM,kBAAkB,IAAI,kBAAkB,EAAE,GAAG,YAAY,CAChE;CAID,OAAO;EAAE;EAAW,aAHA,UAAU,KAAK,MAAM,oBAAoB,IAAI,cAGlC;EAAE,kBAFR,oBAAoB;EAEM"}
@@ -50,7 +50,7 @@ function useBlurView() {
50
50
  *
51
51
  * @example
52
52
  * ```tsx
53
- * import { Box } from '@yahoo/uds-mobile';
53
+ * import { Box } from '@yahoo/uds-mobile/Box';
54
54
  *
55
55
  * <Box backgroundColor="primary" spacing="6">
56
56
  * Any kind of content can go here!
@@ -83,7 +83,7 @@ interface BoxProps extends ViewProps, SizeProps {
83
83
  *
84
84
  * @example
85
85
  * ```tsx
86
- * import { Box } from '@yahoo/uds-mobile';
86
+ * import { Box } from '@yahoo/uds-mobile/Box';
87
87
  *
88
88
  * <Box backgroundColor="primary" spacing="6">
89
89
  * Any kind of content can go here!
@@ -83,7 +83,7 @@ interface BoxProps extends ViewProps, SizeProps {
83
83
  *
84
84
  * @example
85
85
  * ```tsx
86
- * import { Box } from '@yahoo/uds-mobile';
86
+ * import { Box } from '@yahoo/uds-mobile/Box';
87
87
  *
88
88
  * <Box backgroundColor="primary" spacing="6">
89
89
  * Any kind of content can go here!
@@ -48,7 +48,7 @@ function useBlurView() {
48
48
  *
49
49
  * @example
50
50
  * ```tsx
51
- * import { Box } from '@yahoo/uds-mobile';
51
+ * import { Box } from '@yahoo/uds-mobile/Box';
52
52
  *
53
53
  * <Box backgroundColor="primary" spacing="6">
54
54
  * Any kind of content can go here!
@@ -1 +1 @@
1
- {"version":3,"file":"Box.js","names":["StyleSheet"],"sources":["../../src/components/Box.tsx"],"sourcesContent":["import type { ElevationLevel } from '@yahoo/uds-types';\nimport type { ComponentType, Ref, RefObject } from 'react';\nimport { memo, useEffect, useMemo, useRef, useState } from 'react';\nimport type { ViewProps } from 'react-native';\nimport { Platform, View } from 'react-native';\n// eslint-disable-next-line uds/no-use-unistyles -- blur intensity is not a style property, requires direct theme access\nimport { StyleSheet, useUnistyles } from 'react-native-unistyles';\n\nimport type { StyleProps } from '../../generated/styles';\nimport { styles } from '../../generated/styles';\nimport type { SizeProps } from '../types';\n\n// Optional expo-blur dependency - loaded via dynamic import for Metro compatibility\n// Metro can statically analyze import() and will include expo-blur in the bundle if installed\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet BlurView: ComponentType<any> | null = null;\nlet blurLoadState: 'pending' | 'loaded' | 'failed' = 'pending';\nconst blurLoadListeners: (() => void)[] = [];\n\n// Start loading expo-blur immediately (Metro will bundle it if installed)\nimport('expo-blur')\n .then((mod) => {\n BlurView = mod.BlurView;\n blurLoadState = 'loaded';\n blurLoadListeners.forEach((cb) => cb());\n })\n .catch(() => {\n blurLoadState = 'failed';\n blurLoadListeners.forEach((cb) => cb());\n });\n\n/** Hook to get BlurView component, re-renders when loaded */\nfunction useBlurView() {\n const [, forceUpdate] = useState(0);\n\n useEffect(() => {\n if (blurLoadState === 'pending') {\n const listener = () => forceUpdate((n) => n + 1);\n blurLoadListeners.push(listener);\n return () => {\n const idx = blurLoadListeners.indexOf(listener);\n if (idx >= 0) {\n blurLoadListeners.splice(idx, 1);\n }\n };\n }\n }, []);\n\n return { BlurView, isLoaded: blurLoadState !== 'pending', isFailed: blurLoadState === 'failed' };\n}\n\ninterface BoxProps extends ViewProps, SizeProps {\n ref?: Ref<View>;\n elevation?: ElevationLevel;\n backgroundColor?: StyleProps['backgroundColor'];\n borderRadius?: StyleProps['borderRadius'];\n borderTopStartRadius?: StyleProps['borderTopStartRadius'];\n borderTopEndRadius?: StyleProps['borderTopEndRadius'];\n borderBottomStartRadius?: StyleProps['borderBottomStartRadius'];\n borderBottomEndRadius?: StyleProps['borderBottomEndRadius'];\n borderColor?: StyleProps['borderColor'];\n borderStartColor?: StyleProps['borderStartColor'];\n borderEndColor?: StyleProps['borderEndColor'];\n borderTopColor?: StyleProps['borderTopColor'];\n borderBottomColor?: StyleProps['borderBottomColor'];\n borderWidth?: StyleProps['borderWidth'];\n borderVerticalWidth?: StyleProps['borderVerticalWidth'];\n borderHorizontalWidth?: StyleProps['borderHorizontalWidth'];\n borderStartWidth?: StyleProps['borderStartWidth'];\n borderEndWidth?: StyleProps['borderEndWidth'];\n borderTopWidth?: StyleProps['borderTopWidth'];\n borderBottomWidth?: StyleProps['borderBottomWidth'];\n alignContent?: StyleProps['alignContent'];\n alignItems?: StyleProps['alignItems'];\n alignSelf?: StyleProps['alignSelf'];\n flex?: StyleProps['flex'];\n flexDirection?: StyleProps['flexDirection'];\n flexGrow?: StyleProps['flexGrow'];\n flexShrink?: StyleProps['flexShrink'];\n flexWrap?: StyleProps['flexWrap'];\n justifyContent?: StyleProps['justifyContent'];\n // flexBasis?: StyleProps['flexBasis'];\n display?: StyleProps['display'];\n overflow?: StyleProps['overflow'];\n // overflowX?: StyleProps['overflowX'];\n // overflowY?: StyleProps['overflowY'];\n // position?: StyleProps['position'];\n spacing?: StyleProps['spacing'];\n spacingHorizontal?: StyleProps['spacingHorizontal'];\n spacingVertical?: StyleProps['spacingVertical'];\n spacingBottom?: StyleProps['spacingBottom'];\n spacingEnd?: StyleProps['spacingEnd'];\n spacingStart?: StyleProps['spacingStart'];\n spacingTop?: StyleProps['spacingTop'];\n offset?: StyleProps['offset'];\n offsetVertical?: StyleProps['offsetVertical'];\n offsetHorizontal?: StyleProps['offsetHorizontal'];\n offsetBottom?: StyleProps['offsetBottom'];\n offsetEnd?: StyleProps['offsetEnd'];\n offsetStart?: StyleProps['offsetStart'];\n offsetTop?: StyleProps['offsetTop'];\n columnGap?: StyleProps['columnGap'];\n rowGap?: StyleProps['rowGap'];\n dropShadow?: StyleProps['dropShadow'];\n insetShadow?: StyleProps['insetShadow'];\n dangerouslySetBackgroundColor?: string;\n dangerouslySetBorderColor?: string;\n /**\n * Manual blur intensity (0-100). When set, renders as a BlurView.\n * Requires `blurTarget` pointing to a BlurTarget ref wrapping the content to blur.\n */\n blur?: number;\n /**\n * Reference to a BlurTarget component wrapping the content to blur.\n * Required when using `blur` prop or elevation with blur configured.\n */\n blurTarget?: RefObject<View | null>;\n}\n\n/**\n * **📦 A layout component that can be used to compose other components**\n *\n * @description\n * The most simple component we ship - a View. But with all the power of the UDS design system.\n * By default, `Box` is a flexbox container. When working with vertical or horizontal layouts,\n * consider using VStack or HStack respectively.\n *\n * @category Layout\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { Box } from '@yahoo/uds-mobile';\n *\n * <Box backgroundColor=\"primary\" spacing=\"6\">\n * Any kind of content can go here!\n * </Box>\n * ```\n *\n * @usage\n * - Use as a container to apply padding, shapes, or other styling\n * - Use for creating card components\n * - Use HStack/VStack for directional layouts\n *\n * @see {@link HStack} for horizontal layouts\n * @see {@link VStack} for vertical layouts\n */\nconst Box = memo(function Box({\n // elevation\n elevation,\n // background\n backgroundColor,\n dangerouslySetBackgroundColor,\n // border\n borderRadius,\n borderTopStartRadius,\n borderTopEndRadius,\n borderBottomStartRadius,\n borderBottomEndRadius,\n borderColor,\n dangerouslySetBorderColor,\n borderStartColor,\n borderEndColor,\n borderTopColor,\n borderBottomColor,\n borderWidth,\n borderVerticalWidth,\n borderHorizontalWidth,\n borderStartWidth,\n borderEndWidth,\n borderTopWidth,\n borderBottomWidth,\n // flex\n alignContent,\n alignItems,\n alignSelf,\n flex,\n flexDirection,\n flexGrow,\n flexShrink,\n flexWrap,\n justifyContent,\n // flexBasis,\n // layout\n display = 'flex',\n overflow,\n // overflowX,\n // overflowY,\n // position,\n // spacing\n spacing,\n spacingHorizontal,\n spacingVertical,\n spacingBottom,\n spacingEnd,\n spacingStart,\n spacingTop,\n offset,\n offsetVertical,\n offsetHorizontal,\n offsetBottom,\n offsetEnd,\n offsetStart,\n offsetTop,\n columnGap,\n rowGap,\n // size\n width,\n height,\n minWidth,\n maxWidth,\n minHeight,\n maxHeight,\n // shadow\n dropShadow,\n insetShadow,\n // blur\n blur,\n blurTarget,\n // // nested border radius\n // nestedBorderRadius,\n // nestedBorderRadiusSize = nestedBorderRadius ? borderRadius : undefined,\n // nestedBorderRadiusSpacing = nestedBorderRadius ? spacing : undefined,\n // nestedBorderRadiusWidth = nestedBorderRadius ? borderWidth : undefined,\n // style - extracted to merge with variants\n style,\n ref,\n // rest\n ...props\n}: BoxProps) {\n const { theme, rt } = useUnistyles();\n const {\n BlurView: BlurViewComponent,\n isLoaded: blurIsLoaded,\n isFailed: blurFailed,\n } = useBlurView();\n const elevationAlias = elevation !== undefined ? (`elevation-${elevation}` as const) : undefined;\n\n // Track if blur prop was explicitly set (even to 0) vs derived from elevation\n const blurExplicitlySet = blur !== undefined;\n\n // Get blur intensity from manual blur prop or elevation config\n const blurIntensity = useMemo(() => {\n // Manual blur prop takes precedence (including 0)\n if (blur !== undefined) {\n return blur;\n }\n // Fall back to elevation-based blur\n if (elevation === undefined || !theme.blur) {\n return 0;\n }\n const blurKey = `elevation-${elevation}` as keyof typeof theme.blur;\n return theme.blur[blurKey] ?? 0;\n }, [blur, elevation, theme]);\n\n // Warn in development about blur issues (once per mount)\n const hasWarnedRef = useRef<'none' | 'no-expo-blur' | 'no-blur-target'>('none');\n useEffect(() => {\n if (!__DEV__ || blurIntensity === 0 || !blurIsLoaded) {\n return;\n }\n\n // Warn if expo-blur is not installed\n if (blurFailed && hasWarnedRef.current !== 'no-expo-blur') {\n hasWarnedRef.current = 'no-expo-blur';\n console.warn('[UDS Mobile] Box: Blur effect requested but expo-blur is not installed. ');\n return;\n }\n\n // Warn if blur is used without blurTarget\n if (!blurTarget && hasWarnedRef.current !== 'no-blur-target') {\n hasWarnedRef.current = 'no-blur-target';\n console.warn(\n '[UDS Mobile] Box: Blur effect requires a blurTarget ref. ' +\n 'Wrap the content to blur in <BlurTarget ref={ref}> and pass the ref to blurTarget prop. ' +\n 'See BACKGROUND_BLUR.md for details.',\n );\n }\n }, [blurIntensity, blurTarget, blurIsLoaded, blurFailed]);\n\n const variants = {\n // background\n backgroundColor: backgroundColor ?? elevationAlias,\n // border\n borderRadius,\n borderTopStartRadius,\n borderTopEndRadius,\n borderBottomStartRadius,\n borderBottomEndRadius,\n // nestedBorderRadius,\n // nestedBorderRadiusSize,\n // nestedBorderRadiusSpacing,\n // nestedBorderRadiusWidth,\n borderColor: borderColor ?? elevationAlias,\n borderStartColor,\n borderEndColor,\n borderTopColor,\n borderBottomColor,\n borderWidth: borderWidth ?? elevationAlias,\n borderVerticalWidth,\n borderHorizontalWidth,\n borderStartWidth,\n borderEndWidth,\n borderTopWidth,\n borderBottomWidth,\n // flex\n alignContent,\n alignItems,\n alignSelf,\n flex,\n flexDirection,\n flexGrow,\n flexShrink,\n flexWrap,\n justifyContent,\n // flexBasis,\n // layout\n display,\n overflow,\n // overflowX,\n // overflowY,\n // position,\n // spacing\n spacing,\n spacingHorizontal,\n spacingVertical,\n spacingBottom,\n spacingEnd,\n spacingStart,\n spacingTop,\n offset,\n offsetVertical,\n offsetHorizontal,\n offsetBottom,\n offsetEnd,\n offsetStart,\n offsetTop,\n columnGap,\n rowGap,\n // rest\n };\n\n styles.useVariants(variants);\n\n const effectiveDropShadow = dropShadow ?? elevationAlias;\n const shadowStyle =\n effectiveDropShadow || insetShadow\n ? shadowSheet.shadow(effectiveDropShadow, insetShadow)\n : undefined;\n\n // styles.foundation must be in deps - it returns a new reference when variants change\n const boxStyles = useMemo(\n () => [\n dangerouslySetBackgroundColor\n ? { backgroundColor: dangerouslySetBackgroundColor }\n : undefined,\n dangerouslySetBorderColor ? { borderColor: dangerouslySetBorderColor } : undefined,\n width ? { width } : undefined,\n height ? { height } : undefined,\n minWidth ? { minWidth } : undefined,\n maxWidth ? { maxWidth } : undefined,\n minHeight ? { minHeight } : undefined,\n maxHeight ? { maxHeight } : undefined,\n shadowStyle,\n styles.foundation,\n style,\n ],\n [\n dangerouslySetBackgroundColor,\n dangerouslySetBorderColor,\n width,\n height,\n minWidth,\n maxWidth,\n minHeight,\n maxHeight,\n shadowStyle,\n styles.foundation,\n style,\n ],\n );\n\n // Merge variant styles with user-provided style prop\n // User styles come last so they can override variant styles if needed\n\n // If blur is configured (manual or via elevation), render BlurView instead of View\n // Note: On Android, blur requires BlurTarget setup by the developer\n // See BACKGROUND_BLUR.md for Android-specific instructions\n // When blur prop is explicitly set (even to 0), use BlurView so blur=0 is transparent, not solid\n const shouldUseBlurView = BlurViewComponent && (blurIntensity > 0 || blurExplicitlySet);\n if (shouldUseBlurView) {\n const isAndroid = Platform.OS === 'android';\n // Match blur tint to the app's theme (from unistyles runtime)\n const blurTint = rt.themeName === 'dark' ? 'dark' : 'light';\n\n // On iOS, BlurView provides its own frosted background via the tint prop.\n // We must NOT apply an opaque backgroundColor or it will cover the blur effect.\n // On Android, the blur is applied to the blurTarget, so backgroundColor is fine.\n const blurStyles = isAndroid\n ? boxStyles\n : [\n // Exclude backgroundColor for iOS - the blur tint provides the visual background\n dangerouslySetBorderColor ? { borderColor: dangerouslySetBorderColor } : undefined,\n width ? { width } : undefined,\n height ? { height } : undefined,\n minWidth ? { minWidth } : undefined,\n maxWidth ? { maxWidth } : undefined,\n minHeight ? { minHeight } : undefined,\n maxHeight ? { maxHeight } : undefined,\n shadowStyle,\n // Filter out backgroundColor from foundation styles\n {\n ...styles.foundation,\n backgroundColor: undefined,\n },\n // Also filter backgroundColor from user style if provided (handles both object and array styles)\n style\n ? Array.isArray(style)\n ? style.map((s) =>\n s && typeof s === 'object' ? { ...s, backgroundColor: undefined } : s,\n )\n : typeof style === 'object'\n ? { ...style, backgroundColor: undefined }\n : style\n : undefined,\n ];\n\n // Scale Android intensity to keep tint overlay subtle (Android overlays become\n // too opaque at high intensities). 0.4 scale maps intensity=100 to effective ~40.\n const effectiveIntensity = isAndroid ? Math.min(blurIntensity * 0.4, 40) : blurIntensity;\n\n return (\n <BlurViewComponent\n ref={ref}\n intensity={effectiveIntensity}\n tint={blurTint}\n blurTarget={isAndroid ? blurTarget : undefined}\n blurMethod={isAndroid ? 'dimezisBlurView' : undefined}\n style={blurStyles}\n {...props}\n >\n {props.children}\n </BlurViewComponent>\n );\n }\n\n // Fallback to regular View (blur not available or not requested)\n\n return <View ref={ref} style={boxStyles} {...props} />;\n});\n\nBox.displayName = 'Box';\n\n/**\n * Dynamic shadow stylesheet that merges drop and inset shadows into a single\n * boxShadow CSS string. Theme-reactive so shadows update on color mode change.\n */\nconst shadowSheet = StyleSheet.create((theme) => ({\n shadow: (drop?: string, inset?: string) => {\n const parts = [\n drop ? theme.boxShadow.drop[drop as keyof typeof theme.boxShadow.drop] : '',\n inset ? theme.boxShadow.inset[inset as keyof typeof theme.boxShadow.inset] : '',\n ].filter(Boolean);\n return parts.length > 0 ? { boxShadow: parts.join(', ') } : {};\n },\n}));\n\nexport { Box, type BoxProps };\n"],"mappings":"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"}
1
+ {"version":3,"file":"Box.js","names":["StyleSheet"],"sources":["../../src/components/Box.tsx"],"sourcesContent":["import type { ElevationLevel } from '@yahoo/uds-types';\nimport type { ComponentType, Ref, RefObject } from 'react';\nimport { memo, useEffect, useMemo, useRef, useState } from 'react';\nimport type { ViewProps } from 'react-native';\nimport { Platform, View } from 'react-native';\n// eslint-disable-next-line uds/no-use-unistyles -- blur intensity is not a style property, requires direct theme access\nimport { StyleSheet, useUnistyles } from 'react-native-unistyles';\n\nimport type { StyleProps } from '../../generated/styles';\nimport { styles } from '../../generated/styles';\nimport type { SizeProps } from '../types';\n\n// Optional expo-blur dependency - loaded via dynamic import for Metro compatibility\n// Metro can statically analyze import() and will include expo-blur in the bundle if installed\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet BlurView: ComponentType<any> | null = null;\nlet blurLoadState: 'pending' | 'loaded' | 'failed' = 'pending';\nconst blurLoadListeners: (() => void)[] = [];\n\n// Start loading expo-blur immediately (Metro will bundle it if installed)\nimport('expo-blur')\n .then((mod) => {\n BlurView = mod.BlurView;\n blurLoadState = 'loaded';\n blurLoadListeners.forEach((cb) => cb());\n })\n .catch(() => {\n blurLoadState = 'failed';\n blurLoadListeners.forEach((cb) => cb());\n });\n\n/** Hook to get BlurView component, re-renders when loaded */\nfunction useBlurView() {\n const [, forceUpdate] = useState(0);\n\n useEffect(() => {\n if (blurLoadState === 'pending') {\n const listener = () => forceUpdate((n) => n + 1);\n blurLoadListeners.push(listener);\n return () => {\n const idx = blurLoadListeners.indexOf(listener);\n if (idx >= 0) {\n blurLoadListeners.splice(idx, 1);\n }\n };\n }\n }, []);\n\n return { BlurView, isLoaded: blurLoadState !== 'pending', isFailed: blurLoadState === 'failed' };\n}\n\ninterface BoxProps extends ViewProps, SizeProps {\n ref?: Ref<View>;\n elevation?: ElevationLevel;\n backgroundColor?: StyleProps['backgroundColor'];\n borderRadius?: StyleProps['borderRadius'];\n borderTopStartRadius?: StyleProps['borderTopStartRadius'];\n borderTopEndRadius?: StyleProps['borderTopEndRadius'];\n borderBottomStartRadius?: StyleProps['borderBottomStartRadius'];\n borderBottomEndRadius?: StyleProps['borderBottomEndRadius'];\n borderColor?: StyleProps['borderColor'];\n borderStartColor?: StyleProps['borderStartColor'];\n borderEndColor?: StyleProps['borderEndColor'];\n borderTopColor?: StyleProps['borderTopColor'];\n borderBottomColor?: StyleProps['borderBottomColor'];\n borderWidth?: StyleProps['borderWidth'];\n borderVerticalWidth?: StyleProps['borderVerticalWidth'];\n borderHorizontalWidth?: StyleProps['borderHorizontalWidth'];\n borderStartWidth?: StyleProps['borderStartWidth'];\n borderEndWidth?: StyleProps['borderEndWidth'];\n borderTopWidth?: StyleProps['borderTopWidth'];\n borderBottomWidth?: StyleProps['borderBottomWidth'];\n alignContent?: StyleProps['alignContent'];\n alignItems?: StyleProps['alignItems'];\n alignSelf?: StyleProps['alignSelf'];\n flex?: StyleProps['flex'];\n flexDirection?: StyleProps['flexDirection'];\n flexGrow?: StyleProps['flexGrow'];\n flexShrink?: StyleProps['flexShrink'];\n flexWrap?: StyleProps['flexWrap'];\n justifyContent?: StyleProps['justifyContent'];\n // flexBasis?: StyleProps['flexBasis'];\n display?: StyleProps['display'];\n overflow?: StyleProps['overflow'];\n // overflowX?: StyleProps['overflowX'];\n // overflowY?: StyleProps['overflowY'];\n // position?: StyleProps['position'];\n spacing?: StyleProps['spacing'];\n spacingHorizontal?: StyleProps['spacingHorizontal'];\n spacingVertical?: StyleProps['spacingVertical'];\n spacingBottom?: StyleProps['spacingBottom'];\n spacingEnd?: StyleProps['spacingEnd'];\n spacingStart?: StyleProps['spacingStart'];\n spacingTop?: StyleProps['spacingTop'];\n offset?: StyleProps['offset'];\n offsetVertical?: StyleProps['offsetVertical'];\n offsetHorizontal?: StyleProps['offsetHorizontal'];\n offsetBottom?: StyleProps['offsetBottom'];\n offsetEnd?: StyleProps['offsetEnd'];\n offsetStart?: StyleProps['offsetStart'];\n offsetTop?: StyleProps['offsetTop'];\n columnGap?: StyleProps['columnGap'];\n rowGap?: StyleProps['rowGap'];\n dropShadow?: StyleProps['dropShadow'];\n insetShadow?: StyleProps['insetShadow'];\n dangerouslySetBackgroundColor?: string;\n dangerouslySetBorderColor?: string;\n /**\n * Manual blur intensity (0-100). When set, renders as a BlurView.\n * Requires `blurTarget` pointing to a BlurTarget ref wrapping the content to blur.\n */\n blur?: number;\n /**\n * Reference to a BlurTarget component wrapping the content to blur.\n * Required when using `blur` prop or elevation with blur configured.\n */\n blurTarget?: RefObject<View | null>;\n}\n\n/**\n * **📦 A layout component that can be used to compose other components**\n *\n * @description\n * The most simple component we ship - a View. But with all the power of the UDS design system.\n * By default, `Box` is a flexbox container. When working with vertical or horizontal layouts,\n * consider using VStack or HStack respectively.\n *\n * @category Layout\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { Box } from '@yahoo/uds-mobile/Box';\n *\n * <Box backgroundColor=\"primary\" spacing=\"6\">\n * Any kind of content can go here!\n * </Box>\n * ```\n *\n * @usage\n * - Use as a container to apply padding, shapes, or other styling\n * - Use for creating card components\n * - Use HStack/VStack for directional layouts\n *\n * @see {@link HStack} for horizontal layouts\n * @see {@link VStack} for vertical layouts\n */\nconst Box = memo(function Box({\n // elevation\n elevation,\n // background\n backgroundColor,\n dangerouslySetBackgroundColor,\n // border\n borderRadius,\n borderTopStartRadius,\n borderTopEndRadius,\n borderBottomStartRadius,\n borderBottomEndRadius,\n borderColor,\n dangerouslySetBorderColor,\n borderStartColor,\n borderEndColor,\n borderTopColor,\n borderBottomColor,\n borderWidth,\n borderVerticalWidth,\n borderHorizontalWidth,\n borderStartWidth,\n borderEndWidth,\n borderTopWidth,\n borderBottomWidth,\n // flex\n alignContent,\n alignItems,\n alignSelf,\n flex,\n flexDirection,\n flexGrow,\n flexShrink,\n flexWrap,\n justifyContent,\n // flexBasis,\n // layout\n display = 'flex',\n overflow,\n // overflowX,\n // overflowY,\n // position,\n // spacing\n spacing,\n spacingHorizontal,\n spacingVertical,\n spacingBottom,\n spacingEnd,\n spacingStart,\n spacingTop,\n offset,\n offsetVertical,\n offsetHorizontal,\n offsetBottom,\n offsetEnd,\n offsetStart,\n offsetTop,\n columnGap,\n rowGap,\n // size\n width,\n height,\n minWidth,\n maxWidth,\n minHeight,\n maxHeight,\n // shadow\n dropShadow,\n insetShadow,\n // blur\n blur,\n blurTarget,\n // // nested border radius\n // nestedBorderRadius,\n // nestedBorderRadiusSize = nestedBorderRadius ? borderRadius : undefined,\n // nestedBorderRadiusSpacing = nestedBorderRadius ? spacing : undefined,\n // nestedBorderRadiusWidth = nestedBorderRadius ? borderWidth : undefined,\n // style - extracted to merge with variants\n style,\n ref,\n // rest\n ...props\n}: BoxProps) {\n const { theme, rt } = useUnistyles();\n const {\n BlurView: BlurViewComponent,\n isLoaded: blurIsLoaded,\n isFailed: blurFailed,\n } = useBlurView();\n const elevationAlias = elevation !== undefined ? (`elevation-${elevation}` as const) : undefined;\n\n // Track if blur prop was explicitly set (even to 0) vs derived from elevation\n const blurExplicitlySet = blur !== undefined;\n\n // Get blur intensity from manual blur prop or elevation config\n const blurIntensity = useMemo(() => {\n // Manual blur prop takes precedence (including 0)\n if (blur !== undefined) {\n return blur;\n }\n // Fall back to elevation-based blur\n if (elevation === undefined || !theme.blur) {\n return 0;\n }\n const blurKey = `elevation-${elevation}` as keyof typeof theme.blur;\n return theme.blur[blurKey] ?? 0;\n }, [blur, elevation, theme]);\n\n // Warn in development about blur issues (once per mount)\n const hasWarnedRef = useRef<'none' | 'no-expo-blur' | 'no-blur-target'>('none');\n useEffect(() => {\n if (!__DEV__ || blurIntensity === 0 || !blurIsLoaded) {\n return;\n }\n\n // Warn if expo-blur is not installed\n if (blurFailed && hasWarnedRef.current !== 'no-expo-blur') {\n hasWarnedRef.current = 'no-expo-blur';\n console.warn('[UDS Mobile] Box: Blur effect requested but expo-blur is not installed. ');\n return;\n }\n\n // Warn if blur is used without blurTarget\n if (!blurTarget && hasWarnedRef.current !== 'no-blur-target') {\n hasWarnedRef.current = 'no-blur-target';\n console.warn(\n '[UDS Mobile] Box: Blur effect requires a blurTarget ref. ' +\n 'Wrap the content to blur in <BlurTarget ref={ref}> and pass the ref to blurTarget prop. ' +\n 'See BACKGROUND_BLUR.md for details.',\n );\n }\n }, [blurIntensity, blurTarget, blurIsLoaded, blurFailed]);\n\n const variants = {\n // background\n backgroundColor: backgroundColor ?? elevationAlias,\n // border\n borderRadius,\n borderTopStartRadius,\n borderTopEndRadius,\n borderBottomStartRadius,\n borderBottomEndRadius,\n // nestedBorderRadius,\n // nestedBorderRadiusSize,\n // nestedBorderRadiusSpacing,\n // nestedBorderRadiusWidth,\n borderColor: borderColor ?? elevationAlias,\n borderStartColor,\n borderEndColor,\n borderTopColor,\n borderBottomColor,\n borderWidth: borderWidth ?? elevationAlias,\n borderVerticalWidth,\n borderHorizontalWidth,\n borderStartWidth,\n borderEndWidth,\n borderTopWidth,\n borderBottomWidth,\n // flex\n alignContent,\n alignItems,\n alignSelf,\n flex,\n flexDirection,\n flexGrow,\n flexShrink,\n flexWrap,\n justifyContent,\n // flexBasis,\n // layout\n display,\n overflow,\n // overflowX,\n // overflowY,\n // position,\n // spacing\n spacing,\n spacingHorizontal,\n spacingVertical,\n spacingBottom,\n spacingEnd,\n spacingStart,\n spacingTop,\n offset,\n offsetVertical,\n offsetHorizontal,\n offsetBottom,\n offsetEnd,\n offsetStart,\n offsetTop,\n columnGap,\n rowGap,\n // rest\n };\n\n styles.useVariants(variants);\n\n const effectiveDropShadow = dropShadow ?? elevationAlias;\n const shadowStyle =\n effectiveDropShadow || insetShadow\n ? shadowSheet.shadow(effectiveDropShadow, insetShadow)\n : undefined;\n\n // styles.foundation must be in deps - it returns a new reference when variants change\n const boxStyles = useMemo(\n () => [\n dangerouslySetBackgroundColor\n ? { backgroundColor: dangerouslySetBackgroundColor }\n : undefined,\n dangerouslySetBorderColor ? { borderColor: dangerouslySetBorderColor } : undefined,\n width ? { width } : undefined,\n height ? { height } : undefined,\n minWidth ? { minWidth } : undefined,\n maxWidth ? { maxWidth } : undefined,\n minHeight ? { minHeight } : undefined,\n maxHeight ? { maxHeight } : undefined,\n shadowStyle,\n styles.foundation,\n style,\n ],\n [\n dangerouslySetBackgroundColor,\n dangerouslySetBorderColor,\n width,\n height,\n minWidth,\n maxWidth,\n minHeight,\n maxHeight,\n shadowStyle,\n styles.foundation,\n style,\n ],\n );\n\n // Merge variant styles with user-provided style prop\n // User styles come last so they can override variant styles if needed\n\n // If blur is configured (manual or via elevation), render BlurView instead of View\n // Note: On Android, blur requires BlurTarget setup by the developer\n // See BACKGROUND_BLUR.md for Android-specific instructions\n // When blur prop is explicitly set (even to 0), use BlurView so blur=0 is transparent, not solid\n const shouldUseBlurView = BlurViewComponent && (blurIntensity > 0 || blurExplicitlySet);\n if (shouldUseBlurView) {\n const isAndroid = Platform.OS === 'android';\n // Match blur tint to the app's theme (from unistyles runtime)\n const blurTint = rt.themeName === 'dark' ? 'dark' : 'light';\n\n // On iOS, BlurView provides its own frosted background via the tint prop.\n // We must NOT apply an opaque backgroundColor or it will cover the blur effect.\n // On Android, the blur is applied to the blurTarget, so backgroundColor is fine.\n const blurStyles = isAndroid\n ? boxStyles\n : [\n // Exclude backgroundColor for iOS - the blur tint provides the visual background\n dangerouslySetBorderColor ? { borderColor: dangerouslySetBorderColor } : undefined,\n width ? { width } : undefined,\n height ? { height } : undefined,\n minWidth ? { minWidth } : undefined,\n maxWidth ? { maxWidth } : undefined,\n minHeight ? { minHeight } : undefined,\n maxHeight ? { maxHeight } : undefined,\n shadowStyle,\n // Filter out backgroundColor from foundation styles\n {\n ...styles.foundation,\n backgroundColor: undefined,\n },\n // Also filter backgroundColor from user style if provided (handles both object and array styles)\n style\n ? Array.isArray(style)\n ? style.map((s) =>\n s && typeof s === 'object' ? { ...s, backgroundColor: undefined } : s,\n )\n : typeof style === 'object'\n ? { ...style, backgroundColor: undefined }\n : style\n : undefined,\n ];\n\n // Scale Android intensity to keep tint overlay subtle (Android overlays become\n // too opaque at high intensities). 0.4 scale maps intensity=100 to effective ~40.\n const effectiveIntensity = isAndroid ? Math.min(blurIntensity * 0.4, 40) : blurIntensity;\n\n return (\n <BlurViewComponent\n ref={ref}\n intensity={effectiveIntensity}\n tint={blurTint}\n blurTarget={isAndroid ? blurTarget : undefined}\n blurMethod={isAndroid ? 'dimezisBlurView' : undefined}\n style={blurStyles}\n {...props}\n >\n {props.children}\n </BlurViewComponent>\n );\n }\n\n // Fallback to regular View (blur not available or not requested)\n\n return <View ref={ref} style={boxStyles} {...props} />;\n});\n\nBox.displayName = 'Box';\n\n/**\n * Dynamic shadow stylesheet that merges drop and inset shadows into a single\n * boxShadow CSS string. Theme-reactive so shadows update on color mode change.\n */\nconst shadowSheet = StyleSheet.create((theme) => ({\n shadow: (drop?: string, inset?: string) => {\n const parts = [\n drop ? theme.boxShadow.drop[drop as keyof typeof theme.boxShadow.drop] : '',\n inset ? theme.boxShadow.inset[inset as keyof typeof theme.boxShadow.inset] : '',\n ].filter(Boolean);\n return parts.length > 0 ? { boxShadow: parts.join(', ') } : {};\n },\n}));\n\nexport { Box, type BoxProps };\n"],"mappings":"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"}
@@ -64,7 +64,7 @@ function AnimatedIconSlot({ children, visible, iconSize, gap }) {
64
64
  *
65
65
  * @example
66
66
  * ```tsx
67
- * import { Button } from '@yahoo/uds-mobile';
67
+ * import { Button } from '@yahoo/uds-mobile/Button';
68
68
  *
69
69
  * <Button onPress={() => console.log('pressed')}>Save</Button>
70
70
  * <Button variant="secondary">Cancel</Button>
@@ -48,7 +48,7 @@ interface ButtonProps extends Omit<PressableProps$1, 'children' | 'disabled'> {
48
48
  *
49
49
  * @example
50
50
  * ```tsx
51
- * import { Button } from '@yahoo/uds-mobile';
51
+ * import { Button } from '@yahoo/uds-mobile/Button';
52
52
  *
53
53
  * <Button onPress={() => console.log('pressed')}>Save</Button>
54
54
  * <Button variant="secondary">Cancel</Button>
@@ -48,7 +48,7 @@ interface ButtonProps extends Omit<PressableProps$1, 'children' | 'disabled'> {
48
48
  *
49
49
  * @example
50
50
  * ```tsx
51
- * import { Button } from '@yahoo/uds-mobile';
51
+ * import { Button } from '@yahoo/uds-mobile/Button';
52
52
  *
53
53
  * <Button onPress={() => console.log('pressed')}>Save</Button>
54
54
  * <Button variant="secondary">Cancel</Button>
@@ -61,7 +61,7 @@ function AnimatedIconSlot({ children, visible, iconSize, gap }) {
61
61
  *
62
62
  * @example
63
63
  * ```tsx
64
- * import { Button } from '@yahoo/uds-mobile';
64
+ * import { Button } from '@yahoo/uds-mobile/Button';
65
65
  *
66
66
  * <Button onPress={() => console.log('pressed')}>Save</Button>
67
67
  * <Button variant="secondary">Cancel</Button>
@@ -1 +1 @@
1
- {"version":3,"file":"Button.js","names":["Text"],"sources":["../../src/components/Button.tsx"],"sourcesContent":["import type { ButtonSize, ButtonVariantFlat, IconSize, IconVariant } from '@yahoo/uds-types';\nimport type { Ref } from 'react';\nimport { isValidElement, memo, useCallback, useMemo, useState } from 'react';\nimport type { View } from 'react-native';\nimport { ActivityIndicator } from 'react-native';\nimport Animated, {\n Easing,\n interpolate,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n withSpring,\n withTiming,\n} from 'react-native-reanimated';\nimport { useAnimatedTheme } from 'react-native-unistyles/reanimated';\n\nimport { buttonStyles } from '../../generated/styles';\nimport { BUTTON_SPRING_CONFIG, SCALE_EFFECTS } from '../motion';\nimport type { IconSlotType } from './IconSlot';\nimport { IconSlot } from './IconSlot';\nimport type { PressableProps } from './Pressable';\nimport { AnimatedPressable } from './Pressable';\nimport { Text } from './Text';\n\n/* -------------------------------------------------------------------------- */\n/* Animation Helpers */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Interpolates a boxShadow string by scaling the alpha of all colors.\n * This allows smooth fade-in/out of shadows.\n */\nfunction interpolateShadowAlpha(shadow: string | undefined, alpha: number): string {\n 'worklet';\n if (!shadow) {\n return '';\n }\n if (alpha >= 1) {\n return shadow;\n }\n if (alpha <= 0) {\n return '';\n }\n\n return shadow.replace(/rgba\\(([^,]+),\\s*([^,]+),\\s*([^,]+),\\s*([^)]+)\\)/g, (_, r, g, b, a) => {\n const newAlpha = parseFloat(a) * alpha;\n return `rgba(${r}, ${g}, ${b}, ${newAlpha.toFixed(3)})`;\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* Animated Icon Slot */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Animated wrapper for icon/loading content.\n * Matches web Button icon animation: scale 0.7→1, opacity 0→1, width 0→auto\n * Uses staggered animation: opacity waits until halfway through width animation.\n */\nfunction AnimatedIconSlot({\n children,\n visible,\n iconSize,\n gap,\n}: {\n children: React.ReactNode;\n visible: boolean;\n iconSize: number;\n gap: number;\n}) {\n // Use useDerivedValue instead of useEffect + useSharedValue\n // This is the idiomatic Reanimated pattern for deriving animated values from React state\n const progress = useDerivedValue(\n () => withSpring(visible ? 1 : 0, BUTTON_SPRING_CONFIG),\n [visible],\n );\n\n const animatedStyle = useAnimatedStyle(() => {\n // Total width includes icon + gap when visible\n const totalWidth = iconSize + gap;\n const width = interpolate(progress.value, [0, 1], [0, totalWidth]);\n\n // Staggered animation: opacity starts at 50% of width animation\n // On enter: width expands first, then icon fades in\n // On exit: icon fades out first, then width collapses\n const opacity = interpolate(progress.value, [0.5, 1], [0, 1], 'clamp');\n const scale = interpolate(progress.value, [0.5, 1], [0.7, 1], 'clamp');\n\n return {\n width,\n opacity,\n transform: [{ scale }],\n overflow: 'hidden' as const,\n };\n });\n\n return <Animated.View style={animatedStyle}>{children}</Animated.View>;\n}\n\n// function LoadingIcon({ size, variant }: { size: ButtonSize, variant: ButtonVariantFlat }) {\n// const { theme } = useUnistyles();\n// const themeKey = `buttonVariant${variantToCapitalMap[variant]}IconRest` as const;\n// const iconSize = theme.components.buttonSizeLgIconRest.fontSize;\n// return <ActivityIndicator size={iconSize} color={theme.colors.text.primary} />;\n// }\n\n/* -------------------------------------------------------------------------- */\n/* Button Props */\n/* -------------------------------------------------------------------------- */\n\ninterface ButtonProps extends Omit<PressableProps, 'children' | 'disabled'> {\n /** The visual style variant of the button @default 'primary' */\n variant?: ButtonVariantFlat;\n /** The size of the button @default 'md' */\n size?: ButtonSize;\n /** The icon style variant @default 'outline' */\n iconVariant?: IconVariant;\n /** Icon displayed before the button label */\n startIcon?: IconSlotType;\n /** Icon displayed after the button label */\n endIcon?: IconSlotType;\n /** Shows a loading spinner and disables the button */\n loading?: boolean;\n /** Whether the button is disabled */\n disabled?: boolean;\n /** Button label content */\n children?: React.ReactNode;\n /**\n * Disable motion effects (scale on press, icon animations)\n * @default false\n */\n disableEffects?: boolean;\n /** Ref to the underlying View */\n ref?: Ref<View>;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Button Component */\n/* -------------------------------------------------------------------------- */\n\n/**\n * **🖲️ A button element that can be used to trigger an action**\n *\n * @description\n * A button is a fundamental component used to trigger an action or event.\n * Buttons are interactive elements that users can click, tap, or otherwise\n * engage with to submit forms, open dialogues, or perform any other interaction.\n *\n * Features animated scale effect on press and smooth icon transitions matching\n * the web UDS Button behavior.\n *\n * @category Interactive\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { Button } from '@yahoo/uds-mobile';\n *\n * <Button onPress={() => console.log('pressed')}>Save</Button>\n * <Button variant=\"secondary\">Cancel</Button>\n * <Button startIcon=\"Add\" variant=\"brand\">Add Item</Button>\n * <Button loading>Saving...</Button>\n * ```\n *\n * @accessibility\n * - Sets `accessibilityRole=\"button\"` automatically\n * - Announces loading state to screen readers\n * - Use `accessibilityLabel` for icon-only buttons\n *\n * @see {@link IconButton} for icon-only buttons\n * @see {@link Link} for navigation actions\n */\nconst Button = memo(function Button({\n variant = 'primary',\n size = 'md',\n iconVariant = 'outline',\n startIcon,\n endIcon,\n loading,\n disabled,\n width: _width,\n children,\n style,\n accessibilityLabel,\n accessibilityHint,\n disableEffects = false,\n onPressIn,\n onPressOut,\n ref,\n ...props\n}: ButtonProps) {\n const shouldAnimate = !disableEffects;\n\n /* --------------------------------- State ---------------------------------- */\n const [pressed, setPressed] = useState(false);\n\n buttonStyles.useVariants({ size, variant, disabled, pressed });\n\n // Get gap and icon size from current variant styles\n const buttonGap = buttonStyles.root.gap;\n const iconSize = buttonStyles.icon.fontSize;\n\n // Get animated theme for boxShadow (useAnimatedVariantColor doesn't support non-color props)\n const animatedTheme = useAnimatedTheme();\n\n /* ------------------------------- Animation -------------------------------- */\n const scale = useSharedValue<number>(SCALE_EFFECTS.none);\n\n const handlePressIn = useCallback(\n (event: Parameters<NonNullable<PressableProps['onPressIn']>>[0]) => {\n setPressed(true);\n if (shouldAnimate) {\n scale.value = withSpring(SCALE_EFFECTS.down, BUTTON_SPRING_CONFIG);\n }\n onPressIn?.(event);\n },\n [shouldAnimate, scale, onPressIn],\n );\n\n const handlePressOut = useCallback(\n (event: Parameters<NonNullable<PressableProps['onPressOut']>>[0]) => {\n setPressed(false);\n if (shouldAnimate) {\n scale.value = withSpring(SCALE_EFFECTS.none, BUTTON_SPRING_CONFIG);\n }\n onPressOut?.(event);\n },\n [shouldAnimate, scale, onPressOut],\n );\n\n /* -------------------------------- Content --------------------------------- */\n const childrenNode =\n children &&\n (isValidElement(children) ? (\n children\n ) : (\n <Text numberOfLines={1} textAlign=\"center\" style={buttonStyles.text}>\n {children}\n </Text>\n ));\n\n const a11yState = useMemo(() => ({ disabled, busy: loading }), [disabled, loading]);\n\n /* --------------------------------- Styles --------------------------------- */\n // Animate pressed state for shadow (0 = rest, 1 = pressed)\n const pressProgress = useDerivedValue(\n () => withTiming(pressed ? 1 : 0, { duration: 220, easing: Easing.bezier(0, 0, 0.2, 1) }),\n [pressed],\n );\n\n // Animate using Unistyles' variant color system + boxShadow from theme\n const animatedStyles = useAnimatedStyle(() => {\n // Get boxShadow from theme using flattened path (no camelCase conversion needed!)\n const components = animatedTheme.value.components;\n const buttonVariantPath = `button/variant/${variant}/root/pressed` as const;\n const shadowPressed = components[buttonVariantPath]?.boxShadow;\n\n return {\n transform: [{ scale: scale.value }],\n // Only animate shadow if the theme defines one for this variant\n ...(shadowPressed && {\n boxShadow: interpolateShadowAlpha(shadowPressed, pressProgress.value),\n }),\n };\n });\n\n // Determine what should be visible in start slot\n const showLoading = !!loading;\n const showStartIcon = !!startIcon && !loading;\n const showEndIcon = !!endIcon && !loading;\n\n const iconSizeToken = (buttonStyles.icon.iconSizeToken as IconSize) ?? 'sm';\n\n // Start slot: either loading spinner or start icon\n const startContent = (\n <AnimatedIconSlot visible={showLoading || showStartIcon} iconSize={iconSize} gap={buttonGap}>\n {showLoading ? (\n <ActivityIndicator size={buttonStyles.icon.fontSize} color={buttonStyles.icon.color} />\n ) : (\n <IconSlot\n icon={startIcon}\n size={iconSizeToken}\n variant={iconVariant}\n style={buttonStyles.icon}\n />\n )}\n </AnimatedIconSlot>\n );\n\n // End slot: only end icon (no loading here)\n const endContent = (\n <AnimatedIconSlot visible={showEndIcon} iconSize={iconSize} gap={buttonGap}>\n <IconSlot\n icon={endIcon}\n size={iconSizeToken}\n variant={iconVariant}\n style={buttonStyles.icon}\n />\n </AnimatedIconSlot>\n );\n\n const rootStyles = useMemo(\n () => [\n buttonStyles.root,\n animatedStyles,\n typeof style === 'function' ? style({ pressed }) : style,\n ],\n [buttonStyles.root, animatedStyles, style, pressed],\n );\n\n /* --------------------------------- Render --------------------------------- */\n return (\n <AnimatedPressable\n ref={ref}\n disabled={disabled}\n onPressIn={handlePressIn}\n onPressOut={handlePressOut}\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"center\"\n overflow=\"hidden\"\n accessibilityLabel={loading ? `${accessibilityLabel ?? ''}, loading` : accessibilityLabel}\n accessibilityHint={accessibilityHint}\n accessibilityRole=\"button\"\n accessibilityState={a11yState}\n alignContent=\"center\"\n style={rootStyles}\n {...props}\n >\n {startContent}\n {childrenNode}\n {endContent}\n </AnimatedPressable>\n );\n});\n\nButton.displayName = 'Button';\n\nexport { Button, type ButtonProps };\n"],"mappings":"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"}
1
+ {"version":3,"file":"Button.js","names":["Text"],"sources":["../../src/components/Button.tsx"],"sourcesContent":["import type { ButtonSize, ButtonVariantFlat, IconSize, IconVariant } from '@yahoo/uds-types';\nimport type { Ref } from 'react';\nimport { isValidElement, memo, useCallback, useMemo, useState } from 'react';\nimport type { View } from 'react-native';\nimport { ActivityIndicator } from 'react-native';\nimport Animated, {\n Easing,\n interpolate,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n withSpring,\n withTiming,\n} from 'react-native-reanimated';\nimport { useAnimatedTheme } from 'react-native-unistyles/reanimated';\n\nimport { buttonStyles } from '../../generated/styles';\nimport { BUTTON_SPRING_CONFIG, SCALE_EFFECTS } from '../motion';\nimport type { IconSlotType } from './IconSlot';\nimport { IconSlot } from './IconSlot';\nimport type { PressableProps } from './Pressable';\nimport { AnimatedPressable } from './Pressable';\nimport { Text } from './Text';\n\n/* -------------------------------------------------------------------------- */\n/* Animation Helpers */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Interpolates a boxShadow string by scaling the alpha of all colors.\n * This allows smooth fade-in/out of shadows.\n */\nfunction interpolateShadowAlpha(shadow: string | undefined, alpha: number): string {\n 'worklet';\n if (!shadow) {\n return '';\n }\n if (alpha >= 1) {\n return shadow;\n }\n if (alpha <= 0) {\n return '';\n }\n\n return shadow.replace(/rgba\\(([^,]+),\\s*([^,]+),\\s*([^,]+),\\s*([^)]+)\\)/g, (_, r, g, b, a) => {\n const newAlpha = parseFloat(a) * alpha;\n return `rgba(${r}, ${g}, ${b}, ${newAlpha.toFixed(3)})`;\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* Animated Icon Slot */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Animated wrapper for icon/loading content.\n * Matches web Button icon animation: scale 0.7→1, opacity 0→1, width 0→auto\n * Uses staggered animation: opacity waits until halfway through width animation.\n */\nfunction AnimatedIconSlot({\n children,\n visible,\n iconSize,\n gap,\n}: {\n children: React.ReactNode;\n visible: boolean;\n iconSize: number;\n gap: number;\n}) {\n // Use useDerivedValue instead of useEffect + useSharedValue\n // This is the idiomatic Reanimated pattern for deriving animated values from React state\n const progress = useDerivedValue(\n () => withSpring(visible ? 1 : 0, BUTTON_SPRING_CONFIG),\n [visible],\n );\n\n const animatedStyle = useAnimatedStyle(() => {\n // Total width includes icon + gap when visible\n const totalWidth = iconSize + gap;\n const width = interpolate(progress.value, [0, 1], [0, totalWidth]);\n\n // Staggered animation: opacity starts at 50% of width animation\n // On enter: width expands first, then icon fades in\n // On exit: icon fades out first, then width collapses\n const opacity = interpolate(progress.value, [0.5, 1], [0, 1], 'clamp');\n const scale = interpolate(progress.value, [0.5, 1], [0.7, 1], 'clamp');\n\n return {\n width,\n opacity,\n transform: [{ scale }],\n overflow: 'hidden' as const,\n };\n });\n\n return <Animated.View style={animatedStyle}>{children}</Animated.View>;\n}\n\n// function LoadingIcon({ size, variant }: { size: ButtonSize, variant: ButtonVariantFlat }) {\n// const { theme } = useUnistyles();\n// const themeKey = `buttonVariant${variantToCapitalMap[variant]}IconRest` as const;\n// const iconSize = theme.components.buttonSizeLgIconRest.fontSize;\n// return <ActivityIndicator size={iconSize} color={theme.colors.text.primary} />;\n// }\n\n/* -------------------------------------------------------------------------- */\n/* Button Props */\n/* -------------------------------------------------------------------------- */\n\ninterface ButtonProps extends Omit<PressableProps, 'children' | 'disabled'> {\n /** The visual style variant of the button @default 'primary' */\n variant?: ButtonVariantFlat;\n /** The size of the button @default 'md' */\n size?: ButtonSize;\n /** The icon style variant @default 'outline' */\n iconVariant?: IconVariant;\n /** Icon displayed before the button label */\n startIcon?: IconSlotType;\n /** Icon displayed after the button label */\n endIcon?: IconSlotType;\n /** Shows a loading spinner and disables the button */\n loading?: boolean;\n /** Whether the button is disabled */\n disabled?: boolean;\n /** Button label content */\n children?: React.ReactNode;\n /**\n * Disable motion effects (scale on press, icon animations)\n * @default false\n */\n disableEffects?: boolean;\n /** Ref to the underlying View */\n ref?: Ref<View>;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Button Component */\n/* -------------------------------------------------------------------------- */\n\n/**\n * **🖲️ A button element that can be used to trigger an action**\n *\n * @description\n * A button is a fundamental component used to trigger an action or event.\n * Buttons are interactive elements that users can click, tap, or otherwise\n * engage with to submit forms, open dialogues, or perform any other interaction.\n *\n * Features animated scale effect on press and smooth icon transitions matching\n * the web UDS Button behavior.\n *\n * @category Interactive\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { Button } from '@yahoo/uds-mobile/Button';\n *\n * <Button onPress={() => console.log('pressed')}>Save</Button>\n * <Button variant=\"secondary\">Cancel</Button>\n * <Button startIcon=\"Add\" variant=\"brand\">Add Item</Button>\n * <Button loading>Saving...</Button>\n * ```\n *\n * @accessibility\n * - Sets `accessibilityRole=\"button\"` automatically\n * - Announces loading state to screen readers\n * - Use `accessibilityLabel` for icon-only buttons\n *\n * @see {@link IconButton} for icon-only buttons\n * @see {@link Link} for navigation actions\n */\nconst Button = memo(function Button({\n variant = 'primary',\n size = 'md',\n iconVariant = 'outline',\n startIcon,\n endIcon,\n loading,\n disabled,\n width: _width,\n children,\n style,\n accessibilityLabel,\n accessibilityHint,\n disableEffects = false,\n onPressIn,\n onPressOut,\n ref,\n ...props\n}: ButtonProps) {\n const shouldAnimate = !disableEffects;\n\n /* --------------------------------- State ---------------------------------- */\n const [pressed, setPressed] = useState(false);\n\n buttonStyles.useVariants({ size, variant, disabled, pressed });\n\n // Get gap and icon size from current variant styles\n const buttonGap = buttonStyles.root.gap;\n const iconSize = buttonStyles.icon.fontSize;\n\n // Get animated theme for boxShadow (useAnimatedVariantColor doesn't support non-color props)\n const animatedTheme = useAnimatedTheme();\n\n /* ------------------------------- Animation -------------------------------- */\n const scale = useSharedValue<number>(SCALE_EFFECTS.none);\n\n const handlePressIn = useCallback(\n (event: Parameters<NonNullable<PressableProps['onPressIn']>>[0]) => {\n setPressed(true);\n if (shouldAnimate) {\n scale.value = withSpring(SCALE_EFFECTS.down, BUTTON_SPRING_CONFIG);\n }\n onPressIn?.(event);\n },\n [shouldAnimate, scale, onPressIn],\n );\n\n const handlePressOut = useCallback(\n (event: Parameters<NonNullable<PressableProps['onPressOut']>>[0]) => {\n setPressed(false);\n if (shouldAnimate) {\n scale.value = withSpring(SCALE_EFFECTS.none, BUTTON_SPRING_CONFIG);\n }\n onPressOut?.(event);\n },\n [shouldAnimate, scale, onPressOut],\n );\n\n /* -------------------------------- Content --------------------------------- */\n const childrenNode =\n children &&\n (isValidElement(children) ? (\n children\n ) : (\n <Text numberOfLines={1} textAlign=\"center\" style={buttonStyles.text}>\n {children}\n </Text>\n ));\n\n const a11yState = useMemo(() => ({ disabled, busy: loading }), [disabled, loading]);\n\n /* --------------------------------- Styles --------------------------------- */\n // Animate pressed state for shadow (0 = rest, 1 = pressed)\n const pressProgress = useDerivedValue(\n () => withTiming(pressed ? 1 : 0, { duration: 220, easing: Easing.bezier(0, 0, 0.2, 1) }),\n [pressed],\n );\n\n // Animate using Unistyles' variant color system + boxShadow from theme\n const animatedStyles = useAnimatedStyle(() => {\n // Get boxShadow from theme using flattened path (no camelCase conversion needed!)\n const components = animatedTheme.value.components;\n const buttonVariantPath = `button/variant/${variant}/root/pressed` as const;\n const shadowPressed = components[buttonVariantPath]?.boxShadow;\n\n return {\n transform: [{ scale: scale.value }],\n // Only animate shadow if the theme defines one for this variant\n ...(shadowPressed && {\n boxShadow: interpolateShadowAlpha(shadowPressed, pressProgress.value),\n }),\n };\n });\n\n // Determine what should be visible in start slot\n const showLoading = !!loading;\n const showStartIcon = !!startIcon && !loading;\n const showEndIcon = !!endIcon && !loading;\n\n const iconSizeToken = (buttonStyles.icon.iconSizeToken as IconSize) ?? 'sm';\n\n // Start slot: either loading spinner or start icon\n const startContent = (\n <AnimatedIconSlot visible={showLoading || showStartIcon} iconSize={iconSize} gap={buttonGap}>\n {showLoading ? (\n <ActivityIndicator size={buttonStyles.icon.fontSize} color={buttonStyles.icon.color} />\n ) : (\n <IconSlot\n icon={startIcon}\n size={iconSizeToken}\n variant={iconVariant}\n style={buttonStyles.icon}\n />\n )}\n </AnimatedIconSlot>\n );\n\n // End slot: only end icon (no loading here)\n const endContent = (\n <AnimatedIconSlot visible={showEndIcon} iconSize={iconSize} gap={buttonGap}>\n <IconSlot\n icon={endIcon}\n size={iconSizeToken}\n variant={iconVariant}\n style={buttonStyles.icon}\n />\n </AnimatedIconSlot>\n );\n\n const rootStyles = useMemo(\n () => [\n buttonStyles.root,\n animatedStyles,\n typeof style === 'function' ? style({ pressed }) : style,\n ],\n [buttonStyles.root, animatedStyles, style, pressed],\n );\n\n /* --------------------------------- Render --------------------------------- */\n return (\n <AnimatedPressable\n ref={ref}\n disabled={disabled}\n onPressIn={handlePressIn}\n onPressOut={handlePressOut}\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"center\"\n overflow=\"hidden\"\n accessibilityLabel={loading ? `${accessibilityLabel ?? ''}, loading` : accessibilityLabel}\n accessibilityHint={accessibilityHint}\n accessibilityRole=\"button\"\n accessibilityState={a11yState}\n alignContent=\"center\"\n style={rootStyles}\n {...props}\n >\n {startContent}\n {childrenNode}\n {endContent}\n </AnimatedPressable>\n );\n});\n\nButton.displayName = 'Button';\n\nexport { Button, type ButtonProps };\n"],"mappings":"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"}