@hjmds/react-native 0.8.2

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 (94) hide show
  1. package/README.md +310 -0
  2. package/dist/actions.d.ts +128 -0
  3. package/dist/actions.d.ts.map +1 -0
  4. package/dist/actions.js +157 -0
  5. package/dist/actions.js.map +1 -0
  6. package/dist/data-display.d.ts +285 -0
  7. package/dist/data-display.d.ts.map +1 -0
  8. package/dist/data-display.js +775 -0
  9. package/dist/data-display.js.map +1 -0
  10. package/dist/date-picker.d.ts +25 -0
  11. package/dist/date-picker.d.ts.map +1 -0
  12. package/dist/date-picker.js +39 -0
  13. package/dist/date-picker.js.map +1 -0
  14. package/dist/evidence.d.ts +954 -0
  15. package/dist/evidence.d.ts.map +1 -0
  16. package/dist/evidence.js +98 -0
  17. package/dist/evidence.js.map +1 -0
  18. package/dist/feedback.d.ts +148 -0
  19. package/dist/feedback.d.ts.map +1 -0
  20. package/dist/feedback.js +559 -0
  21. package/dist/feedback.js.map +1 -0
  22. package/dist/file-picker.d.ts +20 -0
  23. package/dist/file-picker.d.ts.map +1 -0
  24. package/dist/file-picker.js +37 -0
  25. package/dist/file-picker.js.map +1 -0
  26. package/dist/forms.d.ts +170 -0
  27. package/dist/forms.d.ts.map +1 -0
  28. package/dist/forms.js +595 -0
  29. package/dist/forms.js.map +1 -0
  30. package/dist/index.d.ts +16 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +16 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/inputs-public.d.ts +6 -0
  35. package/dist/inputs-public.d.ts.map +1 -0
  36. package/dist/inputs-public.js +6 -0
  37. package/dist/inputs-public.js.map +1 -0
  38. package/dist/inputs.d.ts +258 -0
  39. package/dist/inputs.d.ts.map +1 -0
  40. package/dist/inputs.js +681 -0
  41. package/dist/inputs.js.map +1 -0
  42. package/dist/internal/modal-lifecycle.d.ts +15 -0
  43. package/dist/internal/modal-lifecycle.d.ts.map +1 -0
  44. package/dist/internal/modal-lifecycle.js +17 -0
  45. package/dist/internal/modal-lifecycle.js.map +1 -0
  46. package/dist/internal/state.d.ts +8 -0
  47. package/dist/internal/state.d.ts.map +1 -0
  48. package/dist/internal/state.js +18 -0
  49. package/dist/internal/state.js.map +1 -0
  50. package/dist/internal/styles.d.ts +29 -0
  51. package/dist/internal/styles.d.ts.map +1 -0
  52. package/dist/internal/styles.js +51 -0
  53. package/dist/internal/styles.js.map +1 -0
  54. package/dist/navigation.d.ts +271 -0
  55. package/dist/navigation.d.ts.map +1 -0
  56. package/dist/navigation.js +978 -0
  57. package/dist/navigation.js.map +1 -0
  58. package/dist/number-field.d.ts +58 -0
  59. package/dist/number-field.d.ts.map +1 -0
  60. package/dist/number-field.js +172 -0
  61. package/dist/number-field.js.map +1 -0
  62. package/dist/otp-field.d.ts +2 -0
  63. package/dist/otp-field.d.ts.map +1 -0
  64. package/dist/otp-field.js +2 -0
  65. package/dist/otp-field.js.map +1 -0
  66. package/dist/overlays.d.ts +68 -0
  67. package/dist/overlays.d.ts.map +1 -0
  68. package/dist/overlays.js +757 -0
  69. package/dist/overlays.js.map +1 -0
  70. package/dist/password-field.d.ts +2 -0
  71. package/dist/password-field.d.ts.map +1 -0
  72. package/dist/password-field.js +2 -0
  73. package/dist/password-field.js.map +1 -0
  74. package/dist/primitives.d.ts +132 -0
  75. package/dist/primitives.d.ts.map +1 -0
  76. package/dist/primitives.js +238 -0
  77. package/dist/primitives.js.map +1 -0
  78. package/dist/provider.d.ts +41 -0
  79. package/dist/provider.d.ts.map +1 -0
  80. package/dist/provider.js +78 -0
  81. package/dist/provider.js.map +1 -0
  82. package/dist/slider.d.ts +45 -0
  83. package/dist/slider.d.ts.map +1 -0
  84. package/dist/slider.js +200 -0
  85. package/dist/slider.js.map +1 -0
  86. package/dist/steps.d.ts +13 -0
  87. package/dist/steps.d.ts.map +1 -0
  88. package/dist/steps.js +13 -0
  89. package/dist/steps.js.map +1 -0
  90. package/dist/upload-item.d.ts +13 -0
  91. package/dist/upload-item.d.ts.map +1 -0
  92. package/dist/upload-item.js +25 -0
  93. package/dist/upload-item.js.map +1 -0
  94. package/package.json +166 -0
