@hjmds/react-native 1.27.2 → 1.28.1
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.
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +9 -8
- package/dist/actions.js.map +1 -1
- package/dist/auth-screen.d.ts +41 -4
- package/dist/auth-screen.d.ts.map +1 -1
- package/dist/auth-screen.js +52 -8
- package/dist/auth-screen.js.map +1 -1
- package/dist/chart.d.ts +1 -1
- package/dist/chart.d.ts.map +1 -1
- package/dist/chart.js +8 -6
- package/dist/chart.js.map +1 -1
- package/dist/collection-rail.d.ts +7 -1
- package/dist/collection-rail.d.ts.map +1 -1
- package/dist/collection-rail.js +12 -5
- package/dist/collection-rail.js.map +1 -1
- package/dist/data-display.d.ts.map +1 -1
- package/dist/data-display.js +33 -10
- package/dist/data-display.js.map +1 -1
- package/dist/data-table.d.ts +2 -2
- package/dist/data-table.d.ts.map +1 -1
- package/dist/data-table.js +6 -5
- package/dist/data-table.js.map +1 -1
- package/dist/evidence.d.ts +1 -1
- package/dist/evidence.js +2 -2
- package/dist/evidence.js.map +1 -1
- package/dist/feedback.d.ts +12 -1
- package/dist/feedback.d.ts.map +1 -1
- package/dist/feedback.js +40 -18
- package/dist/feedback.js.map +1 -1
- package/dist/forms.d.ts.map +1 -1
- package/dist/forms.js +3 -1
- package/dist/forms.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inputs.d.ts +6 -0
- package/dist/inputs.d.ts.map +1 -1
- package/dist/inputs.js +24 -6
- package/dist/inputs.js.map +1 -1
- package/dist/internal/icon-button-edge.d.ts +12 -0
- package/dist/internal/icon-button-edge.d.ts.map +1 -0
- package/dist/internal/icon-button-edge.js +20 -0
- package/dist/internal/icon-button-edge.js.map +1 -0
- package/dist/internal/initial-focus.d.ts +11 -0
- package/dist/internal/initial-focus.d.ts.map +1 -0
- package/dist/internal/initial-focus.js +60 -0
- package/dist/internal/initial-focus.js.map +1 -0
- package/dist/internal/list-row-scroll-room.d.ts +20 -0
- package/dist/internal/list-row-scroll-room.d.ts.map +1 -0
- package/dist/internal/list-row-scroll-room.js +19 -0
- package/dist/internal/list-row-scroll-room.js.map +1 -0
- package/dist/internal/message-reactions.d.ts +1 -1
- package/dist/internal/message-reactions.d.ts.map +1 -1
- package/dist/internal/screen-body-inset.d.ts +9 -0
- package/dist/internal/screen-body-inset.d.ts.map +1 -0
- package/dist/internal/screen-body-inset.js +10 -0
- package/dist/internal/screen-body-inset.js.map +1 -0
- package/dist/internal/screen-footer.d.ts +15 -2
- package/dist/internal/screen-footer.d.ts.map +1 -1
- package/dist/internal/screen-footer.js +7 -4
- package/dist/internal/screen-footer.js.map +1 -1
- package/dist/keyboard.d.ts +1 -1
- package/dist/keyboard.d.ts.map +1 -1
- package/dist/keyboard.js.map +1 -1
- package/dist/notification-bell.d.ts +4 -1
- package/dist/notification-bell.d.ts.map +1 -1
- package/dist/notification-bell.js +7 -2
- package/dist/notification-bell.js.map +1 -1
- package/dist/overlays.d.ts +10 -2
- package/dist/overlays.d.ts.map +1 -1
- package/dist/overlays.js +230 -193
- package/dist/overlays.js.map +1 -1
- package/dist/primitives.js +2 -2
- package/dist/primitives.js.map +1 -1
- package/dist/screen-flows.d.ts +11 -6
- package/dist/screen-flows.d.ts.map +1 -1
- package/dist/screen-flows.js +13 -10
- package/dist/screen-flows.js.map +1 -1
- package/dist/screens.d.ts +31 -3
- package/dist/screens.d.ts.map +1 -1
- package/dist/screens.js +27 -7
- package/dist/screens.js.map +1 -1
- package/dist/sheet-gesture.d.ts.map +1 -1
- package/dist/sheet-gesture.js +1 -2
- package/dist/sheet-gesture.js.map +1 -1
- package/dist/table-family.d.ts +6 -1
- package/dist/table-family.d.ts.map +1 -1
- package/dist/table-family.js +10 -10
- package/dist/table-family.js.map +1 -1
- package/dist/top.js +1 -1
- package/dist/virtual-list.d.ts.map +1 -1
- package/dist/virtual-list.js +5 -4
- package/dist/virtual-list.js.map +1 -1
- package/package.json +3 -3
package/dist/overlays.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ScreenBodyInsetContext } from "./internal/screen-body-inset.js";
|
|
3
|
+
import { InitialFocusScope } from "./internal/initial-focus.js";
|
|
4
|
+
import { initialFocusDefaults } from "@hjmds/design-contracts/screen-patterns";
|
|
2
5
|
import { resolveOriginTransition } from "@hjmds/design-contracts/content-transition";
|
|
3
6
|
import { focusAccessibleHost } from "./internal/accessible-focus.js";
|
|
4
7
|
import { createAlertDialogSession, getAlertDialogInitialFocus, validateAlertDialogRequest, } from "@hjmds/design-contracts/components/alert-dialog";
|
|
@@ -14,6 +17,7 @@ import { Button, IconButton } from "./actions.js";
|
|
|
14
17
|
import { scheduleAfterNativeModalTeardown, shouldAwaitNativeModalDismiss, } from "./internal/modal-lifecycle.js";
|
|
15
18
|
import { isDevelopment } from "./internal/diagnostics.js";
|
|
16
19
|
import { FixedGlyph } from "./internal/fixed-glyph.js";
|
|
20
|
+
import { listRowScrollRoom } from "./internal/list-row-scroll-room.js";
|
|
17
21
|
import { minimumTargetStyle } from "./internal/styles.js";
|
|
18
22
|
import { Text } from "./primitives.js";
|
|
19
23
|
import { useHjmNativeSafeAreaInsets, useHjmNativeTheme } from "./provider.js";
|
|
@@ -148,7 +152,7 @@ function resolveOverlayAccessibleTitle(title, accessibilityTitle) {
|
|
|
148
152
|
throw new TypeError("An element `title` requires `accessibilityTitle` for the modal accessible name");
|
|
149
153
|
}
|
|
150
154
|
/** Native modal boundary with one reasoned close intent for each user attempt. */
|
|
151
|
-
export function Dialog({ open, defaultOpen, onOpenChange, title, accessibilityTitle, description, children, primaryAction, secondaryAction, dismissible = true, busy: externalBusy = false, onActionError, size = dialogRecipe.defaults.size, motionOrigin, closeLabel, returnFocusRef, contentStyle, onShow, ...modalProps }) {
|
|
155
|
+
export function Dialog({ open, defaultOpen, onOpenChange, title, accessibilityTitle, description, children, primaryAction, secondaryAction, dismissible = true, busy: externalBusy = false, onActionError, size = dialogRecipe.defaults.size, motionOrigin, closeLabel, returnFocusRef, contentStyle, onShow, initialFocus = initialFocusDefaults.overlay, ...modalProps }) {
|
|
152
156
|
const [actionPending, setActionPending] = useState(null);
|
|
153
157
|
const actionRun = useRef(null);
|
|
154
158
|
const busy = externalBusy || actionPending !== null;
|
|
@@ -176,6 +180,10 @@ export function Dialog({ open, defaultOpen, onOpenChange, title, accessibilityTi
|
|
|
176
180
|
const visibleRef = useRef(visible);
|
|
177
181
|
visibleRef.current = visible;
|
|
178
182
|
const [nativeVisible, setNativeVisible] = useState(visible);
|
|
183
|
+
// Entry focus waits for the native presentation (onShow) so it runs after the enter animation's interaction.
|
|
184
|
+
const [entered, setEntered] = useState(false);
|
|
185
|
+
useEffect(() => { if (!visible)
|
|
186
|
+
setEntered(false); }, [visible]);
|
|
179
187
|
const keyboardViewport = useModalKeyboardViewport(nativeVisible);
|
|
180
188
|
const nativeVisibleRef = useRef(visible);
|
|
181
189
|
nativeVisibleRef.current = nativeVisible;
|
|
@@ -360,66 +368,70 @@ export function Dialog({ open, defaultOpen, onOpenChange, title, accessibilityTi
|
|
|
360
368
|
const contentBackground = resolveColorReference(dialogRecipe.content.background, palette);
|
|
361
369
|
const contentBorder = resolveColorReference(dialogRecipe.content.border, palette);
|
|
362
370
|
const stackActions = environment.textScale >= 1.6 || windowWidth < 480;
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
371
|
+
// Overlay content is not in the screen body: a bleeding rail inside must not reach for the screen gutter.
|
|
372
|
+
return (_jsx(ScreenBodyInsetContext.Provider, { value: 0, children: _jsx(InitialFocusScope, { mode: initialFocus, active: visible && entered, children: _jsx(Modal, { ...modalProps, animationType: "none", onDismiss: () => {
|
|
373
|
+
nativeShownRef.current = false;
|
|
374
|
+
const pending = pendingExitRef.current;
|
|
375
|
+
if (pending)
|
|
376
|
+
completeExit(pending.token);
|
|
377
|
+
}, onRequestClose: () => requestClose("back"), onShow: (event) => {
|
|
378
|
+
nativeShownRef.current = true;
|
|
379
|
+
startEnter();
|
|
380
|
+
setEntered(true);
|
|
381
|
+
onShow?.(event);
|
|
382
|
+
}, statusBarTranslucent: true, transparent: true, visible: nativeVisible, children: _jsx(ModalInteractionBoundary, { visible: visible, children: _jsxs(Animated.View, { onLayout: (event) => keyboardViewport.setModalHeight(event.nativeEvent.layout.height), style: {
|
|
383
|
+
flex: 1,
|
|
384
|
+
justifyContent: "center",
|
|
385
|
+
opacity: motionProgress,
|
|
386
|
+
...viewportPadding,
|
|
387
|
+
paddingBottom: keyboardViewport.dockedKeyboard
|
|
388
|
+
? spacing.md + keyboardViewport.keyboardInset
|
|
389
|
+
: viewportPadding.paddingBottom,
|
|
390
|
+
}, 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) => {
|
|
391
|
+
const { width, height } = event.nativeEvent.layout;
|
|
392
|
+
const previous = originLayout.current;
|
|
393
|
+
originLayout.current = { width, height };
|
|
394
|
+
// Error copy, keyboard or rotation may change the destination after entry.
|
|
395
|
+
// Fade instead of returning through stale dimensions; a later open remeasures.
|
|
396
|
+
if (previous && (previous.width !== width || previous.height !== height))
|
|
397
|
+
setOriginTransform(null);
|
|
398
|
+
}, accessibilityLabel: [accessibleTitle, description].filter(Boolean).join(", "), accessibilityState: { busy }, accessibilityViewIsModal: true, importantForAccessibility: "yes", role: "dialog", style: [
|
|
399
|
+
{
|
|
400
|
+
alignSelf: "center",
|
|
401
|
+
backgroundColor: contentBackground,
|
|
402
|
+
borderColor: contentBorder,
|
|
403
|
+
borderRadius: tokens.radius[dialogRecipe.content.radius],
|
|
404
|
+
borderWidth: dialogRecipe.content.borderWidth,
|
|
405
|
+
...resolveNativeShadowElevation(surfaceShadow, designProfile !== undefined, 8),
|
|
406
|
+
gap: dialogRecipe.content.gap,
|
|
407
|
+
maxWidth: sizeRecipe.maxWidth,
|
|
408
|
+
maxHeight: "100%",
|
|
409
|
+
flexShrink: 1,
|
|
410
|
+
padding: sizeRecipe.padding,
|
|
411
|
+
shadowColor: surfaceShadow.color,
|
|
412
|
+
shadowOffset: { width: 0, height: surfaceShadow.offsetY },
|
|
413
|
+
shadowOpacity: surfaceShadow.opacity,
|
|
414
|
+
shadowRadius: surfaceShadow.radius,
|
|
415
|
+
width: "100%",
|
|
416
|
+
},
|
|
417
|
+
originTransform && !environment.reducedMotion ? { transform: [
|
|
418
|
+
{ translateX: motionProgress.interpolate({ inputRange: [0, 1], outputRange: [originTransform.translateX, 0] }) },
|
|
419
|
+
{ translateY: motionProgress.interpolate({ inputRange: [0, 1], outputRange: [originTransform.translateY, 0] }) },
|
|
420
|
+
{ scaleX: motionProgress.interpolate({ inputRange: [0, 1], outputRange: [originTransform.scaleX, 1] }) },
|
|
421
|
+
{ scaleY: motionProgress.interpolate({ inputRange: [0, 1], outputRange: [originTransform.scaleY, 1] }) },
|
|
422
|
+
] } : undefined,
|
|
423
|
+
contentStyle,
|
|
424
|
+
], children: [_jsxs(View, { style: { flexShrink: 1, gap: description ? spacing.xs : dialogRecipe.content.gap }, children: [_jsxs(View, { style: {
|
|
425
|
+
alignItems: "flex-start",
|
|
426
|
+
direction: environment.direction,
|
|
427
|
+
flexDirection: "row",
|
|
428
|
+
gap: spacing.sm,
|
|
429
|
+
}, 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, _jsx(KeyboardAvoidanceContext.Provider, { value: true, children: children })] })) : null] }), _jsx(OverlayActions, { busy: busy, onAction: runAction, pendingAction: actionPending, stacked: stackActions, ...(primaryAction === undefined ? {} : { primaryAction }), ...(secondaryAction === undefined ? {} : { secondaryAction }) })] })] }) }) }) }) }));
|
|
420
430
|
}
|
|
421
431
|
/** Contract session owns duplicate confirms, busy dismissal, error and settlement. */
|
|
422
432
|
export function AlertDialog({ open, defaultOpen, onOpenChange, request, size = alertDialogRecipe.defaults.size, returnFocusRef, onResult, contentStyle, onShow, ...modalProps }) {
|
|
433
|
+
// Prompt-style content with one text entry is focused on open; confirm-only dialogs have no field to choose.
|
|
434
|
+
const initialFocus = initialFocusDefaults.overlay;
|
|
423
435
|
validateAlertDialogRequest(request);
|
|
424
436
|
const viewportPadding = useDialogViewportPadding();
|
|
425
437
|
const { colors, environment, palette, tokens, designProfile } = useHjmNativeTheme();
|
|
@@ -440,6 +452,10 @@ export function AlertDialog({ open, defaultOpen, onOpenChange, request, size = a
|
|
|
440
452
|
const visibleRef = useRef(visible);
|
|
441
453
|
visibleRef.current = visible;
|
|
442
454
|
const [nativeVisible, setNativeVisible] = useState(visible);
|
|
455
|
+
// Entry focus waits for the native presentation (onShow) so it runs after the enter animation's interaction.
|
|
456
|
+
const [entered, setEntered] = useState(false);
|
|
457
|
+
useEffect(() => { if (!visible)
|
|
458
|
+
setEntered(false); }, [visible]);
|
|
443
459
|
const keyboardViewport = useModalKeyboardViewport(nativeVisible);
|
|
444
460
|
const nativeVisibleRef = useRef(visible);
|
|
445
461
|
nativeVisibleRef.current = nativeVisible;
|
|
@@ -645,71 +661,72 @@ export function AlertDialog({ open, defaultOpen, onOpenChange, request, size = a
|
|
|
645
661
|
paddingHorizontal: spacing.md,
|
|
646
662
|
},
|
|
647
663
|
], children: _jsx(Text, { style: { color: confirmContent }, variant: "label", children: request.confirmLabel }) }));
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
664
|
+
// Overlay content is not in the screen body: a bleeding rail inside must not reach for the screen gutter.
|
|
665
|
+
return (_jsx(ScreenBodyInsetContext.Provider, { value: 0, children: _jsx(InitialFocusScope, { mode: initialFocus, active: visible && entered, children: _jsx(Modal, { ...modalProps, animationType: "none", onDismiss: () => {
|
|
666
|
+
nativeShownRef.current = false;
|
|
667
|
+
const pending = pendingExitSessions.current[0];
|
|
668
|
+
if (pending)
|
|
669
|
+
completeExit(pending.session);
|
|
670
|
+
}, onRequestClose: () => cancel("back"), onShow: (event) => { focusLeastDestructive(event); setEntered(true); }, statusBarTranslucent: true, transparent: true, visible: nativeVisible, children: _jsx(ModalInteractionBoundary, { visible: visible, children: _jsxs(Animated.View, { onLayout: (event) => keyboardViewport.setModalHeight(event.nativeEvent.layout.height), style: {
|
|
671
|
+
flex: 1,
|
|
672
|
+
justifyContent: "center",
|
|
673
|
+
opacity: motionProgress,
|
|
674
|
+
...viewportPadding,
|
|
675
|
+
paddingBottom: keyboardViewport.dockedKeyboard
|
|
676
|
+
? spacing.md + keyboardViewport.keyboardInset
|
|
677
|
+
: viewportPadding.paddingBottom,
|
|
678
|
+
}, children: [_jsx(Scrim, {}), _jsxs(View, { accessibilityLabel: `${request.title}, ${request.description}`, accessibilityState: { busy }, accessibilityViewIsModal: true, importantForAccessibility: "yes", role: "alertdialog", style: [
|
|
679
|
+
{
|
|
680
|
+
alignSelf: "center",
|
|
681
|
+
backgroundColor: contentBackground,
|
|
682
|
+
borderColor: contentBorder,
|
|
683
|
+
borderRadius: tokens.radius[alertDialogRecipe.content.radius],
|
|
684
|
+
borderWidth: alertDialogRecipe.content.borderWidth,
|
|
685
|
+
...resolveNativeShadowElevation(surfaceShadow, designProfile !== undefined, 8),
|
|
686
|
+
gap: alertDialogRecipe.content.gap,
|
|
687
|
+
maxWidth: sizeRecipe.maxWidth,
|
|
688
|
+
maxHeight: "100%",
|
|
689
|
+
flexShrink: 1,
|
|
690
|
+
padding: sizeRecipe.padding,
|
|
691
|
+
shadowColor: surfaceShadow.color,
|
|
692
|
+
shadowOffset: {
|
|
693
|
+
width: 0,
|
|
694
|
+
height: surfaceShadow.offsetY,
|
|
695
|
+
},
|
|
696
|
+
shadowOpacity: surfaceShadow.opacity,
|
|
697
|
+
shadowRadius: surfaceShadow.radius,
|
|
698
|
+
width: "100%",
|
|
699
|
+
},
|
|
700
|
+
contentStyle,
|
|
701
|
+
], 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: {
|
|
702
|
+
direction: environment.direction,
|
|
703
|
+
flexDirection: stackActions ? "column" : "row",
|
|
704
|
+
gap: stackActions
|
|
705
|
+
? alertDialogRecipe.actions.stackedGap
|
|
706
|
+
: alertDialogRecipe.actions.gap,
|
|
707
|
+
}, 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 }) => [
|
|
708
|
+
minimumTargetStyle,
|
|
709
|
+
{
|
|
710
|
+
alignItems: "center",
|
|
711
|
+
// The cancel action is a quiet outline on the dialog surface. surfaceAlt
|
|
712
|
+
// turned it into a tinted slab wherever a product brands that token
|
|
713
|
+
// (BurnTok's pale blue, 2026-09-27), competing with the confirm fill.
|
|
714
|
+
backgroundColor: colors.bg,
|
|
715
|
+
borderColor: colors.border,
|
|
716
|
+
borderRadius: tokens.radius.md,
|
|
717
|
+
borderWidth: 1,
|
|
718
|
+
flex: stackActions ? undefined : 1,
|
|
719
|
+
justifyContent: "center",
|
|
720
|
+
minWidth: alertDialogRecipe.actions.minButtonWidth,
|
|
721
|
+
opacity: busy ? 0.5 : pressed ? 0.86 : 1,
|
|
722
|
+
paddingHorizontal: spacing.md,
|
|
723
|
+
},
|
|
724
|
+
], children: _jsx(Text, { variant: "label", children: request.cancelLabel }) })) : null, stackActions ? null : confirmAction] })] })] }) }) }) }) }));
|
|
708
725
|
}
|
|
709
726
|
/** Native Sheet applies policy before emitting a concrete dismissal reason. */
|
|
710
727
|
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,
|
|
711
728
|
// Default on since the 2026-10-16 keyboard audit: input sheets that forgot the opt-in were covered.
|
|
712
|
-
keyboardAvoidance = true, scrollable = false, showClose, onShow, ...modalProps }) {
|
|
729
|
+
keyboardAvoidance = true, scrollable = false, showClose, onShow, initialFocus = initialFocusDefaults.overlay, ...modalProps }) {
|
|
713
730
|
const { environment, palette, tokens, designProfile } = useHjmNativeTheme();
|
|
714
731
|
// The profile floating role is shared with Web. Modal lifecycle, inset and
|
|
715
732
|
// action state remain recipe-owned; changing decoration must not replace them.
|
|
@@ -738,6 +755,10 @@ keyboardAvoidance = true, scrollable = false, showClose, onShow, ...modalProps }
|
|
|
738
755
|
const visibleRef = useRef(visible);
|
|
739
756
|
visibleRef.current = visible;
|
|
740
757
|
const [nativeVisible, setNativeVisible] = useState(visible);
|
|
758
|
+
// Entry focus waits for the native presentation (onShow) so it runs after the enter animation's interaction.
|
|
759
|
+
const [entered, setEntered] = useState(false);
|
|
760
|
+
useEffect(() => { if (!visible)
|
|
761
|
+
setEntered(false); }, [visible]);
|
|
741
762
|
const { viewportHeight, keyboardInset, dockedKeyboard, setModalHeight } = useModalKeyboardViewport(nativeVisible, keyboardAvoidance);
|
|
742
763
|
const nativeVisibleRef = useRef(visible);
|
|
743
764
|
nativeVisibleRef.current = nativeVisible;
|
|
@@ -949,82 +970,98 @@ keyboardAvoidance = true, scrollable = false, showClose, onShow, ...modalProps }
|
|
|
949
970
|
// `auto` stays content-sized: growing there has no free space to take. The ScrollView body grows too, so the
|
|
950
971
|
// footer sits at the bottom on both platforms; its content container does not, so scroll content keeps its height.
|
|
951
972
|
const bodyFill = side || sizeRatio !== null ? { flexGrow: 1, minHeight: 0 } : {};
|
|
973
|
+
const sheetBodyRoom = listRowScrollRoom(sheetRecipe.content.paddingHorizontal);
|
|
952
974
|
const contentBackground = resolveColorReference(sheetRecipe.content.background, palette);
|
|
953
975
|
const contentBorder = resolveColorReference(sheetRecipe.content.border, palette);
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
:
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
976
|
+
// Overlay content is not in the screen body: a bleeding rail inside must not reach for the screen gutter.
|
|
977
|
+
return (_jsx(ScreenBodyInsetContext.Provider, { value: 0, children: _jsx(InitialFocusScope, { mode: initialFocus, active: visible && entered, children: _jsx(Modal, { ...modalProps, animationType: "none", onDismiss: () => {
|
|
978
|
+
nativeShownRef.current = false;
|
|
979
|
+
const pending = pendingDismissals.current[0];
|
|
980
|
+
if (pending)
|
|
981
|
+
completeDismiss(pending.cycle);
|
|
982
|
+
}, onRequestClose: () => requestClose("back"), onShow: (event) => {
|
|
983
|
+
nativeShownRef.current = true;
|
|
984
|
+
startEnter();
|
|
985
|
+
setEntered(true);
|
|
986
|
+
onShow?.(event);
|
|
987
|
+
}, statusBarTranslucent: true, transparent: true, visible: nativeVisible, children: _jsx(ModalInteractionBoundary, { visible: visible, children: _jsxs(Animated.View, { onLayout: (event) => setModalHeight(event.nativeEvent.layout.height), style: {
|
|
988
|
+
alignItems: physicalPlacement === "right" ? "flex-end" : "flex-start",
|
|
989
|
+
flex: 1,
|
|
990
|
+
justifyContent: physicalPlacement === "bottom" ? "flex-end" : "flex-start",
|
|
991
|
+
opacity: motionProgress,
|
|
992
|
+
paddingTop: insets.top,
|
|
993
|
+
paddingBottom: keyboardInset,
|
|
994
|
+
}, children: [_jsx(Scrim, {}), policy.dismissible && policy.outsideDismiss ? (
|
|
995
|
+
// Backdrop press closes every sheet by default (2026-10-17 user decision) through the same
|
|
996
|
+
// requestClose path as the handle, so busy and dismissPolicy guards apply. Named by closeLabel.
|
|
997
|
+
_jsx(Pressable, { accessibilityLabel: closeLabel, accessibilityRole: "button", accessibilityState: { disabled: closeBlocked }, disabled: closeBlocked, 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", onAccessibilityEscape: () => requestClose("escape"), onLayout: (event) => { sheetHeight.current = event.nativeEvent.layout.height; }, role: "dialog", style: [
|
|
998
|
+
{
|
|
999
|
+
backgroundColor: contentBackground,
|
|
1000
|
+
borderColor: contentBorder,
|
|
1001
|
+
borderRadius: tokens.radius[sheetRecipe.content.radius],
|
|
1002
|
+
borderBottomLeftRadius: physicalPlacement === "bottom" ? 0 : tokens.radius[sheetRecipe.content.radius],
|
|
1003
|
+
borderBottomRightRadius: physicalPlacement === "bottom" ? 0 : tokens.radius[sheetRecipe.content.radius],
|
|
1004
|
+
borderWidth: sheetRecipe.content.borderWidth,
|
|
1005
|
+
...resolveNativeShadowElevation(surfaceShadow, designProfile !== undefined, 8),
|
|
1006
|
+
gap: sheetRecipe.body.gap,
|
|
1007
|
+
// A fixed size ratio drives the height; `auto` lets the content decide
|
|
1008
|
+
// and the recipe's maxHeightRatio caps it.
|
|
1009
|
+
height: side
|
|
1010
|
+
? availableHeight
|
|
1011
|
+
: sizeRatio === null
|
|
1012
|
+
? undefined
|
|
1013
|
+
: Math.min(viewportHeight * sizeRatio, availableHeight),
|
|
1014
|
+
maxWidth: side ? 420 : undefined,
|
|
1015
|
+
// maxHeightRatio caps only `auto`. A fixed size is already bounded by availableHeight
|
|
1016
|
+
// (viewport − top inset − keyboard); capping it too stopped `full` at 90% (2026-10-06 follow-up).
|
|
1017
|
+
maxHeight: side || sizeRatio !== null
|
|
1018
|
+
? availableHeight
|
|
1019
|
+
: Math.min(availableHeight, viewportHeight * sheetRecipe.content.maxHeightRatio),
|
|
1020
|
+
paddingBottom: sheetRecipe.content.paddingBottom + bottomInset,
|
|
1021
|
+
paddingLeft: sheetRecipe.content.paddingHorizontal + insets.left,
|
|
1022
|
+
paddingRight: sheetRecipe.content.paddingHorizontal + insets.right,
|
|
1023
|
+
// The top safe area limits the viewport, rather than adding a second
|
|
1024
|
+
// notch-sized gap inside a sheet anchored to the bottom.
|
|
1025
|
+
paddingTop: sheetRecipe.content.paddingTop,
|
|
1026
|
+
shadowColor: surfaceShadow.color,
|
|
1027
|
+
shadowOffset: {
|
|
1028
|
+
width: 0,
|
|
1029
|
+
height: surfaceShadow.offsetY,
|
|
1030
|
+
},
|
|
1031
|
+
shadowOpacity: surfaceShadow.opacity,
|
|
1032
|
+
shadowRadius: surfaceShadow.radius,
|
|
1033
|
+
transform: environment.reducedMotion
|
|
1034
|
+
? undefined
|
|
1035
|
+
: side
|
|
1036
|
+
? [{ translateX: translate }]
|
|
1037
|
+
// Two translateY entries compose: the enter/exit motion plus the live drag offset.
|
|
1038
|
+
: [{ translateY: translate }, { translateY: dragOffset }],
|
|
1039
|
+
width: side ? "88%" : "100%",
|
|
1040
|
+
},
|
|
1041
|
+
contentStyle,
|
|
1042
|
+
], children: [_jsx(View, { accessible: true, accessibilityRole: "header", accessibilityLabel: accessibleTitle, style: { position: "absolute", top: 0, start: 0, width: 1, height: 1, overflow: "hidden" } }), _jsxs(View, { ...(canSwipe ? dragResponder.panHandlers : {}), style: { flexShrink: 0 }, children: [showHandle && sheetRecipe.handle.visibleByDefault ? (
|
|
1043
|
+
// The handle doubles as a close control so switch and screen-reader users need no drag.
|
|
1044
|
+
_jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: closeLabel, accessibilityState: { disabled: closeBlocked }, disabled: closeBlocked, onPress: () => requestClose("close-action"), hitSlop: spacing.sm, style: { alignSelf: "center", paddingVertical: spacing.xs }, children: _jsx(View, { style: { width: sheetRecipe.handle.width, height: sheetRecipe.handle.height, borderRadius: tokens.radius[sheetRecipe.handle.radius], backgroundColor: resolveColorReference(sheetRecipe.handle.color, palette) } }) })) : null, description || (policy.dismissible && closeVisible) ? (_jsxs(View, { style: {
|
|
1045
|
+
alignItems: "center",
|
|
1046
|
+
minHeight: sheetRecipe.header.minHeight,
|
|
1047
|
+
flexShrink: 0,
|
|
1048
|
+
direction: environment.direction,
|
|
1049
|
+
flexDirection: "row",
|
|
1050
|
+
// Without a description the × keeps its inline-end place (2026-10-17: no visible title).
|
|
1051
|
+
justifyContent: "flex-end",
|
|
1052
|
+
gap: spacing.sm,
|
|
1053
|
+
}, children: [description ? _jsx(View, { style: { flex: 1 }, children: _jsx(Text, { tone: "muted", children: description }) }) : null, policy.dismissible && closeVisible ? (_jsx(IconButton, { disabled: busy && !policy.dismissWhileBusy, label: closeLabel, onPress: () => requestClose("close-action"), children: _jsx(CloseGlyph, {}) })) : null] })) : null] }), scrollable ? (_jsx(ScrollView
|
|
1054
|
+
// The sheet's inline padding used to sit outside this clipping viewport, so ListRows cut their surface
|
|
1055
|
+
// bleed. The viewport now reaches the padding edge and carries the padding inside its content (Web's
|
|
1056
|
+
// body scroll box does the same); content keeps its position (2026-10-17 1.27.2 adoption).
|
|
1057
|
+
, {
|
|
1058
|
+
// The sheet's inline padding used to sit outside this clipping viewport, so ListRows cut their surface
|
|
1059
|
+
// bleed. The viewport now reaches the padding edge and carries the padding inside its content (Web's
|
|
1060
|
+
// body scroll box does the same); content keeps its position (2026-10-17 1.27.2 adoption).
|
|
1061
|
+
style: { flexShrink: 1, minHeight: 0, ...bodyFill, ...sheetBodyRoom.viewport }, contentContainerStyle: { gap: sheetRecipe.body.gap, ...sheetBodyRoom.content }, keyboardShouldPersistTaps: "handled", keyboardDismissMode: Platform.OS === "ios" ? "interactive" : "on-drag", automaticallyAdjustKeyboardInsets: false, children: _jsx(KeyboardAvoidanceContext.Provider, { value: keyboardAvoidance, children: children }) })) : _jsx(View, { style: { gap: sheetRecipe.body.gap, flexShrink: 1, ...bodyFill }, children: _jsx(KeyboardAvoidanceContext.Provider, { value: keyboardAvoidance, children: children }) }), footer ? (_jsx(View, { style: {
|
|
1062
|
+
gap: sheetRecipe.footer.gap,
|
|
1020
1063
|
flexShrink: 0,
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
gap: spacing.sm,
|
|
1024
|
-
}, 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 && closeVisible ? (_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: _jsx(KeyboardAvoidanceContext.Provider, { value: keyboardAvoidance, children: children }) })) : _jsx(View, { style: { gap: sheetRecipe.body.gap, flexShrink: 1, ...bodyFill }, children: _jsx(KeyboardAvoidanceContext.Provider, { value: keyboardAvoidance, children: children }) }), footer ? (_jsx(View, { style: {
|
|
1025
|
-
gap: sheetRecipe.footer.gap,
|
|
1026
|
-
flexShrink: 0,
|
|
1027
|
-
paddingTop: sheetRecipe.footer.paddingTop,
|
|
1028
|
-
}, children: footer })) : null] })] }) }) }));
|
|
1064
|
+
paddingTop: sheetRecipe.footer.paddingTop,
|
|
1065
|
+
}, children: footer })) : null] })] }) }) }) }) }));
|
|
1029
1066
|
}
|
|
1030
1067
|
//# sourceMappingURL=overlays.js.map
|