@oxyhq/bloom 0.46.0 → 0.49.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/bottom-sheet/BottomSheetBase.js +36 -12
- package/lib/commonjs/bottom-sheet/BottomSheetBase.js.map +1 -1
- package/lib/commonjs/combobox/Combobox.js +3 -3
- package/lib/commonjs/combobox/Combobox.js.map +1 -1
- package/lib/commonjs/command/Command.js +3 -3
- package/lib/commonjs/command/Command.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.js +103 -8
- package/lib/commonjs/dialog/Dialog.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.web.js +189 -12
- package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
- package/lib/commonjs/dialog/DialogBottomSheet.js +111 -31
- package/lib/commonjs/dialog/DialogBottomSheet.js.map +1 -1
- package/lib/commonjs/dialog/DialogHeader.js +371 -0
- package/lib/commonjs/dialog/DialogHeader.js.map +1 -0
- package/lib/commonjs/dialog/DialogMorph.js +458 -0
- package/lib/commonjs/dialog/DialogMorph.js.map +1 -0
- package/lib/commonjs/dialog/index.js +14 -0
- package/lib/commonjs/dialog/index.js.map +1 -1
- package/lib/commonjs/dialog/index.web.js +14 -0
- package/lib/commonjs/dialog/index.web.js.map +1 -1
- package/lib/commonjs/index.js +11 -4
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +11 -4
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/react-native-className.d.js +6 -0
- package/lib/commonjs/react-native-className.d.js.map +1 -0
- package/lib/commonjs/{search-input → search}/index.js +5 -3
- package/lib/commonjs/search/index.js.map +1 -0
- package/lib/commonjs/settings-list/SettingsList.js +6 -2
- package/lib/commonjs/settings-list/SettingsList.js.map +1 -1
- package/lib/commonjs/surfaces/SurfaceHost.js +23 -9
- package/lib/commonjs/surfaces/SurfaceHost.js.map +1 -1
- package/lib/commonjs/text-field/index.js +22 -6
- package/lib/commonjs/text-field/index.js.map +1 -1
- package/lib/module/bottom-sheet/BottomSheetBase.js +36 -12
- package/lib/module/bottom-sheet/BottomSheetBase.js.map +1 -1
- package/lib/module/combobox/Combobox.js +3 -3
- package/lib/module/combobox/Combobox.js.map +1 -1
- package/lib/module/command/Command.js +3 -3
- package/lib/module/command/Command.js.map +1 -1
- package/lib/module/dialog/Dialog.js +104 -9
- package/lib/module/dialog/Dialog.js.map +1 -1
- package/lib/module/dialog/Dialog.web.js +192 -16
- package/lib/module/dialog/Dialog.web.js.map +1 -1
- package/lib/module/dialog/DialogBottomSheet.js +113 -33
- package/lib/module/dialog/DialogBottomSheet.js.map +1 -1
- package/lib/module/dialog/DialogHeader.js +362 -0
- package/lib/module/dialog/DialogHeader.js.map +1 -0
- package/lib/module/dialog/DialogMorph.js +451 -0
- package/lib/module/dialog/DialogMorph.js.map +1 -0
- package/lib/module/dialog/index.js +2 -0
- package/lib/module/dialog/index.js.map +1 -1
- package/lib/module/dialog/index.web.js +2 -0
- package/lib/module/dialog/index.web.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +2 -2
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/react-native-className.d.js +20 -0
- package/lib/module/react-native-className.d.js.map +1 -0
- package/lib/module/{search-input → search}/index.js +6 -2
- package/lib/module/search/index.js.map +1 -0
- package/lib/module/settings-list/SettingsList.js +6 -2
- package/lib/module/settings-list/SettingsList.js.map +1 -1
- package/lib/module/surfaces/SurfaceHost.js +23 -9
- package/lib/module/surfaces/SurfaceHost.js.map +1 -1
- package/lib/module/text-field/index.js +22 -6
- package/lib/module/text-field/index.js.map +1 -1
- package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts +31 -1
- package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/combobox/Combobox.d.ts +1 -1
- package/lib/typescript/commonjs/command/Command.d.ts +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 +86 -0
- package/lib/typescript/commonjs/dialog/DialogHeader.d.ts.map +1 -0
- 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 +4 -1
- package/lib/typescript/commonjs/dialog/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/index.web.d.ts +4 -1
- package/lib/typescript/commonjs/dialog/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/dialog/types.d.ts +105 -0
- package/lib/typescript/commonjs/dialog/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/icons/common.d.ts +1 -0
- package/lib/typescript/commonjs/icons/common.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +3 -3
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +3 -3
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/input-group/types.d.ts +1 -1
- package/lib/typescript/commonjs/{search-input → search}/index.d.ts +1 -1
- package/lib/typescript/commonjs/search/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/surfaces/SurfaceHost.d.ts.map +1 -1
- package/lib/typescript/commonjs/surfaces/types.d.ts +26 -1
- package/lib/typescript/commonjs/surfaces/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/text-field/index.d.ts +2 -1
- package/lib/typescript/commonjs/text-field/index.d.ts.map +1 -1
- package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts +31 -1
- package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
- package/lib/typescript/module/combobox/Combobox.d.ts +1 -1
- package/lib/typescript/module/command/Command.d.ts +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 +86 -0
- package/lib/typescript/module/dialog/DialogHeader.d.ts.map +1 -0
- 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 +4 -1
- package/lib/typescript/module/dialog/index.d.ts.map +1 -1
- package/lib/typescript/module/dialog/index.web.d.ts +4 -1
- package/lib/typescript/module/dialog/index.web.d.ts.map +1 -1
- package/lib/typescript/module/dialog/types.d.ts +105 -0
- package/lib/typescript/module/dialog/types.d.ts.map +1 -1
- package/lib/typescript/module/icons/common.d.ts +1 -0
- package/lib/typescript/module/icons/common.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +3 -3
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +3 -3
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/input-group/types.d.ts +1 -1
- package/lib/typescript/module/{search-input → search}/index.d.ts +1 -1
- package/lib/typescript/module/search/index.d.ts.map +1 -0
- package/lib/typescript/module/surfaces/SurfaceHost.d.ts.map +1 -1
- package/lib/typescript/module/surfaces/types.d.ts +26 -1
- package/lib/typescript/module/surfaces/types.d.ts.map +1 -1
- package/lib/typescript/module/text-field/index.d.ts +2 -1
- package/lib/typescript/module/text-field/index.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/__tests__/BottomSheet.test.tsx +47 -0
- package/src/__tests__/DialogBottomSheet.test.tsx +30 -5
- package/src/__tests__/DialogMorph.test.tsx +230 -0
- package/src/bottom-sheet/BottomSheetBase.tsx +72 -11
- package/src/combobox/Combobox.tsx +3 -3
- package/src/command/Command.tsx +3 -3
- package/src/dialog/Dialog.tsx +111 -14
- package/src/dialog/Dialog.web.tsx +186 -25
- package/src/dialog/DialogBottomSheet.tsx +122 -30
- package/src/dialog/DialogHeader.tsx +429 -0
- package/src/dialog/DialogMorph.tsx +576 -0
- package/src/dialog/index.ts +4 -0
- package/src/dialog/index.web.ts +4 -0
- package/src/dialog/types.ts +106 -0
- package/src/index.ts +3 -1
- package/src/index.web.ts +3 -1
- package/src/input-group/types.ts +1 -1
- package/src/react-native-className.d.ts +38 -0
- package/src/{search-input → search}/index.tsx +8 -5
- package/src/settings-list/SettingsList.tsx +6 -2
- package/src/surfaces/SurfaceHost.tsx +17 -1
- package/src/surfaces/types.ts +26 -1
- package/src/text-field/index.tsx +24 -5
- package/lib/commonjs/search-input/index.js.map +0 -1
- package/lib/module/search-input/index.js.map +0 -1
- package/lib/typescript/commonjs/search-input/index.d.ts.map +0 -1
- package/lib/typescript/module/search-input/index.d.ts.map +0 -1
|
@@ -19,7 +19,7 @@ export interface InputGroupAddonProps {
|
|
|
19
19
|
export interface InputGroupProps {
|
|
20
20
|
/**
|
|
21
21
|
* Group children. Place `InputGroup.Addon` elements before/after the input
|
|
22
|
-
* (a Bloom `TextFieldInput`, a `
|
|
22
|
+
* (a Bloom `TextFieldInput`, a `Search`, or any control). The middle
|
|
23
23
|
* child stretches to fill.
|
|
24
24
|
*/
|
|
25
25
|
children: React.ReactNode;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type TextInput } from 'react-native';
|
|
2
2
|
import { type TextFieldInputProps } from '../text-field';
|
|
3
|
-
export declare const
|
|
3
|
+
export declare const Search: import("react").ForwardRefExoticComponent<Omit<TextFieldInputProps, "label"> & {
|
|
4
4
|
label?: TextFieldInputProps["label"];
|
|
5
5
|
/**
|
|
6
6
|
* Called when the user presses the clear (X) button.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/search/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,SAAS,EAAQ,MAAM,cAAc,CAAC;AAK9D,OAAO,EAA4C,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAenG,eAAO,MAAM,MAAM;YAPT,mBAAmB,CAAC,OAAO,CAAC;IACpC;;OAEG;kBACW,MAAM,IAAI;6CAwDzB,CAAC"}
|
|
@@ -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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
-
import type { DialogInset, ResponsiveDialogPlacement } from '../dialog/types';
|
|
3
|
+
import type { DialogHeaderConfig, DialogInset, ResponsiveDialogPlacement } from '../dialog/types';
|
|
4
4
|
/**
|
|
5
5
|
* How a surface renders — the placement + the `Dialog` sizing/chrome options a
|
|
6
6
|
* surface needs. This is the CONTENT-AGNOSTIC presentation config: it never
|
|
@@ -37,6 +37,31 @@ export interface SurfacePresentation {
|
|
|
37
37
|
dismissOnBackdrop?: boolean;
|
|
38
38
|
/** Inner padding (px) of the surface content container. Forwarded to `Dialog`. */
|
|
39
39
|
contentPadding?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Turns on the Dialog's OWN navigation header for this surface (sticky gradient
|
|
42
|
+
* nav bar + large collapsing title over the surface's scroll content). Seeds
|
|
43
|
+
* the header content; a mounted screen refines it at runtime via
|
|
44
|
+
* `useDialogHeader`. Omit for a plain surface (a confirm/alert dialog). Forwarded
|
|
45
|
+
* to `Dialog`.
|
|
46
|
+
*/
|
|
47
|
+
header?: DialogHeaderConfig;
|
|
48
|
+
/**
|
|
49
|
+
* Whether the surface wraps its content in an internal scroll container.
|
|
50
|
+
* Defaults to `true`. Set `false` when the surface's content owns its own
|
|
51
|
+
* scrolling primitive (a `FlatList` / `SectionList` / VirtualizedList, or its
|
|
52
|
+
* own `ScrollView`) so the host does not nest it in a ScrollView. Forwarded
|
|
53
|
+
* to `Dialog`.
|
|
54
|
+
*/
|
|
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;
|
|
40
65
|
/** Style overrides applied to the inner content container. */
|
|
41
66
|
style?: StyleProp<ViewStyle>;
|
|
42
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":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,KAAK,EAAE,WAAW,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/surfaces/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,8 +3,9 @@ import { type TextStyleProp, type ViewStyleProp } from '../styles';
|
|
|
3
3
|
import { type Props as SVGIconProps } from '../icons/common';
|
|
4
4
|
export type TextFieldProps = React.PropsWithChildren<{
|
|
5
5
|
isInvalid?: boolean;
|
|
6
|
+
radius?: number;
|
|
6
7
|
} & ViewStyleProp>;
|
|
7
|
-
export declare function TextField({ children, isInvalid, style }: TextFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export declare function TextField({ children, isInvalid, radius, style }: TextFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
8
9
|
export declare function useSharedInputStyles(): {
|
|
9
10
|
chromeHover: ViewStyle;
|
|
10
11
|
chromeFocus: ViewStyle;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/text-field/index.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,kBAAkB,EAIvB,SAAS,EACT,KAAK,cAAc,EAGnB,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAMtB,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAkB,KAAK,KAAK,IAAI,YAAY,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/text-field/index.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,kBAAkB,EAIvB,SAAS,EACT,KAAK,cAAc,EAGnB,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAMtB,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAkB,KAAK,KAAK,IAAI,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAwC7E,MAAM,MAAM,cAAc,GAAG,KAAK,CAAC,iBAAiB,CAClD;IAAE,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CACzD,CAAC;AAEF,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,SAAiB,EAAE,MAAW,EAAE,KAAK,EAAE,EAAE,cAAc,2CA2C5F;AAED,wBAAgB,oBAAoB;;;;;EA0BnC;AAED,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,cAAc,EACd,OAAO,GAAG,cAAc,GAAG,aAAa,CACzC,GAAG;IACF,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IAC7E,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACxC;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,WAAW,EACX,KAAK,EACL,YAAY,EACZ,OAAO,EACP,MAAM,EACN,SAAS,EACT,QAAQ,EACR,KAAK,EACL,aAAqB,EACrB,GAAG,IAAI,EACR,EAAE,mBAAmB,2CA6HrB;AA6KD,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,QAAQ,GACT,EAAE,KAAK,CAAC,iBAAiB,CAAC;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,2CAchD;AAED,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,YAAY,CAAC,CAAA;CAAE,2CA8BxF;AAED,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,KAAK,EACL,iBAAiB,EACjB,KAAK,GACN,EAAE,KAAK,CAAC,iBAAiB,CACxB,aAAa,GAAG;IACd,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,kBAAkB,CAAC,mBAAmB,CAAC,CAAC;CAC7D,CACF,2CAqBA;AAED,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,2CAkDA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/bloom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
4
4
|
"description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -617,15 +617,15 @@
|
|
|
617
617
|
"default": "./lib/commonjs/segmented-control/index.js"
|
|
618
618
|
}
|
|
619
619
|
},
|
|
620
|
-
"./search
|
|
621
|
-
"react-native": "./src/search
|
|
620
|
+
"./search": {
|
|
621
|
+
"react-native": "./src/search/index.tsx",
|
|
622
622
|
"import": {
|
|
623
|
-
"types": "./lib/typescript/module/search
|
|
624
|
-
"default": "./lib/module/search
|
|
623
|
+
"types": "./lib/typescript/module/search/index.d.ts",
|
|
624
|
+
"default": "./lib/module/search/index.js"
|
|
625
625
|
},
|
|
626
626
|
"require": {
|
|
627
|
-
"types": "./lib/typescript/commonjs/search
|
|
628
|
-
"default": "./lib/commonjs/search
|
|
627
|
+
"types": "./lib/typescript/commonjs/search/index.d.ts",
|
|
628
|
+
"default": "./lib/commonjs/search/index.js"
|
|
629
629
|
}
|
|
630
630
|
},
|
|
631
631
|
"./admonition": {
|
|
@@ -1043,7 +1043,7 @@
|
|
|
1043
1043
|
"grid",
|
|
1044
1044
|
"text-field",
|
|
1045
1045
|
"segmented-control",
|
|
1046
|
-
"search
|
|
1046
|
+
"search",
|
|
1047
1047
|
"admonition",
|
|
1048
1048
|
"menu",
|
|
1049
1049
|
"tooltip",
|
|
@@ -248,6 +248,53 @@ describe('BottomSheet', () => {
|
|
|
248
248
|
});
|
|
249
249
|
});
|
|
250
250
|
|
|
251
|
+
describe('unmount-while-open does not fire onDismiss (responsive placement swap)', () => {
|
|
252
|
+
it('skips onDismiss when a fully-open sheet is unmounted (yanked, not closing)', () => {
|
|
253
|
+
// A responsive Dialog crossing its breakpoint swaps its bottom-sheet branch
|
|
254
|
+
// for the centered-card branch, unmounting this sheet while it is still
|
|
255
|
+
// fully OPEN. That is NOT a dismissal — firing onDismiss here would tear the
|
|
256
|
+
// surface down mid-swap. The unmount-safety only flushes when a close was
|
|
257
|
+
// already underway (visible=false).
|
|
258
|
+
const onDismiss = jest.fn();
|
|
259
|
+
const ref = createRef<BottomSheetRef>();
|
|
260
|
+
const { unmount } = renderWithTheme(
|
|
261
|
+
<BottomSheet ref={ref} onDismiss={onDismiss}>
|
|
262
|
+
<React.Fragment>Open content</React.Fragment>
|
|
263
|
+
</BottomSheet>,
|
|
264
|
+
);
|
|
265
|
+
act(() => {
|
|
266
|
+
ref.current?.present();
|
|
267
|
+
});
|
|
268
|
+
act(() => {
|
|
269
|
+
unmount();
|
|
270
|
+
});
|
|
271
|
+
expect(onDismiss).not.toHaveBeenCalled();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('still flushes onDismiss when unmounted mid-close (visible already false)', () => {
|
|
275
|
+
// The original guard's purpose: a sheet yanked mid-close-animation must
|
|
276
|
+
// still flush its onDismiss so queued post-close callbacks are not lost.
|
|
277
|
+
const onDismiss = jest.fn();
|
|
278
|
+
const ref = createRef<BottomSheetRef>();
|
|
279
|
+
const { unmount } = renderWithTheme(
|
|
280
|
+
<BottomSheet ref={ref} onDismiss={onDismiss}>
|
|
281
|
+
<React.Fragment>Closing content</React.Fragment>
|
|
282
|
+
</BottomSheet>,
|
|
283
|
+
);
|
|
284
|
+
act(() => {
|
|
285
|
+
ref.current?.present();
|
|
286
|
+
});
|
|
287
|
+
// Start the close (visible → false), then yank before the exit settles.
|
|
288
|
+
act(() => {
|
|
289
|
+
ref.current?.dismiss();
|
|
290
|
+
});
|
|
291
|
+
act(() => {
|
|
292
|
+
unmount();
|
|
293
|
+
});
|
|
294
|
+
expect(onDismiss).toHaveBeenCalledTimes(1);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
251
298
|
describe('close generation tracking', () => {
|
|
252
299
|
it('re-opening the sheet after dismiss does not throw or get stuck', () => {
|
|
253
300
|
// Regression test for the "tap to open does nothing" bug: when the
|
|
@@ -206,7 +206,7 @@ describe('Dialog bottom placement delegates to BottomSheet', () => {
|
|
|
206
206
|
expect(flattened.padding).toBe(0);
|
|
207
207
|
});
|
|
208
208
|
|
|
209
|
-
it('
|
|
209
|
+
it('wraps pure custom children in the internal scroll container by default (Dialog owns scroll)', () => {
|
|
210
210
|
let control: ReturnType<typeof useDialogControl> | undefined;
|
|
211
211
|
const result = renderWithTheme(
|
|
212
212
|
<Harness>
|
|
@@ -214,7 +214,7 @@ describe('Dialog bottom placement delegates to BottomSheet', () => {
|
|
|
214
214
|
control = c;
|
|
215
215
|
return (
|
|
216
216
|
<Dialog control={c} placement="bottom">
|
|
217
|
-
<Text>Custom children
|
|
217
|
+
<Text>Custom children scroll inside the sheet by default</Text>
|
|
218
218
|
</Dialog>
|
|
219
219
|
);
|
|
220
220
|
}}
|
|
@@ -223,9 +223,34 @@ describe('Dialog bottom placement delegates to BottomSheet', () => {
|
|
|
223
223
|
act(() => {
|
|
224
224
|
control?.open();
|
|
225
225
|
});
|
|
226
|
-
// Pure custom children
|
|
227
|
-
//
|
|
228
|
-
//
|
|
226
|
+
// Pure custom children default to `scrollable` (undefined → true), so the
|
|
227
|
+
// sheet DOES add its internal ScrollView and the screen — stripped of its
|
|
228
|
+
// own scroller — scrolls inside it. The Dialog owns the scroll boundary.
|
|
229
|
+
const tree: RenderedNode | RenderedNode[] | null = result.toJSON();
|
|
230
|
+
expect(countNodesByType(tree, 'Animated.ScrollView')).toBe(1);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('opts custom children OUT of the internal scroll container when scrollable={false}', () => {
|
|
234
|
+
let control: ReturnType<typeof useDialogControl> | undefined;
|
|
235
|
+
const result = renderWithTheme(
|
|
236
|
+
<Harness>
|
|
237
|
+
{(c) => {
|
|
238
|
+
control = c;
|
|
239
|
+
return (
|
|
240
|
+
<Dialog control={c} placement="bottom" scrollable={false}>
|
|
241
|
+
<Text>Owns its own FlatList</Text>
|
|
242
|
+
</Dialog>
|
|
243
|
+
);
|
|
244
|
+
}}
|
|
245
|
+
</Harness>,
|
|
246
|
+
);
|
|
247
|
+
act(() => {
|
|
248
|
+
control?.open();
|
|
249
|
+
});
|
|
250
|
+
// A surface whose content owns its own scroller (a FlatList / VirtualizedList)
|
|
251
|
+
// passes `scrollable={false}`, so the sheet does NOT add its internal
|
|
252
|
+
// ScrollView — the child is then the ONLY scroll container (no scroll-in-
|
|
253
|
+
// scroll, no "VirtualizedList inside a plain ScrollView" warning).
|
|
229
254
|
const tree: RenderedNode | RenderedNode[] | null = result.toJSON();
|
|
230
255
|
expect(countNodesByType(tree, 'Animated.ScrollView')).toBe(0);
|
|
231
256
|
});
|
|
@@ -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,6 +6,7 @@ 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';
|
|
@@ -55,6 +56,16 @@ export interface BottomSheetRef {
|
|
|
55
56
|
|
|
56
57
|
export interface BottomSheetProps {
|
|
57
58
|
children: React.ReactNode;
|
|
59
|
+
/**
|
|
60
|
+
* Controlled open state. When provided, the sheet mounts already-open if
|
|
61
|
+
* `true` (seeding its internal visible/rendered state) instead of relying on
|
|
62
|
+
* an imperative `present()` after mount. This is what makes a responsive
|
|
63
|
+
* `Dialog` survive a placement swap (centered card ↔ bottom sheet on resize):
|
|
64
|
+
* the freshly-mounted sheet is visible on its first commit, with no
|
|
65
|
+
* present()-in-effect race that would otherwise leave it blank. Omit for the
|
|
66
|
+
* purely imperative `present()`/`dismiss()` API.
|
|
67
|
+
*/
|
|
68
|
+
open?: boolean;
|
|
58
69
|
onDismiss?: () => void;
|
|
59
70
|
enablePanDownToClose?: boolean;
|
|
60
71
|
backgroundComponent?: (props: { style?: StyleProp<ViewStyle> }) => React.ReactElement | null;
|
|
@@ -128,6 +139,26 @@ export interface BottomSheetProps {
|
|
|
128
139
|
* `showHandle` is `true`.
|
|
129
140
|
*/
|
|
130
141
|
handleComponent?: () => React.ReactNode;
|
|
142
|
+
/**
|
|
143
|
+
* External shared value the internal scroll handler mirrors the content
|
|
144
|
+
* offset into (in addition to its own gesture-gating offset). Lets a host —
|
|
145
|
+
* e.g. a Dialog nav-header — drive a collapse from the sheet's OWN scroller
|
|
146
|
+
* without owning the ScrollView. Optional; the standalone sheet ignores it.
|
|
147
|
+
*/
|
|
148
|
+
scrollY?: SharedValue<number>;
|
|
149
|
+
/**
|
|
150
|
+
* Overlay slot rendered absolutely at the TOP of the sheet card, painted
|
|
151
|
+
* above the scrolling body (clipped to the sheet's rounded top). Used by the
|
|
152
|
+
* Dialog to float its sticky nav header over the sheet's scroll content.
|
|
153
|
+
*/
|
|
154
|
+
headerOverlay?: React.ReactNode;
|
|
155
|
+
/**
|
|
156
|
+
* Layout callback for the SHEET CARD — the same box `style` targets. Lets a
|
|
157
|
+
* host observe the card's rendered size (the Dialog measures it to morph the
|
|
158
|
+
* sheet between two content frames). Fires on every size change, including
|
|
159
|
+
* while a host animates the card's height.
|
|
160
|
+
*/
|
|
161
|
+
onLayout?: (event: LayoutChangeEvent) => void;
|
|
131
162
|
}
|
|
132
163
|
|
|
133
164
|
/**
|
|
@@ -169,6 +200,7 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
169
200
|
const {
|
|
170
201
|
Shell,
|
|
171
202
|
children,
|
|
203
|
+
open,
|
|
172
204
|
onDismiss,
|
|
173
205
|
enablePanDownToClose = true,
|
|
174
206
|
backgroundComponent,
|
|
@@ -183,14 +215,20 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
183
215
|
manualActivation = false,
|
|
184
216
|
dynamicBackdrop = false,
|
|
185
217
|
handleComponent,
|
|
218
|
+
scrollY: externalScrollY,
|
|
219
|
+
headerOverlay,
|
|
220
|
+
onLayout,
|
|
186
221
|
} = props;
|
|
187
222
|
|
|
188
223
|
const insets = useSafeAreaInsets();
|
|
189
224
|
const theme = useTheme();
|
|
190
225
|
const { colors } = theme;
|
|
191
226
|
const { height: screenHeight } = useScreenDimensions();
|
|
192
|
-
|
|
193
|
-
|
|
227
|
+
// Seed from the controlled `open` prop so a sheet that should be open is
|
|
228
|
+
// visible on its FIRST commit (no present()-in-effect race) — this is what
|
|
229
|
+
// lets a responsive Dialog survive a resize placement swap without going blank.
|
|
230
|
+
const [visible, setVisible] = useState(() => open ?? false);
|
|
231
|
+
const [rendered, setRendered] = useState(() => open ?? false); // keep mounted for exit animation
|
|
194
232
|
const closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
195
233
|
const hasClosedRef = useRef(false);
|
|
196
234
|
const scrollViewRef = useRef<Animated.ScrollView>(null);
|
|
@@ -266,6 +304,12 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
266
304
|
useEffect(() => {
|
|
267
305
|
renderedRef.current = rendered;
|
|
268
306
|
}, [rendered]);
|
|
307
|
+
// Mirror `visible` so the unmount cleanup can tell "closing" (visible=false,
|
|
308
|
+
// animating out) from "fully open" (visible=true) at teardown time.
|
|
309
|
+
const visibleRef = useRef(visible);
|
|
310
|
+
useEffect(() => {
|
|
311
|
+
visibleRef.current = visible;
|
|
312
|
+
}, [visible]);
|
|
269
313
|
|
|
270
314
|
/**
|
|
271
315
|
* Commit a close. Two guards prevent stale callbacks from firing:
|
|
@@ -325,18 +369,24 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
325
369
|
}, [visible, rendered, finishClose, screenHeight, closeGeneration, opacity, translateY]);
|
|
326
370
|
|
|
327
371
|
// On unmount: ensure pending close callbacks (e.g. consumer's `onDismiss`)
|
|
328
|
-
// still fire if the BS is yanked mid-animation by a parent re-render
|
|
329
|
-
// Without this, `Dialog`'s `handleDismiss` never runs
|
|
330
|
-
// callbacks (post-close handlers) are silently lost.
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
//
|
|
372
|
+
// still fire if the BS is yanked mid-animation by a parent re-render while a
|
|
373
|
+
// close is IN PROGRESS. Without this, `Dialog`'s `handleDismiss` never runs
|
|
374
|
+
// and queued callbacks (post-close handlers) are silently lost.
|
|
375
|
+
//
|
|
376
|
+
// Guarded on `!visibleRef.current`: only flush when a close was already
|
|
377
|
+
// underway (visible=false → closing/animating out). A sheet yanked while
|
|
378
|
+
// still FULLY OPEN (visible=true) is not closing — it is being swapped out by
|
|
379
|
+
// a parent, e.g. a responsive `Dialog` crossing its breakpoint between the
|
|
380
|
+
// bottom-sheet and centered-card branches. Firing onDismiss there would
|
|
381
|
+
// wrongly dismiss the surface mid-swap (there are no pending close callbacks
|
|
382
|
+
// to flush anyway), so the new branch can take over still-open. Refs are read
|
|
383
|
+
// inside the cleanup, so the latest values are captured.
|
|
334
384
|
useEffect(() => () => {
|
|
335
385
|
if (closeTimeoutRef.current) {
|
|
336
386
|
clearTimeout(closeTimeoutRef.current);
|
|
337
387
|
closeTimeoutRef.current = null;
|
|
338
388
|
}
|
|
339
|
-
if (renderedRef.current && !hasClosedRef.current) {
|
|
389
|
+
if (renderedRef.current && !hasClosedRef.current && !visibleRef.current) {
|
|
340
390
|
hasClosedRef.current = true;
|
|
341
391
|
safeCloseRef.current();
|
|
342
392
|
}
|
|
@@ -639,8 +689,12 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
639
689
|
onScroll: (event) => {
|
|
640
690
|
scrollOffsetY.value = event.contentOffset.y;
|
|
641
691
|
isScrollAtTop.value = event.contentOffset.y <= 0;
|
|
692
|
+
// Mirror into the host's collapse offset (a Dialog nav header) when
|
|
693
|
+
// provided. Listed in deps so the RN-Web (no worklets plugin) handler
|
|
694
|
+
// re-binds if the shared value identity changes.
|
|
695
|
+
if (externalScrollY) externalScrollY.value = event.contentOffset.y;
|
|
642
696
|
},
|
|
643
|
-
}, []);
|
|
697
|
+
}, [externalScrollY]);
|
|
644
698
|
|
|
645
699
|
const dynamicStyles = useMemo(() => {
|
|
646
700
|
return StyleSheet.create({
|
|
@@ -735,12 +789,19 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
|
|
|
735
789
|
</Animated.View>
|
|
736
790
|
|
|
737
791
|
<GestureDetector gesture={panGesture}>
|
|
738
|
-
<Animated.View
|
|
792
|
+
<Animated.View
|
|
793
|
+
onLayout={onLayout}
|
|
794
|
+
style={[dynamicStyles.sheet, sheetMarginStyle, sheetStyle, sheetHeightStyle, style]}
|
|
795
|
+
>
|
|
739
796
|
{backgroundComponent?.({ style: styles.background })}
|
|
740
797
|
|
|
741
798
|
{handleSlot}
|
|
742
799
|
|
|
743
800
|
{bodyContent}
|
|
801
|
+
|
|
802
|
+
{/* Sticky nav-header overlay (Dialog nav-header mode): floats
|
|
803
|
+
above the scroll body, clipped to the sheet's rounded top. */}
|
|
804
|
+
{headerOverlay}
|
|
744
805
|
</Animated.View>
|
|
745
806
|
</GestureDetector>
|
|
746
807
|
</View>
|