@hjmds/react-native 1.17.2 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/README.md +8 -8
  2. package/dist/actions.d.ts +3 -23
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +8 -11
  5. package/dist/actions.js.map +1 -1
  6. package/dist/activity-chip.d.ts +29 -0
  7. package/dist/activity-chip.d.ts.map +1 -0
  8. package/dist/activity-chip.js +54 -0
  9. package/dist/activity-chip.js.map +1 -0
  10. package/dist/agreement.d.ts +1 -7
  11. package/dist/agreement.d.ts.map +1 -1
  12. package/dist/agreement.js +21 -15
  13. package/dist/agreement.js.map +1 -1
  14. package/dist/asset.d.ts +2 -13
  15. package/dist/asset.d.ts.map +1 -1
  16. package/dist/asset.js +4 -7
  17. package/dist/asset.js.map +1 -1
  18. package/dist/bottom-info.d.ts +1 -7
  19. package/dist/bottom-info.d.ts.map +1 -1
  20. package/dist/bottom-info.js +2 -4
  21. package/dist/bottom-info.js.map +1 -1
  22. package/dist/carousel-motion.d.ts +1 -0
  23. package/dist/carousel-motion.d.ts.map +1 -1
  24. package/dist/carousel-motion.js +5 -3
  25. package/dist/carousel-motion.js.map +1 -1
  26. package/dist/carousel.d.ts +1 -7
  27. package/dist/carousel.d.ts.map +1 -1
  28. package/dist/carousel.js +14 -6
  29. package/dist/carousel.js.map +1 -1
  30. package/dist/collapsible.d.ts +1 -7
  31. package/dist/collapsible.d.ts.map +1 -1
  32. package/dist/collapsible.js +4 -4
  33. package/dist/collapsible.js.map +1 -1
  34. package/dist/content-transition.d.ts +2 -1
  35. package/dist/content-transition.d.ts.map +1 -1
  36. package/dist/content-transition.js +3 -3
  37. package/dist/content-transition.js.map +1 -1
  38. package/dist/data-display.d.ts +23 -154
  39. package/dist/data-display.d.ts.map +1 -1
  40. package/dist/data-display.js +86 -92
  41. package/dist/data-display.js.map +1 -1
  42. package/dist/date-entry.d.ts +1 -0
  43. package/dist/date-entry.d.ts.map +1 -1
  44. package/dist/date-entry.js +22 -20
  45. package/dist/date-entry.js.map +1 -1
  46. package/dist/date-picker.d.ts +1 -7
  47. package/dist/date-picker.d.ts.map +1 -1
  48. package/dist/date-picker.js +19 -11
  49. package/dist/date-picker.js.map +1 -1
  50. package/dist/duration-field.d.ts +1 -1
  51. package/dist/duration-field.d.ts.map +1 -1
  52. package/dist/duration-field.js +11 -9
  53. package/dist/duration-field.js.map +1 -1
  54. package/dist/evidence.d.ts +1 -1
  55. package/dist/evidence.js +1 -1
  56. package/dist/feedback.d.ts +9 -88
  57. package/dist/feedback.d.ts.map +1 -1
  58. package/dist/feedback.js +124 -81
  59. package/dist/feedback.js.map +1 -1
  60. package/dist/field-group.d.ts +3 -2
  61. package/dist/field-group.d.ts.map +1 -1
  62. package/dist/field-group.js +7 -4
  63. package/dist/field-group.js.map +1 -1
  64. package/dist/file-picker.d.ts +1 -7
  65. package/dist/file-picker.d.ts.map +1 -1
  66. package/dist/file-picker.js +21 -11
  67. package/dist/file-picker.js.map +1 -1
  68. package/dist/forms.d.ts +6 -21
  69. package/dist/forms.d.ts.map +1 -1
  70. package/dist/forms.js +29 -35
  71. package/dist/forms.js.map +1 -1
  72. package/dist/gravity-letters.d.ts.map +1 -1
  73. package/dist/gravity-letters.js +13 -5
  74. package/dist/gravity-letters.js.map +1 -1
  75. package/dist/grid-reveal.d.ts.map +1 -1
  76. package/dist/grid-reveal.js +10 -2
  77. package/dist/grid-reveal.js.map +1 -1
  78. package/dist/heading.d.ts +1 -7
  79. package/dist/heading.d.ts.map +1 -1
  80. package/dist/heading.js +1 -4
  81. package/dist/heading.js.map +1 -1
  82. package/dist/image-viewer.d.ts.map +1 -1
  83. package/dist/image-viewer.js +2 -1
  84. package/dist/image-viewer.js.map +1 -1
  85. package/dist/index.d.ts +1 -0
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +1 -0
  88. package/dist/index.js.map +1 -1
  89. package/dist/inline-confirm.d.ts.map +1 -1
  90. package/dist/inline-confirm.js +5 -1
  91. package/dist/inline-confirm.js.map +1 -1
  92. package/dist/inputs.d.ts +4 -71
  93. package/dist/inputs.d.ts.map +1 -1
  94. package/dist/inputs.js +52 -67
  95. package/dist/inputs.js.map +1 -1
  96. package/dist/internal/accessible-focus.d.ts +3 -0
  97. package/dist/internal/accessible-focus.d.ts.map +1 -0
  98. package/dist/internal/accessible-focus.js +15 -0
  99. package/dist/internal/accessible-focus.js.map +1 -0
  100. package/dist/internal/diagnostics.d.ts +7 -0
  101. package/dist/internal/diagnostics.d.ts.map +1 -0
  102. package/dist/internal/diagnostics.js +20 -0
  103. package/dist/internal/diagnostics.js.map +1 -0
  104. package/dist/internal/document-resource.d.ts.map +1 -1
  105. package/dist/internal/document-resource.js +2 -2
  106. package/dist/internal/document-resource.js.map +1 -1
  107. package/dist/internal/recipe-button.js +1 -1
  108. package/dist/internal/recipe-button.js.map +1 -1
  109. package/dist/internal/spinner.d.ts +1 -7
  110. package/dist/internal/spinner.d.ts.map +1 -1
  111. package/dist/internal/spinner.js +2 -4
  112. package/dist/internal/spinner.js.map +1 -1
  113. package/dist/masonry.d.ts.map +1 -1
  114. package/dist/masonry.js +2 -1
  115. package/dist/masonry.js.map +1 -1
  116. package/dist/mentions.d.ts +1 -7
  117. package/dist/mentions.d.ts.map +1 -1
  118. package/dist/mentions.js +11 -7
  119. package/dist/mentions.js.map +1 -1
  120. package/dist/navigation.d.ts +4 -74
  121. package/dist/navigation.d.ts.map +1 -1
  122. package/dist/navigation.js +86 -45
  123. package/dist/navigation.js.map +1 -1
  124. package/dist/number-field.d.ts +1 -21
  125. package/dist/number-field.d.ts.map +1 -1
  126. package/dist/number-field.js +6 -5
  127. package/dist/number-field.js.map +1 -1
  128. package/dist/overlays.d.ts +7 -16
  129. package/dist/overlays.d.ts.map +1 -1
  130. package/dist/overlays.js +220 -213
  131. package/dist/overlays.js.map +1 -1
  132. package/dist/primitives.d.ts +3 -18
  133. package/dist/primitives.d.ts.map +1 -1
  134. package/dist/primitives.js +8 -10
  135. package/dist/primitives.js.map +1 -1
  136. package/dist/provider-button.d.ts +1 -7
  137. package/dist/provider-button.d.ts.map +1 -1
  138. package/dist/provider-button.js +1 -4
  139. package/dist/provider-button.js.map +1 -1
  140. package/dist/provider.d.ts.map +1 -1
  141. package/dist/provider.js +9 -1
  142. package/dist/provider.js.map +1 -1
  143. package/dist/saved-items.d.ts.map +1 -1
  144. package/dist/saved-items.js +6 -1
  145. package/dist/saved-items.js.map +1 -1
  146. package/dist/screen-flows.d.ts +1 -2
  147. package/dist/screen-flows.d.ts.map +1 -1
  148. package/dist/screen-flows.js +60 -16
  149. package/dist/screen-flows.js.map +1 -1
  150. package/dist/screens.d.ts +1 -1
  151. package/dist/screens.d.ts.map +1 -1
  152. package/dist/screens.js +32 -15
  153. package/dist/screens.js.map +1 -1
  154. package/dist/sheet-gesture.d.ts.map +1 -1
  155. package/dist/sheet-gesture.js +6 -3
  156. package/dist/sheet-gesture.js.map +1 -1
  157. package/dist/slider.d.ts +1 -21
  158. package/dist/slider.d.ts.map +1 -1
  159. package/dist/slider.js +3 -5
  160. package/dist/slider.js.map +1 -1
  161. package/dist/sortable.d.ts.map +1 -1
  162. package/dist/sortable.js +2 -1
  163. package/dist/sortable.js.map +1 -1
  164. package/dist/status-text.d.ts +17 -0
  165. package/dist/status-text.d.ts.map +1 -0
  166. package/dist/status-text.js +18 -0
  167. package/dist/status-text.js.map +1 -0
  168. package/dist/steps.d.ts +1 -7
  169. package/dist/steps.d.ts.map +1 -1
  170. package/dist/steps.js +2 -4
  171. package/dist/steps.js.map +1 -1
  172. package/dist/swipe-actions.d.ts.map +1 -1
  173. package/dist/swipe-actions.js +13 -2
  174. package/dist/swipe-actions.js.map +1 -1
  175. package/dist/tags-input.d.ts +1 -7
  176. package/dist/tags-input.d.ts.map +1 -1
  177. package/dist/tags-input.js +2 -4
  178. package/dist/tags-input.js.map +1 -1
  179. package/dist/toggle-group.d.ts +1 -7
  180. package/dist/toggle-group.d.ts.map +1 -1
  181. package/dist/toggle-group.js +10 -5
  182. package/dist/toggle-group.js.map +1 -1
  183. package/dist/top.d.ts +1 -7
  184. package/dist/top.d.ts.map +1 -1
  185. package/dist/top.js +1 -4
  186. package/dist/top.js.map +1 -1
  187. package/dist/transfer-list.d.ts +2 -8
  188. package/dist/transfer-list.d.ts.map +1 -1
  189. package/dist/transfer-list.js +30 -11
  190. package/dist/transfer-list.js.map +1 -1
  191. package/dist/upload-item.d.ts +1 -7
  192. package/dist/upload-item.d.ts.map +1 -1
  193. package/dist/upload-item.js +2 -4
  194. package/dist/upload-item.js.map +1 -1
  195. package/dist/virtual-list.d.ts.map +1 -1
  196. package/dist/virtual-list.js +2 -1
  197. package/dist/virtual-list.js.map +1 -1
  198. package/docs/liquid-toast.md +20 -0
  199. package/package.json +17 -4
  200. package/dist/internal/deprecated-style.d.ts +0 -8
  201. package/dist/internal/deprecated-style.d.ts.map +0 -1
  202. package/dist/internal/deprecated-style.js +0 -34
  203. package/dist/internal/deprecated-style.js.map +0 -1
