@oxyhq/bloom 0.14.0 → 0.16.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.
- package/lib/commonjs/alert-dialog/AlertDialog.js +35 -27
- package/lib/commonjs/alert-dialog/AlertDialog.js.map +1 -1
- package/lib/commonjs/alert-dialog/index.js +1 -1
- package/lib/commonjs/alert-dialog/index.js.map +1 -1
- package/lib/commonjs/alert-dialog/index.web.js +1 -1
- package/lib/commonjs/alert-dialog/index.web.js.map +1 -1
- package/lib/commonjs/command/Command.js +27 -16
- package/lib/commonjs/command/Command.js.map +1 -1
- package/lib/commonjs/command/index.js +1 -1
- package/lib/commonjs/command/index.js.map +1 -1
- package/lib/commonjs/command/index.web.js +1 -1
- package/lib/commonjs/command/index.web.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.js +336 -43
- package/lib/commonjs/dialog/Dialog.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.web.js +420 -66
- package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
- package/lib/commonjs/dialog/index.js +2 -9
- package/lib/commonjs/dialog/index.js.map +1 -1
- package/lib/commonjs/dialog/index.web.js +2 -15
- package/lib/commonjs/dialog/index.web.js.map +1 -1
- package/lib/commonjs/dialog/placement.js +104 -0
- package/lib/commonjs/dialog/placement.js.map +1 -0
- package/lib/commonjs/index.js +7 -29
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +13 -35
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/module/alert-dialog/AlertDialog.js +35 -27
- package/lib/module/alert-dialog/AlertDialog.js.map +1 -1
- package/lib/module/alert-dialog/index.js +2 -2
- package/lib/module/alert-dialog/index.js.map +1 -1
- package/lib/module/alert-dialog/index.web.js +2 -2
- package/lib/module/alert-dialog/index.web.js.map +1 -1
- package/lib/module/command/Command.js +27 -16
- package/lib/module/command/Command.js.map +1 -1
- package/lib/module/command/index.js +2 -2
- package/lib/module/command/index.js.map +1 -1
- package/lib/module/command/index.web.js +2 -2
- package/lib/module/command/index.web.js.map +1 -1
- package/lib/module/dialog/Dialog.js +335 -46
- package/lib/module/dialog/Dialog.js.map +1 -1
- package/lib/module/dialog/Dialog.web.js +418 -68
- package/lib/module/dialog/Dialog.web.js.map +1 -1
- package/lib/module/dialog/index.js +1 -2
- package/lib/module/dialog/index.js.map +1 -1
- package/lib/module/dialog/index.web.js +1 -2
- package/lib/module/dialog/index.web.js.map +1 -1
- package/lib/module/dialog/placement.js +100 -0
- package/lib/module/dialog/placement.js.map +1 -0
- package/lib/module/index.js +1 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +1 -3
- package/lib/module/index.web.js.map +1 -1
- package/lib/typescript/commonjs/alert-dialog/AlertDialog.d.ts +13 -12
- package/lib/typescript/commonjs/alert-dialog/AlertDialog.d.ts.map +1 -1
- package/lib/typescript/commonjs/alert-dialog/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/alert-dialog/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/command/Command.d.ts +8 -8
- package/lib/typescript/commonjs/command/Command.d.ts.map +1 -1
- package/lib/typescript/commonjs/command/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/command/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.d.ts +17 -15
- package/lib/typescript/commonjs/dialog/Dialog.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.web.d.ts +18 -5
- package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/index.d.ts +2 -4
- package/lib/typescript/commonjs/dialog/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/index.web.d.ts +2 -4
- package/lib/typescript/commonjs/dialog/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/placement.d.ts +71 -0
- package/lib/typescript/commonjs/dialog/placement.d.ts.map +1 -0
- package/lib/typescript/commonjs/dialog/types.d.ts +66 -3
- package/lib/typescript/commonjs/dialog/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +2 -4
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +2 -4
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/module/alert-dialog/AlertDialog.d.ts +13 -12
- package/lib/typescript/module/alert-dialog/AlertDialog.d.ts.map +1 -1
- package/lib/typescript/module/alert-dialog/index.d.ts.map +1 -1
- package/lib/typescript/module/alert-dialog/index.web.d.ts.map +1 -1
- package/lib/typescript/module/command/Command.d.ts +8 -8
- package/lib/typescript/module/command/Command.d.ts.map +1 -1
- package/lib/typescript/module/command/index.d.ts.map +1 -1
- package/lib/typescript/module/command/index.web.d.ts.map +1 -1
- package/lib/typescript/module/dialog/Dialog.d.ts +17 -15
- package/lib/typescript/module/dialog/Dialog.d.ts.map +1 -1
- package/lib/typescript/module/dialog/Dialog.web.d.ts +18 -5
- package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/module/dialog/index.d.ts +2 -4
- package/lib/typescript/module/dialog/index.d.ts.map +1 -1
- package/lib/typescript/module/dialog/index.web.d.ts +2 -4
- package/lib/typescript/module/dialog/index.web.d.ts.map +1 -1
- package/lib/typescript/module/dialog/placement.d.ts +71 -0
- package/lib/typescript/module/dialog/placement.d.ts.map +1 -0
- package/lib/typescript/module/dialog/types.d.ts +66 -3
- package/lib/typescript/module/dialog/types.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +2 -4
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +2 -4
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/package.json +1 -18
- package/src/__tests__/AlertDialog.test.tsx +122 -0
- package/src/__tests__/Command.test.tsx +107 -0
- package/src/__tests__/DialogPlacement.test.tsx +144 -0
- package/src/alert-dialog/AlertDialog.tsx +46 -36
- package/src/alert-dialog/index.ts +2 -2
- package/src/alert-dialog/index.web.ts +2 -2
- package/src/command/Command.tsx +30 -19
- package/src/command/index.ts +2 -2
- package/src/command/index.web.ts +2 -2
- package/src/dialog/Dialog.tsx +406 -50
- package/src/dialog/Dialog.web.tsx +496 -78
- package/src/dialog/index.ts +4 -3
- package/src/dialog/index.web.ts +4 -7
- package/src/dialog/placement.ts +113 -0
- package/src/dialog/types.ts +69 -3
- package/src/index.ts +4 -11
- package/src/index.web.ts +4 -11
- package/lib/commonjs/dialog/CenteredDialog.js +0 -250
- package/lib/commonjs/dialog/CenteredDialog.js.map +0 -1
- package/lib/commonjs/dialog/CenteredDialog.web.js +0 -328
- package/lib/commonjs/dialog/CenteredDialog.web.js.map +0 -1
- package/lib/commonjs/dialog/centered-dialog-tokens.js +0 -62
- package/lib/commonjs/dialog/centered-dialog-tokens.js.map +0 -1
- package/lib/commonjs/dialog/centered-dialog-types.js +0 -6
- package/lib/commonjs/dialog/centered-dialog-types.js.map +0 -1
- package/lib/commonjs/responsive-sheet/ResponsiveSheet.js +0 -300
- package/lib/commonjs/responsive-sheet/ResponsiveSheet.js.map +0 -1
- package/lib/commonjs/responsive-sheet/ResponsiveSheet.native.js +0 -226
- package/lib/commonjs/responsive-sheet/ResponsiveSheet.native.js.map +0 -1
- package/lib/commonjs/responsive-sheet/const.js +0 -60
- package/lib/commonjs/responsive-sheet/const.js.map +0 -1
- package/lib/commonjs/responsive-sheet/index.js +0 -19
- package/lib/commonjs/responsive-sheet/index.js.map +0 -1
- package/lib/commonjs/responsive-sheet/index.web.js +0 -19
- package/lib/commonjs/responsive-sheet/index.web.js.map +0 -1
- package/lib/commonjs/responsive-sheet/types.js +0 -6
- package/lib/commonjs/responsive-sheet/types.js.map +0 -1
- package/lib/module/dialog/CenteredDialog.js +0 -244
- package/lib/module/dialog/CenteredDialog.js.map +0 -1
- package/lib/module/dialog/CenteredDialog.web.js +0 -322
- package/lib/module/dialog/CenteredDialog.web.js.map +0 -1
- package/lib/module/dialog/centered-dialog-tokens.js +0 -58
- package/lib/module/dialog/centered-dialog-tokens.js.map +0 -1
- package/lib/module/dialog/centered-dialog-types.js +0 -4
- package/lib/module/dialog/centered-dialog-types.js.map +0 -1
- package/lib/module/responsive-sheet/ResponsiveSheet.js +0 -293
- package/lib/module/responsive-sheet/ResponsiveSheet.js.map +0 -1
- package/lib/module/responsive-sheet/ResponsiveSheet.native.js +0 -219
- package/lib/module/responsive-sheet/ResponsiveSheet.native.js.map +0 -1
- package/lib/module/responsive-sheet/const.js +0 -56
- package/lib/module/responsive-sheet/const.js.map +0 -1
- package/lib/module/responsive-sheet/index.js +0 -4
- package/lib/module/responsive-sheet/index.js.map +0 -1
- package/lib/module/responsive-sheet/index.web.js +0 -14
- package/lib/module/responsive-sheet/index.web.js.map +0 -1
- package/lib/module/responsive-sheet/types.js +0 -4
- package/lib/module/responsive-sheet/types.js.map +0 -1
- package/lib/typescript/commonjs/dialog/CenteredDialog.d.ts +0 -20
- package/lib/typescript/commonjs/dialog/CenteredDialog.d.ts.map +0 -1
- package/lib/typescript/commonjs/dialog/CenteredDialog.web.d.ts +0 -29
- package/lib/typescript/commonjs/dialog/CenteredDialog.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/dialog/centered-dialog-tokens.d.ts +0 -47
- package/lib/typescript/commonjs/dialog/centered-dialog-tokens.d.ts.map +0 -1
- package/lib/typescript/commonjs/dialog/centered-dialog-types.d.ts +0 -70
- package/lib/typescript/commonjs/dialog/centered-dialog-types.d.ts.map +0 -1
- package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.d.ts +0 -26
- package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.d.ts.map +0 -1
- package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.native.d.ts +0 -20
- package/lib/typescript/commonjs/responsive-sheet/ResponsiveSheet.native.d.ts.map +0 -1
- package/lib/typescript/commonjs/responsive-sheet/const.d.ts +0 -43
- package/lib/typescript/commonjs/responsive-sheet/const.d.ts.map +0 -1
- package/lib/typescript/commonjs/responsive-sheet/index.d.ts +0 -3
- package/lib/typescript/commonjs/responsive-sheet/index.d.ts.map +0 -1
- package/lib/typescript/commonjs/responsive-sheet/index.web.d.ts +0 -3
- package/lib/typescript/commonjs/responsive-sheet/index.web.d.ts.map +0 -1
- package/lib/typescript/commonjs/responsive-sheet/types.d.ts +0 -55
- package/lib/typescript/commonjs/responsive-sheet/types.d.ts.map +0 -1
- package/lib/typescript/module/dialog/CenteredDialog.d.ts +0 -20
- package/lib/typescript/module/dialog/CenteredDialog.d.ts.map +0 -1
- package/lib/typescript/module/dialog/CenteredDialog.web.d.ts +0 -29
- package/lib/typescript/module/dialog/CenteredDialog.web.d.ts.map +0 -1
- package/lib/typescript/module/dialog/centered-dialog-tokens.d.ts +0 -47
- package/lib/typescript/module/dialog/centered-dialog-tokens.d.ts.map +0 -1
- package/lib/typescript/module/dialog/centered-dialog-types.d.ts +0 -70
- package/lib/typescript/module/dialog/centered-dialog-types.d.ts.map +0 -1
- package/lib/typescript/module/responsive-sheet/ResponsiveSheet.d.ts +0 -26
- package/lib/typescript/module/responsive-sheet/ResponsiveSheet.d.ts.map +0 -1
- package/lib/typescript/module/responsive-sheet/ResponsiveSheet.native.d.ts +0 -20
- package/lib/typescript/module/responsive-sheet/ResponsiveSheet.native.d.ts.map +0 -1
- package/lib/typescript/module/responsive-sheet/const.d.ts +0 -43
- package/lib/typescript/module/responsive-sheet/const.d.ts.map +0 -1
- package/lib/typescript/module/responsive-sheet/index.d.ts +0 -3
- package/lib/typescript/module/responsive-sheet/index.d.ts.map +0 -1
- package/lib/typescript/module/responsive-sheet/index.web.d.ts +0 -3
- package/lib/typescript/module/responsive-sheet/index.web.d.ts.map +0 -1
- package/lib/typescript/module/responsive-sheet/types.d.ts +0 -55
- package/lib/typescript/module/responsive-sheet/types.d.ts.map +0 -1
- package/src/__tests__/CenteredDialog.test.tsx +0 -108
- package/src/__tests__/ResponsiveSheet.test.tsx +0 -104
- package/src/dialog/CenteredDialog.stories.tsx +0 -125
- package/src/dialog/CenteredDialog.tsx +0 -303
- package/src/dialog/CenteredDialog.web.tsx +0 -372
- package/src/dialog/centered-dialog-tokens.ts +0 -68
- package/src/dialog/centered-dialog-types.ts +0 -70
- package/src/responsive-sheet/ResponsiveSheet.native.tsx +0 -257
- package/src/responsive-sheet/ResponsiveSheet.stories.tsx +0 -54
- package/src/responsive-sheet/ResponsiveSheet.tsx +0 -327
- package/src/responsive-sheet/const.ts +0 -53
- package/src/responsive-sheet/index.ts +0 -6
- package/src/responsive-sheet/index.web.ts +0 -16
- package/src/responsive-sheet/types.ts +0 -57
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import React, { createContext, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
4
|
-
import { Pressable, Text, TouchableOpacity, View } from 'react-native';
|
|
4
|
+
import { Pressable, Text, TouchableOpacity, useWindowDimensions, View } from 'react-native';
|
|
5
5
|
import { RemoveScrollBar } from 'react-remove-scroll-bar';
|
|
6
6
|
import { Portal } from "../portal/index.web.js";
|
|
7
7
|
import { createOverlayZIndex } from "../styles/z-index.js";
|
|
8
8
|
import { useTheme } from "../theme/use-theme.js";
|
|
9
9
|
import { Context, useDialogContext, useDialogControl } from "./context.js";
|
|
10
|
-
import {
|
|
11
|
-
|
|
10
|
+
import { ANIMATION_DURATION, CENTER_FADE_OUT_DURATION, DEFAULT_CENTER_MAX_WIDTH, DEFAULT_MAX_HEIGHT_RATIO, DEFAULT_SIDE_WIDTH, DIALOG_SHEET_BACKDROP_TESTID, EASE_OUT, HANDLE_HEIGHT, HANDLE_RADIUS, HANDLE_WIDTH, PANEL_RADIUS, SHEET_BACKDROP_OPACITY, SIDE_SHEET_MIN_GUTTER, useResolvedPlacement } from "./placement.js";
|
|
11
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
12
|
+
const FADE_OUT_DURATION = CENTER_FADE_OUT_DURATION;
|
|
12
13
|
const dialogZIndex = createOverlayZIndex();
|
|
13
14
|
const stopPropagation = e => e.stopPropagation();
|
|
14
15
|
const ClosingContext = /*#__PURE__*/createContext(false);
|
|
@@ -16,40 +17,94 @@ const ClosingContext = /*#__PURE__*/createContext(false);
|
|
|
16
17
|
/**
|
|
17
18
|
* Web variant of `<Dialog>`.
|
|
18
19
|
*
|
|
19
|
-
* A centered modal card rendered into the bloom Portal at the end of
|
|
20
|
-
* `document.body
|
|
21
|
-
*
|
|
22
|
-
* agnostic.
|
|
20
|
+
* A centered modal card (default) rendered into the bloom Portal at the end of
|
|
21
|
+
* `document.body`, or — when `placement` is `left`/`right`/`bottom` — an
|
|
22
|
+
* anchored side-sheet or bottom-sheet. Same prop API as native, so call sites
|
|
23
|
+
* are platform agnostic.
|
|
24
|
+
*
|
|
25
|
+
* Open/close is driven by EITHER the imperative `control` (legacy) OR the
|
|
26
|
+
* controlled `open` prop. When `open` is provided it wins.
|
|
23
27
|
*
|
|
24
28
|
* Accessibility: the panel has `role="dialog"` and the `title`/`description`
|
|
25
29
|
* props (when provided) become the `aria-labelledby` / `aria-describedby`
|
|
26
30
|
* targets. Pressing the backdrop dismisses. Pressing `Escape` dismisses.
|
|
27
31
|
* Focus is locked inside the dialog while it's open.
|
|
32
|
+
*
|
|
33
|
+
* Web motion never uses reanimated `exiting` (which throws `removeChild` on
|
|
34
|
+
* concurrent React unmounts): the centered card uses CSS keyframes and the
|
|
35
|
+
* side/bottom modes use self-contained CSS transitions on a
|
|
36
|
+
* mounted-through-exit node.
|
|
28
37
|
*/
|
|
29
38
|
export function Dialog({
|
|
30
39
|
control,
|
|
40
|
+
open: controlledOpen,
|
|
31
41
|
onClose,
|
|
32
42
|
testID,
|
|
33
43
|
title,
|
|
34
44
|
description,
|
|
35
45
|
actions,
|
|
46
|
+
placement,
|
|
47
|
+
width = DEFAULT_SIDE_WIDTH,
|
|
48
|
+
maxWidth = DEFAULT_CENTER_MAX_WIDTH,
|
|
49
|
+
maxHeightRatio = DEFAULT_MAX_HEIGHT_RATIO,
|
|
50
|
+
inset,
|
|
51
|
+
showHandle = true,
|
|
52
|
+
dismissOnBackdrop = true,
|
|
36
53
|
style,
|
|
54
|
+
panelStyle,
|
|
55
|
+
panelClassName,
|
|
56
|
+
containerStyle,
|
|
57
|
+
containerClassName,
|
|
37
58
|
label,
|
|
38
59
|
children
|
|
39
60
|
}) {
|
|
61
|
+
// Controlled mode is opt-in: when `open` is a boolean the host owns the
|
|
62
|
+
// visible state; otherwise the legacy imperative `control` path drives it.
|
|
63
|
+
const isControlled = controlledOpen !== undefined;
|
|
64
|
+
const resolvedPlacement = useResolvedPlacement(placement);
|
|
40
65
|
const [isOpen, setIsOpen] = useState(false);
|
|
41
66
|
const [isClosing, setIsClosing] = useState(false);
|
|
42
67
|
const closeCallbacksRef = useRef([]);
|
|
68
|
+
// Read the latest controlled flag inside stable callbacks without re-binding.
|
|
69
|
+
const isControlledRef = useRef(isControlled);
|
|
70
|
+
isControlledRef.current = isControlled;
|
|
43
71
|
const open = useCallback(() => {
|
|
44
72
|
setIsClosing(false);
|
|
45
73
|
setIsOpen(true);
|
|
46
74
|
}, []);
|
|
75
|
+
|
|
76
|
+
// A dismissal request (backdrop, Escape, action button). In controlled mode
|
|
77
|
+
// it asks the host to close via `onClose` (the host flips `open`); in
|
|
78
|
+
// imperative mode it starts the exit animation directly. Either way an
|
|
79
|
+
// optional callback runs after the dialog has finished closing.
|
|
47
80
|
const close = useCallback(cb => {
|
|
48
81
|
if (typeof cb === 'function') {
|
|
49
82
|
closeCallbacksRef.current.push(cb);
|
|
50
83
|
}
|
|
84
|
+
if (isControlledRef.current) {
|
|
85
|
+
onCloseRef.current?.();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
51
88
|
setIsClosing(true);
|
|
52
89
|
}, []);
|
|
90
|
+
|
|
91
|
+
// `onClose` mirrored into a ref so `close` stays referentially stable (the
|
|
92
|
+
// dialog context + imperative handle depend on it).
|
|
93
|
+
const onCloseRef = useRef(onClose);
|
|
94
|
+
onCloseRef.current = onClose;
|
|
95
|
+
|
|
96
|
+
// In controlled mode, mirror the `open` prop into the internal open/closing
|
|
97
|
+
// state machine so both modes share the same exit-animation lifecycle.
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (!isControlled) return;
|
|
100
|
+
if (controlledOpen) {
|
|
101
|
+
setIsClosing(false);
|
|
102
|
+
setIsOpen(true);
|
|
103
|
+
} else {
|
|
104
|
+
setIsClosing(true);
|
|
105
|
+
}
|
|
106
|
+
}, [isControlled, controlledOpen]);
|
|
107
|
+
const exitDuration = resolvedPlacement === 'center' ? FADE_OUT_DURATION : ANIMATION_DURATION;
|
|
53
108
|
useEffect(() => {
|
|
54
109
|
if (!isClosing) return;
|
|
55
110
|
const timer = setTimeout(() => {
|
|
@@ -66,16 +121,22 @@ export function Dialog({
|
|
|
66
121
|
}
|
|
67
122
|
}
|
|
68
123
|
}
|
|
69
|
-
onClose
|
|
70
|
-
|
|
124
|
+
// Imperative mode fires `onClose` when the exit settles. Controlled mode
|
|
125
|
+
// already requested the close through `onClose` (the host then flipped
|
|
126
|
+
// `open`), so it must NOT fire it a second time here.
|
|
127
|
+
if (!isControlledRef.current) onCloseRef.current?.();
|
|
128
|
+
}, exitDuration);
|
|
71
129
|
return () => clearTimeout(timer);
|
|
72
|
-
}, [isClosing,
|
|
130
|
+
}, [isClosing, exitDuration]);
|
|
73
131
|
|
|
74
132
|
// Escape-to-close while open. The listener is intentionally scoped to the
|
|
75
133
|
// open lifetime so stacked dialogs don't fight for the keydown — the
|
|
76
|
-
// top-most one wins via document-level event order.
|
|
134
|
+
// top-most one wins via document-level event order. Escape honors
|
|
135
|
+
// `dismissOnBackdrop`: a blocking dialog (e.g. an unanswered confirm) is not
|
|
136
|
+
// dismissible by Escape, matching the backdrop's behavior. The default
|
|
137
|
+
// (`dismissOnBackdrop` true) is unchanged — Escape still closes.
|
|
77
138
|
useEffect(() => {
|
|
78
|
-
if (!isOpen || typeof document === 'undefined') return;
|
|
139
|
+
if (!isOpen || !dismissOnBackdrop || typeof document === 'undefined') return;
|
|
79
140
|
const handler = e => {
|
|
80
141
|
if (e.key === 'Escape') {
|
|
81
142
|
e.stopPropagation();
|
|
@@ -84,8 +145,8 @@ export function Dialog({
|
|
|
84
145
|
};
|
|
85
146
|
document.addEventListener('keydown', handler);
|
|
86
147
|
return () => document.removeEventListener('keydown', handler);
|
|
87
|
-
}, [close, isOpen]);
|
|
88
|
-
useImperativeHandle(control
|
|
148
|
+
}, [close, dismissOnBackdrop, isOpen]);
|
|
149
|
+
useImperativeHandle(control?.ref, () => ({
|
|
89
150
|
open,
|
|
90
151
|
close
|
|
91
152
|
}), [open, close]);
|
|
@@ -94,46 +155,121 @@ export function Dialog({
|
|
|
94
155
|
isWithinDialog: true
|
|
95
156
|
}), [close]);
|
|
96
157
|
if (!isOpen) return null;
|
|
158
|
+
if (resolvedPlacement === 'center') {
|
|
159
|
+
return /*#__PURE__*/_jsx(Portal, {
|
|
160
|
+
children: /*#__PURE__*/_jsx(Context.Provider, {
|
|
161
|
+
value: context,
|
|
162
|
+
children: /*#__PURE__*/_jsxs(ClosingContext.Provider, {
|
|
163
|
+
value: isClosing,
|
|
164
|
+
children: [/*#__PURE__*/_jsx(RemoveScrollBar, {}), /*#__PURE__*/_jsxs(Pressable, {
|
|
165
|
+
onPress: dismissOnBackdrop ? () => close() : undefined,
|
|
166
|
+
disabled: !dismissOnBackdrop
|
|
167
|
+
// `pointerEvents: 'auto'` opts back in from the Portal root's
|
|
168
|
+
// `pointer-events: none`, which is set so the idle portal
|
|
169
|
+
// doesn't intercept clicks on the underlying app.
|
|
170
|
+
,
|
|
171
|
+
style: {
|
|
172
|
+
position: 'fixed',
|
|
173
|
+
top: 0,
|
|
174
|
+
left: 0,
|
|
175
|
+
right: 0,
|
|
176
|
+
bottom: 0,
|
|
177
|
+
zIndex: dialogZIndex.backdrop,
|
|
178
|
+
alignItems: 'center',
|
|
179
|
+
justifyContent: 'center',
|
|
180
|
+
paddingHorizontal: 20,
|
|
181
|
+
pointerEvents: 'auto'
|
|
182
|
+
},
|
|
183
|
+
children: [/*#__PURE__*/_jsx(DialogBackdrop, {
|
|
184
|
+
isClosing: isClosing
|
|
185
|
+
}), /*#__PURE__*/_jsx(DialogPanel, {
|
|
186
|
+
testID: testID,
|
|
187
|
+
label: label,
|
|
188
|
+
title: title,
|
|
189
|
+
description: description,
|
|
190
|
+
actions: actions,
|
|
191
|
+
style: style,
|
|
192
|
+
maxWidth: maxWidth,
|
|
193
|
+
isClosing: isClosing,
|
|
194
|
+
children: children
|
|
195
|
+
})]
|
|
196
|
+
})]
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
});
|
|
200
|
+
}
|
|
97
201
|
return /*#__PURE__*/_jsx(Portal, {
|
|
98
202
|
children: /*#__PURE__*/_jsx(Context.Provider, {
|
|
99
203
|
value: context,
|
|
100
204
|
children: /*#__PURE__*/_jsxs(ClosingContext.Provider, {
|
|
101
205
|
value: isClosing,
|
|
102
|
-
children: [/*#__PURE__*/_jsx(RemoveScrollBar, {}), /*#__PURE__*/
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}), /*#__PURE__*/_jsx(DialogPanel, {
|
|
123
|
-
testID: testID,
|
|
124
|
-
label: label,
|
|
125
|
-
title: title,
|
|
126
|
-
description: description,
|
|
127
|
-
actions: actions,
|
|
128
|
-
style: style,
|
|
129
|
-
isClosing: isClosing,
|
|
130
|
-
children: children
|
|
131
|
-
})]
|
|
206
|
+
children: [/*#__PURE__*/_jsx(RemoveScrollBar, {}), /*#__PURE__*/_jsx(SheetSurface, {
|
|
207
|
+
testID: testID,
|
|
208
|
+
label: label,
|
|
209
|
+
title: title,
|
|
210
|
+
description: description,
|
|
211
|
+
actions: actions,
|
|
212
|
+
placement: resolvedPlacement,
|
|
213
|
+
shown: !isClosing,
|
|
214
|
+
width: width,
|
|
215
|
+
maxHeightRatio: maxHeightRatio,
|
|
216
|
+
inset: inset,
|
|
217
|
+
showHandle: showHandle,
|
|
218
|
+
dismissOnBackdrop: dismissOnBackdrop,
|
|
219
|
+
onDismiss: close,
|
|
220
|
+
panelStyle: panelStyle,
|
|
221
|
+
panelClassName: panelClassName,
|
|
222
|
+
containerStyle: containerStyle,
|
|
223
|
+
containerClassName: containerClassName,
|
|
224
|
+
style: style,
|
|
225
|
+
children: children
|
|
132
226
|
})]
|
|
133
227
|
})
|
|
134
228
|
})
|
|
135
229
|
});
|
|
136
230
|
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Renders the dialog's body: optional declarative title + description, any
|
|
234
|
+
* `children`, then the action row. Shared by the centered panel and the
|
|
235
|
+
* side/bottom sheet so all placements present identical content. The
|
|
236
|
+
* `titleId`/`descriptionId` are wired by the caller onto the `role="dialog"`
|
|
237
|
+
* element for `aria-labelledby` / `aria-describedby`.
|
|
238
|
+
*/
|
|
239
|
+
function DialogBody({
|
|
240
|
+
titleId,
|
|
241
|
+
descriptionId,
|
|
242
|
+
title,
|
|
243
|
+
description,
|
|
244
|
+
actions,
|
|
245
|
+
children
|
|
246
|
+
}) {
|
|
247
|
+
const theme = useTheme();
|
|
248
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
249
|
+
children: [title ? /*#__PURE__*/_jsx(Text, {
|
|
250
|
+
nativeID: titleId,
|
|
251
|
+
style: {
|
|
252
|
+
fontSize: 22,
|
|
253
|
+
fontWeight: '600',
|
|
254
|
+
color: theme.colors.text,
|
|
255
|
+
paddingBottom: description ? 4 : 16,
|
|
256
|
+
lineHeight: 30
|
|
257
|
+
},
|
|
258
|
+
children: title
|
|
259
|
+
}) : null, description ? /*#__PURE__*/_jsx(Text, {
|
|
260
|
+
nativeID: descriptionId,
|
|
261
|
+
style: {
|
|
262
|
+
fontSize: 16,
|
|
263
|
+
color: theme.colors.textSecondary,
|
|
264
|
+
paddingBottom: 16,
|
|
265
|
+
lineHeight: 22
|
|
266
|
+
},
|
|
267
|
+
children: description
|
|
268
|
+
}) : null, children, actions && actions.length > 0 ? /*#__PURE__*/_jsx(ActionRow, {
|
|
269
|
+
actions: actions
|
|
270
|
+
}) : null]
|
|
271
|
+
});
|
|
272
|
+
}
|
|
137
273
|
function DialogPanel({
|
|
138
274
|
testID,
|
|
139
275
|
label,
|
|
@@ -141,13 +277,14 @@ function DialogPanel({
|
|
|
141
277
|
description,
|
|
142
278
|
actions,
|
|
143
279
|
style,
|
|
280
|
+
maxWidth,
|
|
144
281
|
isClosing,
|
|
145
282
|
children
|
|
146
283
|
}) {
|
|
147
284
|
const theme = useTheme();
|
|
148
285
|
const titleId = useId();
|
|
149
286
|
const descriptionId = useId();
|
|
150
|
-
return /*#__PURE__*/
|
|
287
|
+
return /*#__PURE__*/_jsx(View, {
|
|
151
288
|
role: "dialog",
|
|
152
289
|
"aria-label": label,
|
|
153
290
|
"aria-labelledby": title ? titleId : undefined,
|
|
@@ -160,7 +297,7 @@ function DialogPanel({
|
|
|
160
297
|
position: 'relative',
|
|
161
298
|
borderRadius: 20,
|
|
162
299
|
width: '100%',
|
|
163
|
-
maxWidth
|
|
300
|
+
maxWidth,
|
|
164
301
|
backgroundColor: theme.colors.background,
|
|
165
302
|
borderWidth: 1,
|
|
166
303
|
borderColor: theme.isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)',
|
|
@@ -178,30 +315,196 @@ function DialogPanel({
|
|
|
178
315
|
} : {
|
|
179
316
|
animation: 'bloomDialogZoomFadeIn cubic-bezier(0.16, 1, 0.3, 1) 0.3s'
|
|
180
317
|
}, style],
|
|
181
|
-
children:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
318
|
+
children: /*#__PURE__*/_jsx(DialogBody, {
|
|
319
|
+
titleId: titleId,
|
|
320
|
+
descriptionId: descriptionId,
|
|
321
|
+
title: title,
|
|
322
|
+
description: description,
|
|
323
|
+
actions: actions,
|
|
324
|
+
children: children
|
|
325
|
+
})
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
export { DIALOG_SHEET_BACKDROP_TESTID };
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Side-sheet / bottom-sheet surface for the `left`/`right`/`bottom`
|
|
332
|
+
* placements. Pure CSS transitions: the
|
|
333
|
+
* node stays mounted through the exit while a `shown` flag drives the
|
|
334
|
+
* transform/opacity, so both directions animate without reanimated `exiting`.
|
|
335
|
+
*/
|
|
336
|
+
function SheetSurface({
|
|
337
|
+
testID,
|
|
338
|
+
label,
|
|
339
|
+
title,
|
|
340
|
+
description,
|
|
341
|
+
actions,
|
|
342
|
+
placement,
|
|
343
|
+
shown,
|
|
344
|
+
width,
|
|
345
|
+
maxHeightRatio,
|
|
346
|
+
inset,
|
|
347
|
+
showHandle,
|
|
348
|
+
dismissOnBackdrop,
|
|
349
|
+
onDismiss,
|
|
350
|
+
panelStyle,
|
|
351
|
+
panelClassName,
|
|
352
|
+
containerStyle,
|
|
353
|
+
containerClassName,
|
|
354
|
+
style,
|
|
355
|
+
children
|
|
356
|
+
}) {
|
|
357
|
+
const theme = useTheme();
|
|
358
|
+
const titleId = useId();
|
|
359
|
+
const descriptionId = useId();
|
|
360
|
+
const {
|
|
361
|
+
width: viewportWidth,
|
|
362
|
+
height: viewportHeight
|
|
363
|
+
} = useWindowDimensions();
|
|
364
|
+
|
|
365
|
+
// Defer the entry transition by a frame so the start state paints before the
|
|
366
|
+
// browser animates to `shown`. `entered` is false on the first committed
|
|
367
|
+
// frame, then flips true so the CSS transition runs.
|
|
368
|
+
const frameRef = useRef(null);
|
|
369
|
+
const [entered, setEntered] = useState(false);
|
|
370
|
+
useEffect(() => {
|
|
371
|
+
frameRef.current = scheduleFrame(() => {
|
|
372
|
+
frameRef.current = scheduleFrame(() => setEntered(true));
|
|
373
|
+
});
|
|
374
|
+
return () => {
|
|
375
|
+
if (frameRef.current !== null) cancelFrame(frameRef.current);
|
|
376
|
+
frameRef.current = null;
|
|
377
|
+
};
|
|
378
|
+
}, []);
|
|
379
|
+
const visible = shown && entered;
|
|
380
|
+
const isBottom = placement === 'bottom';
|
|
381
|
+
const panelTransition = useMemo(() => ({
|
|
382
|
+
transitionProperty: 'transform, opacity',
|
|
383
|
+
transitionDuration: `${ANIMATION_DURATION}ms`,
|
|
384
|
+
transitionTimingFunction: EASE_OUT
|
|
385
|
+
}), []);
|
|
386
|
+
const backdropTransition = useMemo(() => ({
|
|
387
|
+
transitionProperty: 'opacity',
|
|
388
|
+
transitionDuration: `${ANIMATION_DURATION}ms`,
|
|
389
|
+
transitionTimingFunction: EASE_OUT
|
|
390
|
+
}), []);
|
|
391
|
+
const panelGeometry = useMemo(() => {
|
|
392
|
+
if (isBottom) {
|
|
393
|
+
return {
|
|
394
|
+
left: 0,
|
|
395
|
+
right: 0,
|
|
396
|
+
bottom: 0,
|
|
397
|
+
maxHeight: Math.round(viewportHeight * maxHeightRatio),
|
|
398
|
+
borderTopLeftRadius: PANEL_RADIUS,
|
|
399
|
+
borderTopRightRadius: PANEL_RADIUS,
|
|
400
|
+
transform: [{
|
|
401
|
+
translateY: visible ? 0 : '100%'
|
|
402
|
+
}],
|
|
403
|
+
opacity: visible ? 1 : 0
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
const insetTop = inset?.top ?? 0;
|
|
407
|
+
const insetBottom = inset?.bottom ?? 0;
|
|
408
|
+
const insetLeft = inset?.left ?? 0;
|
|
409
|
+
const insetRight = inset?.right ?? 0;
|
|
410
|
+
const anchorInset = placement === 'left' ? insetLeft : insetRight;
|
|
411
|
+
const oppositeInset = placement === 'left' ? insetRight : insetLeft;
|
|
412
|
+
const available = viewportWidth - anchorInset - oppositeInset - SIDE_SHEET_MIN_GUTTER;
|
|
413
|
+
const cappedWidth = Math.max(0, Math.min(width, available));
|
|
414
|
+
const hiddenSign = placement === 'left' ? '-100%' : '100%';
|
|
415
|
+
return {
|
|
416
|
+
top: insetTop,
|
|
417
|
+
bottom: insetBottom,
|
|
418
|
+
[placement]: anchorInset,
|
|
419
|
+
width: cappedWidth,
|
|
420
|
+
borderRadius: PANEL_RADIUS,
|
|
421
|
+
transform: [{
|
|
422
|
+
translateX: visible ? 0 : hiddenSign
|
|
423
|
+
}],
|
|
424
|
+
opacity: visible ? 1 : 0
|
|
425
|
+
};
|
|
426
|
+
}, [isBottom, visible, placement, width, inset, viewportWidth, viewportHeight, maxHeightRatio]);
|
|
427
|
+
const handleBackdropPress = useCallback(() => {
|
|
428
|
+
if (dismissOnBackdrop) onDismiss();
|
|
429
|
+
}, [dismissOnBackdrop, onDismiss]);
|
|
430
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
431
|
+
style: [sheetStyles.root, containerStyle],
|
|
432
|
+
...(containerClassName ? {
|
|
433
|
+
className: containerClassName
|
|
434
|
+
} : {}),
|
|
435
|
+
pointerEvents: "box-none",
|
|
436
|
+
children: [/*#__PURE__*/_jsx(Pressable, {
|
|
437
|
+
testID: testID ? `${testID}-backdrop` : DIALOG_SHEET_BACKDROP_TESTID,
|
|
438
|
+
accessibilityRole: "button",
|
|
439
|
+
accessibilityLabel: label ? `Dismiss ${label}` : 'Dismiss dialog',
|
|
440
|
+
onPress: handleBackdropPress,
|
|
441
|
+
disabled: !dismissOnBackdrop,
|
|
442
|
+
style: [sheetStyles.backdrop, backdropTransition, {
|
|
443
|
+
opacity: visible ? SHEET_BACKDROP_OPACITY : 0
|
|
444
|
+
}]
|
|
445
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
446
|
+
role: "dialog",
|
|
447
|
+
"aria-label": label,
|
|
448
|
+
"aria-labelledby": title ? titleId : undefined,
|
|
449
|
+
"aria-describedby": description ? descriptionId : undefined,
|
|
450
|
+
accessibilityViewIsModal: true,
|
|
451
|
+
testID: testID,
|
|
452
|
+
onStartShouldSetResponder: () => true,
|
|
453
|
+
onResponderRelease: stopPropagation,
|
|
454
|
+
onClick: stopPropagation,
|
|
455
|
+
...(panelClassName ? {
|
|
456
|
+
className: panelClassName
|
|
457
|
+
} : {}),
|
|
458
|
+
style: [sheetStyles.panel, {
|
|
459
|
+
backgroundColor: theme.colors.background,
|
|
460
|
+
shadowColor: theme.colors.shadow
|
|
461
|
+
}, panelGeometry, panelTransition, panelStyle, style],
|
|
462
|
+
pointerEvents: "auto",
|
|
463
|
+
children: [isBottom && showHandle ? /*#__PURE__*/_jsx(View, {
|
|
464
|
+
style: sheetStyles.handleRow,
|
|
465
|
+
pointerEvents: "none",
|
|
466
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
467
|
+
style: [sheetStyles.handle, {
|
|
468
|
+
backgroundColor: theme.colors.border
|
|
469
|
+
}]
|
|
470
|
+
})
|
|
471
|
+
}) : null, /*#__PURE__*/_jsx(View, {
|
|
472
|
+
style: sheetStyles.body,
|
|
473
|
+
children: /*#__PURE__*/_jsx(DialogBody, {
|
|
474
|
+
titleId: titleId,
|
|
475
|
+
descriptionId: descriptionId,
|
|
476
|
+
title: title,
|
|
477
|
+
description: description,
|
|
478
|
+
actions: actions,
|
|
479
|
+
children: children
|
|
480
|
+
})
|
|
481
|
+
})]
|
|
482
|
+
})]
|
|
203
483
|
});
|
|
204
484
|
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Opaque token returned by `scheduleFrame`. `requestAnimationFrame` returns a
|
|
488
|
+
* number; the `setTimeout` fallback returns a timer handle.
|
|
489
|
+
*/
|
|
490
|
+
|
|
491
|
+
function scheduleFrame(cb) {
|
|
492
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
493
|
+
return {
|
|
494
|
+
raf: requestAnimationFrame(cb)
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
timer: setTimeout(cb, 0)
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
function cancelFrame(token) {
|
|
502
|
+
if ('raf' in token) {
|
|
503
|
+
if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(token.raf);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
clearTimeout(token.timer);
|
|
507
|
+
}
|
|
205
508
|
function ActionRow({
|
|
206
509
|
actions
|
|
207
510
|
}) {
|
|
@@ -336,6 +639,50 @@ export function AutoMountedDialog({
|
|
|
336
639
|
onClose: onResolve
|
|
337
640
|
});
|
|
338
641
|
}
|
|
642
|
+
const sheetStyles = {
|
|
643
|
+
root: {
|
|
644
|
+
position: 'fixed',
|
|
645
|
+
top: 0,
|
|
646
|
+
left: 0,
|
|
647
|
+
right: 0,
|
|
648
|
+
bottom: 0,
|
|
649
|
+
zIndex: dialogZIndex.backdrop
|
|
650
|
+
},
|
|
651
|
+
backdrop: {
|
|
652
|
+
position: 'absolute',
|
|
653
|
+
top: 0,
|
|
654
|
+
left: 0,
|
|
655
|
+
right: 0,
|
|
656
|
+
bottom: 0,
|
|
657
|
+
backgroundColor: '#000'
|
|
658
|
+
},
|
|
659
|
+
panel: {
|
|
660
|
+
position: 'absolute',
|
|
661
|
+
overflow: 'hidden',
|
|
662
|
+
shadowOpacity: 0.18,
|
|
663
|
+
shadowRadius: 24,
|
|
664
|
+
shadowOffset: {
|
|
665
|
+
width: 0,
|
|
666
|
+
height: 8
|
|
667
|
+
},
|
|
668
|
+
zIndex: dialogZIndex.surface
|
|
669
|
+
},
|
|
670
|
+
handleRow: {
|
|
671
|
+
width: '100%',
|
|
672
|
+
alignItems: 'center',
|
|
673
|
+
paddingTop: 8,
|
|
674
|
+
paddingBottom: 4
|
|
675
|
+
},
|
|
676
|
+
handle: {
|
|
677
|
+
width: HANDLE_WIDTH,
|
|
678
|
+
height: HANDLE_HEIGHT,
|
|
679
|
+
borderRadius: HANDLE_RADIUS,
|
|
680
|
+
opacity: 0.5
|
|
681
|
+
},
|
|
682
|
+
body: {
|
|
683
|
+
padding: 20
|
|
684
|
+
}
|
|
685
|
+
};
|
|
339
686
|
|
|
340
687
|
/**
|
|
341
688
|
* CSS keyframes required for web dialog animations.
|
|
@@ -347,6 +694,9 @@ export function AutoMountedDialog({
|
|
|
347
694
|
* @keyframes bloomDialogZoomFadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
|
|
348
695
|
* @keyframes bloomDialogZoomFadeOut { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.95); } }
|
|
349
696
|
* ```
|
|
697
|
+
*
|
|
698
|
+
* The `left`/`right`/`bottom` placements do NOT depend on this — they animate
|
|
699
|
+
* via self-contained inline CSS transitions and need no keyframe injection.
|
|
350
700
|
*/
|
|
351
701
|
export const BLOOM_DIALOG_CSS = `
|
|
352
702
|
@keyframes bloomDialogFadeIn { from { opacity: 0; } to { opacity: 1; } }
|