@lmb-it/kitsconcerto-native 0.1.288 → 0.1.290

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 (258) hide show
  1. package/apps/mobile/src/Core/Dropdown/index.cjs +1 -0
  2. package/apps/mobile/src/Core/Dropdown/index.cjs.map +1 -1
  3. package/apps/mobile/src/Core/Dropdown/index.js +1 -0
  4. package/apps/mobile/src/Core/Dropdown/index.js.map +1 -1
  5. package/apps/mobile/src/Core/Trees/components/CoreToolbar.cjs +1 -0
  6. package/apps/mobile/src/Core/Trees/components/CoreToolbar.cjs.map +1 -1
  7. package/apps/mobile/src/Core/Trees/components/CoreToolbar.js +1 -0
  8. package/apps/mobile/src/Core/Trees/components/CoreToolbar.js.map +1 -1
  9. package/apps/mobile/src/Factory/ResponsiveElement.cjs +1 -0
  10. package/apps/mobile/src/Factory/ResponsiveElement.cjs.map +1 -1
  11. package/apps/mobile/src/Factory/ResponsiveElement.js +1 -0
  12. package/apps/mobile/src/Factory/ResponsiveElement.js.map +1 -1
  13. package/apps/mobile/src/Factory/helpers/cssVarMap.cjs +1 -0
  14. package/apps/mobile/src/Factory/helpers/cssVarMap.cjs.map +1 -1
  15. package/apps/mobile/src/Factory/helpers/cssVarMap.js +1 -0
  16. package/apps/mobile/src/Factory/helpers/cssVarMap.js.map +1 -1
  17. package/apps/mobile/src/Factory/helpers/style.cjs +1 -0
  18. package/apps/mobile/src/Factory/helpers/style.cjs.map +1 -1
  19. package/apps/mobile/src/Factory/helpers/style.js +1 -0
  20. package/apps/mobile/src/Factory/helpers/style.js.map +1 -1
  21. package/apps/mobile/src/Factory/useSeparator.cjs +1 -0
  22. package/apps/mobile/src/Factory/useSeparator.cjs.map +1 -1
  23. package/apps/mobile/src/Factory/useSeparator.js +1 -0
  24. package/apps/mobile/src/Factory/useSeparator.js.map +1 -1
  25. package/components/Accordion/accordion.cjs +1 -0
  26. package/components/Accordion/accordion.cjs.map +1 -1
  27. package/components/Accordion/accordion.js +1 -0
  28. package/components/Accordion/accordion.js.map +1 -1
  29. package/components/Badge/index.cjs +1 -0
  30. package/components/Badge/index.cjs.map +1 -1
  31. package/components/Badge/index.js +1 -0
  32. package/components/Badge/index.js.map +1 -1
  33. package/components/BottomTabBar/index.cjs +1 -0
  34. package/components/BottomTabBar/index.cjs.map +1 -1
  35. package/components/BottomTabBar/index.js +1 -0
  36. package/components/BottomTabBar/index.js.map +1 -1
  37. package/components/Button/Button.cjs +1 -0
  38. package/components/Button/Button.cjs.map +1 -1
  39. package/components/Button/Button.js +1 -0
  40. package/components/Button/Button.js.map +1 -1
  41. package/components/ImageCropper/index.cjs +584 -550
  42. package/components/ImageCropper/index.cjs.map +1 -1
  43. package/components/ImageCropper/index.js +585 -551
  44. package/components/ImageCropper/index.js.map +1 -1
  45. package/components/MessageComposer/index.cjs +1 -0
  46. package/components/MessageComposer/index.cjs.map +1 -1
  47. package/components/MessageComposer/index.js +1 -0
  48. package/components/MessageComposer/index.js.map +1 -1
  49. package/components/SegmentedControl/index.cjs +1 -0
  50. package/components/SegmentedControl/index.cjs.map +1 -1
  51. package/components/SegmentedControl/index.js +1 -0
  52. package/components/SegmentedControl/index.js.map +1 -1
  53. package/components/SkeletonText/index.cjs +1 -0
  54. package/components/SkeletonText/index.cjs.map +1 -1
  55. package/components/SkeletonText/index.js +1 -0
  56. package/components/SkeletonText/index.js.map +1 -1
  57. package/components/Tabs/index.cjs +3 -2
  58. package/components/Tabs/index.cjs.map +1 -1
  59. package/components/Tabs/index.js +3 -2
  60. package/components/Tabs/index.js.map +1 -1
  61. package/contexts/Locale/index.cjs +1 -0
  62. package/contexts/Locale/index.cjs.map +1 -1
  63. package/contexts/Locale/index.js +1 -0
  64. package/contexts/Locale/index.js.map +1 -1
  65. package/contexts/Main/index.cjs +1 -0
  66. package/contexts/Main/index.cjs.map +1 -1
  67. package/contexts/Main/index.js +1 -0
  68. package/contexts/Main/index.js.map +1 -1
  69. package/contexts/Theme/index.cjs +1 -0
  70. package/contexts/Theme/index.cjs.map +1 -1
  71. package/contexts/Theme/index.js +1 -0
  72. package/contexts/Theme/index.js.map +1 -1
  73. package/data/DataView/Pagination/index.cjs +1 -0
  74. package/data/DataView/Pagination/index.cjs.map +1 -1
  75. package/data/DataView/Pagination/index.js +1 -0
  76. package/data/DataView/Pagination/index.js.map +1 -1
  77. package/data/TreeView/index.cjs +1 -0
  78. package/data/TreeView/index.cjs.map +1 -1
  79. package/data/TreeView/index.js +1 -0
  80. package/data/TreeView/index.js.map +1 -1
  81. package/form/controls/Checkbox/checkbox.cjs +1 -0
  82. package/form/controls/Checkbox/checkbox.cjs.map +1 -1
  83. package/form/controls/Checkbox/checkbox.js +1 -0
  84. package/form/controls/Checkbox/checkbox.js.map +1 -1
  85. package/form/controls/RadioButton/radiobutton.cjs +1 -0
  86. package/form/controls/RadioButton/radiobutton.cjs.map +1 -1
  87. package/form/controls/RadioButton/radiobutton.js +1 -0
  88. package/form/controls/RadioButton/radiobutton.js.map +1 -1
  89. package/form/controls/SelectButton/index.cjs +1 -0
  90. package/form/controls/SelectButton/index.cjs.map +1 -1
  91. package/form/controls/SelectButton/index.js +1 -0
  92. package/form/controls/SelectButton/index.js.map +1 -1
  93. package/form/controls/TriStateCheckbox/index.cjs +1 -0
  94. package/form/controls/TriStateCheckbox/index.cjs.map +1 -1
  95. package/form/controls/TriStateCheckbox/index.js +1 -0
  96. package/form/controls/TriStateCheckbox/index.js.map +1 -1
  97. package/form/file-pickers/MainUploader/index.cjs +1 -0
  98. package/form/file-pickers/MainUploader/index.cjs.map +1 -1
  99. package/form/file-pickers/MainUploader/index.js +1 -0
  100. package/form/file-pickers/MainUploader/index.js.map +1 -1
  101. package/form/helpers/ErrorMessage/errormessage.cjs +1 -0
  102. package/form/helpers/ErrorMessage/errormessage.cjs.map +1 -1
  103. package/form/helpers/ErrorMessage/errormessage.js +1 -0
  104. package/form/helpers/ErrorMessage/errormessage.js.map +1 -1
  105. package/form/helpers/FormContainer/index.cjs +1 -0
  106. package/form/helpers/FormContainer/index.cjs.map +1 -1
  107. package/form/helpers/FormContainer/index.js +1 -0
  108. package/form/helpers/FormContainer/index.js.map +1 -1
  109. package/form/helpers/Label/label.cjs +1 -0
  110. package/form/helpers/Label/label.cjs.map +1 -1
  111. package/form/helpers/Label/label.js +1 -0
  112. package/form/helpers/Label/label.js.map +1 -1
  113. package/form/inputs/InputMask/index.cjs +1 -0
  114. package/form/inputs/InputMask/index.cjs.map +1 -1
  115. package/form/inputs/InputMask/index.js +1 -0
  116. package/form/inputs/InputMask/index.js.map +1 -1
  117. package/form/inputs/InputNumber/index.cjs +1 -0
  118. package/form/inputs/InputNumber/index.cjs.map +1 -1
  119. package/form/inputs/InputNumber/index.js +1 -0
  120. package/form/inputs/InputNumber/index.js.map +1 -1
  121. package/form/inputs/InputPassword/index.cjs +1 -0
  122. package/form/inputs/InputPassword/index.cjs.map +1 -1
  123. package/form/inputs/InputPassword/index.js +1 -0
  124. package/form/inputs/InputPassword/index.js.map +1 -1
  125. package/form/inputs/InputSwitch/index.cjs +1 -0
  126. package/form/inputs/InputSwitch/index.cjs.map +1 -1
  127. package/form/inputs/InputSwitch/index.js +1 -0
  128. package/form/inputs/InputSwitch/index.js.map +1 -1
  129. package/form/inputs/InputText/index.cjs +1 -0
  130. package/form/inputs/InputText/index.cjs.map +1 -1
  131. package/form/inputs/InputText/index.js +1 -0
  132. package/form/inputs/InputText/index.js.map +1 -1
  133. package/form/inputs/InputTextarea/index.cjs +1 -0
  134. package/form/inputs/InputTextarea/index.cjs.map +1 -1
  135. package/form/inputs/InputTextarea/index.js +1 -0
  136. package/form/inputs/InputTextarea/index.js.map +1 -1
  137. package/form/inputs/PhoneInput/index.cjs +1 -0
  138. package/form/inputs/PhoneInput/index.cjs.map +1 -1
  139. package/form/inputs/PhoneInput/index.js +1 -0
  140. package/form/inputs/PhoneInput/index.js.map +1 -1
  141. package/form/inputs/RadiusPicker/components/NumericRadiusInput.cjs +1 -0
  142. package/form/inputs/RadiusPicker/components/NumericRadiusInput.cjs.map +1 -1
  143. package/form/inputs/RadiusPicker/components/NumericRadiusInput.js +1 -0
  144. package/form/inputs/RadiusPicker/components/NumericRadiusInput.js.map +1 -1
  145. package/form/selects/AutoComplete/index.cjs +1 -0
  146. package/form/selects/AutoComplete/index.cjs.map +1 -1
  147. package/form/selects/AutoComplete/index.js +1 -0
  148. package/form/selects/AutoComplete/index.js.map +1 -1
  149. package/form/selects/Dropdown/index.cjs +1 -0
  150. package/form/selects/Dropdown/index.cjs.map +1 -1
  151. package/form/selects/Dropdown/index.js +1 -0
  152. package/form/selects/Dropdown/index.js.map +1 -1
  153. package/form/selects/ListBox/index.cjs +1 -0
  154. package/form/selects/ListBox/index.cjs.map +1 -1
  155. package/form/selects/ListBox/index.js +1 -0
  156. package/form/selects/ListBox/index.js.map +1 -1
  157. package/form/selects/MultiSelect/index.cjs +1 -0
  158. package/form/selects/MultiSelect/index.cjs.map +1 -1
  159. package/form/selects/MultiSelect/index.js +1 -0
  160. package/form/selects/MultiSelect/index.js.map +1 -1
  161. package/form/selects/TreeSelect/index.cjs +1 -0
  162. package/form/selects/TreeSelect/index.cjs.map +1 -1
  163. package/form/selects/TreeSelect/index.js +1 -0
  164. package/form/selects/TreeSelect/index.js.map +1 -1
  165. package/form/selects/helpers/SelectPopupOverlay.cjs +1 -0
  166. package/form/selects/helpers/SelectPopupOverlay.cjs.map +1 -1
  167. package/form/selects/helpers/SelectPopupOverlay.js +1 -0
  168. package/form/selects/helpers/SelectPopupOverlay.js.map +1 -1
  169. package/form-renderer/components/layout/FieldWrapper.cjs +1 -0
  170. package/form-renderer/components/layout/FieldWrapper.cjs.map +1 -1
  171. package/form-renderer/components/layout/FieldWrapper.js +1 -0
  172. package/form-renderer/components/layout/FieldWrapper.js.map +1 -1
  173. package/hooks/shadowPresets.cjs +1 -0
  174. package/hooks/shadowPresets.cjs.map +1 -1
  175. package/hooks/shadowPresets.js +1 -0
  176. package/hooks/shadowPresets.js.map +1 -1
  177. package/hooks/useFocusStyles.cjs +1 -0
  178. package/hooks/useFocusStyles.cjs.map +1 -1
  179. package/hooks/useFocusStyles.js +1 -0
  180. package/hooks/useFocusStyles.js.map +1 -1
  181. package/index.cjs +2 -0
  182. package/index.cjs.map +1 -1
  183. package/index.d.ts +117 -5
  184. package/index.js +1 -0
  185. package/index.js.map +1 -1
  186. package/layout/Center/index.cjs +1 -0
  187. package/layout/Center/index.cjs.map +1 -1
  188. package/layout/Center/index.js +1 -0
  189. package/layout/Center/index.js.map +1 -1
  190. package/layout/Container/container.cjs +1 -0
  191. package/layout/Container/container.cjs.map +1 -1
  192. package/layout/Container/container.js +1 -0
  193. package/layout/Container/container.js.map +1 -1
  194. package/layout/Flex/index.cjs +1 -0
  195. package/layout/Flex/index.cjs.map +1 -1
  196. package/layout/Flex/index.js +1 -0
  197. package/layout/Flex/index.js.map +1 -1
  198. package/layout/HStack/index.cjs +1 -0
  199. package/layout/HStack/index.cjs.map +1 -1
  200. package/layout/HStack/index.js +1 -0
  201. package/layout/HStack/index.js.map +1 -1
  202. package/layout/List/index.cjs +1 -0
  203. package/layout/List/index.cjs.map +1 -1
  204. package/layout/List/index.js +1 -0
  205. package/layout/List/index.js.map +1 -1
  206. package/layout/ListItem/index.cjs +1 -0
  207. package/layout/ListItem/index.cjs.map +1 -1
  208. package/layout/ListItem/index.js +1 -0
  209. package/layout/ListItem/index.js.map +1 -1
  210. package/layout/ScrollView/index.cjs +1 -0
  211. package/layout/ScrollView/index.cjs.map +1 -1
  212. package/layout/ScrollView/index.js +1 -0
  213. package/layout/ScrollView/index.js.map +1 -1
  214. package/layout/VStack/index.cjs +1 -0
  215. package/layout/VStack/index.cjs.map +1 -1
  216. package/layout/VStack/index.js +1 -0
  217. package/layout/VStack/index.js.map +1 -1
  218. package/package.json +1 -1
  219. package/packages/types/src/Theme/resolvers.cjs +2 -55
  220. package/packages/types/src/Theme/resolvers.cjs.map +1 -1
  221. package/packages/types/src/Theme/resolvers.js +2 -55
  222. package/packages/types/src/Theme/resolvers.js.map +1 -1
  223. package/packages/types/src/Theme/semanticTokenNames.cjs +64 -0
  224. package/packages/types/src/Theme/semanticTokenNames.cjs.map +1 -0
  225. package/packages/types/src/Theme/semanticTokenNames.js +62 -0
  226. package/packages/types/src/Theme/semanticTokenNames.js.map +1 -0
  227. package/primitives/Box/index.cjs +1 -0
  228. package/primitives/Box/index.cjs.map +1 -1
  229. package/primitives/Box/index.js +1 -0
  230. package/primitives/Box/index.js.map +1 -1
  231. package/primitives/Card/index.cjs +1 -0
  232. package/primitives/Card/index.cjs.map +1 -1
  233. package/primitives/Card/index.js +1 -0
  234. package/primitives/Card/index.js.map +1 -1
  235. package/primitives/Divider/index.cjs +1 -0
  236. package/primitives/Divider/index.cjs.map +1 -1
  237. package/primitives/Divider/index.js +1 -0
  238. package/primitives/Divider/index.js.map +1 -1
  239. package/primitives/Heading/index.cjs +1 -0
  240. package/primitives/Heading/index.cjs.map +1 -1
  241. package/primitives/Heading/index.js +1 -0
  242. package/primitives/Heading/index.js.map +1 -1
  243. package/primitives/Image/index.cjs +1 -0
  244. package/primitives/Image/index.cjs.map +1 -1
  245. package/primitives/Image/index.js +1 -0
  246. package/primitives/Image/index.js.map +1 -1
  247. package/primitives/Skeleton/index.cjs +1 -0
  248. package/primitives/Skeleton/index.cjs.map +1 -1
  249. package/primitives/Skeleton/index.js +1 -0
  250. package/primitives/Skeleton/index.js.map +1 -1
  251. package/primitives/Svg/index.cjs +1 -0
  252. package/primitives/Svg/index.cjs.map +1 -1
  253. package/primitives/Svg/index.js +1 -0
  254. package/primitives/Svg/index.js.map +1 -1
  255. package/primitives/Text/index.cjs +1 -0
  256. package/primitives/Text/index.cjs.map +1 -1
  257. package/primitives/Text/index.js +1 -0
  258. package/primitives/Text/index.js.map +1 -1