package/dist/overlays.js CHANGED
@@ -1,16 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { resolveOriginTransition } from "@hjmds/design-contracts/content-transition";
3
+ import { focusAccessibleHost } from "./internal/accessible-focus.js";
3
4
  import { createAlertDialogSession, getAlertDialogInitialFocus, validateAlertDialogRequest, } from "@hjmds/design-contracts/components/alert-dialog";
4
- import { hjmCompositionStyleKeys } from "./composition-style.js";
5
+ import {} from "./composition-style.js";
5
6
  import { canDismissSheet, createSheetLifecycle, sheetBehaviorDefaults, } from "@hjmds/design-contracts/components/sheet";
6
7
  import { resolveColorReference } from "@hjmds/design-contracts/color-references";
7
8
  import { backdrop, easing, glyph, spacing } from "@hjmds/design-contracts/foundations";
8
9
  import { alertDialogRecipe, dialogRecipe, sheetRecipe, } from "@hjmds/design-contracts/recipes";
9
10
  import { useCallback, useEffect, useRef, useState, useSyncExternalStore, } from "react";
10
- import { AccessibilityInfo, Animated, Easing, Keyboard, Modal, Platform, ScrollView, Pressable, StyleSheet, View, findNodeHandle, useWindowDimensions, } from "react-native";
11
+ import { Animated, Easing, Keyboard, Modal, Platform, ScrollView, Pressable, View, useWindowDimensions, } from "react-native";
11
12
  import { Button, IconButton } from "./actions.js";
