@oxyhq/bloom 0.48.0 → 0.49.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/lib/commonjs/bottom-sheet/BottomSheetBase.js +3 -1
- package/lib/commonjs/bottom-sheet/BottomSheetBase.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.js +35 -4
- package/lib/commonjs/dialog/Dialog.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.web.js +48 -14
- package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
- package/lib/commonjs/dialog/DialogBottomSheet.js +30 -5
- package/lib/commonjs/dialog/DialogBottomSheet.js.map +1 -1
- package/lib/commonjs/dialog/DialogHeader.js +12 -6
- package/lib/commonjs/dialog/DialogHeader.js.map +1 -1
- package/lib/commonjs/dialog/DialogMorph.js +458 -0
- package/lib/commonjs/dialog/DialogMorph.js.map +1 -0
- package/lib/commonjs/dialog/index.js +7 -0
- package/lib/commonjs/dialog/index.js.map +1 -1
- package/lib/commonjs/dialog/index.web.js +7 -0
- package/lib/commonjs/dialog/index.web.js.map +1 -1
- package/lib/commonjs/settings-list/SettingsList.js +1 -1
- package/lib/commonjs/surfaces/SurfaceHost.js +1 -0
- package/lib/commonjs/surfaces/SurfaceHost.js.map +1 -1
- package/lib/module/bottom-sheet/BottomSheetBase.js +3 -1
- package/lib/module/bottom-sheet/BottomSheetBase.js.map +1 -1
- package/lib/module/dialog/Dialog.js +35 -4
- package/lib/module/dialog/Dialog.js.map +1 -1
- package/lib/module/dialog/Dialog.web.js +48 -15
- package/lib/module/dialog/Dialog.web.js.map +1 -1
- package/lib/module/dialog/DialogBottomSheet.js +31 -6
- package/lib/module/dialog/DialogBottomSheet.js.map +1 -1
- package/lib/module/dialog/DialogHeader.js +12 -6
- package/lib/module/dialog/DialogHeader.js.map +1 -1
- package/lib/module/dialog/DialogMorph.js +451 -0
- package/lib/module/dialog/DialogMorph.js.map +1 -0
- package/lib/module/dialog/index.js +1 -0
- package/lib/module/dialog/index.js.map +1 -1
- package/lib/module/dialog/index.web.js +1 -0
- package/lib/module/dialog/index.web.js.map +1 -1
- package/lib/module/settings-list/SettingsList.js +1 -1
- package/lib/module/surfaces/SurfaceHost.js +1 -0
- package/lib/module/surfaces/SurfaceHost.js.map +1 -1
- package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts +10 -3
- package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/DialogBottomSheet.d.ts +2 -2
- package/lib/typescript/commonjs/dialog/DialogBottomSheet.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/DialogHeader.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/DialogMorph.d.ts +150 -0
- package/lib/typescript/commonjs/dialog/DialogMorph.d.ts.map +1 -0
- package/lib/typescript/commonjs/dialog/index.d.ts +2 -0
- package/lib/typescript/commonjs/dialog/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/index.web.d.ts +2 -0
- package/lib/typescript/commonjs/dialog/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/types.d.ts +30 -0
- package/lib/typescript/commonjs/dialog/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/surfaces/SurfaceHost.d.ts.map +1 -1
- package/lib/typescript/commonjs/surfaces/types.d.ts +9 -0
- package/lib/typescript/commonjs/surfaces/types.d.ts.map +1 -1
- package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts +10 -3
- package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
- package/lib/typescript/module/dialog/Dialog.d.ts.map +1 -1
- package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
- package/lib/typescript/module/dialog/DialogBottomSheet.d.ts +2 -2
- package/lib/typescript/module/dialog/DialogBottomSheet.d.ts.map +1 -1
- package/lib/typescript/module/dialog/DialogHeader.d.ts.map +1 -1
- package/lib/typescript/module/dialog/DialogMorph.d.ts +150 -0
- package/lib/typescript/module/dialog/DialogMorph.d.ts.map +1 -0
- package/lib/typescript/module/dialog/index.d.ts +2 -0
- package/lib/typescript/module/dialog/index.d.ts.map +1 -1
- package/lib/typescript/module/dialog/index.web.d.ts +2 -0
- package/lib/typescript/module/dialog/index.web.d.ts.map +1 -1
- package/lib/typescript/module/dialog/types.d.ts +30 -0
- package/lib/typescript/module/dialog/types.d.ts.map +1 -1
- package/lib/typescript/module/surfaces/SurfaceHost.d.ts.map +1 -1
- package/lib/typescript/module/surfaces/types.d.ts +9 -0
- package/lib/typescript/module/surfaces/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/DialogMorph.test.tsx +230 -0
- package/src/bottom-sheet/BottomSheetBase.tsx +15 -2
- package/src/dialog/Dialog.tsx +43 -11
- package/src/dialog/Dialog.web.tsx +40 -10
- package/src/dialog/DialogBottomSheet.tsx +37 -6
- package/src/dialog/DialogHeader.tsx +14 -5
- package/src/dialog/DialogMorph.tsx +576 -0
- package/src/dialog/index.ts +2 -0
- package/src/dialog/index.web.ts +2 -0
- package/src/dialog/types.ts +30 -0
- package/src/settings-list/SettingsList.tsx +1 -1
- package/src/surfaces/SurfaceHost.tsx +1 -0
- package/src/surfaces/types.ts +9 -0
|
@@ -17,6 +17,15 @@ export type { DialogPlacement, ResponsiveDialogPlacement } from './placement';
|
|
|
17
17
|
export interface DialogHeaderConfig {
|
|
18
18
|
/** Collapsing title — rendered large in-content and small in the nav bar. */
|
|
19
19
|
title?: string;
|
|
20
|
+
/**
|
|
21
|
+
* A branded node rendered centered in the nav bar INSTEAD of the collapsing
|
|
22
|
+
* text title — e.g. a product wordmark. Always visible (it never collapses),
|
|
23
|
+
* and it suppresses the large in-content title/subtitle entirely, so the
|
|
24
|
+
* surface starts flush under the bar. `title`/`subtitle` are ignored while
|
|
25
|
+
* this is set. Must be referentially stable (memoize it) so the header does
|
|
26
|
+
* not thrash.
|
|
27
|
+
*/
|
|
28
|
+
titleContent?: ReactNode;
|
|
20
29
|
/** Supporting copy under the large title (and, optionally, in the bar). */
|
|
21
30
|
subtitle?: string;
|
|
22
31
|
/**
|
|
@@ -214,6 +223,27 @@ export type DialogProps = React.PropsWithChildren<{
|
|
|
214
223
|
contentPadding?: number;
|
|
215
224
|
/** Whether tapping the backdrop dismisses the dialog. Defaults to `true`. */
|
|
216
225
|
dismissOnBackdrop?: boolean;
|
|
226
|
+
/**
|
|
227
|
+
* Whether the panel MORPHS across an in-place content swap. Defaults to
|
|
228
|
+
* `true`.
|
|
229
|
+
*
|
|
230
|
+
* When the content inside an OPEN dialog is replaced (a nav-within drill-in,
|
|
231
|
+
* as opposed to presenting a new surface on top), the panel animates from the
|
|
232
|
+
* outgoing content's size to the incoming one's while the incoming content
|
|
233
|
+
* fades in, instead of hard-cutting. The swap is signalled by the content
|
|
234
|
+
* itself through `useDialogFrame` — a dialog whose content never declares a
|
|
235
|
+
* frame is unaffected either way.
|
|
236
|
+
*
|
|
237
|
+
* The centered card morphs its height (about its centre) and its `maxWidth`;
|
|
238
|
+
* the bottom sheet morphs its height from its anchored edge; side sheets have
|
|
239
|
+
* fixed geometry and never morph. Morphing also requires the Dialog to own the
|
|
240
|
+
* scroll container: a `scrollable={false}` surface owns its own size, so its
|
|
241
|
+
* content only cross-fades.
|
|
242
|
+
*
|
|
243
|
+
* Set `false` for content that must never be resized into — e.g. a full-bleed
|
|
244
|
+
* picker that always fills the surface.
|
|
245
|
+
*/
|
|
246
|
+
morph?: boolean;
|
|
217
247
|
/**
|
|
218
248
|
* Style overrides applied to the inner content container on native (the
|
|
219
249
|
* floating bottom-sheet card) and to the modal panel on web.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/dialog/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/dialog/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SurfaceHost.d.ts","sourceRoot":"","sources":["../../../../src/surfaces/SurfaceHost.tsx"],"names":[],"mappings":"AAAA,OAAO,KAKN,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAYnD,KAAK,eAAe,GAAG,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"SurfaceHost.d.ts","sourceRoot":"","sources":["../../../../src/surfaces/SurfaceHost.tsx"],"names":[],"mappings":"AAAA,OAAO,KAKN,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAYnD,KAAK,eAAe,GAAG,KAAK,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;AAgCxD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe;;;;;;uBAqGhB;YAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;SAAE;;;EAcrE"}
|
|
@@ -53,6 +53,15 @@ export interface SurfacePresentation {
|
|
|
53
53
|
* to `Dialog`.
|
|
54
54
|
*/
|
|
55
55
|
scrollable?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Whether the surface MORPHS its panel when its content is swapped in place —
|
|
58
|
+
* navigation WITHIN this surface, as opposed to presenting a new surface on
|
|
59
|
+
* top. Defaults to `true`. The content signals the swap through
|
|
60
|
+
* `useDialogFrame`; a surface whose content never declares a frame is
|
|
61
|
+
* unaffected. Set `false` for content that must never be resized into (a
|
|
62
|
+
* full-bleed picker). Forwarded to `Dialog`.
|
|
63
|
+
*/
|
|
64
|
+
morph?: boolean;
|
|
56
65
|
/** Style overrides applied to the inner content container. */
|
|
57
66
|
style?: StyleProp<ViewStyle>;
|
|
58
67
|
/** Style overrides for the side/bottom placement panel surface. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/surfaces/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/surfaces/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { act, render } from '@testing-library/react-native';
|
|
3
|
+
import type { LayoutChangeEvent, ViewStyle } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
DialogMorphContent,
|
|
7
|
+
useDialogFrame,
|
|
8
|
+
useDialogMorph,
|
|
9
|
+
} from '../dialog/DialogMorph';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Size morphing across an in-place content swap.
|
|
13
|
+
*
|
|
14
|
+
* These assert the STATE MACHINE — when the panel height is taken over and when
|
|
15
|
+
* it is handed back to natural sizing. The reanimated mock settles `withTiming`
|
|
16
|
+
* synchronously, so the frame-by-frame interpolation is NOT observable here; it
|
|
17
|
+
* is verified in a real browser, the only place reanimated's web mapper ticks.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const layout = (height: number): LayoutChangeEvent =>
|
|
21
|
+
({
|
|
22
|
+
nativeEvent: { layout: { x: 0, y: 0, width: 320, height } },
|
|
23
|
+
}) as LayoutChangeEvent;
|
|
24
|
+
|
|
25
|
+
interface HarnessProps {
|
|
26
|
+
morphEnabled?: boolean;
|
|
27
|
+
measurable?: boolean;
|
|
28
|
+
frameKey: string;
|
|
29
|
+
frameMorph?: boolean;
|
|
30
|
+
onState: (state: {
|
|
31
|
+
panelStyle: ViewStyle;
|
|
32
|
+
onPanelLayout: (e: LayoutChangeEvent) => void;
|
|
33
|
+
onContentLayout: (e: LayoutChangeEvent) => void;
|
|
34
|
+
}) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Drives one surface: measurement in, panel style + a frame-declaring child out. */
|
|
38
|
+
function Harness({
|
|
39
|
+
morphEnabled = true,
|
|
40
|
+
measurable = true,
|
|
41
|
+
frameKey,
|
|
42
|
+
frameMorph,
|
|
43
|
+
onState,
|
|
44
|
+
}: HarnessProps) {
|
|
45
|
+
const morph = useDialogMorph({ enabled: morphEnabled, measurable, maxHeight: 800 });
|
|
46
|
+
onState({
|
|
47
|
+
panelStyle: morph.panelStyle as ViewStyle,
|
|
48
|
+
onPanelLayout: morph.onPanelLayout,
|
|
49
|
+
onContentLayout: morph.onContentLayout,
|
|
50
|
+
});
|
|
51
|
+
return (
|
|
52
|
+
<DialogMorphContent morph={morph}>
|
|
53
|
+
<Frame frameKey={frameKey} frameMorph={frameMorph} />
|
|
54
|
+
</DialogMorphContent>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function Frame({ frameKey, frameMorph }: { frameKey: string; frameMorph?: boolean }) {
|
|
59
|
+
useDialogFrame({ key: frameKey, morph: frameMorph });
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Mount the harness and return a handle for driving layout + swapping frames. */
|
|
64
|
+
function mountHarness(props: Omit<Partial<HarnessProps>, 'onState' | 'frameKey'> = {}) {
|
|
65
|
+
let state: Parameters<HarnessProps['onState']>[0] | null = null;
|
|
66
|
+
const onState: HarnessProps['onState'] = (next) => {
|
|
67
|
+
state = next;
|
|
68
|
+
};
|
|
69
|
+
const element = (frameKey: string, extra: Omit<Partial<HarnessProps>, 'onState'> = {}) => (
|
|
70
|
+
<Harness frameKey={frameKey} onState={onState} {...props} {...extra} />
|
|
71
|
+
);
|
|
72
|
+
const { rerender, unmount } = render(element('a'));
|
|
73
|
+
const read = () => {
|
|
74
|
+
if (!state) throw new Error('harness did not render');
|
|
75
|
+
return state;
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
/** The panel height the surface currently resolves to. */
|
|
79
|
+
panelHeight: () => read().panelStyle.height,
|
|
80
|
+
/** Report the panel's and then the content's laid-out heights. */
|
|
81
|
+
measure(panelHeight: number, contentHeight: number) {
|
|
82
|
+
act(() => {
|
|
83
|
+
read().onPanelLayout(layout(panelHeight));
|
|
84
|
+
read().onContentLayout(layout(contentHeight));
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* Report only the content's natural height (the incoming frame settling).
|
|
89
|
+
* Followed by a re-render: the mock's `useAnimatedStyle` only re-evaluates
|
|
90
|
+
* on render, whereas the real one re-runs on the shared-value write.
|
|
91
|
+
*/
|
|
92
|
+
measureContent(contentHeight: number, frameKey = 'b') {
|
|
93
|
+
act(() => read().onContentLayout(layout(contentHeight)));
|
|
94
|
+
rerender(element(frameKey));
|
|
95
|
+
},
|
|
96
|
+
/** Declare a frame, then re-render so the resulting panel style is observable. */
|
|
97
|
+
swapTo(frameKey: string, extra: Omit<Partial<HarnessProps>, 'onState'> = {}) {
|
|
98
|
+
rerender(element(frameKey, extra));
|
|
99
|
+
rerender(element(frameKey, extra));
|
|
100
|
+
},
|
|
101
|
+
unmount,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
describe('Dialog size morphing', () => {
|
|
106
|
+
it('leaves the panel naturally sized until a frame actually changes', () => {
|
|
107
|
+
const h = mountHarness();
|
|
108
|
+
expect(h.panelHeight()).toBe('auto');
|
|
109
|
+
|
|
110
|
+
h.measure(300, 298);
|
|
111
|
+
expect(h.panelHeight()).toBe('auto');
|
|
112
|
+
|
|
113
|
+
// Re-declaring the SAME frame is not a swap.
|
|
114
|
+
h.swapTo('a');
|
|
115
|
+
expect(h.panelHeight()).toBe('auto');
|
|
116
|
+
h.unmount();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('pins the panel at the outgoing size, then releases onto the incoming one', () => {
|
|
120
|
+
const h = mountHarness();
|
|
121
|
+
h.measure(300, 298);
|
|
122
|
+
|
|
123
|
+
h.swapTo('b');
|
|
124
|
+
// Held at the size the outgoing frame was painted at — no jump while the
|
|
125
|
+
// incoming content is still being measured.
|
|
126
|
+
expect(h.panelHeight()).toBe(300);
|
|
127
|
+
|
|
128
|
+
// The incoming frame reports 500 of natural height; the panel carries the
|
|
129
|
+
// 2px of chrome calibrated from the outgoing frame, then hands the height
|
|
130
|
+
// back so later content changes size the panel normally again.
|
|
131
|
+
h.measureContent(500);
|
|
132
|
+
expect(h.panelHeight()).toBe('auto');
|
|
133
|
+
h.unmount();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('releases the pin even when the incoming frame is taller than the bound', () => {
|
|
137
|
+
const h = mountHarness();
|
|
138
|
+
h.measure(300, 300);
|
|
139
|
+
h.swapTo('b');
|
|
140
|
+
expect(h.panelHeight()).toBe(300);
|
|
141
|
+
|
|
142
|
+
h.measureContent(5000);
|
|
143
|
+
expect(h.panelHeight()).toBe('auto');
|
|
144
|
+
h.unmount();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('releases without reshaping when the incoming frame fills the space it is given', () => {
|
|
148
|
+
const h = mountHarness();
|
|
149
|
+
h.measure(300, 300);
|
|
150
|
+
h.swapTo('b');
|
|
151
|
+
expect(h.panelHeight()).toBe(300);
|
|
152
|
+
|
|
153
|
+
// A child that flexes to its container measures ~0 inside a content-sized
|
|
154
|
+
// wrapper; collapsing the panel onto that would empty the surface.
|
|
155
|
+
h.measureContent(0);
|
|
156
|
+
expect(h.panelHeight()).toBe('auto');
|
|
157
|
+
h.unmount();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('never pins when nothing has been measured yet', () => {
|
|
161
|
+
const h = mountHarness();
|
|
162
|
+
h.swapTo('b');
|
|
163
|
+
expect(h.panelHeight()).toBe('auto');
|
|
164
|
+
h.unmount();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('honours the per-frame morph opt-out', () => {
|
|
168
|
+
const h = mountHarness();
|
|
169
|
+
h.measure(300, 300);
|
|
170
|
+
h.swapTo('b', { frameMorph: false });
|
|
171
|
+
expect(h.panelHeight()).toBe('auto');
|
|
172
|
+
h.unmount();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('honours the surface-level morph opt-out', () => {
|
|
176
|
+
const h = mountHarness({ morphEnabled: false });
|
|
177
|
+
h.measure(300, 300);
|
|
178
|
+
h.swapTo('b');
|
|
179
|
+
expect(h.panelHeight()).toBe('auto');
|
|
180
|
+
h.unmount();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('does not pin a surface whose content owns its own size', () => {
|
|
184
|
+
const h = mountHarness({ measurable: false });
|
|
185
|
+
h.measure(300, 300);
|
|
186
|
+
h.swapTo('b');
|
|
187
|
+
expect(h.panelHeight()).toBe('auto');
|
|
188
|
+
h.unmount();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('drives maxWidth only on a placement that can vary it', () => {
|
|
192
|
+
let fixedWidth: ViewStyle | null = null;
|
|
193
|
+
let varyingWidth: ViewStyle | null = null;
|
|
194
|
+
render(
|
|
195
|
+
<WidthHarness
|
|
196
|
+
onStyle={(s) => {
|
|
197
|
+
fixedWidth = s;
|
|
198
|
+
}}
|
|
199
|
+
/>,
|
|
200
|
+
);
|
|
201
|
+
render(
|
|
202
|
+
<WidthHarness
|
|
203
|
+
maxWidth={480}
|
|
204
|
+
onStyle={(s) => {
|
|
205
|
+
varyingWidth = s;
|
|
206
|
+
}}
|
|
207
|
+
/>,
|
|
208
|
+
);
|
|
209
|
+
expect((fixedWidth as unknown as ViewStyle).maxWidth).toBeUndefined();
|
|
210
|
+
expect((varyingWidth as unknown as ViewStyle).maxWidth).toBe(480);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
function WidthHarness({
|
|
215
|
+
maxWidth,
|
|
216
|
+
onStyle,
|
|
217
|
+
}: {
|
|
218
|
+
maxWidth?: number;
|
|
219
|
+
onStyle: (style: ViewStyle) => void;
|
|
220
|
+
}) {
|
|
221
|
+
const morph = useDialogMorph({ enabled: true, measurable: true, maxHeight: 800, maxWidth });
|
|
222
|
+
onStyle(morph.panelStyle as ViewStyle);
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
describe('useDialogFrame outside a morph-capable surface', () => {
|
|
227
|
+
it('is a no-op (a side sheet, or a plain confirm dialog)', () => {
|
|
228
|
+
expect(() => render(<Frame frameKey="only" />)).not.toThrow();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
@@ -6,12 +6,14 @@ import {
|
|
|
6
6
|
Pressable,
|
|
7
7
|
Dimensions,
|
|
8
8
|
Platform,
|
|
9
|
+
type LayoutChangeEvent,
|
|
9
10
|
type ViewStyle,
|
|
10
11
|
type StyleProp,
|
|
11
12
|
} from 'react-native';
|
|
12
13
|
import { Gesture, GestureDetector, type GestureType } from 'react-native-gesture-handler';
|
|
13
14
|
import { Z_INDEX } from '../styles/z-index';
|
|
14
15
|
import Animated, {
|
|
16
|
+
type AnimatedStyle,
|
|
15
17
|
interpolate,
|
|
16
18
|
runOnJS,
|
|
17
19
|
type SharedValue,
|
|
@@ -75,7 +77,7 @@ export interface BottomSheetProps {
|
|
|
75
77
|
* background color, border radius, etc. Composed AFTER the internal sheet
|
|
76
78
|
* styles so it can override them.
|
|
77
79
|
*/
|
|
78
|
-
style?: StyleProp<ViewStyle
|
|
80
|
+
style?: StyleProp<AnimatedStyle<ViewStyle>>;
|
|
79
81
|
enableHandlePanningGesture?: boolean;
|
|
80
82
|
onDismissAttempt?: () => boolean;
|
|
81
83
|
detached?: boolean; // If true, shows with margins and rounded corners. If false, full width with rounded top only.
|
|
@@ -151,6 +153,13 @@ export interface BottomSheetProps {
|
|
|
151
153
|
* Dialog to float its sticky nav header over the sheet's scroll content.
|
|
152
154
|
*/
|
|
153
155
|
headerOverlay?: React.ReactNode;
|
|
156
|
+
/**
|
|
157
|
+
* Layout callback for the SHEET CARD — the same box `style` targets. Lets a
|
|
158
|
+
* host observe the card's rendered size (the Dialog measures it to morph the
|
|
159
|
+
* sheet between two content frames). Fires on every size change, including
|
|
160
|
+
* while a host animates the card's height.
|
|
161
|
+
*/
|
|
162
|
+
onLayout?: (event: LayoutChangeEvent) => void;
|
|
154
163
|
}
|
|
155
164
|
|
|
156
165
|
/**
|
|
@@ -209,6 +218,7 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
209
218
|
handleComponent,
|
|
210
219
|
scrollY: externalScrollY,
|
|
211
220
|
headerOverlay,
|
|
221
|
+
onLayout,
|
|
212
222
|
} = props;
|
|
213
223
|
|
|
214
224
|
const insets = useSafeAreaInsets();
|
|
@@ -780,7 +790,10 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
780
790
|
</Animated.View>
|
|
781
791
|
|
|
782
792
|
<GestureDetector gesture={panGesture}>
|
|
783
|
-
<Animated.View
|
|
793
|
+
<Animated.View
|
|
794
|
+
onLayout={onLayout}
|
|
795
|
+
style={[dynamicStyles.sheet, sheetMarginStyle, sheetStyle, sheetHeightStyle, style]}
|
|
796
|
+
>
|
|
784
797
|
{backgroundComponent?.({ style: styles.background })}
|
|
785
798
|
|
|
786
799
|
{handleSlot}
|
package/src/dialog/Dialog.tsx
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type ViewStyle,
|
|
17
17
|
} from 'react-native';
|
|
18
18
|
import Animated, {
|
|
19
|
+
type AnimatedStyle,
|
|
19
20
|
cancelAnimation,
|
|
20
21
|
Easing,
|
|
21
22
|
runOnJS,
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
DialogNavHeader,
|
|
39
40
|
useDialogHeaderController,
|
|
40
41
|
} from './DialogHeader';
|
|
42
|
+
import { DialogMorphContent, useDialogMorph } from './DialogMorph';
|
|
41
43
|
import {
|
|
42
44
|
ANIMATION_DURATION,
|
|
43
45
|
DEFAULT_DIALOG_CONTENT_PADDING,
|
|
@@ -113,6 +115,7 @@ function CenteredOrSideDialog({
|
|
|
113
115
|
actions,
|
|
114
116
|
header,
|
|
115
117
|
scrollable,
|
|
118
|
+
morph,
|
|
116
119
|
placement,
|
|
117
120
|
layer,
|
|
118
121
|
width = DEFAULT_SIDE_WIDTH,
|
|
@@ -131,6 +134,19 @@ function CenteredOrSideDialog({
|
|
|
131
134
|
const isSide = placement === 'left' || placement === 'right';
|
|
132
135
|
const headerController = useDialogHeaderController();
|
|
133
136
|
const scrollableResolved = scrollable ?? true;
|
|
137
|
+
const { height: viewportHeight } = useWindowDimensions();
|
|
138
|
+
|
|
139
|
+
// Size morphing across an in-place content swap. The detached centered card
|
|
140
|
+
// is content-hugging, so animating its height reshapes it about its own
|
|
141
|
+
// centre; the side placements are anchored top-to-bottom at a fixed width and
|
|
142
|
+
// have no size to morph, so they never pin (`measurable: false`). The card's
|
|
143
|
+
// own `maxHeight` (inside `BottomSheet`) does the exact capping — the viewport
|
|
144
|
+
// is just the bound the reshape may not exceed.
|
|
145
|
+
const morphState = useDialogMorph({
|
|
146
|
+
enabled: morph !== false && !isSide,
|
|
147
|
+
measurable: scrollableResolved && !isSide,
|
|
148
|
+
maxHeight: viewportHeight,
|
|
149
|
+
});
|
|
134
150
|
|
|
135
151
|
// Imperative open state for the side placement (the BottomSheet path owns its
|
|
136
152
|
// own visibility via its ref instead).
|
|
@@ -223,16 +239,25 @@ function CenteredOrSideDialog({
|
|
|
223
239
|
[close],
|
|
224
240
|
);
|
|
225
241
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
242
|
+
// The array composes the plain surface styles with the morph's reanimated
|
|
243
|
+
// animated style (`morphState.panelStyle` from `useAnimatedStyle`), so the
|
|
244
|
+
// element type must admit an animated style, not just a plain `ViewStyle`.
|
|
245
|
+
// It flows to `<BottomSheet style>`, which applies it to its outer Animated.View.
|
|
246
|
+
const sheetStyle = useMemo<StyleProp<AnimatedStyle<ViewStyle>>>(
|
|
247
|
+
() => [
|
|
248
|
+
{
|
|
249
|
+
maxWidth: 500,
|
|
250
|
+
backgroundColor: theme.colors.background,
|
|
251
|
+
// All four corners rounded — bloom's BottomSheet defaults to top-only
|
|
252
|
+
// radius in flush mode, but we use `detached` so the whole card is
|
|
253
|
+
// floating and rounded uniformly.
|
|
254
|
+
borderRadius: 20,
|
|
255
|
+
},
|
|
256
|
+
// Drives the card's `height` only while a morph is in flight; at rest it
|
|
257
|
+
// resolves to `height: 'auto'` — the card's normal content sizing.
|
|
258
|
+
morphState.panelStyle,
|
|
259
|
+
],
|
|
260
|
+
[theme.colors.background, morphState.panelStyle],
|
|
236
261
|
);
|
|
237
262
|
|
|
238
263
|
const bodyNode = (
|
|
@@ -322,6 +347,8 @@ function CenteredOrSideDialog({
|
|
|
322
347
|
// Stronger dim when a Dialog is stacked over another sheet so the
|
|
323
348
|
// underlying sheet's handle/content doesn't bleed through.
|
|
324
349
|
backdropOpacity={0.7}
|
|
350
|
+
// Measures the floating card — the size a morph starts from.
|
|
351
|
+
onLayout={morphState.onPanelLayout}
|
|
325
352
|
style={sheetStyle}
|
|
326
353
|
>
|
|
327
354
|
<Context.Provider value={context}>
|
|
@@ -342,7 +369,12 @@ function CenteredOrSideDialog({
|
|
|
342
369
|
panelStyle,
|
|
343
370
|
]}
|
|
344
371
|
>
|
|
345
|
-
{
|
|
372
|
+
{/* The morph layer is its own node INSIDE the class-name'd container:
|
|
373
|
+
a className'd component's `onLayout` never fires on web, and this
|
|
374
|
+
one has to measure the incoming frame. */}
|
|
375
|
+
<DialogMorphContent morph={morphState}>
|
|
376
|
+
{header ? headerBody : bodyNode}
|
|
377
|
+
</DialogMorphContent>
|
|
346
378
|
</View>
|
|
347
379
|
</Context.Provider>
|
|
348
380
|
</BottomSheet>
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type StyleProp,
|
|
17
17
|
type ViewStyle,
|
|
18
18
|
} from 'react-native';
|
|
19
|
+
import Animated from 'react-native-reanimated';
|
|
19
20
|
import { RemoveScrollBar } from 'react-remove-scroll-bar';
|
|
20
21
|
|
|
21
22
|
import { Portal } from '../portal/index.web';
|
|
@@ -31,11 +32,13 @@ import {
|
|
|
31
32
|
DialogNavHeader,
|
|
32
33
|
useDialogHeaderController,
|
|
33
34
|
} from './DialogHeader';
|
|
35
|
+
import { DialogMorphContent, useDialogMorph } from './DialogMorph';
|
|
34
36
|
import {
|
|
35
37
|
ANIMATION_DURATION,
|
|
36
38
|
CENTER_FADE_OUT_DURATION,
|
|
37
39
|
DEFAULT_CENTER_MAX_WIDTH,
|
|
38
40
|
DEFAULT_DIALOG_CONTENT_PADDING,
|
|
41
|
+
DEFAULT_MAX_HEIGHT_RATIO,
|
|
39
42
|
DEFAULT_SIDE_WIDTH,
|
|
40
43
|
DIALOG_SHEET_BACKDROP_TESTID,
|
|
41
44
|
EASE_OUT,
|
|
@@ -149,6 +152,7 @@ function CenterOrSideDialog({
|
|
|
149
152
|
contentPadding = DEFAULT_DIALOG_CONTENT_PADDING,
|
|
150
153
|
maxHeightRatio,
|
|
151
154
|
scrollable,
|
|
155
|
+
morph,
|
|
152
156
|
style,
|
|
153
157
|
panelStyle,
|
|
154
158
|
panelClassName,
|
|
@@ -323,6 +327,7 @@ function CenterOrSideDialog({
|
|
|
323
327
|
contentPadding={contentPadding}
|
|
324
328
|
maxHeightRatio={maxHeightRatio}
|
|
325
329
|
scrollable={scrollable}
|
|
330
|
+
morph={morph}
|
|
326
331
|
surfaceZIndex={dialogZIndex.surface}
|
|
327
332
|
isClosing={isClosing}
|
|
328
333
|
>
|
|
@@ -383,6 +388,7 @@ function DialogPanel({
|
|
|
383
388
|
contentPadding,
|
|
384
389
|
maxHeightRatio,
|
|
385
390
|
scrollable,
|
|
391
|
+
morph,
|
|
386
392
|
surfaceZIndex,
|
|
387
393
|
isClosing,
|
|
388
394
|
children,
|
|
@@ -398,6 +404,7 @@ function DialogPanel({
|
|
|
398
404
|
contentPadding: number;
|
|
399
405
|
maxHeightRatio?: number;
|
|
400
406
|
scrollable?: boolean;
|
|
407
|
+
morph?: boolean;
|
|
401
408
|
surfaceZIndex: number;
|
|
402
409
|
isClosing: boolean;
|
|
403
410
|
children?: React.ReactNode;
|
|
@@ -407,6 +414,19 @@ function DialogPanel({
|
|
|
407
414
|
const titleId = useId();
|
|
408
415
|
const descriptionId = useId();
|
|
409
416
|
const headerController = useDialogHeaderController();
|
|
417
|
+
const { height: viewportHeight } = useWindowDimensions();
|
|
418
|
+
const heightRatio = maxHeightRatio ?? DEFAULT_MAX_HEIGHT_RATIO;
|
|
419
|
+
|
|
420
|
+
// Size morphing across an in-place content swap. The centered card is the one
|
|
421
|
+
// placement whose width can vary too, so `maxWidth` is handed over as well.
|
|
422
|
+
// The card's own `maxHeight` (the ratio percentage below) does the exact
|
|
423
|
+
// capping — the viewport is just the bound the reshape may not exceed.
|
|
424
|
+
const morphState = useDialogMorph({
|
|
425
|
+
enabled: morph !== false,
|
|
426
|
+
measurable: scrollable !== false,
|
|
427
|
+
maxHeight: viewportHeight,
|
|
428
|
+
maxWidth,
|
|
429
|
+
});
|
|
410
430
|
|
|
411
431
|
const body = (
|
|
412
432
|
<DialogBody
|
|
@@ -421,12 +441,13 @@ function DialogPanel({
|
|
|
421
441
|
);
|
|
422
442
|
|
|
423
443
|
return (
|
|
424
|
-
<View
|
|
444
|
+
<Animated.View
|
|
425
445
|
role="dialog"
|
|
426
446
|
aria-label={label}
|
|
427
447
|
aria-labelledby={title ? titleId : undefined}
|
|
428
448
|
aria-describedby={description ? descriptionId : undefined}
|
|
429
449
|
testID={testID}
|
|
450
|
+
onLayout={morphState.onPanelLayout}
|
|
430
451
|
onStartShouldSetResponder={() => true}
|
|
431
452
|
onResponderRelease={stopPropagation}
|
|
432
453
|
{...({ onClick: stopPropagation } as Record<string, unknown>)}
|
|
@@ -441,7 +462,7 @@ function DialogPanel({
|
|
|
441
462
|
// capped at `maxHeightRatio` of the viewport and scrolls inside the
|
|
442
463
|
// rounded card (via the ScrollView below) — consumers never add their
|
|
443
464
|
// own height cap / ScrollView. `overflow: hidden` clips to the radius.
|
|
444
|
-
maxHeight: `${Math.round(
|
|
465
|
+
maxHeight: `${Math.round(heightRatio * 100)}%`,
|
|
445
466
|
overflow: 'hidden',
|
|
446
467
|
backgroundColor: theme.colors.background,
|
|
447
468
|
borderWidth: 1,
|
|
@@ -455,6 +476,10 @@ function DialogPanel({
|
|
|
455
476
|
isClosing
|
|
456
477
|
? ({ animation: `bloomDialogZoomFadeOut ease-in ${FADE_OUT_DURATION}ms forwards` } as ViewStyle)
|
|
457
478
|
: ({ animation: 'bloomDialogZoomFadeIn cubic-bezier(0.16, 1, 0.3, 1) 0.3s' } as ViewStyle),
|
|
479
|
+
// Drives `height` (and `maxWidth`) only while a morph is in flight; at
|
|
480
|
+
// rest it resolves to `height: 'auto'` — the natural sizing above. Placed
|
|
481
|
+
// before `style` so a consumer's explicit size still wins.
|
|
482
|
+
morphState.panelStyle,
|
|
458
483
|
style,
|
|
459
484
|
]}
|
|
460
485
|
>
|
|
@@ -471,10 +496,10 @@ function DialogPanel({
|
|
|
471
496
|
collapse={scrollable !== false}
|
|
472
497
|
/>
|
|
473
498
|
{scrollable === false ? (
|
|
474
|
-
<
|
|
499
|
+
<DialogMorphContent morph={morphState} style={{ flex: 1, minHeight: 0 }}>
|
|
475
500
|
<View style={{ height: DIALOG_NAV_BAR_HEIGHT }} />
|
|
476
501
|
<DialogHeaderProvider controller={headerController}>{body}</DialogHeaderProvider>
|
|
477
|
-
</
|
|
502
|
+
</DialogMorphContent>
|
|
478
503
|
) : (
|
|
479
504
|
<ScrollView
|
|
480
505
|
style={{ flexShrink: 1 }}
|
|
@@ -485,8 +510,10 @@ function DialogPanel({
|
|
|
485
510
|
headerController.scrollY.value = e.nativeEvent.contentOffset.y;
|
|
486
511
|
}}
|
|
487
512
|
>
|
|
488
|
-
<
|
|
489
|
-
|
|
513
|
+
<DialogMorphContent morph={morphState}>
|
|
514
|
+
<DialogLargeTitle controller={headerController} header={header} />
|
|
515
|
+
<DialogHeaderProvider controller={headerController}>{body}</DialogHeaderProvider>
|
|
516
|
+
</DialogMorphContent>
|
|
490
517
|
</ScrollView>
|
|
491
518
|
)}
|
|
492
519
|
</>
|
|
@@ -496,19 +523,22 @@ function DialogPanel({
|
|
|
496
523
|
// `maxHeight` + `overflow: hidden` cap it and `flex: 1` + `minHeight: 0`
|
|
497
524
|
// hand the child a bounded height to scroll into — with NO wrapping
|
|
498
525
|
// ScrollView, so a VirtualizedList never nests inside one.
|
|
499
|
-
<
|
|
526
|
+
<DialogMorphContent
|
|
527
|
+
morph={morphState}
|
|
528
|
+
style={{ flex: 1, minHeight: 0, padding: contentPadding }}
|
|
529
|
+
>
|
|
500
530
|
{body}
|
|
501
|
-
</
|
|
531
|
+
</DialogMorphContent>
|
|
502
532
|
) : (
|
|
503
533
|
<ScrollView
|
|
504
534
|
style={{ flexShrink: 1 }}
|
|
505
535
|
contentContainerStyle={{ padding: contentPadding }}
|
|
506
536
|
showsVerticalScrollIndicator={false}
|
|
507
537
|
>
|
|
508
|
-
{body}
|
|
538
|
+
<DialogMorphContent morph={morphState}>{body}</DialogMorphContent>
|
|
509
539
|
</ScrollView>
|
|
510
540
|
)}
|
|
511
|
-
</View>
|
|
541
|
+
</Animated.View>
|
|
512
542
|
);
|
|
513
543
|
}
|
|
514
544
|
|