@@ -45,181 +45,216 @@ const touchMidpoint = (event, origin) => {
45
45
  y: ((a.pageY ?? 0) + (b.pageY ?? 0)) / 2 - origin.y
46
46
  };
47
47
  };
48
- function ImageCropper(rawProps) {
49
- const { mergedProps: props, elementStyles } = useComponentDefaults.default("ImageCropper", rawProps);
50
- const el = useResolvedElementStyles.default(elementStyles);
51
- const {
52
- source,
53
- aspect = constants.DEFAULT_ASPECT,
54
- maskShape = "rectangle",
55
- output,
56
- onCrop,
57
- onCancel,
58
- onError,
59
- onInteractionChange,
60
- title,
61
- hidePreview,
62
- hideGrid,
63
- maxZoom = constants.DEFAULT_MAX_ZOOM,
64
- labels,
65
- testID
66
- } = props;
67
- const text = { ...constants.DEFAULT_LABELS, ...labels ?? {} };
68
- const ratio = geometry.aspectOf(aspect);
69
- const isCircle = maskShape === "circle";
70
- const { image, loading, error } = useImageSource_native.useImageSource(source, onError);
71
- const [viewport, setViewport] = React.useState({ width: 0, height: 0 });
72
- const originRef = React.useRef({ x: 0, y: 0 });
73
- const stageRef = React.useRef(null);
74
- const onStageLayout = React.useCallback((e) => {
75
- const { width, height } = e.nativeEvent.layout;
76
- setViewport({ width: Math.round(width), height: Math.round(height) });
77
- stageRef.current?.measureInWindow((x, y) => {
78
- originRef.current = { x, y };
79
- });
80
- }, []);
81
- const config = React.useMemo(
82
- () => ({
83
- source: image ? { width: image.width, height: image.height } : { width: 1, height: 1 },
84
- viewport: viewport.width > 0 ? viewport : { width: 1, height: 1 },
85
- ratio,
86
- maxZoom,
87
- // ⚠️ ALWAYS FALSE ON NATIVE — see the note at the top of the file.
88
- resizable: false,
89
- inset: constants.STAGE_INSET_NATIVE,
90
- squareFactor: constants.SQUARE_CROP_FACTOR
91
- }),
92
- [image, viewport, ratio, maxZoom]
93
- );
94
- const [state, dispatch] = React.useReducer(cropperState.cropperReducer, config, cropperState.initialCropperState);
95
- React.useEffect(() => {
96
- dispatch({ type: "configure", config });
97
- }, [config]);
98
- const ready = !!image && viewport.width > 1 && viewport.height > 1;
99
- const pinchRef = React.useRef(null);
100
- const lastPanRef = React.useRef({ x: 0, y: 0 });
101
- const scaleRef = React.useRef(state.transform.scale);
102
- scaleRef.current = state.transform.scale;
103
- const resetPan = React.useCallback(() => {
104
- lastPanRef.current = { x: 0, y: 0 };
105
- }, []);
106
- const beginGesture = React.useCallback(() => {
107
- pinchRef.current = null;
108
- lastPanRef.current = { x: 0, y: 0 };
109
- }, []);
110
- const applyPan = React.useCallback((dx, dy) => {
111
- dispatch({
112
- type: "pan",
113
- dx: dx - lastPanRef.current.x,
114
- dy: dy - lastPanRef.current.y
115
- });
116
- lastPanRef.current = { x: dx, y: dy };
117
- }, []);
118
- const applyPinch = React.useCallback((reading, anchor) => {
119
- if (!pinchZoom.isUsablePinchReading(reading)) return;
120
- const pinch = pinchRef.current;
121
- if (!pinch) {
122
- pinchRef.current = { reading, scale: scaleRef.current };
123
- return;
124
- }
125
- dispatch({ type: "zoomAt", anchor, scale: pinchZoom.pinchScale(pinch, reading) });
126
- }, []);
127
- const interactingRef = React.useRef(false);
128
- const setInteracting = React.useCallback(
129
- (active) => {
130
- if (interactingRef.current === active) return;
131
- interactingRef.current = active;
132
- onInteractionChange?.(active);
133
- },
134
- [onInteractionChange]
135
- );
136
- const detector = React.useMemo(() => {
137
- if (!gestureHandler) return null;
138
- const { Gesture } = gestureHandler;
139
- const pan = Gesture.Pan().runOnJS(true).onBegin(() => {
140
- setInteracting(true);
141
- resetPan();
142
- }).onUpdate((e) => {
143
- if (e.numberOfPointers > 1 || pinchRef.current) {
144
- lastPanRef.current = { x: e.translationX, y: e.translationY };
48
+ const ImageCropper = React.forwardRef(
49
+ function ImageCropper2(rawProps, ref) {
50
+ const { mergedProps: props, elementStyles } = useComponentDefaults.default("ImageCropper", rawProps);
51
+ const el = useResolvedElementStyles.default(elementStyles);
52
+ const {
53
+ source,
54
+ aspect = constants.DEFAULT_ASPECT,
55
+ maskShape = "rectangle",
56
+ output,
57
+ onCrop,
58
+ onCancel,
59
+ onError,
60
+ onInteractionChange,
61
+ title,
62
+ hidePreview,
63
+ hideFooter,
64
+ onFrameChange,
65
+ hideGrid,
66
+ maxZoom = constants.DEFAULT_MAX_ZOOM,
67
+ labels,
68
+ testID
69
+ } = props;
70
+ const text = { ...constants.DEFAULT_LABELS, ...labels ?? {} };
71
+ const ratio = geometry.aspectOf(aspect);
72
+ const isCircle = maskShape === "circle";
73
+ const { image, loading, error } = useImageSource_native.useImageSource(source, onError);
74
+ const [viewport, setViewport] = React.useState({ width: 0, height: 0 });
75
+ const originRef = React.useRef({ x: 0, y: 0 });
76
+ const stageRef = React.useRef(null);
77
+ const onStageLayout = React.useCallback((e) => {
78
+ const { width, height } = e.nativeEvent.layout;
79
+ setViewport({ width: Math.round(width), height: Math.round(height) });
80
+ stageRef.current?.measureInWindow((x, y) => {
81
+ originRef.current = { x, y };
82
+ });
83
+ }, []);
84
+ const config = React.useMemo(
85
+ () => ({
86
+ source: image ? { width: image.width, height: image.height } : { width: 1, height: 1 },
87
+ viewport: viewport.width > 0 ? viewport : { width: 1, height: 1 },
88
+ ratio,
89
+ maxZoom,
90
+ // ⚠️ ALWAYS FALSE ON NATIVE — see the note at the top of the file.
91
+ resizable: false,
92
+ inset: constants.STAGE_INSET_NATIVE,
93
+ squareFactor: constants.SQUARE_CROP_FACTOR
94
+ }),
95
+ [image, viewport, ratio, maxZoom]
96
+ );
97
+ const [state, dispatch] = React.useReducer(cropperState.cropperReducer, config, cropperState.initialCropperState);
98
+ React.useEffect(() => {
99
+ dispatch({ type: "configure", config });
100
+ }, [config]);
101
+ const ready = !!image && viewport.width > 1 && viewport.height > 1;
102
+ const pinchRef = React.useRef(null);
103
+ const lastPanRef = React.useRef({ x: 0, y: 0 });
104
+ const scaleRef = React.useRef(state.transform.scale);
105
+ scaleRef.current = state.transform.scale;
106
+ const resetPan = React.useCallback(() => {
107
+ lastPanRef.current = { x: 0, y: 0 };
108
+ }, []);
109
+ const beginGesture = React.useCallback(() => {
110
+ pinchRef.current = null;
111
+ lastPanRef.current = { x: 0, y: 0 };
112
+ }, []);
113
+ const applyPan = React.useCallback((dx, dy) => {
114
+ dispatch({
115
+ type: "pan",
116
+ dx: dx - lastPanRef.current.x,
117
+ dy: dy - lastPanRef.current.y
118
+ });
119
+ lastPanRef.current = { x: dx, y: dy };
120
+ }, []);
121
+ const applyPinch = React.useCallback((reading, anchor) => {
122
+ if (!pinchZoom.isUsablePinchReading(reading)) return;
123
+ const pinch = pinchRef.current;
124
+ if (!pinch) {
125
+ pinchRef.current = { reading, scale: scaleRef.current };
145
126
  return;
146
127
  }
147
- applyPan(e.translationX, e.translationY);
148
- }).onFinalize(() => {
149
- setInteracting(false);
150
- resetPan();
151
- });
152
- const pinch = Gesture.Pinch().runOnJS(true).onBegin(() => {
153
- setInteracting(true);
154
- pinchRef.current = { reading: 1, scale: scaleRef.current };
155
- }).onUpdate((e) => {
156
- applyPinch(e.scale, { x: e.focalX, y: e.focalY });
157
- }).onFinalize(() => {
158
- setInteracting(false);
159
- pinchRef.current = null;
160
- });
161
- return Gesture.Simultaneous(pan, pinch);
162
- }, [applyPan, applyPinch, resetPan, setInteracting]);
163
- const scrollBlocker = React.useMemo(
164
- () => reactNative.PanResponder.create({
165
- onStartShouldSetPanResponder: () => true,
166
- onMoveShouldSetPanResponder: () => true,
167
- onPanResponderTerminationRequest: () => false,
168
- onShouldBlockNativeResponder: () => true,
169
- onPanResponderGrant: () => setInteracting(true),
170
- onPanResponderRelease: () => setInteracting(false),
171
- onPanResponderTerminate: () => setInteracting(false)
172
- }),
173
- [setInteracting]
174
- );
175
- const responder = React.useMemo(
176
- () => reactNative.PanResponder.create({
177
- onStartShouldSetPanResponder: () => true,
178
- onMoveShouldSetPanResponder: () => true,
179
- // ⚠️ Refuses a JS-level handover. It does NOT save a vertical
180
- // drag inside a ScrollView — that cancellation is native and
181
- // never asks — but it does hold the gesture against JS
182
- // responders nearer the touch.
183
- onPanResponderTerminationRequest: () => false,
184
- onPanResponderGrant: () => {
185
- setInteracting(true);
186
- beginGesture();
128
+ dispatch({ type: "zoomAt", anchor, scale: pinchZoom.pinchScale(pinch, reading) });
129
+ }, []);
130
+ const interactingRef = React.useRef(false);
131
+ const setInteracting = React.useCallback(
132
+ (active) => {
133
+ if (interactingRef.current === active) return;
134
+ interactingRef.current = active;
135
+ onInteractionChange?.(active);
187
136
  },
188
- onPanResponderMove: (event, gesture) => {
189
- const touches = event.nativeEvent.touches.length;
190
- if (touches >= 2) {
191
- applyPinch(
192
- touchDistance(event),
193
- touchMidpoint(event, originRef.current)
194
- );
195
- return;
196
- }
197
- if (pinchRef.current) {
198
- pinchRef.current = null;
199
- lastPanRef.current = { x: gesture.dx, y: gesture.dy };
137
+ [onInteractionChange]
138
+ );
139
+ const detector = React.useMemo(() => {
140
+ if (!gestureHandler) return null;
141
+ const { Gesture } = gestureHandler;
142
+ const pan = Gesture.Pan().runOnJS(true).onBegin(() => {
143
+ setInteracting(true);
144
+ resetPan();
145
+ }).onUpdate((e) => {
146
+ if (e.numberOfPointers > 1 || pinchRef.current) {
147
+ lastPanRef.current = { x: e.translationX, y: e.translationY };
200
148
  return;
201
149
  }
202
- applyPan(gesture.dx, gesture.dy);
203
- },
204
- onPanResponderRelease: () => {
150
+ applyPan(e.translationX, e.translationY);
151
+ }).onFinalize(() => {
205
152
  setInteracting(false);
206
- beginGesture();
207
- },
208
- onPanResponderTerminate: () => {
153
+ resetPan();
154
+ });
155
+ const pinch = Gesture.Pinch().runOnJS(true).onBegin(() => {
156
+ setInteracting(true);
157
+ pinchRef.current = { reading: 1, scale: scaleRef.current };
158
+ }).onUpdate((e) => {
159
+ applyPinch(e.scale, { x: e.focalX, y: e.focalY });
160
+ }).onFinalize(() => {
209
161
  setInteracting(false);
210
- beginGesture();
162
+ pinchRef.current = null;
163
+ });
164
+ return Gesture.Simultaneous(pan, pinch);
165
+ }, [applyPan, applyPinch, resetPan, setInteracting]);
166
+ const scrollBlocker = React.useMemo(
167
+ () => reactNative.PanResponder.create({
168
+ onStartShouldSetPanResponder: () => true,
169
+ onMoveShouldSetPanResponder: () => true,
170
+ onPanResponderTerminationRequest: () => false,
171
+ onShouldBlockNativeResponder: () => true,
172
+ onPanResponderGrant: () => setInteracting(true),
173
+ onPanResponderRelease: () => setInteracting(false),
174
+ onPanResponderTerminate: () => setInteracting(false)
175
+ }),
176
+ [setInteracting]
177
+ );
178
+ const responder = React.useMemo(
179
+ () => reactNative.PanResponder.create({
180
+ onStartShouldSetPanResponder: () => true,
181
+ onMoveShouldSetPanResponder: () => true,
182
+ // ⚠️ Refuses a JS-level handover. It does NOT save a vertical
183
+ // drag inside a ScrollView — that cancellation is native and
184
+ // never asks — but it does hold the gesture against JS
185
+ // responders nearer the touch.
186
+ onPanResponderTerminationRequest: () => false,
187
+ onPanResponderGrant: () => {
188
+ setInteracting(true);
189
+ beginGesture();
190
+ },
191
+ onPanResponderMove: (event, gesture) => {
192
+ const touches = event.nativeEvent.touches.length;
193
+ if (touches >= 2) {
194
+ applyPinch(
195
+ touchDistance(event),
196
+ touchMidpoint(event, originRef.current)
197
+ );
198
+ return;
199
+ }
200
+ if (pinchRef.current) {
201
+ pinchRef.current = null;
202
+ lastPanRef.current = { x: gesture.dx, y: gesture.dy };
203
+ return;
204
+ }
205
+ applyPan(gesture.dx, gesture.dy);
206
+ },
207
+ onPanResponderRelease: () => {
208
+ setInteracting(false);
209
+ beginGesture();
210
+ },
211
+ onPanResponderTerminate: () => {
212
+ setInteracting(false);
213
+ beginGesture();
214
+ }
215
+ }),
216
+ [applyPan, applyPinch, beginGesture, setInteracting]
217
+ );
218
+ const [busy, setBusy] = React.useState(false);
219
+ const canSave = ready && !busy && encoderRegistry.hasCropEncoder();
220
+ const save = React.useCallback(async () => {
221
+ if (!ready || !image || busy) return;
222
+ setBusy(true);
223
+ try {
224
+ const rect = geometry.cropToSourceRect(state.crop, state.transform, config.source, state.rotation);
225
+ const format = output?.format ?? constants.DEFAULT_FORMAT;
226
+ const result = await encode_native.encodeCropNative(
227
+ image.uri,
228
+ rect,
229
+ config.source,
230
+ state.rotation,
231
+ {
232
+ width: output?.maxWidth,
233
+ height: output?.maxHeight,
234
+ bytes: output?.maxBytes ?? constants.DEFAULT_MAX_BYTES
235
+ },
236
+ format
237
+ );
238
+ if (!result.withinBudget) {
239
+ onError?.({
240
+ reason: "too-large",
241
+ message: `The smallest version this could produce is ${Math.round(result.size / 1024)} KB.`
242
+ });
243
+ }
244
+ const { withinBudget: _ignored, ...payload } = result;
245
+ onCrop?.(payload);
246
+ } catch (cause) {
247
+ onError?.({
248
+ reason: "encode-failed",
249
+ message: cause instanceof Error ? cause.message : "The image could not be saved."
250
+ });
251
+ } finally {
252
+ setBusy(false);
211
253
  }
212
- }),
213
- [applyPan, applyPinch, beginGesture, setInteracting]
214
- );
215
- const [busy, setBusy] = React.useState(false);
216
- const canSave = ready && !busy && encoderRegistry.hasCropEncoder();
217
- const save = React.useCallback(async () => {
218
- if (!ready || !image || busy) return;
219
- setBusy(true);
220
- try {
254
+ }, [ready, image, busy, state, config.source, output, onCrop, onError]);
255
+ const estimate = React.useCallback(async () => {
256
+ if (!ready || !image || !encoderRegistry.hasCropEncoder()) return null;
221
257
  const rect = geometry.cropToSourceRect(state.crop, state.transform, config.source, state.rotation);
222
- const format = output?.format ?? constants.DEFAULT_FORMAT;
223
258
  const result = await encode_native.encodeCropNative(
224
259
  image.uri,
225
260
  rect,
@@ -230,411 +265,410 @@ function ImageCropper(rawProps) {
230
265
  height: output?.maxHeight,
231
266
  bytes: output?.maxBytes ?? constants.DEFAULT_MAX_BYTES
232
267
  },
233
- format
268
+ output?.format ?? constants.DEFAULT_FORMAT
234
269
  );
235
- if (!result.withinBudget) {
236
- onError?.({
237
- reason: "too-large",
238
- message: `The smallest version this could produce is ${Math.round(result.size / 1024)} KB.`
239
- });
240
- }
241
- const { withinBudget: _ignored, ...payload } = result;
242
- onCrop?.(payload);
243
- } catch (cause) {
244
- onError?.({
245
- reason: "encode-failed",
246
- message: cause instanceof Error ? cause.message : "The image could not be saved."
270
+ return result.size;
271
+ }, [ready, image, state, config.source, output]);
272
+ React.useImperativeHandle(ref, () => ({ save: () => void save(), estimate }), [save, estimate]);
273
+ const frameCallbackRef = React.useRef(onFrameChange);
274
+ frameCallbackRef.current = onFrameChange;
275
+ React.useEffect(() => {
276
+ if (!ready || !image) return;
277
+ frameCallbackRef.current?.({
278
+ crop: state.crop,
279
+ transform: state.transform,
280
+ rotation: state.rotation,
281
+ source: { width: image.width, height: image.height },
282
+ oriented: geometry.orientedSize({ width: image.width, height: image.height }, state.rotation)
247
283
  });
248
- } finally {
249
- setBusy(false);
250
- }
251
- }, [ready, image, busy, state, config.source, output, onCrop, onError]);
252
- const { crop, transform, rotation } = state;
253
- const oriented = image ? geometry.orientedSize({ width: image.width, height: image.height }, rotation) : null;
254
- const band = cropperState.scaleRange(state);
255
- const zoomLabel = Number.isFinite(band.min) && band.min > 0 ? `${Math.round(transform.scale / band.min * 100)}%` : "100%";
256
- const windowRadius = isCircle ? Math.max(crop.width, crop.height) / 2 : Number(el.mask?.borderRadius ?? 0);
257
- const SCRIM = Math.ceil(Math.max(viewport.width, viewport.height)) || 1;
258
- const aspectLabel = `${aspect[0]}:${aspect[1]}`;
259
- const subtitle = ready ? "Drag to move \xB7 pinch to zoom" : isCircle ? `Circle \xB7 ${aspectLabel}` : `Banner \xB7 ${aspectLabel}`;
260
- const budgetKb = Math.round((output?.maxBytes ?? constants.DEFAULT_MAX_BYTES) / 1024);
261
- const previewWidth = isCircle ? 60 : 160;
262
- const previewHeight = isCircle ? 60 : Math.round(160 / ratio);
263
- const stageTool = (icon, label, onPress) => /* @__PURE__ */ jsxRuntime.jsxs(
264
- reactNative.Pressable,
265
- {
266
- onPress,
267
- disabled: !ready,
268
- accessibilityRole: "button",
269
- accessibilityLabel: label,
270
- accessibilityState: { disabled: !ready },
271
- style: { width: 58, alignItems: "center", gap: 4 },
272
- children: [
273
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: {
274
- width: 44,
275
- height: 44,
276
- borderRadius: 22,
277
- backgroundColor: "rgba(255,255,255,0.09)",
278
- alignItems: "center",
279
- justifyContent: "center"
280
- }, children: /* @__PURE__ */ jsxRuntime.jsx(index_native.Icon, { name: icon, size: 19, color: "#FFFFFF" }) }),
281
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 11, fontWeight: "600", color: "rgba(255,255,255,0.75)" }, children: label })
282
- ]
283
- }
284
- );
285
- return /* @__PURE__ */ jsxRuntime.jsxs(
286
- reactNative.View,
287
- {
288
- testID,
289
- style: [
290
- /**
291
- * ⚠️ NO `flex: 1` HERE, THOUGH THE MOCKUP IS FULL-SCREEN. A
292
- * child with `flex: 1` inside a parent that was never given a
293
- * height measures 0 on Yoga, and this component does not own
294
- * the screen — a consumer mounts it in a modal, a sheet or a
295
- * form. The footer's `marginTop: 'auto'` gets the mockup's
296
- * pinned-to-the-bottom footer when the parent DOES bound the
297
- * height, and does nothing when it does not.
298
- */
299
- { width: "100%" },
300
- el.root,
301
- /**
302
- * ⛔ THE PHONE CROPPER IS A DARK SHEET, AND THAT OVERRIDES THE
303
- * `root` SLOT ON NATIVE ONLY. The mockup draws web as a light
304
- * modal card and the phone as a full-screen dark surface, and
305
- * one `root` token cannot be both. It has to be the dark one
306
- * here because the controls below it are load-bearing white:
307
- * on a light sheet the Rotate and Reset labels vanish.
308
- *
309
- * ⚠️ SO `slots.root.backgroundColor` DOES NOT REACH THIS ON
310
- * NATIVE. That is the deliberate cost; the stage's own colour
311
- * has carried the same argument since the component shipped.
312
- */
313
- { backgroundColor: "#0B1517" }
314
- ],
315
- children: [
316
- title != null && /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [
317
- { alignItems: "center" },
318
- el.header,
319
- {
320
- paddingLeft: 18,
321
- paddingRight: 18,
322
- paddingTop: 12,
323
- paddingBottom: 14,
324
- borderBottomWidth: 0,
325
- gap: 0
326
- }
327
- ], children: [
328
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [el.title, { fontSize: 16, fontWeight: "800", color: "#FFFFFF" }], children: title }),
329
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { marginTop: 3, fontSize: 12, color: "rgba(255,255,255,0.6)" }, children: subtitle })
330
- ] }),
331
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [el.content, { paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0 }], children: [
332
- /* @__PURE__ */ jsxRuntime.jsx(StageFrame, { gesture: detector, children: /* @__PURE__ */ jsxRuntime.jsxs(
333
- reactNative.View,
284
+ }, [ready, image, state]);
285
+ const { crop, transform, rotation } = state;
286
+ const oriented = image ? geometry.orientedSize({ width: image.width, height: image.height }, rotation) : null;
287
+ const band = cropperState.scaleRange(state);
288
+ const zoomLabel = Number.isFinite(band.min) && band.min > 0 ? `${Math.round(transform.scale / band.min * 100)}%` : "100%";
289
+ const windowRadius = isCircle ? Math.max(crop.width, crop.height) / 2 : Number(el.mask?.borderRadius ?? 0);
290
+ const SCRIM = Math.ceil(Math.max(viewport.width, viewport.height)) || 1;
291
+ const aspectLabel = `${aspect[0]}:${aspect[1]}`;
292
+ const subtitle = ready ? "Drag to move \xB7 pinch to zoom" : isCircle ? `Circle \xB7 ${aspectLabel}` : `Banner \xB7 ${aspectLabel}`;
293
+ const budgetKb = Math.round((output?.maxBytes ?? constants.DEFAULT_MAX_BYTES) / 1024);
294
+ const previewWidth = isCircle ? 60 : 160;
295
+ const previewHeight = isCircle ? 60 : Math.round(160 / ratio);
296
+ const stageTool = (icon, label, onPress) => /* @__PURE__ */ jsxRuntime.jsxs(
297
+ reactNative.Pressable,
298
+ {
299
+ onPress,
300
+ disabled: !ready,
301
+ accessibilityRole: "button",
302
+ accessibilityLabel: label,
303
+ accessibilityState: { disabled: !ready },
304
+ style: { width: 58, alignItems: "center", gap: 4 },
305
+ children: [
306
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: {
307
+ width: 44,
308
+ height: 44,
309
+ borderRadius: 22,
310
+ backgroundColor: "rgba(255,255,255,0.09)",
311
+ alignItems: "center",
312
+ justifyContent: "center"
313
+ }, children: /* @__PURE__ */ jsxRuntime.jsx(index_native.Icon, { name: icon, size: 19, color: "#FFFFFF" }) }),
314
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 11, fontWeight: "600", color: "rgba(255,255,255,0.75)" }, children: label })
315
+ ]
316
+ }
317
+ );
318
+ return /* @__PURE__ */ jsxRuntime.jsxs(
319
+ reactNative.View,
320
+ {
321
+ testID,
322
+ style: [
323
+ /**
324
+ * ⚠️ NO `flex: 1` HERE, THOUGH THE MOCKUP IS FULL-SCREEN. A
325
+ * child with `flex: 1` inside a parent that was never given a
326
+ * height measures 0 on Yoga, and this component does not own
327
+ * the screen — a consumer mounts it in a modal, a sheet or a
328
+ * form. The footer's `marginTop: 'auto'` gets the mockup's
329
+ * pinned-to-the-bottom footer when the parent DOES bound the
330
+ * height, and does nothing when it does not.
331
+ */
332
+ { width: "100%" },
333
+ el.root,
334
+ /**
335
+ * ⛔ THE PHONE CROPPER IS A DARK SHEET, AND THAT OVERRIDES THE
336
+ * `root` SLOT ON NATIVE ONLY. The mockup draws web as a light
337
+ * modal card and the phone as a full-screen dark surface, and
338
+ * one `root` token cannot be both. It has to be the dark one
339
+ * here because the controls below it are load-bearing white:
340
+ * on a light sheet the Rotate and Reset labels vanish.
341
+ *
342
+ * ⚠️ SO `slots.root.backgroundColor` DOES NOT REACH THIS ON
343
+ * NATIVE. That is the deliberate cost; the stage's own colour
344
+ * has carried the same argument since the component shipped.
345
+ */
346
+ { backgroundColor: "#0B1517" }
347
+ ],
348
+ children: [
349
+ title != null && /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [
350
+ { alignItems: "center" },
351
+ el.header,
334
352
  {
335
- ref: stageRef,
336
- onLayout: onStageLayout,
337
- ...detector ? scrollBlocker.panHandlers : responder.panHandlers,
338
- style: [
339
- { width: "100%", aspectRatio: constants.STAGE_ASPECT_NATIVE, overflow: "hidden" },
340
- el.container
341
- ],
342
- children: [
343
- ready && image && oriented && /* @__PURE__ */ jsxRuntime.jsx(
344
- reactNative.Image,
345
- {
346
- source: { uri: image.uri },
347
- resizeMode: "stretch",
348
- style: {
349
- position: "absolute",
350
- left: transform.x,
351
- top: transform.y,
352
- width: oriented.width * transform.scale,
353
- height: oriented.height * transform.scale,
354
- transform: [{ rotate: `${rotation}deg` }]
355
- }
356
- }
357
- ),
358
- (!source || error) && /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [
359
- {
360
- position: "absolute",
361
- left: 18,
362
- top: 18,
363
- right: 18,
364
- bottom: 18,
365
- alignItems: "center",
366
- justifyContent: "center"
367
- },
368
- el.dropZone,
369
- { borderRadius: 18 }
370
- ], children: [
371
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: {
372
- width: 60,
373
- height: 60,
374
- borderRadius: 30,
375
- backgroundColor: "rgba(32,170,176,0.16)",
376
- alignItems: "center",
377
- justifyContent: "center"
378
- }, children: /* @__PURE__ */ jsxRuntime.jsx(index_native.Icon, { name: "image", size: 26, color: constants.CROP_ACCENT_SOFT }) }),
379
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 16, fontWeight: "800", color: "#FFFFFF", textAlign: "center" }, children: text.dropZone }),
380
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 12.5, color: "rgba(255,255,255,0.6)", textAlign: "center" }, children: "JPG, PNG or WebP" }),
381
- error && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: {
382
- fontSize: 12.5,
383
- fontWeight: "600",
384
- color: constants.CROP_DANGER,
385
- textAlign: "center"
386
- }, children: error.message })
387
- ] }),
388
- ready && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
389
- /* @__PURE__ */ jsxRuntime.jsx(
390
- reactNative.View,
353
+ paddingLeft: 18,
354
+ paddingRight: 18,
355
+ paddingTop: 12,
356
+ paddingBottom: 14,
357
+ borderBottomWidth: 0,
358
+ gap: 0
359
+ }
360
+ ], children: [
361
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [el.title, { fontSize: 16, fontWeight: "800", color: "#FFFFFF" }], children: title }),
362
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { marginTop: 3, fontSize: 12, color: "rgba(255,255,255,0.6)" }, children: subtitle })
363
+ ] }),
364
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [el.content, { paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0 }], children: [
365
+ /* @__PURE__ */ jsxRuntime.jsx(StageFrame, { gesture: detector, children: /* @__PURE__ */ jsxRuntime.jsxs(
366
+ reactNative.View,
367
+ {
368
+ ref: stageRef,
369
+ onLayout: onStageLayout,
370
+ ...detector ? scrollBlocker.panHandlers : responder.panHandlers,
371
+ style: [
372
+ { width: "100%", aspectRatio: constants.STAGE_ASPECT_NATIVE, overflow: "hidden" },
373
+ el.container
374
+ ],
375
+ children: [
376
+ ready && image && oriented && /* @__PURE__ */ jsxRuntime.jsx(
377
+ reactNative.Image,
391
378
  {
392
- pointerEvents: "none",
393
- style: [
394
- {
395
- position: "absolute",
396
- left: crop.x - SCRIM,
397
- top: crop.y - SCRIM,
398
- width: crop.width + SCRIM * 2,
399
- height: crop.height + SCRIM * 2,
400
- borderWidth: SCRIM,
401
- borderColor: el.shade?.backgroundColor ?? "rgba(0,0,0,0.6)",
402
- borderRadius: windowRadius + SCRIM
403
- }
404
- ]
379
+ source: { uri: image.uri },
380
+ resizeMode: "stretch",
381
+ style: {
382
+ position: "absolute",
383
+ left: transform.x,
384
+ top: transform.y,
385
+ width: oriented.width * transform.scale,
386
+ height: oriented.height * transform.scale,
387
+ transform: [{ rotate: `${rotation}deg` }]
388
+ }
405
389
  }
406
390
  ),
407
- /* @__PURE__ */ jsxRuntime.jsx(
408
- reactNative.View,
391
+ (!source || error) && /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [
409
392
  {
410
- pointerEvents: "none",
411
- style: [
412
- {
413
- position: "absolute",
414
- left: crop.x,
415
- top: crop.y,
416
- width: crop.width,
417
- height: crop.height,
418
- borderStyle: "solid"
419
- },
420
- el.cropWindow,
421
- { borderRadius: windowRadius }
422
- ],
423
- children: !hideGrid && [1, 2].map((n) => /* @__PURE__ */ jsxRuntime.jsxs(React.Fragment, { children: [
424
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
393
+ position: "absolute",
394
+ left: 18,
395
+ top: 18,
396
+ right: 18,
397
+ bottom: 18,
398
+ alignItems: "center",
399
+ justifyContent: "center"
400
+ },
401
+ el.dropZone,
402
+ { borderRadius: 18 }
403
+ ], children: [
404
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: {
405
+ width: 60,
406
+ height: 60,
407
+ borderRadius: 30,
408
+ backgroundColor: "rgba(32,170,176,0.16)",
409
+ alignItems: "center",
410
+ justifyContent: "center"
411
+ }, children: /* @__PURE__ */ jsxRuntime.jsx(index_native.Icon, { name: "image", size: 26, color: constants.CROP_ACCENT_SOFT }) }),
412
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 16, fontWeight: "800", color: "#FFFFFF", textAlign: "center" }, children: text.dropZone }),
413
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 12.5, color: "rgba(255,255,255,0.6)", textAlign: "center" }, children: "JPG, PNG or WebP" }),
414
+ error && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: {
415
+ fontSize: 12.5,
416
+ fontWeight: "600",
417
+ color: constants.CROP_DANGER,
418
+ textAlign: "center"
419
+ }, children: error.message })
420
+ ] }),
421
+ ready && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
422
+ /* @__PURE__ */ jsxRuntime.jsx(
423
+ reactNative.View,
424
+ {
425
+ pointerEvents: "none",
426
+ style: [
425
427
  {
426
428
  position: "absolute",
427
- top: 0,
428
- bottom: 0,
429
- width: 1,
430
- left: crop.width * n / 3
431
- },
432
- el.gridLine
433
- ] }),
434
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
429
+ left: crop.x - SCRIM,
430
+ top: crop.y - SCRIM,
431
+ width: crop.width + SCRIM * 2,
432
+ height: crop.height + SCRIM * 2,
433
+ borderWidth: SCRIM,
434
+ borderColor: el.shade?.backgroundColor ?? "rgba(0,0,0,0.6)",
435
+ borderRadius: windowRadius + SCRIM
436
+ }
437
+ ]
438
+ }
439
+ ),
440
+ /* @__PURE__ */ jsxRuntime.jsx(
441
+ reactNative.View,
442
+ {
443
+ pointerEvents: "none",
444
+ style: [
435
445
  {
436
446
  position: "absolute",
437
- left: 0,
438
- right: 0,
439
- height: 1,
440
- top: crop.height * n / 3
447
+ left: crop.x,
448
+ top: crop.y,
449
+ width: crop.width,
450
+ height: crop.height,
451
+ borderStyle: "solid"
441
452
  },
442
- el.gridLine
443
- ] })
444
- ] }, n))
445
- }
446
- ),
447
- /* @__PURE__ */ jsxRuntime.jsx(
453
+ el.cropWindow,
454
+ { borderRadius: windowRadius }
455
+ ],
456
+ children: !hideGrid && [1, 2].map((n) => /* @__PURE__ */ jsxRuntime.jsxs(React.Fragment, { children: [
457
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
458
+ {
459
+ position: "absolute",
460
+ top: 0,
461
+ bottom: 0,
462
+ width: 1,
463
+ left: crop.width * n / 3
464
+ },
465
+ el.gridLine
466
+ ] }),
467
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
468
+ {
469
+ position: "absolute",
470
+ left: 0,
471
+ right: 0,
472
+ height: 1,
473
+ top: crop.height * n / 3
474
+ },
475
+ el.gridLine
476
+ ] })
477
+ ] }, n))
478
+ }
479
+ ),
480
+ /* @__PURE__ */ jsxRuntime.jsx(
481
+ reactNative.View,
482
+ {
483
+ pointerEvents: "none",
484
+ style: { position: "absolute", top: 12, left: 0, right: 0, alignItems: "center" },
485
+ children: /* @__PURE__ */ jsxRuntime.jsx(
486
+ reactNative.View,
487
+ {
488
+ style: [
489
+ { alignItems: "center", justifyContent: "center" },
490
+ el.zoomChip
491
+ ],
492
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: {
493
+ color: el.zoomChip?.color ?? "#FFFFFF",
494
+ fontSize: 11.5,
495
+ fontWeight: "700"
496
+ }, children: zoomLabel })
497
+ }
498
+ )
499
+ }
500
+ )
501
+ ] }),
502
+ (loading || busy) && /* @__PURE__ */ jsxRuntime.jsxs(
448
503
  reactNative.View,
449
504
  {
450
505
  pointerEvents: "none",
451
- style: { position: "absolute", top: 12, left: 0, right: 0, alignItems: "center" },
452
- children: /* @__PURE__ */ jsxRuntime.jsx(
453
- reactNative.View,
454
- {
455
- style: [
456
- { alignItems: "center", justifyContent: "center" },
457
- el.zoomChip
458
- ],
459
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: {
460
- color: el.zoomChip?.color ?? "#FFFFFF",
461
- fontSize: 11.5,
462
- fontWeight: "700"
463
- }, children: zoomLabel })
464
- }
465
- )
506
+ style: {
507
+ position: "absolute",
508
+ left: 0,
509
+ top: 0,
510
+ right: 0,
511
+ bottom: 0,
512
+ backgroundColor: "rgba(6,14,16,0.74)",
513
+ alignItems: "center",
514
+ justifyContent: "center",
515
+ gap: 14
516
+ },
517
+ children: [
518
+ /* @__PURE__ */ jsxRuntime.jsx(
519
+ reactNative.ActivityIndicator,
520
+ {
521
+ size: "large",
522
+ color: el.progressFill?.backgroundColor ?? constants.CROP_ACCENT
523
+ }
524
+ ),
525
+ busy && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 14, fontWeight: "700", color: "#FFFFFF" }, children: text.processing })
526
+ ]
466
527
  }