12
13
  import { scheduleAfterNativeModalTeardown, shouldAwaitNativeModalDismiss, } from "./internal/modal-lifecycle.js";
13
- import { isDevelopment, warnDeprecatedStyleProps } from "./internal/deprecated-style.js";
14
+ import { isDevelopment } from "./internal/diagnostics.js";
14
15
  import { FixedGlyph } from "./internal/fixed-glyph.js";
15
16
  import { minimumTargetStyle } from "./internal/styles.js";
16
17
  import { Text } from "./primitives.js";
@@ -20,6 +21,12 @@ import { resolveNativeShadowElevation } from "./internal/styles.js";
20
21
  function CloseGlyph() {
21
22
  return _jsx(FixedGlyph, { fontSize: glyph.sm, children: "\u00D7" });
22
23
  }
24
+ // Keep interaction visibility off native-driven animation hosts. In iOS 26.5,
25
+ // Dialog/AlertDialog/Sheet lost their AX descendants on a second presentation
26
+ // while still painting; a non-animated boundary preserves reopen accessibility.
27
+ function ModalInteractionBoundary({ visible, children }) {
28
+ return _jsx(View, { accessibilityElementsHidden: !visible, importantForAccessibility: visible ? "auto" : "no-hide-descendants", pointerEvents: visible ? "auto" : "none", style: { flex: 1 }, children: children });
29
+ }
23
30
  // BT-QA-027: maximum text made a centered BurnTok confirmation taller than the
24
31
  // window, putting its title/cancel off-screen. Keep full copy in a bounded scroll
25
32
  // and actions outside it; capping fonts or clipping copy would hide the decision.
@@ -28,6 +35,47 @@ function DialogScrollBody({ children, gap }) {
28
35
  // Positioner owns safe-area clearance.
29
36
  automaticallyAdjustContentInsets: false, automaticallyAdjustKeyboardInsets: false, children: children });
30
37
  }