@@ -0,0 +1,757 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createAlertDialogSession, getAlertDialogInitialFocus, validateAlertDialogRequest, } from "@hjmds/design-contracts/components/alert-dialog";
3
+ import { canDismissSheet, createSheetLifecycle, sheetBehaviorDefaults, } from "@hjmds/design-contracts/components/sheet";
4
+ import { resolveColorReference } from "@hjmds/design-contracts/color-references";
5
+ import { easing, overlay, radius, spacing } from "@hjmds/design-contracts/foundations";
6
+ import { alertDialogRecipe, dialogRecipe, sheetRecipe, } from "@hjmds/design-contracts/recipes";
7
+ import { useCallback, useEffect, useRef, useState, useSyncExternalStore, } from "react";
8
+ import { AccessibilityInfo, Animated, Easing, Modal, Pressable, View, findNodeHandle, useWindowDimensions, } from "react-native";
9
+ import { Button, IconButton } from "./actions.js";
10
+ import { scheduleAfterNativeModalTeardown, shouldAwaitNativeModalDismiss, } from "./internal/modal-lifecycle.js";
11
+ import { minimumTargetStyle } from "./internal/styles.js";
12
+ import { Text } from "./primitives.js";
13
+ import { useHjmNativeTheme } from "./provider.js";
14
+ function useReasonedOpenState({ open, defaultOpen = false, onOpenChange, }) {
15
+ const controlledAtMount = useRef(open !== undefined);
16
+ const controlled = open !== undefined;
17
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
18
+ const pendingClose = useRef(false);
19
+ if (controlledAtMount.current !== controlled) {
20
+ throw new Error("HJM overlays cannot switch between controlled and uncontrolled state");
21
+ }
22
+ const visible = controlled ? open : internalOpen;
23
+ const changeOpen = useCallback((next, detail) => {
24
+ if (next === visible || (!next && pendingClose.current))
25
+ return false;
26
+ if (!next) {
27
+ pendingClose.current = true;
28
+ setTimeout(() => {
29
+ pendingClose.current = false;
30
+ }, 0);
31
+ }
32
+ if (!controlled)
33
+ setInternalOpen(next);
34
+ onOpenChange?.(next, detail);
35
+ return true;
36
+ }, [controlled, onOpenChange, visible]);
37
+ return [visible, changeOpen];
38
+ }
39
+ function focusNativeTarget(target) {
40
+ if (!target?.current)
41
+ return;
42
+ const handle = findNodeHandle(target.current);
43
+ if (handle !== null)
44
+ AccessibilityInfo.setAccessibilityFocus(handle);
45
+ }
46
+ function Scrim() {
47
+ return (_jsx(View, { accessible: false, importantForAccessibility: "no-hide-descendants", style: {
48
+ backgroundColor: "#000000",
49
+ bottom: 0,
50
+ left: 0,
51
+ opacity: overlay.scrim,
52
+ position: "absolute",
53
+ right: 0,
54
+ top: 0,
55
+ } }));
56
+ }
57
+ function OverlayActions({ primaryAction, secondaryAction, busy, stacked = false, onActionComplete, }) {
58
+ const { environment } = useHjmNativeTheme();
59
+ if (!primaryAction && !secondaryAction)
60
+ return null;
61
+ const renderAction = (action, fallbackTone) => (_jsx(View, { style: { flex: stacked ? undefined : 1, width: stacked ? "100%" : undefined }, children: _jsx(Button, { ...(action.accessibilityHint === undefined
62
+ ? {}
63
+ : { accessibilityHint: action.accessibilityHint }), disabled: busy || action.disabled === true, onPress: () => {
64
+ void action.onPress();
65
+ onActionComplete();
66
+ }, tone: action.tone ?? fallbackTone, children: action.label }) }));
67
+ return (_jsxs(View, { style: {
68
+ direction: environment.direction,
69
+ flexDirection: stacked ? "column" : "row",
70
+ gap: spacing.sm,
71
+ }, children: [secondaryAction ? renderAction(secondaryAction, "secondary") : null, primaryAction ? renderAction(primaryAction, "primary") : null] }));
72
+ }
73
+ /** Native modal boundary with one reasoned close intent for each user attempt. */
74
+ export function Dialog({ open, defaultOpen, onOpenChange, title, description, children, primaryAction, secondaryAction, dismissible = true, busy = false, size = dialogRecipe.defaults.size, closeLabel, returnFocusRef, contentStyle, onShow, ...modalProps }) {
75
+ const { environment, palette } = useHjmNativeTheme();
76
+ const { width: windowWidth } = useWindowDimensions();
77
+ const [visible, changeOpen] = useReasonedOpenState({
78
+ ...(open === undefined ? {} : { open }),
79
+ ...(defaultOpen === undefined ? {} : { defaultOpen }),
80
+ ...(onOpenChange === undefined ? {} : { onOpenChange }),
81
+ });
82
+ const previousVisible = useRef(visible);
83
+ const visibleRef = useRef(visible);
84
+ visibleRef.current = visible;
85
+ const [nativeVisible, setNativeVisible] = useState(visible);
86
+ const nativeVisibleRef = useRef(visible);
87
+ nativeVisibleRef.current = nativeVisible;
88
+ const nativeShownRef = useRef(false);
89
+ const reopenAfterExitRef = useRef(false);
90
+ const sequenceRef = useRef(0);
91
+ const pendingExitRef = useRef(null);
92
+ const [motionProgress] = useState(() => new Animated.Value(0));
93
+ const reducedMotionRef = useRef(environment.reducedMotion);
94
+ reducedMotionRef.current = environment.reducedMotion;
95
+ const exitFallbackTask = useRef(null);
96
+ const cancelExitFallback = useCallback(() => {
97
+ exitFallbackTask.current?.cancel();
98
+ exitFallbackTask.current = null;
99
+ }, []);
100
+ const startEnter = useCallback(() => {
101
+ motionProgress.stopAnimation();
102
+ Animated.timing(motionProgress, {
103
+ toValue: 1,
104
+ duration: dialogRecipe.transition.enter.duration,
105
+ easing: Easing.bezier(...easing[dialogRecipe.transition.enter.easing]),
106
+ useNativeDriver: true,
107
+ }).start();
108
+ }, [motionProgress]);
109
+ const completeExit = useCallback((token) => {
110
+ if (pendingExitRef.current?.token !== token)
111
+ return;
112
+ cancelExitFallback();
113
+ pendingExitRef.current = null;
114
+ if (visibleRef.current && reopenAfterExitRef.current) {
115
+ reopenAfterExitRef.current = false;
116
+ motionProgress.setValue(0);
117
+ const keptNativeHost = nativeVisibleRef.current;
118
+ nativeVisibleRef.current = true;
119
+ setNativeVisible(true);
120
+ if (keptNativeHost)
121
+ startEnter();
122
+ return;
123
+ }
124
+ if (!visibleRef.current)
125
+ focusNativeTarget(returnFocusRef);
126
+ }, [cancelExitFallback, motionProgress, returnFocusRef, startEnter]);
127
+ const startExit = useCallback((token) => {
128
+ motionProgress.stopAnimation();
129
+ Animated.timing(motionProgress, {
130
+ toValue: 0,
131
+ duration: reducedMotionRef.current ? 0 : dialogRecipe.transition.exit.duration,
132
+ easing: Easing.bezier(...easing[dialogRecipe.transition.exit.easing]),
133
+ useNativeDriver: true,
134
+ }).start(({ finished }) => {
135
+ if (!finished || pendingExitRef.current?.token !== token)
136
+ return;
137
+ if (visibleRef.current && reopenAfterExitRef.current) {
138
+ completeExit(token);
139
+ return;
140
+ }
141
+ pendingExitRef.current.awaitNativeDismiss = shouldAwaitNativeModalDismiss(nativeShownRef.current);
142
+ nativeShownRef.current = false;
143
+ nativeVisibleRef.current = false;
144
+ setNativeVisible(false);
145
+ });
146
+ }, [completeExit, motionProgress]);
147
+ useEffect(() => {
148
+ if (!previousVisible.current && visible) {
149
+ if (pendingExitRef.current) {
150
+ reopenAfterExitRef.current = true;
151
+ }
152
+ else {
153
+ motionProgress.setValue(0);
154
+ nativeVisibleRef.current = true;
155
+ setNativeVisible(true);
156
+ }
157
+ }
158
+ else if (previousVisible.current && !visible) {
159
+ const token = sequenceRef.current + 1;
160
+ sequenceRef.current = token;
161
+ pendingExitRef.current = { token, awaitNativeDismiss: false };
162
+ startExit(token);
163
+ }
164
+ previousVisible.current = visible;
165
+ }, [motionProgress, startExit, visible]);
166
+ useEffect(() => {
167
+ cancelExitFallback();
168
+ if (nativeVisible)
169
+ return undefined;
170
+ const pending = pendingExitRef.current;
171
+ if (!pending || pending.awaitNativeDismiss)
172
+ return undefined;
173
+ const task = scheduleAfterNativeModalTeardown(() => {
174
+ exitFallbackTask.current = null;
175
+ if (!nativeVisibleRef.current && pendingExitRef.current?.token === pending.token) {
176
+ completeExit(pending.token);
177
+ }
178
+ });
179
+ exitFallbackTask.current = task;
180
+ return cancelExitFallback;
181
+ }, [cancelExitFallback, completeExit, nativeVisible]);
182
+ useEffect(() => () => {
183
+ cancelExitFallback();
184
+ motionProgress.stopAnimation();
185
+ }, [cancelExitFallback, motionProgress]);
186
+ const requestClose = (reason) => {
187
+ if (dismissible && !busy)
188
+ changeOpen(false, { reason });
189
+ };
190
+ const sizeRecipe = dialogRecipe.sizes[size];
191
+ const contentBackground = resolveColorReference(dialogRecipe.content.background, palette);
192
+ const contentBorder = resolveColorReference(dialogRecipe.content.border, palette);
193
+ const stackActions = environment.textScale >= 1.6 || windowWidth < 480;
194
+ return (_jsx(Modal, { ...modalProps, animationType: "none", onDismiss: () => {
195
+ nativeShownRef.current = false;
196
+ const pending = pendingExitRef.current;
197
+ if (pending)
198
+ completeExit(pending.token);
199
+ }, onRequestClose: () => requestClose("back"), onShow: (event) => {
200
+ nativeShownRef.current = true;
201
+ startEnter();
202
+ onShow?.(event);
203
+ }, statusBarTranslucent: true, transparent: true, visible: nativeVisible, children: _jsxs(Animated.View, { accessibilityElementsHidden: !visible, importantForAccessibility: visible ? "auto" : "no-hide-descendants", pointerEvents: visible ? "auto" : "none", style: {
204
+ flex: 1,
205
+ justifyContent: "center",
206
+ opacity: motionProgress,
207
+ padding: spacing.md,
208
+ }, children: [_jsx(Scrim, {}), dismissible ? (_jsx(Pressable, { accessible: false, importantForAccessibility: "no-hide-descendants", onPress: () => requestClose("outside"), style: { bottom: 0, left: 0, position: "absolute", right: 0, top: 0 } })) : null, _jsxs(View, { accessibilityLabel: [title, description].filter(Boolean).join(", "), accessibilityState: { busy }, accessibilityViewIsModal: true, importantForAccessibility: "yes", role: "dialog", style: [
209
+ {
210
+ alignSelf: "center",
211
+ backgroundColor: contentBackground,
212
+ borderColor: contentBorder,
213
+ borderRadius: radius[dialogRecipe.content.radius],
214
+ borderWidth: dialogRecipe.content.borderWidth,
215
+ elevation: 8,
216
+ gap: dialogRecipe.content.gap,
217
+ maxWidth: sizeRecipe.maxWidth,
218
+ padding: sizeRecipe.padding,
219
+ shadowColor: dialogRecipe.content.shadow.color,
220
+ shadowOffset: { width: 0, height: dialogRecipe.content.shadow.offsetY },
221
+ shadowOpacity: dialogRecipe.content.shadow.opacity,
222
+ shadowRadius: dialogRecipe.content.shadow.radius,
223
+ width: "100%",
224
+ },
225
+ contentStyle,
226
+ ], children: [_jsxs(View, { style: {
227
+ alignItems: "flex-start",
228
+ direction: environment.direction,
229
+ flexDirection: "row",
230
+ gap: spacing.sm,
231
+ }, children: [_jsxs(View, { style: { flex: 1, gap: spacing.xs }, children: [_jsx(Text, { accessibilityRole: "header", tone: "primary", variant: "title", children: title }), description ? _jsx(Text, { tone: "muted", children: description }) : null] }), dismissible ? (_jsx(IconButton, { disabled: busy, label: closeLabel, onPress: () => requestClose("close-action"), children: _jsx(Text, { accessible: false, variant: "title", children: "\u00D7" }) })) : null] }), children, _jsx(OverlayActions, { busy: busy, onActionComplete: () => requestClose("close-action"), stacked: stackActions, ...(primaryAction === undefined ? {} : { primaryAction }), ...(secondaryAction === undefined ? {} : { secondaryAction }) })] })] }) }));
232
+ }
233
+ /** Contract session owns duplicate confirms, busy dismissal, error and settlement. */
234
+ export function AlertDialog({ open, defaultOpen, onOpenChange, request, returnFocusRef, onResult, contentStyle, onShow, ...modalProps }) {
235
+ validateAlertDialogRequest(request);
236
+ const { colors, environment, palette } = useHjmNativeTheme();
237
+ const { width: windowWidth } = useWindowDimensions();
238
+ const [visible, changeOpen] = useReasonedOpenState({
239
+ ...(open === undefined ? {} : { open }),
240
+ ...(defaultOpen === undefined ? {} : { defaultOpen }),
241
+ ...(onOpenChange === undefined ? {} : { onOpenChange }),
242
+ });
243
+ const requestRef = useRef(request);
244
+ requestRef.current = request;
245
+ const [session, setSession] = useState(() => createAlertDialogSession(request));
246
+ const phase = useSyncExternalStore(session.subscribe, session.getSnapshot, session.getSnapshot);
247
+ const previousVisible = useRef(visible);
248
+ const visibleRef = useRef(visible);
249
+ visibleRef.current = visible;
250
+ const [nativeVisible, setNativeVisible] = useState(visible);
251
+ const nativeVisibleRef = useRef(visible);
252
+ nativeVisibleRef.current = nativeVisible;
253
+ const nativeShownRef = useRef(false);
254
+ const reopenAfterExitRef = useRef(false);
255
+ const [motionProgress] = useState(() => new Animated.Value(0));
256
+ const reducedMotionRef = useRef(environment.reducedMotion);
257
+ reducedMotionRef.current = environment.reducedMotion;
258
+ const sessionRef = useRef(session);
259
+ sessionRef.current = session;
260
+ const pendingExitSessions = useRef([]);
261
+ const suppressedResults = useRef(new WeakSet());
262
+ const rearmTimer = useRef(null);
263
+ const exitFallbackTask = useRef(null);
264
+ const onResultRef = useRef(onResult);
265
+ onResultRef.current = onResult;
266
+ const cancelRef = useRef(null);
267
+ const confirmRef = useRef(null);
268
+ const cancelExitFallback = useCallback(() => {
269
+ exitFallbackTask.current?.cancel();
270
+ exitFallbackTask.current = null;
271
+ }, []);
272
+ const ensurePendingExit = useCallback((target, awaitNativeDismiss = false) => {
273
+ const pending = pendingExitSessions.current.find((candidate) => candidate.session === target);
274
+ if (pending) {
275
+ pending.awaitNativeDismiss ||= awaitNativeDismiss;
276
+ return;
277
+ }
278
+ pendingExitSessions.current.push({ session: target, awaitNativeDismiss });
279
+ }, []);
280
+ const startEnter = useCallback(() => {
281
+ motionProgress.stopAnimation();
282
+ Animated.timing(motionProgress, {
283
+ toValue: 1,
284
+ duration: alertDialogRecipe.transition.enter.duration,
285
+ easing: Easing.bezier(...easing[alertDialogRecipe.transition.enter.easing]),
286
+ useNativeDriver: true,
287
+ }).start();
288
+ }, [motionProgress]);
289
+ const completeExit = useCallback((target) => {
290
+ cancelExitFallback();
291
+ pendingExitSessions.current = pendingExitSessions.current.filter((candidate) => candidate.session !== target);
292
+ if (!target.completeExit())
293
+ return;
294
+ if (visibleRef.current &&
295
+ reopenAfterExitRef.current &&
296
+ pendingExitSessions.current.length === 0) {
297
+ reopenAfterExitRef.current = false;
298
+ const nextSession = createAlertDialogSession(requestRef.current);
299
+ setSession(nextSession);
300
+ motionProgress.setValue(0);
301
+ const keptNativeHost = nativeVisibleRef.current;
302
+ nativeVisibleRef.current = true;
303
+ setNativeVisible(true);
304
+ if (keptNativeHost)
305
+ startEnter();
306
+ return;
307
+ }
308
+ if (!visibleRef.current)
309
+ focusNativeTarget(returnFocusRef);
310
+ }, [cancelExitFallback, motionProgress, returnFocusRef, startEnter]);
311
+ const startExit = useCallback((target) => {
312
+ motionProgress.stopAnimation();
313
+ Animated.timing(motionProgress, {
314
+ toValue: 0,
315
+ duration: reducedMotionRef.current
316
+ ? 0
317
+ : alertDialogRecipe.transition.exit.duration,
318
+ easing: Easing.bezier(...easing[alertDialogRecipe.transition.exit.easing]),
319
+ useNativeDriver: true,
320
+ }).start(({ finished }) => {
321
+ if (!finished ||
322
+ !pendingExitSessions.current.some((candidate) => candidate.session === target)) {
323
+ return;
324
+ }
325
+ if (visibleRef.current && reopenAfterExitRef.current) {
326
+ completeExit(target);
327
+ return;
328
+ }
329
+ const pending = pendingExitSessions.current.find((candidate) => candidate.session === target);
330
+ if (!pending)
331
+ return;
332
+ pending.awaitNativeDismiss = shouldAwaitNativeModalDismiss(nativeShownRef.current);
333
+ nativeShownRef.current = false;
334
+ nativeVisibleRef.current = false;
335
+ setNativeVisible(false);
336
+ });
337
+ }, [completeExit, motionProgress]);
338
+ useEffect(() => {
339
+ if (!previousVisible.current && visible) {
340
+ if (pendingExitSessions.current.length > 0) {
341
+ reopenAfterExitRef.current = true;
342
+ }
343
+ else {
344
+ setSession(createAlertDialogSession(requestRef.current));
345
+ motionProgress.setValue(0);
346
+ nativeVisibleRef.current = true;
347
+ setNativeVisible(true);
348
+ }
349
+ }
350
+ else if (previousVisible.current && !visible) {
351
+ const snapshot = session.getSnapshot();
352
+ if (snapshot.status === "idle" || snapshot.status === "error") {
353
+ session.cancel("programmatic");
354
+ }
355
+ ensurePendingExit(session);
356
+ startExit(session);
357
+ }
358
+ previousVisible.current = visible;
359
+ }, [ensurePendingExit, motionProgress, session, startExit, visible]);
360
+ useEffect(() => {
361
+ cancelExitFallback();
362
+ if (nativeVisible)
363
+ return undefined;
364
+ const pending = pendingExitSessions.current[0];
365
+ if (!pending || pending.awaitNativeDismiss)
366
+ return undefined;
367
+ const task = scheduleAfterNativeModalTeardown(() => {
368
+ exitFallbackTask.current = null;
369
+ if (!nativeVisibleRef.current &&
370
+ pendingExitSessions.current.some((candidate) => candidate.session === pending.session)) {
371
+ completeExit(pending.session);
372
+ }
373
+ });
374
+ exitFallbackTask.current = task;
375
+ return cancelExitFallback;
376
+ }, [cancelExitFallback, completeExit, nativeVisible]);
377
+ useEffect(() => {
378
+ void session.result.then((result) => {
379
+ if (!suppressedResults.current.has(session))
380
+ onResultRef.current?.(result);
381
+ });
382
+ }, [session]);
383
+ useEffect(() => () => {
384
+ if (rearmTimer.current !== null)
385
+ clearTimeout(rearmTimer.current);
386
+ cancelExitFallback();
387
+ motionProgress.stopAnimation();
388
+ const pending = pendingExitSessions.current.map((entry) => entry.session);
389
+ pendingExitSessions.current = [];
390
+ for (const target of new Set([...pending, sessionRef.current])) {
391
+ suppressedResults.current.add(target);
392
+ target.interrupt();
393
+ }
394
+ }, [cancelExitFallback, motionProgress]);
395
+ const requestClose = (target, reason) => {
396
+ ensurePendingExit(target);
397
+ changeOpen(false, { reason });
398
+ if (open !== undefined) {
399
+ if (rearmTimer.current !== null)
400
+ clearTimeout(rearmTimer.current);
401
+ rearmTimer.current = setTimeout(() => {
402
+ rearmTimer.current = null;
403
+ if (!visibleRef.current)
404
+ return;
405
+ pendingExitSessions.current = pendingExitSessions.current.filter((candidate) => candidate.session !== target);
406
+ suppressedResults.current.add(target);
407
+ target.interrupt();
408
+ setSession(createAlertDialogSession(requestRef.current));
409
+ }, 0);
410
+ }
411
+ };
412
+ const busy = phase.status === "busy";
413
+ const error = phase.status === "error" ? phase.message : undefined;
414
+ const cancel = (reason) => {
415
+ if (!session.cancel(reason))
416
+ return;
417
+ requestClose(session, reason);
418
+ };
419
+ const confirm = () => {
420
+ void session.confirm().then((closing) => {
421
+ if (closing)
422
+ requestClose(session, "confirm");
423
+ });
424
+ };
425
+ const focusLeastDestructive = (event) => {
426
+ nativeShownRef.current = true;
427
+ startEnter();
428
+ const focus = getAlertDialogInitialFocus(request.mode) === "cancel" ? cancelRef : confirmRef;
429
+ focusNativeTarget(focus);
430
+ onShow?.(event);
431
+ };
432
+ const resolvedTone = request.tone ?? alertDialogRecipe.defaults.tone;
433
+ const toneRecipe = alertDialogRecipe.tones[resolvedTone];
434
+ const sizeRecipe = alertDialogRecipe.sizes[alertDialogRecipe.defaults.size];
435
+ const stackActions = windowWidth < alertDialogRecipe.actions.stackBelow ||
436
+ environment.textScale >= 1.6;
437
+ const contentBackground = resolveColorReference(alertDialogRecipe.content.background, palette);
438
+ const contentBorder = resolveColorReference(alertDialogRecipe.content.border, palette);
439
+ const confirmBackground = resolveColorReference(toneRecipe.confirm, palette);
440
+ const confirmContent = resolveColorReference(toneRecipe.confirmContent, palette);
441
+ const errorColor = resolveColorReference(alertDialogRecipe.error.color, palette);
442
+ return (_jsx(Modal, { ...modalProps, animationType: "none", onDismiss: () => {
443
+ nativeShownRef.current = false;
444
+ const pending = pendingExitSessions.current[0];
445
+ if (pending)
446
+ completeExit(pending.session);
447
+ }, onRequestClose: () => cancel("back"), onShow: focusLeastDestructive, statusBarTranslucent: true, transparent: true, visible: nativeVisible, children: _jsxs(Animated.View, { accessibilityElementsHidden: !visible, importantForAccessibility: visible ? "auto" : "no-hide-descendants", pointerEvents: visible ? "auto" : "none", style: {
448
+ flex: 1,
449
+ justifyContent: "center",
450
+ opacity: motionProgress,
451
+ padding: spacing.md,
452
+ }, children: [_jsx(Scrim, {}), _jsxs(View, { accessibilityLabel: `${request.title}, ${request.description}`, accessibilityState: { busy }, accessibilityViewIsModal: true, importantForAccessibility: "yes", role: "alertdialog", style: [
453
+ {
454
+ alignSelf: "center",
455
+ backgroundColor: contentBackground,
456
+ borderColor: contentBorder,
457
+ borderRadius: radius[alertDialogRecipe.content.radius],
458
+ borderWidth: alertDialogRecipe.content.borderWidth,
459
+ elevation: 8,
460
+ gap: alertDialogRecipe.content.gap,
461
+ maxWidth: sizeRecipe.maxWidth,
462
+ padding: sizeRecipe.padding,
463
+ shadowColor: alertDialogRecipe.content.shadow.color,
464
+ shadowOffset: {
465
+ width: 0,
466
+ height: alertDialogRecipe.content.shadow.offsetY,
467
+ },
468
+ shadowOpacity: alertDialogRecipe.content.shadow.opacity,
469
+ shadowRadius: alertDialogRecipe.content.shadow.radius,
470
+ width: "100%",
471
+ },
472
+ contentStyle,
473
+ ], children: [_jsxs(View, { style: { gap: spacing.xs }, children: [_jsx(Text, { accessibilityRole: "header", tone: "primary", variant: "title", children: request.title }), _jsx(Text, { tone: "muted", children: request.description }), error ? (_jsx(Text, { accessibilityLiveRegion: "assertive", accessibilityRole: "alert", style: { color: errorColor }, children: error })) : null] }), _jsxs(View, { style: {
474
+ direction: environment.direction,
475
+ flexDirection: stackActions ? "column" : "row",
476
+ gap: alertDialogRecipe.actions.gap,
477
+ }, children: [request.mode === "confirm" ? (_jsx(Pressable, { ref: cancelRef, accessibilityLabel: request.cancelLabel, accessibilityRole: "button", accessibilityState: { disabled: busy }, disabled: busy, onPress: () => cancel("cancel-action"), style: ({ pressed }) => [
478
+ minimumTargetStyle,
479
+ {
480
+ alignItems: "center",
481
+ backgroundColor: colors.surfaceAlt,
482
+ borderColor: colors.border,
483
+ borderRadius: radius.md,
484
+ borderWidth: 1,
485
+ flex: stackActions ? undefined : 1,
486
+ justifyContent: "center",
487
+ minWidth: alertDialogRecipe.actions.minButtonWidth,
488
+ opacity: busy ? 0.5 : pressed ? 0.86 : 1,
489
+ paddingHorizontal: spacing.md,
490
+ },
491
+ ], children: _jsx(Text, { variant: "label", children: request.cancelLabel }) })) : null, _jsx(Pressable, { ref: confirmRef, accessibilityLabel: request.confirmLabel, accessibilityRole: "button", accessibilityState: { busy, disabled: busy }, disabled: busy, onPress: confirm, style: ({ pressed }) => [
492
+ minimumTargetStyle,
493
+ {
494
+ alignItems: "center",
495
+ backgroundColor: confirmBackground,
496
+ borderRadius: radius.md,
497
+ flex: stackActions ? undefined : 1,
498
+ justifyContent: "center",
499
+ minWidth: alertDialogRecipe.actions.minButtonWidth,
500
+ opacity: busy ? 0.5 : pressed ? 0.86 : 1,
501
+ paddingHorizontal: spacing.md,
502
+ },
503
+ ], children: _jsx(Text, { style: { color: confirmContent }, variant: "label", children: request.confirmLabel }) })] })] })] }) }));
504
+ }
505
+ /** Native Sheet applies policy before emitting a concrete dismissal reason. */
506
+ export function Sheet({ open, defaultOpen, onOpenChange, title, description, children, footer, placement = "bottom", busy = false, dismissPolicy, closeLabel, returnFocusRef, safeAreaInsets = {}, onDismissComplete, contentStyle, onShow, ...modalProps }) {
507
+ const { environment, palette } = useHjmNativeTheme();
508
+ const { width: windowWidth, height: windowHeight } = useWindowDimensions();
509
+ const policy = { ...sheetBehaviorDefaults, ...dismissPolicy };
510
+ const [visible, changeOpen] = useReasonedOpenState({
511
+ ...(open === undefined ? {} : { open }),
512
+ ...(defaultOpen === undefined ? {} : { defaultOpen }),
513
+ ...(onOpenChange === undefined ? {} : { onOpenChange }),
514
+ });
515
+ const lifecycle = useRef(createSheetLifecycle(visible));
516
+ const previousVisible = useRef(visible);
517
+ const visibleRef = useRef(visible);
518
+ visibleRef.current = visible;
519
+ const [nativeVisible, setNativeVisible] = useState(visible);
520
+ const nativeVisibleRef = useRef(visible);
521
+ nativeVisibleRef.current = nativeVisible;
522
+ const nativeShownRef = useRef(false);
523
+ const reopenAfterDismissRef = useRef(false);
524
+ const dismissingCycle = useRef(null);
525
+ const [motionProgress] = useState(() => new Animated.Value(0));
526
+ const reducedMotionRef = useRef(environment.reducedMotion);
527
+ reducedMotionRef.current = environment.reducedMotion;
528
+ const pendingDismissReason = useRef(null);
529
+ const pendingDismissals = useRef([]);
530
+ const rearmTimer = useRef(null);
531
+ const dismissFallbackTask = useRef(null);
532
+ const insets = {
533
+ top: safeAreaInsets.top ?? 0,
534
+ right: safeAreaInsets.right ?? 0,
535
+ bottom: safeAreaInsets.bottom ?? 0,
536
+ left: safeAreaInsets.left ?? 0,
537
+ };
538
+ for (const [edge, value] of Object.entries(insets)) {
539
+ if (!Number.isFinite(value) || value < 0) {
540
+ throw new RangeError(`Sheet safeAreaInsets.${edge} must be non-negative`);
541
+ }
542
+ }
543
+ const cancelDismissFallback = useCallback(() => {
544
+ dismissFallbackTask.current?.cancel();
545
+ dismissFallbackTask.current = null;
546
+ }, []);
547
+ const startEnter = useCallback(() => {
548
+ motionProgress.stopAnimation();
549
+ Animated.timing(motionProgress, {
550
+ toValue: 1,
551
+ duration: sheetRecipe.transition.enter.duration,
552
+ easing: Easing.bezier(...easing[sheetRecipe.transition.enter.easing]),
553
+ useNativeDriver: true,
554
+ }).start();
555
+ }, [motionProgress]);
556
+ const startExit = useCallback((cycle) => {
557
+ motionProgress.stopAnimation();
558
+ Animated.timing(motionProgress, {
559
+ toValue: 0,
560
+ duration: reducedMotionRef.current
561
+ ? 0
562
+ : sheetRecipe.transition.exit.duration,
563
+ easing: Easing.bezier(...easing[sheetRecipe.transition.exit.easing]),
564
+ useNativeDriver: true,
565
+ }).start(({ finished }) => {
566
+ if (!finished ||
567
+ visibleRef.current ||
568
+ dismissingCycle.current !== cycle) {
569
+ return;
570
+ }
571
+ const pending = pendingDismissals.current.find((candidate) => candidate.cycle === cycle);
572
+ if (!pending)
573
+ return;
574
+ pending.awaitNativeDismiss = shouldAwaitNativeModalDismiss(nativeShownRef.current);
575
+ nativeShownRef.current = false;
576
+ nativeVisibleRef.current = false;
577
+ setNativeVisible(false);
578
+ });
579
+ }, [motionProgress]);
580
+ const completeDismiss = useCallback((cycle) => {
581
+ cancelDismissFallback();
582
+ const pending = pendingDismissals.current.find((candidate) => candidate.cycle === cycle);
583
+ if (!pending || !lifecycle.current.completeDismiss(cycle))
584
+ return;
585
+ pendingDismissals.current = pendingDismissals.current.filter((candidate) => candidate.cycle !== cycle);
586
+ if (dismissingCycle.current === cycle)
587
+ dismissingCycle.current = null;
588
+ if (visibleRef.current &&
589
+ reopenAfterDismissRef.current &&
590
+ pendingDismissals.current.length === 0) {
591
+ reopenAfterDismissRef.current = false;
592
+ lifecycle.current.open();
593
+ motionProgress.setValue(0);
594
+ nativeVisibleRef.current = true;
595
+ setNativeVisible(true);
596
+ return;
597
+ }
598
+ if (!visibleRef.current) {
599
+ onDismissComplete?.({ reason: pending.reason });
600
+ focusNativeTarget(returnFocusRef);
601
+ }
602
+ }, [cancelDismissFallback, motionProgress, onDismissComplete, returnFocusRef]);
603
+ useEffect(() => {
604
+ if (!previousVisible.current && visible) {
605
+ const activeExit = dismissingCycle.current;
606
+ if (activeExit !== null && nativeVisibleRef.current) {
607
+ motionProgress.stopAnimation();
608
+ lifecycle.current.completeDismiss(activeExit);
609
+ pendingDismissals.current = pendingDismissals.current.filter((candidate) => candidate.cycle !== activeExit);
610
+ dismissingCycle.current = null;
611
+ reopenAfterDismissRef.current = false;
612
+ lifecycle.current.open();
613
+ startEnter();
614
+ }
615
+ else if (pendingDismissals.current.length > 0) {
616
+ reopenAfterDismissRef.current = true;
617
+ }
618
+ else {
619
+ lifecycle.current.open();
620
+ motionProgress.setValue(0);
621
+ nativeVisibleRef.current = true;
622
+ setNativeVisible(true);
623
+ }
624
+ }
625
+ if (previousVisible.current && !visible) {
626
+ pendingDismissReason.current ??= "programmatic";
627
+ const cycle = lifecycle.current.beginDismiss();
628
+ if (cycle !== null) {
629
+ dismissingCycle.current = cycle;
630
+ pendingDismissals.current.push({
631
+ cycle,
632
+ reason: pendingDismissReason.current,
633
+ awaitNativeDismiss: false,
634
+ });
635
+ startExit(cycle);
636
+ }
637
+ pendingDismissReason.current = null;
638
+ }
639
+ previousVisible.current = visible;
640
+ }, [motionProgress, startEnter, startExit, visible]);
641
+ useEffect(() => {
642
+ cancelDismissFallback();
643
+ if (nativeVisible)
644
+ return undefined;
645
+ const pending = pendingDismissals.current[0];
646
+ if (!pending || pending.awaitNativeDismiss)
647
+ return undefined;
648
+ const task = scheduleAfterNativeModalTeardown(() => {
649
+ dismissFallbackTask.current = null;
650
+ if (!nativeVisibleRef.current &&
651
+ pendingDismissals.current.some((candidate) => candidate.cycle === pending.cycle)) {
652
+ completeDismiss(pending.cycle);
653
+ }
654
+ });
655
+ dismissFallbackTask.current = task;
656
+ return cancelDismissFallback;
657
+ }, [cancelDismissFallback, completeDismiss, nativeVisible]);
658
+ useEffect(() => () => {
659
+ if (rearmTimer.current !== null)
660
+ clearTimeout(rearmTimer.current);
661
+ cancelDismissFallback();
662
+ motionProgress.stopAnimation();
663
+ }, [cancelDismissFallback, motionProgress]);
664
+ const requestClose = (reason) => {
665
+ if (!canDismissSheet(reason, busy, policy))
666
+ return;
667
+ if (!lifecycle.current.requestClose(reason, busy, policy))
668
+ return;
669
+ pendingDismissReason.current = reason;
670
+ changeOpen(false, { reason });
671
+ if (open !== undefined) {
672
+ if (rearmTimer.current !== null)
673
+ clearTimeout(rearmTimer.current);
674
+ rearmTimer.current = setTimeout(() => {
675
+ rearmTimer.current = null;
676
+ if (!visibleRef.current)
677
+ return;
678
+ lifecycle.current = createSheetLifecycle(true);
679
+ pendingDismissReason.current = null;
680
+ }, 0);
681
+ }
682
+ };
683
+ const physicalPlacement = placement === "bottom"
684
+ ? "bottom"
685
+ : placement === "start"
686
+ ? environment.direction === "rtl" ? "right" : "left"
687
+ : environment.direction === "rtl" ? "left" : "right";
688
+ const side = physicalPlacement !== "bottom";
689
+ const translationDistance = side ? windowWidth : windowHeight;
690
+ const hiddenTranslation = physicalPlacement === "left"
691
+ ? -translationDistance
692
+ : translationDistance;
693
+ const translate = motionProgress.interpolate({
694
+ inputRange: [0, 1],
695
+ outputRange: [hiddenTranslation, 0],
696
+ });
697
+ const contentBackground = resolveColorReference(sheetRecipe.content.background, palette);
698
+ const contentBorder = resolveColorReference(sheetRecipe.content.border, palette);
699
+ return (_jsx(Modal, { ...modalProps, animationType: "none", onDismiss: () => {
700
+ nativeShownRef.current = false;
701
+ const pending = pendingDismissals.current[0];
702
+ if (pending)
703
+ completeDismiss(pending.cycle);
704
+ }, onRequestClose: () => requestClose("back"), onShow: (event) => {
705
+ nativeShownRef.current = true;
706
+ startEnter();
707
+ onShow?.(event);
708
+ }, statusBarTranslucent: true, transparent: true, visible: nativeVisible, children: _jsxs(Animated.View, { accessibilityElementsHidden: !visible, importantForAccessibility: visible ? "auto" : "no-hide-descendants", pointerEvents: visible ? "auto" : "none", style: {
709
+ alignItems: physicalPlacement === "right" ? "flex-end" : "flex-start",
710
+ flex: 1,
711
+ justifyContent: physicalPlacement === "bottom" ? "flex-end" : "flex-start",
712
+ opacity: motionProgress,
713
+ }, children: [_jsx(Scrim, {}), policy.dismissible && policy.outsideDismiss ? (_jsx(Pressable, { accessible: false, importantForAccessibility: "no-hide-descendants", onPress: () => requestClose("outside"), style: { bottom: 0, left: 0, position: "absolute", right: 0, top: 0 } })) : null, _jsxs(Animated.View, { accessibilityLabel: [title, description].filter(Boolean).join(", "), accessibilityState: { busy }, accessibilityViewIsModal: true, importantForAccessibility: "yes", role: "dialog", style: [
714
+ {
715
+ backgroundColor: contentBackground,
716
+ borderColor: contentBorder,
717
+ borderRadius: radius[sheetRecipe.content.radius],
718
+ borderBottomLeftRadius: physicalPlacement === "bottom" ? 0 : radius[sheetRecipe.content.radius],
719
+ borderBottomRightRadius: physicalPlacement === "bottom" ? 0 : radius[sheetRecipe.content.radius],
720
+ borderWidth: sheetRecipe.content.borderWidth,
721
+ elevation: 8,
722
+ gap: sheetRecipe.body.gap,
723
+ height: side ? "100%" : undefined,
724
+ maxWidth: side ? 420 : undefined,
725
+ maxHeight: side
726
+ ? undefined
727
+ : windowHeight * sheetRecipe.content.maxHeightRatio,
728
+ paddingBottom: sheetRecipe.content.paddingBottom + insets.bottom,
729
+ paddingLeft: sheetRecipe.content.paddingHorizontal + insets.left,
730
+ paddingRight: sheetRecipe.content.paddingHorizontal + insets.right,
731
+ paddingTop: sheetRecipe.content.paddingTop + insets.top,
732
+ shadowColor: sheetRecipe.content.shadow.color,
733
+ shadowOffset: {
734
+ width: 0,
735
+ height: sheetRecipe.content.shadow.offsetY,
736
+ },
737
+ shadowOpacity: sheetRecipe.content.shadow.opacity,
738
+ shadowRadius: sheetRecipe.content.shadow.radius,
739
+ transform: environment.reducedMotion
740
+ ? undefined
741
+ : side
742
+ ? [{ translateX: translate }]
743
+ : [{ translateY: translate }],
744
+ width: side ? "88%" : "100%",
745
+ },
746
+ contentStyle,
747
+ ], children: [_jsxs(View, { style: {
748
+ alignItems: "flex-start",
749
+ direction: environment.direction,
750
+ flexDirection: "row",
751
+ gap: spacing.sm,
752
+ }, children: [_jsxs(View, { style: { flex: 1, gap: spacing.xs }, children: [_jsx(Text, { accessibilityRole: "header", tone: "primary", variant: "title", children: title }), description ? _jsx(Text, { tone: "muted", children: description }) : null] }), policy.dismissible ? (_jsx(IconButton, { disabled: busy && !policy.dismissWhileBusy, label: closeLabel, onPress: () => requestClose("close-action"), children: _jsx(Text, { accessible: false, variant: "title", children: "\u00D7" }) })) : null] }), _jsx(View, { style: { gap: sheetRecipe.body.gap }, children: children }), footer ? (_jsx(View, { style: {
753
+ gap: sheetRecipe.footer.gap,
754
+ paddingTop: sheetRecipe.footer.paddingTop,
755
+ }, children: footer })) : null] })] }) }));
756
+ }
757
+ //# sourceMappingURL=overlays.js.map