467
528
  )
468
- ] }),
469
- (loading || busy) && /* @__PURE__ */ jsxRuntime.jsxs(
470
- reactNative.View,
471
- {
472
- pointerEvents: "none",
473
- style: {
474
- position: "absolute",
475
- left: 0,
476
- top: 0,
477
- right: 0,
478
- bottom: 0,
479
- backgroundColor: "rgba(6,14,16,0.74)",
480
- alignItems: "center",
481
- justifyContent: "center",
482
- gap: 14
483
- },
484
- children: [
485
- /* @__PURE__ */ jsxRuntime.jsx(
486
- reactNative.ActivityIndicator,
487
- {
488
- size: "large",
489
- color: el.progressFill?.backgroundColor ?? constants.CROP_ACCENT
490
- }
491
- ),
492
- busy && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 14, fontWeight: "700", color: "#FFFFFF" }, children: text.processing })
493
- ]
494
- }
495
- )
496
- ]
497
- }
498
- ) }),
499
- /* @__PURE__ */ jsxRuntime.jsxs(
500
- reactNative.View,
501
- {
502
- pointerEvents: ready ? "auto" : "none",
503
- style: {
504
- paddingTop: 16,
505
- paddingLeft: 18,
506
- paddingRight: 18,
507
- gap: 16,
508
- opacity: ready ? 1 : 0.4
509
- },
510
- children: [
511
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [
512
- !hidePreview && ready && image && oriented && /* The preview is the same image, clipped and offset so the
513
- crop fills the frame — no pixel work, and therefore
514
- nothing that has to wait for an encoder. */
515
- /* @__PURE__ */ jsxRuntime.jsx(
516
- reactNative.View,
517
- {
518
- style: [
519
- { width: previewWidth, height: previewHeight, overflow: "hidden" },
520
- el.previewFrame,
521
- isCircle ? { borderRadius: previewWidth / 2 } : null,
522
- /* ⚠️ A 2px RING, NOT THE CARD'S 4. The slot
523
- carries the desktop preview's ring; at 60pt
524
- a 4pt ring eats the thumbnail. */
525
- { borderWidth: 2 },
526
- /* The mockup's float, in the native spelling —
527
- `boxShadow` is the web file's half of this. */
528
- {
529
- shadowColor: "#000000",
530
- shadowOpacity: 0.14,
531
- shadowRadius: 14,
532
- shadowOffset: { width: 0, height: 4 },
533
- elevation: 4
534
- }
535
- ],
536
- children: /* @__PURE__ */ jsxRuntime.jsx(
537
- reactNative.Image,
538
- {
539
- source: { uri: image.uri },
540
- resizeMode: "stretch",
541
- style: {
542
- position: "absolute",
543
- width: oriented.width * transform.scale * (previewWidth / crop.width),
544
- height: oriented.height * transform.scale * (previewWidth / crop.width),
545
- left: (transform.x - crop.x) * (previewWidth / crop.width),
546
- top: (transform.y - crop.y) * (previewWidth / crop.width),
547
- transform: [{ rotate: `${rotation}deg` }]
529
+ ]
530
+ }
531
+ ) }),
532
+ /* @__PURE__ */ jsxRuntime.jsxs(
533
+ reactNative.View,
534
+ {
535
+ pointerEvents: ready ? "auto" : "none",
536
+ style: {
537
+ paddingTop: 16,
538
+ paddingLeft: 18,
539
+ paddingRight: 18,
540
+ gap: 16,
541
+ opacity: ready ? 1 : 0.4
542
+ },
543
+ children: [
544
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [
545
+ !hidePreview && ready && image && oriented && /* The preview is the same image, clipped and offset so the
546
+ crop fills the frame — no pixel work, and therefore
547
+ nothing that has to wait for an encoder. */
548
+ /* @__PURE__ */ jsxRuntime.jsx(
549
+ reactNative.View,
550
+ {
551
+ style: [
552
+ { width: previewWidth, height: previewHeight, overflow: "hidden" },
553
+ el.previewFrame,
554
+ isCircle ? { borderRadius: previewWidth / 2 } : null,
555
+ /* ⚠️ A 2px RING, NOT THE CARD'S 4. The slot
556
+ carries the desktop preview's ring; at 60pt
557
+ a 4pt ring eats the thumbnail. */
558
+ { borderWidth: 2 },
559
+ /* The mockup's float, in the native spelling —
560
+ `boxShadow` is the web file's half of this. */
561
+ {
562
+ shadowColor: "#000000",
563
+ shadowOpacity: 0.14,
564
+ shadowRadius: 14,
565
+ shadowOffset: { width: 0, height: 4 },
566
+ elevation: 4
548
567
  }
549
- }
550
- )
551
- }
552
- ),
553
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1, alignItems: "flex-end" }, children: [
554
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 17, fontWeight: "800", color: "#FFFFFF" }, children: zoomLabel }),
555
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { marginTop: 2, fontSize: 11.5, color: "rgba(255,255,255,0.6)" }, children: `Under ${budgetKb} KB` })
568
+ ],
569
+ children: /* @__PURE__ */ jsxRuntime.jsx(
570
+ reactNative.Image,
571
+ {
572
+ source: { uri: image.uri },
573
+ resizeMode: "stretch",
574
+ style: {
575
+ position: "absolute",
576
+ width: oriented.width * transform.scale * (previewWidth / crop.width),
577
+ height: oriented.height * transform.scale * (previewWidth / crop.width),
578
+ left: (transform.x - crop.x) * (previewWidth / crop.width),
579
+ top: (transform.y - crop.y) * (previewWidth / crop.width),
580
+ transform: [{ rotate: `${rotation}deg` }]
581
+ }
582
+ }
583
+ )
584
+ }
585
+ ),
586
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1, alignItems: "flex-end" }, children: [
587
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 17, fontWeight: "800", color: "#FFFFFF" }, children: zoomLabel }),
588
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { marginTop: 2, fontSize: 11.5, color: "rgba(255,255,255,0.6)" }, children: `Under ${budgetKb} KB` })
589
+ ] })
590
+ ] }),
591
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", alignItems: "center", gap: 10 }, children: [
592
+ stageTool("rotate-cw", text.rotate, () => dispatch({ type: "rotate" })),
593
+ /* @__PURE__ */ jsxRuntime.jsx(
594
+ Slider,
595
+ {
596
+ style: { flex: 1 },
597
+ minimumValue: 0,
598
+ maximumValue: 1,
599
+ value: cropperState.zoomFraction(state),
600
+ disabled: !ready,
601
+ minimumTrackTintColor: el.thumb?.backgroundColor,
602
+ maximumTrackTintColor: el.track?.backgroundColor,
603
+ thumbTintColor: el.thumb?.backgroundColor,
604
+ onValueChange: (value) => dispatch({ type: "setZoom", value })
605
+ }
606
+ ),
607
+ stageTool("rotate-ccw", text.reset, () => dispatch({ type: "reset" }))
556
608
  ] })