38
+ // Dialog input QA on iOS 26.5 exposed a Save action behind the keyboard.
39
+ // Share Sheet's measured viewport math so centered modals also avoid double
40
+ // clearance on Android adjustResize and ignore undocked/floating keyboards.
41
+ function useModalKeyboardViewport(active, enabled = true) {
42
+ const { width: windowWidth, height: windowHeight } = useWindowDimensions();
43
+ const [modalHeight, setModalHeight] = useState(null);
44
+ const [keyboardFrame, setKeyboardFrame] = useState(null);
45
+ useEffect(() => {
46
+ if (!active || !enabled) {
47
+ setKeyboardFrame(null);
48
+ return;
49
+ }
50
+ // A modal may open while an input elsewhere already owns the keyboard. Event-only
51
+ // tracking misses that case; metrics also restores the correct state on reopen.
52
+ // RN Web has no metrics() API; its keyboard events can still update the frame.
53
+ setKeyboardFrame(Keyboard.metrics?.() ?? null);
54
+ const change = (event) => setKeyboardFrame(event.endCoordinates);
55
+ const hide = () => setKeyboardFrame(null);
56
+ const subscriptions = [
57
+ Keyboard.addListener("keyboardDidShow", change),
58
+ Keyboard.addListener("keyboardDidHide", hide),
59
+ ...(Platform.OS === "ios" ? [
60
+ Keyboard.addListener("keyboardWillChangeFrame", change),
61
+ Keyboard.addListener("keyboardWillHide", hide),
62
+ ] : []),
63
+ ];
64
+ return () => subscriptions.forEach((subscription) => subscription.remove());
65
+ }, [enabled, active]);
66
+ // Modal owns a full-screen coordinate space (statusBarTranslucent). Measure its
67
+ // actual height so Android adjustResize does not subtract the keyboard twice.
68
+ const viewportHeight = modalHeight ?? windowHeight;
69
+ const dockedKeyboard = enabled && keyboardFrame !== null
70
+ // Android converts IME pixels to float dp independently from window dimensions;
71
+ // exact width comparison rejected a full-width keyboard by 0.000013 dp on API 36.
72
+ && keyboardFrame.height > 0 && Math.abs(keyboardFrame.screenX) <= 1
73
+ && keyboardFrame.width >= windowWidth - 1;
74
+ const keyboardInset = dockedKeyboard
75
+ ? Math.max(0, viewportHeight - keyboardFrame.screenY)
76
+ : 0;
77
+ return { viewportHeight, keyboardInset, dockedKeyboard, setModalHeight };
78
+ }
31
79
  function useDialogViewportPadding() {
32
80
  const insets = useHjmNativeSafeAreaInsets();
33
81
  // Missing insets count as zero.
@@ -64,11 +112,7 @@ function useReasonedOpenState({ open, defaultOpen = false, onOpenChange, }) {
64
112
  return [visible, changeOpen];
65
113
  }
66
114
  function focusNativeTarget(target) {
67
- if (!target?.current)
68
- return;
69
- const handle = findNodeHandle(target.current);
70
- if (handle !== null)
71
- AccessibilityInfo.setAccessibilityFocus(handle);
115
+ focusAccessibleHost(target?.current);
72
116
  }
73
117
  function Scrim() {
74
118
  return (_jsx(View, { accessible: false, importantForAccessibility: "no-hide-descendants", style: {
@@ -131,6 +175,7 @@ export function Dialog({ open, defaultOpen, onOpenChange, title, accessibilityTi
131
175
  const visibleRef = useRef(visible);
132
176
  visibleRef.current = visible;
133
177
  const [nativeVisible, setNativeVisible] = useState(visible);
178
+ const keyboardViewport = useModalKeyboardViewport(nativeVisible);
134
179
  const nativeVisibleRef = useRef(visible);
135
180
  nativeVisibleRef.current = nativeVisible;
136
181
  const nativeShownRef = useRef(false);
@@ -323,67 +368,58 @@ export function Dialog({ open, defaultOpen, onOpenChange, title, accessibilityTi
323
368
  nativeShownRef.current = true;
324
369
  startEnter();
325
370
  onShow?.(event);
326
- }, statusBarTranslucent: true, transparent: true, visible: nativeVisible, children: _jsxs(Animated.View, { accessibilityElementsHidden: !visible, importantForAccessibility: visible ? "auto" : "no-hide-descendants", pointerEvents: visible ? "auto" : "none", style: {
327
- flex: 1,
328
- justifyContent: "center",
329
- opacity: motionProgress,
330
- ...viewportPadding,
331
- }, 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(Animated.View, { ref: originContentRef, onLayout: (event) => {
332
- const { width, height } = event.nativeEvent.layout;
333
- const previous = originLayout.current;
334
- originLayout.current = { width, height };
335
- // Error copy, keyboard or rotation may change the destination after entry.
336
- // Fade instead of returning through stale dimensions; a later open remeasures.
337
- if (previous && (previous.width !== width || previous.height !== height))
338
- setOriginTransform(null);
339
- }, accessibilityLabel: [accessibleTitle, description].filter(Boolean).join(", "), accessibilityState: { busy }, accessibilityViewIsModal: true, importantForAccessibility: "yes", role: "dialog", style: [
340
- {
341
- alignSelf: "center",
342
- backgroundColor: contentBackground,
343
- borderColor: contentBorder,
344
- borderRadius: tokens.radius[dialogRecipe.content.radius],
345
- borderWidth: dialogRecipe.content.borderWidth,
346
- ...resolveNativeShadowElevation(surfaceShadow, designProfile !== undefined, 8),
347
- gap: dialogRecipe.content.gap,
348
- maxWidth: sizeRecipe.maxWidth,
349
- maxHeight: "100%",
350
- flexShrink: 1,
351
- padding: sizeRecipe.padding,
352
- shadowColor: surfaceShadow.color,
353
- shadowOffset: { width: 0, height: surfaceShadow.offsetY },
354
- shadowOpacity: surfaceShadow.opacity,
355
- shadowRadius: surfaceShadow.radius,
356
- width: "100%",
357
- },
358
- originTransform && !environment.reducedMotion ? { transform: [
359
- { translateX: motionProgress.interpolate({ inputRange: [0, 1], outputRange: [originTransform.translateX, 0] }) },
360
- { translateY: motionProgress.interpolate({ inputRange: [0, 1], outputRange: [originTransform.translateY, 0] }) },
361
- { scaleX: motionProgress.interpolate({ inputRange: [0, 1], outputRange: [originTransform.scaleX, 1] }) },
362
- { scaleY: motionProgress.interpolate({ inputRange: [0, 1], outputRange: [originTransform.scaleY, 1] }) },
363
- ] } : undefined,
364
- contentStyle,
365
- ], children: [_jsxs(View, { style: { flexShrink: 1, gap: description ? spacing.xs : dialogRecipe.content.gap }, children: [_jsxs(View, { style: {
366
- alignItems: "flex-start",
367
- direction: environment.direction,
368
- flexDirection: "row",
369
- gap: spacing.sm,
370
- }, children: [_jsx(View, { style: { flex: 1 }, children: _jsx(Text, { accessibilityRole: "header", tone: "primary", variant: "title", children: title }) }), dismissible ? (_jsx(IconButton, { disabled: busy, label: closeLabel, onPress: () => requestClose("close-action"), children: _jsx(CloseGlyph, {}) })) : null] }), description || children ? (_jsxs(DialogScrollBody, { gap: dialogRecipe.content.gap, children: [description ? _jsx(Text, { tone: "muted", children: description }) : null, children] })) : null] }), _jsx(OverlayActions, { busy: busy, onAction: runAction, pendingAction: actionPending, stacked: stackActions, ...(primaryAction === undefined ? {} : { primaryAction }), ...(secondaryAction === undefined ? {} : { secondaryAction }) })] })] }) }));
371
- }
372
- const compositionKeys = new Set(hjmCompositionStyleKeys);
373
- /**
374
- * 1.x keeps AlertDialog/Sheet `contentStyle` wide; warn only on visual keys (layout use is supported).
375
- */
376
- function warnVisualContentStyle(component, contentStyle) {
377
- if (contentStyle === undefined || contentStyle === null)
378
- return;
379
- const flattened = StyleSheet.flatten(contentStyle) ?? {};
380
- const hasVisualKey = Object.keys(flattened).some(key => !compositionKeys.has(key));
381
- warnDeprecatedStyleProps(component, { contentStyle: hasVisualKey ? contentStyle : undefined }, "layout-only keys in contentStyle and size/placement for appearance");
371
+ }, statusBarTranslucent: true, transparent: true, visible: nativeVisible, children: _jsx(ModalInteractionBoundary, { visible: visible, children: _jsxs(Animated.View, { onLayout: (event) => keyboardViewport.setModalHeight(event.nativeEvent.layout.height), style: {
372
+ flex: 1,
373
+ justifyContent: "center",
374
+ opacity: motionProgress,
375
+ ...viewportPadding,
376
+ paddingBottom: keyboardViewport.dockedKeyboard
377
+ ? spacing.md + keyboardViewport.keyboardInset
378
+ : viewportPadding.paddingBottom,
379
+ }, 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(Animated.View, { ref: originContentRef, onLayout: (event) => {
380
+ const { width, height } = event.nativeEvent.layout;
381
+ const previous = originLayout.current;
382
+ originLayout.current = { width, height };
383
+ // Error copy, keyboard or rotation may change the destination after entry.
384
+ // Fade instead of returning through stale dimensions; a later open remeasures.
385
+ if (previous && (previous.width !== width || previous.height !== height))
386
+ setOriginTransform(null);
387
+ }, accessibilityLabel: [accessibleTitle, description].filter(Boolean).join(", "), accessibilityState: { busy }, accessibilityViewIsModal: true, importantForAccessibility: "yes", role: "dialog", style: [
388
+ {
389
+ alignSelf: "center",
390
+ backgroundColor: contentBackground,
391
+ borderColor: contentBorder,
392
+ borderRadius: tokens.radius[dialogRecipe.content.radius],
393
+ borderWidth: dialogRecipe.content.borderWidth,
394
+ ...resolveNativeShadowElevation(surfaceShadow, designProfile !== undefined, 8),
395
+ gap: dialogRecipe.content.gap,
396
+ maxWidth: sizeRecipe.maxWidth,
397
+ maxHeight: "100%",
398
+ flexShrink: 1,
399
+ padding: sizeRecipe.padding,
400
+ shadowColor: surfaceShadow.color,
401
+ shadowOffset: { width: 0, height: surfaceShadow.offsetY },
402
+ shadowOpacity: surfaceShadow.opacity,
403
+ shadowRadius: surfaceShadow.radius,
404
+ width: "100%",
405
+ },
406
+ originTransform && !environment.reducedMotion ? { transform: [
407
+ { translateX: motionProgress.interpolate({ inputRange: [0, 1], outputRange: [originTransform.translateX, 0] }) },
408
+ { translateY: motionProgress.interpolate({ inputRange: [0, 1], outputRange: [originTransform.translateY, 0] }) },
409
+ { scaleX: motionProgress.interpolate({ inputRange: [0, 1], outputRange: [originTransform.scaleX, 1] }) },
410
+ { scaleY: motionProgress.interpolate({ inputRange: [0, 1], outputRange: [originTransform.scaleY, 1] }) },
411
+ ] } : undefined,
412
+ contentStyle,
413
+ ], children: [_jsxs(View, { style: { flexShrink: 1, gap: description ? spacing.xs : dialogRecipe.content.gap }, children: [_jsxs(View, { style: {
414
+ alignItems: "flex-start",
415
+ direction: environment.direction,
416
+ flexDirection: "row",
417
+ gap: spacing.sm,
418
+ }, children: [_jsx(View, { style: { flex: 1 }, children: _jsx(Text, { accessibilityRole: "header", tone: "primary", variant: "title", children: title }) }), dismissible ? (_jsx(IconButton, { disabled: busy, label: closeLabel, onPress: () => requestClose("close-action"), children: _jsx(CloseGlyph, {}) })) : null] }), description || children ? (_jsxs(DialogScrollBody, { gap: dialogRecipe.content.gap, children: [description ? _jsx(Text, { tone: "muted", children: description }) : null, children] })) : null] }), _jsx(OverlayActions, { busy: busy, onAction: runAction, pendingAction: actionPending, stacked: stackActions, ...(primaryAction === undefined ? {} : { primaryAction }), ...(secondaryAction === undefined ? {} : { secondaryAction }) })] })] }) }) }));
382
419
  }
383
420
  /** Contract session owns duplicate confirms, busy dismissal, error and settlement. */
384
- export function AlertDialog({ open, defaultOpen, onOpenChange, request, returnFocusRef, onResult, contentStyle, onShow, ...modalProps }) {
421
+ export function AlertDialog({ open, defaultOpen, onOpenChange, request, size = alertDialogRecipe.defaults.size, returnFocusRef, onResult, contentStyle, onShow, ...modalProps }) {
385
422
  validateAlertDialogRequest(request);
386
- warnVisualContentStyle("AlertDialog", contentStyle);
387
423
  const viewportPadding = useDialogViewportPadding();
388
424
  const { colors, environment, palette, tokens, designProfile } = useHjmNativeTheme();
389
425
  // The profile floating role is shared with Web. Modal lifecycle, inset and
@@ -403,6 +439,7 @@ export function AlertDialog({ open, defaultOpen, onOpenChange, request, returnFo
403
439
  const visibleRef = useRef(visible);
404
440
  visibleRef.current = visible;
405
441
  const [nativeVisible, setNativeVisible] = useState(visible);
442
+ const keyboardViewport = useModalKeyboardViewport(nativeVisible);
406
443
  const nativeVisibleRef = useRef(visible);
407
444
  nativeVisibleRef.current = nativeVisible;
408
445
  const nativeShownRef = useRef(false);
@@ -586,7 +623,7 @@ export function AlertDialog({ open, defaultOpen, onOpenChange, request, returnFo
586
623
  };
587
624
  const resolvedTone = request.tone ?? alertDialogRecipe.defaults.tone;
588
625
  const toneRecipe = alertDialogRecipe.tones[resolvedTone];
589
- const sizeRecipe = alertDialogRecipe.sizes[alertDialogRecipe.defaults.size];
626
+ const sizeRecipe = alertDialogRecipe.sizes[size];
590
627
  const stackActions = windowWidth < alertDialogRecipe.actions.stackBelow ||
591
628
  environment.textScale >= 1.6;
592
629
  const contentBackground = resolveColorReference(alertDialogRecipe.content.background, palette);
@@ -612,62 +649,64 @@ export function AlertDialog({ open, defaultOpen, onOpenChange, request, returnFo
612
649
  const pending = pendingExitSessions.current[0];
613
650
  if (pending)
614
651
  completeExit(pending.session);
615
- }, 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: {
616
- flex: 1,
617
- justifyContent: "center",
618
- opacity: motionProgress,
619
- ...viewportPadding,
620
- }, children: [_jsx(Scrim, {}), _jsxs(View, { accessibilityLabel: `${request.title}, ${request.description}`, accessibilityState: { busy }, accessibilityViewIsModal: true, importantForAccessibility: "yes", role: "alertdialog", style: [
621
- {
622
- alignSelf: "center",
623
- backgroundColor: contentBackground,
624
- borderColor: contentBorder,
625
- borderRadius: tokens.radius[alertDialogRecipe.content.radius],
626
- borderWidth: alertDialogRecipe.content.borderWidth,
627
- ...resolveNativeShadowElevation(surfaceShadow, designProfile !== undefined, 8),
628
- gap: alertDialogRecipe.content.gap,
629
- maxWidth: sizeRecipe.maxWidth,
630
- maxHeight: "100%",
631
- flexShrink: 1,
632
- padding: sizeRecipe.padding,
633
- shadowColor: surfaceShadow.color,
634
- shadowOffset: {
635
- width: 0,
636
- height: surfaceShadow.offsetY,
652
+ }, onRequestClose: () => cancel("back"), onShow: focusLeastDestructive, statusBarTranslucent: true, transparent: true, visible: nativeVisible, children: _jsx(ModalInteractionBoundary, { visible: visible, children: _jsxs(Animated.View, { onLayout: (event) => keyboardViewport.setModalHeight(event.nativeEvent.layout.height), style: {
653
+ flex: 1,
654
+ justifyContent: "center",
655
+ opacity: motionProgress,
656
+ ...viewportPadding,
657
+ paddingBottom: keyboardViewport.dockedKeyboard
658
+ ? spacing.md + keyboardViewport.keyboardInset
659
+ : viewportPadding.paddingBottom,
660
+ }, children: [_jsx(Scrim, {}), _jsxs(View, { accessibilityLabel: `${request.title}, ${request.description}`, accessibilityState: { busy }, accessibilityViewIsModal: true, importantForAccessibility: "yes", role: "alertdialog", style: [
661
+ {
662
+ alignSelf: "center",
663
+ backgroundColor: contentBackground,
664
+ borderColor: contentBorder,
665
+ borderRadius: tokens.radius[alertDialogRecipe.content.radius],
666
+ borderWidth: alertDialogRecipe.content.borderWidth,
667
+ ...resolveNativeShadowElevation(surfaceShadow, designProfile !== undefined, 8),
668
+ gap: alertDialogRecipe.content.gap,
669
+ maxWidth: sizeRecipe.maxWidth,
670
+ maxHeight: "100%",
671
+ flexShrink: 1,
672
+ padding: sizeRecipe.padding,
673
+ shadowColor: surfaceShadow.color,
674
+ shadowOffset: {
675
+ width: 0,
676
+ height: surfaceShadow.offsetY,
677
+ },
678
+ shadowOpacity: surfaceShadow.opacity,
679
+ shadowRadius: surfaceShadow.radius,
680
+ width: "100%",
637
681
  },
638
- shadowOpacity: surfaceShadow.opacity,
639
- shadowRadius: surfaceShadow.radius,
640
- width: "100%",
641
- },
642
- contentStyle,
643
- ], children: [_jsxs(DialogScrollBody, { 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: {
644
- direction: environment.direction,
645
- flexDirection: stackActions ? "column" : "row",
646
- gap: stackActions
647
- ? alertDialogRecipe.actions.stackedGap
648
- : alertDialogRecipe.actions.gap,
649
- }, children: [stackActions ? confirmAction : null, request.mode === "confirm" ? (_jsx(Pressable, { ref: cancelRef, accessibilityLabel: request.cancelLabel, accessibilityRole: "button", accessibilityState: { disabled: busy }, disabled: busy, onPress: () => cancel("cancel-action"), style: ({ pressed }) => [
650
- minimumTargetStyle,
651
- {
652
- alignItems: "center",
653
- // The cancel action is a quiet outline on the dialog surface. surfaceAlt
654
- // turned it into a tinted slab wherever a product brands that token
655
- // (BurnTok's pale blue, 2026-09-27), competing with the confirm fill.
656
- backgroundColor: colors.bg,
657
- borderColor: colors.border,
658
- borderRadius: tokens.radius.md,
659
- borderWidth: 1,
660
- flex: stackActions ? undefined : 1,
661
- justifyContent: "center",
662
- minWidth: alertDialogRecipe.actions.minButtonWidth,
663
- opacity: busy ? 0.5 : pressed ? 0.86 : 1,
664
- paddingHorizontal: spacing.md,
665
- },
666
- ], children: _jsx(Text, { variant: "label", children: request.cancelLabel }) })) : null, stackActions ? null : confirmAction] })] })] }) }));
682
+ contentStyle,
683
+ ], children: [_jsxs(DialogScrollBody, { 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: {
684
+ direction: environment.direction,
685
+ flexDirection: stackActions ? "column" : "row",
686
+ gap: stackActions
687
+ ? alertDialogRecipe.actions.stackedGap
688
+ : alertDialogRecipe.actions.gap,
689
+ }, children: [stackActions ? confirmAction : null, request.mode === "confirm" ? (_jsx(Pressable, { ref: cancelRef, accessibilityLabel: request.cancelLabel, accessibilityRole: "button", accessibilityState: { disabled: busy }, disabled: busy, onPress: () => cancel("cancel-action"), style: ({ pressed }) => [
690
+ minimumTargetStyle,
691
+ {
692
+ alignItems: "center",
693
+ // The cancel action is a quiet outline on the dialog surface. surfaceAlt
694
+ // turned it into a tinted slab wherever a product brands that token
695
+ // (BurnTok's pale blue, 2026-09-27), competing with the confirm fill.
696
+ backgroundColor: colors.bg,
697
+ borderColor: colors.border,
698
+ borderRadius: tokens.radius.md,
699
+ borderWidth: 1,
700
+ flex: stackActions ? undefined : 1,
701
+ justifyContent: "center",
702
+ minWidth: alertDialogRecipe.actions.minButtonWidth,
703
+ opacity: busy ? 0.5 : pressed ? 0.86 : 1,
704
+ paddingHorizontal: spacing.md,
705
+ },
706
+ ], children: _jsx(Text, { variant: "label", children: request.cancelLabel }) })) : null, stackActions ? null : confirmAction] })] })] }) }) }));
667
707
  }