557
- ] }),
558
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", alignItems: "center", gap: 10 }, children: [
559
- stageTool("rotate-cw", text.rotate, () => dispatch({ type: "rotate" })),
560
- /* @__PURE__ */ jsxRuntime.jsx(
561
- Slider,
562
- {
563
- style: { flex: 1 },
564
- minimumValue: 0,
565
- maximumValue: 1,
566
- value: cropperState.zoomFraction(state),
567
- disabled: !ready,
568
- minimumTrackTintColor: el.thumb?.backgroundColor,
569
- maximumTrackTintColor: el.track?.backgroundColor,
570
- thumbTintColor: el.thumb?.backgroundColor,
571
- onValueChange: (value) => dispatch({ type: "setZoom", value })
572
- }
573
- ),
574
- stageTool("rotate-ccw", text.reset, () => dispatch({ type: "reset" }))
575
- ] })
576
- ]
577
- }
578
- )
579
- ] }),
580
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [
581
- { flexDirection: "row", alignItems: "center", marginTop: "auto" },
582
- el.footer,
583
- {
584
- paddingLeft: 18,
585
- paddingRight: 18,
586
- paddingTop: 12,
587
- paddingBottom: 28,
588
- borderTopWidth: 1,
589
- borderTopColor: "rgba(255,255,255,0.08)",
590
- gap: 12
591
- }
592
- ], children: [
593
- /* @__PURE__ */ jsxRuntime.jsx(
594
- reactNative.Pressable,
609
+ ]
610
+ }
611
+ )
612
+ ] }),
613
+ !hideFooter && /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [
614
+ { flexDirection: "row", alignItems: "center", marginTop: "auto" },
615
+ el.footer,
595
616
  {
596
- onPress: () => onCancel?.(),
597
- accessibilityRole: "button",
598
- accessibilityLabel: text.cancel,
599
- style: {
600
- flex: 1,
601
- height: 52,
602
- borderRadius: 26,
603
- backgroundColor: "rgba(255,255,255,0.1)",
604
- alignItems: "center",
605
- justifyContent: "center"
606
- },
607
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 15, fontWeight: "700", color: "#FFFFFF" }, children: text.cancel })
617
+ paddingLeft: 18,
618
+ paddingRight: 18,
619
+ paddingTop: 12,
620
+ paddingBottom: 28,
621
+ borderTopWidth: 1,
622
+ borderTopColor: "rgba(255,255,255,0.08)",
623
+ gap: 12
608
624
  }