668
708
  /** Native Sheet applies policy before emitting a concrete dismissal reason. */
669
709
  export function Sheet({ open, defaultOpen, onOpenChange, title, accessibilityTitle, description, children, footer, placement = "bottom", size = sheetRecipe.defaults.size, busy = false, dismissPolicy, closeLabel, returnFocusRef, safeAreaInsets: suppliedSafeAreaInsets, onDismissComplete, contentStyle, keyboardAvoidance = false, scrollable = false, onShow, ...modalProps }) {
670
- warnVisualContentStyle("Sheet", contentStyle);
671
710
  const { environment, palette, tokens, designProfile } = useHjmNativeTheme();
672
711
  // The profile floating role is shared with Web. Modal lifecycle, inset and
673
712
  // action state remain recipe-owned; changing decoration must not replace them.
@@ -683,8 +722,6 @@ export function Sheet({ open, defaultOpen, onOpenChange, title, accessibilityTit
683
722
  };
684
723
  const accessibleTitle = resolveOverlayAccessibleTitle(title, accessibilityTitle);
685
724
  const { width: windowWidth, height: windowHeight } = useWindowDimensions();
686
- const [modalHeight, setModalHeight] = useState(null);
687
- const [keyboardFrame, setKeyboardFrame] = useState(null);
688
725
  const sizeRatio = sheetRecipe.sizes[size];
689
726
  const policy = { ...sheetBehaviorDefaults, ...dismissPolicy };
690
727
  const [visible, changeOpen] = useReasonedOpenState({
@@ -697,6 +734,7 @@ export function Sheet({ open, defaultOpen, onOpenChange, title, accessibilityTit
697
734
  const visibleRef = useRef(visible);
698
735
  visibleRef.current = visible;
699
736
  const [nativeVisible, setNativeVisible] = useState(visible);
737
+ const { viewportHeight, keyboardInset, dockedKeyboard, setModalHeight } = useModalKeyboardViewport(nativeVisible, keyboardAvoidance);
700
738
  const nativeVisibleRef = useRef(visible);
701
739
  nativeVisibleRef.current = nativeVisible;
702
740
  const nativeShownRef = useRef(false);
@@ -720,37 +758,6 @@ export function Sheet({ open, defaultOpen, onOpenChange, title, accessibilityTit
720
758
  throw new RangeError(`Sheet safeAreaInsets.${edge} must be non-negative`);
721
759
  }
722
760
  }
723
- useEffect(() => {
724
- if (!nativeVisible || !keyboardAvoidance) {
725
- setKeyboardFrame(null);
726
- return;
727
- }
728
- // A sheet may open while an input elsewhere already owns the keyboard. Event-only
729
- // tracking misses that case; metrics also restores the correct state on reopen.
730
- setKeyboardFrame(Keyboard.metrics() ?? null);
731
- const change = (event) => setKeyboardFrame(event.endCoordinates);
732
- const hide = () => setKeyboardFrame(null);
733
- const subscriptions = [
734
- Keyboard.addListener("keyboardDidShow", change),
735
- Keyboard.addListener("keyboardDidHide", hide),
736
- ...(Platform.OS === "ios" ? [
737
- Keyboard.addListener("keyboardWillChangeFrame", change),
738
- Keyboard.addListener("keyboardWillHide", hide),
739
- ] : []),
740
- ];
741
- return () => subscriptions.forEach((subscription) => subscription.remove());
742
- }, [keyboardAvoidance, nativeVisible]);
743
- // Modal owns a full-screen coordinate space (statusBarTranslucent). Measure its
744
- // actual height so Android adjustResize does not subtract the keyboard twice.
745
- const viewportHeight = modalHeight ?? windowHeight;
746
- const dockedKeyboard = keyboardAvoidance && keyboardFrame !== null
747
- // Android converts IME pixels to float dp independently from window dimensions;
748
- // exact width comparison rejected a full-width keyboard by 0.000013 dp on API 36.
749
- && keyboardFrame.height > 0 && Math.abs(keyboardFrame.screenX) <= 1
750
- && keyboardFrame.width >= windowWidth - 1;
751
- const keyboardInset = dockedKeyboard
752
- ? Math.max(0, viewportHeight - keyboardFrame.screenY)
753
- : 0;
754
761
  const availableHeight = Math.max(0, viewportHeight - keyboardInset - insets.top);
755
762
  const bottomInset = dockedKeyboard ? 0 : insets.bottom;
756
763
  const cancelDismissFallback = useCallback(() => {
@@ -925,68 +932,68 @@ export function Sheet({ open, defaultOpen, onOpenChange, title, accessibilityTit
925
932
  nativeShownRef.current = true;
926
933
  startEnter();
927
934
  onShow?.(event);
928
- }, statusBarTranslucent: true, transparent: true, visible: nativeVisible, children: _jsxs(Animated.View, { accessibilityElementsHidden: !visible, importantForAccessibility: visible ? "auto" : "no-hide-descendants", pointerEvents: visible ? "auto" : "none", onLayout: (event) => setModalHeight(event.nativeEvent.layout.height), style: {
929
- alignItems: physicalPlacement === "right" ? "flex-end" : "flex-start",
930
- flex: 1,
931
- justifyContent: physicalPlacement === "bottom" ? "flex-end" : "flex-start",
932
- opacity: motionProgress,
933
- paddingTop: insets.top,
934
- paddingBottom: keyboardInset,
935
- }, 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: [accessibleTitle, description].filter(Boolean).join(", "), accessibilityState: { busy }, accessibilityViewIsModal: true, importantForAccessibility: "yes", role: "dialog", style: [
936
- {
937
- backgroundColor: contentBackground,
938
- borderColor: contentBorder,
939
- borderRadius: tokens.radius[sheetRecipe.content.radius],
940
- borderBottomLeftRadius: physicalPlacement === "bottom" ? 0 : tokens.radius[sheetRecipe.content.radius],
941
- borderBottomRightRadius: physicalPlacement === "bottom" ? 0 : tokens.radius[sheetRecipe.content.radius],
942
- borderWidth: sheetRecipe.content.borderWidth,
943
- ...resolveNativeShadowElevation(surfaceShadow, designProfile !== undefined, 8),
944
- gap: sheetRecipe.body.gap,
945
- // A fixed size ratio drives the height; `auto` lets the content decide
946
- // and the recipe's maxHeightRatio caps it.
947
- height: side
948
- ? availableHeight
949
- : sizeRatio === null
935
+ }, statusBarTranslucent: true, transparent: true, visible: nativeVisible, children: _jsx(ModalInteractionBoundary, { visible: visible, children: _jsxs(Animated.View, { onLayout: (event) => setModalHeight(event.nativeEvent.layout.height), style: {
936
+ alignItems: physicalPlacement === "right" ? "flex-end" : "flex-start",
937
+ flex: 1,
938
+ justifyContent: physicalPlacement === "bottom" ? "flex-end" : "flex-start",
939
+ opacity: motionProgress,
940
+ paddingTop: insets.top,
941
+ paddingBottom: keyboardInset,
942
+ }, 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: [accessibleTitle, description].filter(Boolean).join(", "), accessibilityState: { busy }, accessibilityViewIsModal: true, importantForAccessibility: "yes", role: "dialog", style: [
943
+ {
944
+ backgroundColor: contentBackground,
945
+ borderColor: contentBorder,
946
+ borderRadius: tokens.radius[sheetRecipe.content.radius],
947
+ borderBottomLeftRadius: physicalPlacement === "bottom" ? 0 : tokens.radius[sheetRecipe.content.radius],
948
+ borderBottomRightRadius: physicalPlacement === "bottom" ? 0 : tokens.radius[sheetRecipe.content.radius],
949
+ borderWidth: sheetRecipe.content.borderWidth,
950
+ ...resolveNativeShadowElevation(surfaceShadow, designProfile !== undefined, 8),
951
+ gap: sheetRecipe.body.gap,
952
+ // A fixed size ratio drives the height; `auto` lets the content decide
953
+ // and the recipe's maxHeightRatio caps it.
954
+ height: side
955
+ ? availableHeight
956
+ : sizeRatio === null
957
+ ? undefined
958
+ : Math.min(viewportHeight * sizeRatio, availableHeight),
959
+ maxWidth: side ? 420 : undefined,
960
+ // maxHeightRatio caps only `auto`. A fixed size is already bounded by availableHeight
961
+ // (viewport − top inset − keyboard); capping it too stopped `full` at 90% (2026-10-06 follow-up).
962
+ maxHeight: side || sizeRatio !== null
963
+ ? availableHeight
964
+ : Math.min(availableHeight, viewportHeight * sheetRecipe.content.maxHeightRatio),
965
+ paddingBottom: sheetRecipe.content.paddingBottom + bottomInset,
966
+ paddingLeft: sheetRecipe.content.paddingHorizontal + insets.left,
967
+ paddingRight: sheetRecipe.content.paddingHorizontal + insets.right,
968
+ // The top safe area limits the viewport, rather than adding a second
969
+ // notch-sized gap inside a sheet anchored to the bottom.
970
+ paddingTop: sheetRecipe.content.paddingTop,
971
+ shadowColor: surfaceShadow.color,
972
+ shadowOffset: {
973
+ width: 0,
974
+ height: surfaceShadow.offsetY,
975
+ },
976
+ shadowOpacity: surfaceShadow.opacity,
977
+ shadowRadius: surfaceShadow.radius,
978
+ transform: environment.reducedMotion
950
979
  ? undefined
951
- : Math.min(viewportHeight * sizeRatio, availableHeight),
952
- maxWidth: side ? 420 : undefined,
953
- // maxHeightRatio caps only `auto`. A fixed size is already bounded by availableHeight
954
- // (viewport − top inset − keyboard); capping it too stopped `full` at 90% (2026-10-06 follow-up).
955
- maxHeight: side || sizeRatio !== null
956
- ? availableHeight
957
- : Math.min(availableHeight, viewportHeight * sheetRecipe.content.maxHeightRatio),
958
- paddingBottom: sheetRecipe.content.paddingBottom + bottomInset,
959
- paddingLeft: sheetRecipe.content.paddingHorizontal + insets.left,
960
- paddingRight: sheetRecipe.content.paddingHorizontal + insets.right,
961
- // The top safe area limits the viewport, rather than adding a second
962
- // notch-sized gap inside a sheet anchored to the bottom.
963
- paddingTop: sheetRecipe.content.paddingTop,
964
- shadowColor: surfaceShadow.color,
965
- shadowOffset: {
966
- width: 0,
967
- height: surfaceShadow.offsetY,
980
+ : side
981
+ ? [{ translateX: translate }]
982
+ : [{ translateY: translate }],
983
+ width: side ? "88%" : "100%",
968
984
  },
969
- shadowOpacity: surfaceShadow.opacity,
970
- shadowRadius: surfaceShadow.radius,
971
- transform: environment.reducedMotion
972
- ? undefined
973
- : side
974
- ? [{ translateX: translate }]
975
- : [{ translateY: translate }],
976
- width: side ? "88%" : "100%",
977
- },
978
- contentStyle,
979
- ], children: [_jsxs(View, { style: {
980
- alignItems: "center",
981
- minHeight: sheetRecipe.header.minHeight,
982
- flexShrink: 0,
983
- direction: environment.direction,
984
- flexDirection: "row",
985
- gap: spacing.sm,
986
- }, 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(CloseGlyph, {}) })) : null] }), scrollable ? (_jsx(ScrollView, { style: { flexShrink: 1, minHeight: 0, ...bodyFill }, contentContainerStyle: { gap: sheetRecipe.body.gap }, keyboardShouldPersistTaps: "handled", keyboardDismissMode: Platform.OS === "ios" ? "interactive" : "on-drag", automaticallyAdjustKeyboardInsets: false, children: children })) : _jsx(View, { style: { gap: sheetRecipe.body.gap, flexShrink: 1, ...bodyFill }, children: children }), footer ? (_jsx(View, { style: {
987
- gap: sheetRecipe.footer.gap,
988
- flexShrink: 0,
989
- paddingTop: sheetRecipe.footer.paddingTop,
990
- }, children: footer })) : null] })] }) }));
985
+ contentStyle,
986
+ ], children: [_jsxs(View, { style: {
987
+ alignItems: "center",
988
+ minHeight: sheetRecipe.header.minHeight,
989
+ flexShrink: 0,
990
+ direction: environment.direction,
991
+ flexDirection: "row",
992
+ gap: spacing.sm,
993
+ }, 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(CloseGlyph, {}) })) : null] }), scrollable ? (_jsx(ScrollView, { style: { flexShrink: 1, minHeight: 0, ...bodyFill }, contentContainerStyle: { gap: sheetRecipe.body.gap }, keyboardShouldPersistTaps: "handled", keyboardDismissMode: Platform.OS === "ios" ? "interactive" : "on-drag", automaticallyAdjustKeyboardInsets: false, children: children })) : _jsx(View, { style: { gap: sheetRecipe.body.gap, flexShrink: 1, ...bodyFill }, children: children }), footer ? (_jsx(View, { style: {
994
+ gap: sheetRecipe.footer.gap,
995
+ flexShrink: 0,
996
+ paddingTop: sheetRecipe.footer.paddingTop,
997
+ }, children: footer })) : null] })] }) }) }));
991
998
  }
992
999
  //# sourceMappingURL=overlays.js.map