609
- ),
610
- /* @__PURE__ */ jsxRuntime.jsx(
611
- reactNative.Pressable,
612
- {
613
- onPress: () => void save(),
614
- disabled: !canSave,
615
- accessibilityRole: "button",
616
- accessibilityLabel: text.save,
617
- accessibilityState: { disabled: !canSave },
618
- style: {
619
- flex: 1.4,
620
- height: 52,
621
- borderRadius: 26,
622
- backgroundColor: canSave ? constants.CROP_ACCENT : "rgba(32,170,176,0.35)",
623
- alignItems: "center",
624
- justifyContent: "center"
625
- },
626
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: {
627
- fontSize: 15,
628
- fontWeight: "800",
629
- color: canSave ? "#FFFFFF" : "rgba(255,255,255,0.55)"
630
- }, children: busy ? text.processing : text.save })
631
- }
632
- )
633
- ] })
634
- ]
635
- }
636
- );
637
- }
625
+ ], children: [
626
+ /* @__PURE__ */ jsxRuntime.jsx(
627
+ reactNative.Pressable,
628
+ {
629
+ onPress: () => onCancel?.(),
630
+ accessibilityRole: "button",
631
+ accessibilityLabel: text.cancel,
632
+ style: {
633
+ flex: 1,
634
+ height: 52,
635
+ borderRadius: 26,
636
+ backgroundColor: "rgba(255,255,255,0.1)",
637
+ alignItems: "center",
638
+ justifyContent: "center"
639
+ },
640
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { fontSize: 15, fontWeight: "700", color: "#FFFFFF" }, children: text.cancel })
641
+ }
642
+ ),
643
+ /* @__PURE__ */ jsxRuntime.jsx(
644
+ reactNative.Pressable,
645
+ {
646
+ onPress: () => void save(),
647
+ disabled: !canSave,
648
+ accessibilityRole: "button",
649
+ accessibilityLabel: text.save,
650
+ accessibilityState: { disabled: !canSave },
651
+ style: {
652
+ flex: 1.4,
653
+ height: 52,
654
+ borderRadius: 26,
655
+ backgroundColor: canSave ? constants.CROP_ACCENT : "rgba(32,170,176,0.35)",
656
+ alignItems: "center",
657
+ justifyContent: "center"
658
+ },
659
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: {
660
+ fontSize: 15,
661
+ fontWeight: "800",
662
+ color: canSave ? "#FFFFFF" : "rgba(255,255,255,0.55)"
663
+ }, children: busy ? text.processing : text.save })
664
+ }
665
+ )
666
+ ] })
667
+ ]
668
+ }
669
+ );
670
+ }
671
+ );
638
672
 
639
673
  exports.ImageCropper = ImageCropper;
640
674
  //# sourceMappingURL=index.cjs.map