@oxyhq/bloom 0.72.2 → 0.73.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/avatar-group/AvatarGroup.web.js +22 -21
- package/lib/commonjs/avatar-group/AvatarGroup.web.js.map +1 -1
- package/lib/commonjs/bottom-sheet/index.web.js +11 -6
- package/lib/commonjs/bottom-sheet/index.web.js.map +1 -1
- package/lib/commonjs/context-menu/index.web.js +20 -22
- package/lib/commonjs/context-menu/index.web.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.js +66 -70
- package/lib/commonjs/dialog/Dialog.js.map +1 -1
- package/lib/commonjs/dialog/Dialog.web.js +49 -51
- package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
- package/lib/commonjs/fonts/font-data.web.js +22 -0
- package/lib/commonjs/fonts/font-data.web.js.map +1 -0
- package/lib/commonjs/menu/index.web.js +15 -11
- package/lib/commonjs/menu/index.web.js.map +1 -1
- package/lib/commonjs/overlay/index.js +133 -7
- package/lib/commonjs/overlay/index.js.map +1 -1
- package/lib/commonjs/overlay/stack.js +170 -0
- package/lib/commonjs/overlay/stack.js.map +1 -0
- package/lib/commonjs/overlay/use-overlay-layer.js +37 -0
- package/lib/commonjs/overlay/use-overlay-layer.js.map +1 -0
- package/lib/commonjs/popover/index.web.js +31 -34
- package/lib/commonjs/popover/index.web.js.map +1 -1
- package/lib/commonjs/prompt-input/PromptInput.js +33 -29
- package/lib/commonjs/prompt-input/PromptInput.js.map +1 -1
- package/lib/commonjs/select/index.web.js +20 -22
- package/lib/commonjs/select/index.web.js.map +1 -1
- package/lib/commonjs/styles/z-index.js +33 -34
- package/lib/commonjs/styles/z-index.js.map +1 -1
- package/lib/commonjs/surfaces/SurfaceHost.js +3 -6
- package/lib/commonjs/surfaces/SurfaceHost.js.map +1 -1
- package/lib/commonjs/toast/ToastHost.js +9 -5
- package/lib/commonjs/toast/ToastHost.js.map +1 -1
- package/lib/commonjs/tooltip/index.js +9 -6
- package/lib/commonjs/tooltip/index.js.map +1 -1
- package/lib/commonjs/tooltip/index.web.js +8 -2
- package/lib/commonjs/tooltip/index.web.js.map +1 -1
- package/lib/commonjs/zoomable-image-gallery/ZoomableImageGallery.js +5 -5
- package/lib/commonjs/zoomable-image-gallery/ZoomableImageGallery.js.map +1 -1
- package/lib/module/avatar-group/AvatarGroup.web.js +21 -20
- package/lib/module/avatar-group/AvatarGroup.web.js.map +1 -1
- package/lib/module/bottom-sheet/index.web.js +11 -6
- package/lib/module/bottom-sheet/index.web.js.map +1 -1
- package/lib/module/context-menu/index.web.js +21 -23
- package/lib/module/context-menu/index.web.js.map +1 -1
- package/lib/module/dialog/Dialog.js +67 -71
- package/lib/module/dialog/Dialog.js.map +1 -1
- package/lib/module/dialog/Dialog.web.js +50 -52
- package/lib/module/dialog/Dialog.web.js.map +1 -1
- package/lib/module/fonts/font-data.web.js +18 -0
- package/lib/module/fonts/font-data.web.js.map +1 -0
- package/lib/module/menu/index.web.js +16 -12
- package/lib/module/menu/index.web.js.map +1 -1
- package/lib/module/overlay/index.js +99 -8
- package/lib/module/overlay/index.js.map +1 -1
- package/lib/module/overlay/stack.js +161 -0
- package/lib/module/overlay/stack.js.map +1 -0
- package/lib/module/overlay/use-overlay-layer.js +33 -0
- package/lib/module/overlay/use-overlay-layer.js.map +1 -0
- package/lib/module/popover/index.web.js +32 -34
- package/lib/module/popover/index.web.js.map +1 -1
- package/lib/module/prompt-input/PromptInput.js +33 -29
- package/lib/module/prompt-input/PromptInput.js.map +1 -1
- package/lib/module/select/index.web.js +21 -23
- package/lib/module/select/index.web.js.map +1 -1
- package/lib/module/styles/z-index.js +32 -30
- package/lib/module/styles/z-index.js.map +1 -1
- package/lib/module/surfaces/SurfaceHost.js +3 -6
- package/lib/module/surfaces/SurfaceHost.js.map +1 -1
- package/lib/module/toast/ToastHost.js +10 -6
- package/lib/module/toast/ToastHost.js.map +1 -1
- package/lib/module/tooltip/index.js +9 -6
- package/lib/module/tooltip/index.js.map +1 -1
- package/lib/module/tooltip/index.web.js +8 -2
- package/lib/module/tooltip/index.web.js.map +1 -1
- package/lib/module/zoomable-image-gallery/ZoomableImageGallery.js +5 -5
- package/lib/module/zoomable-image-gallery/ZoomableImageGallery.js.map +1 -1
- package/lib/typescript/commonjs/avatar-group/AvatarGroup.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/bottom-sheet/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/context-menu/index.web.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/types.d.ts +0 -10
- package/lib/typescript/commonjs/dialog/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/fonts/font-data.web.d.ts +5 -0
- package/lib/typescript/commonjs/fonts/font-data.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/menu/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/overlay/index.d.ts +31 -6
- package/lib/typescript/commonjs/overlay/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/overlay/stack.d.ts +122 -0
- package/lib/typescript/commonjs/overlay/stack.d.ts.map +1 -0
- package/lib/typescript/commonjs/overlay/use-overlay-layer.d.ts +7 -0
- package/lib/typescript/commonjs/overlay/use-overlay-layer.d.ts.map +1 -0
- package/lib/typescript/commonjs/popover/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/prompt-input/PromptInput.d.ts.map +1 -1
- package/lib/typescript/commonjs/select/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/styles/z-index.d.ts +42 -29
- package/lib/typescript/commonjs/styles/z-index.d.ts.map +1 -1
- package/lib/typescript/commonjs/surfaces/SurfaceHost.d.ts.map +1 -1
- package/lib/typescript/commonjs/toast/ToastHost.d.ts.map +1 -1
- package/lib/typescript/commonjs/tooltip/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/zoomable-image-gallery/ZoomableImageGallery.d.ts.map +1 -1
- package/lib/typescript/module/avatar-group/AvatarGroup.web.d.ts.map +1 -1
- package/lib/typescript/module/bottom-sheet/index.web.d.ts.map +1 -1
- package/lib/typescript/module/context-menu/index.web.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/types.d.ts +0 -10
- package/lib/typescript/module/dialog/types.d.ts.map +1 -1
- package/lib/typescript/module/fonts/font-data.web.d.ts +5 -0
- package/lib/typescript/module/fonts/font-data.web.d.ts.map +1 -0
- package/lib/typescript/module/menu/index.web.d.ts.map +1 -1
- package/lib/typescript/module/overlay/index.d.ts +31 -6
- package/lib/typescript/module/overlay/index.d.ts.map +1 -1
- package/lib/typescript/module/overlay/stack.d.ts +122 -0
- package/lib/typescript/module/overlay/stack.d.ts.map +1 -0
- package/lib/typescript/module/overlay/use-overlay-layer.d.ts +7 -0
- package/lib/typescript/module/overlay/use-overlay-layer.d.ts.map +1 -0
- package/lib/typescript/module/popover/index.web.d.ts.map +1 -1
- package/lib/typescript/module/prompt-input/PromptInput.d.ts.map +1 -1
- package/lib/typescript/module/select/index.web.d.ts.map +1 -1
- package/lib/typescript/module/styles/z-index.d.ts +42 -29
- package/lib/typescript/module/styles/z-index.d.ts.map +1 -1
- package/lib/typescript/module/surfaces/SurfaceHost.d.ts.map +1 -1
- package/lib/typescript/module/toast/ToastHost.d.ts.map +1 -1
- package/lib/typescript/module/tooltip/index.d.ts.map +1 -1
- package/lib/typescript/module/zoomable-image-gallery/ZoomableImageGallery.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/__tests__/ToastHostWebFork.test.ts +11 -3
- package/src/__tests__/overlay-stack-order.test.tsx +270 -0
- package/src/avatar-group/AvatarGroup.web.tsx +35 -31
- package/src/bottom-sheet/index.web.tsx +10 -5
- package/src/context-menu/index.web.tsx +33 -30
- package/src/dialog/Dialog.tsx +9 -18
- package/src/dialog/Dialog.web.tsx +62 -60
- package/src/dialog/types.ts +0 -10
- package/src/fonts/font-data.web.ts +15 -0
- package/src/menu/index.web.tsx +33 -22
- package/src/overlay/OverlayStacking.stories.tsx +207 -0
- package/src/overlay/index.tsx +114 -9
- package/src/overlay/stack.ts +166 -0
- package/src/overlay/use-overlay-layer.ts +39 -0
- package/src/popover/index.web.tsx +47 -44
- package/src/prompt-input/PromptInput.tsx +36 -29
- package/src/select/index.web.tsx +35 -32
- package/src/styles/z-index.ts +32 -34
- package/src/surfaces/SurfaceHost.tsx +3 -10
- package/src/toast/ToastHost.tsx +9 -6
- package/src/tooltip/index.tsx +14 -7
- package/src/tooltip/index.web.tsx +8 -2
- package/src/zoomable-image-gallery/ZoomableImageGallery.tsx +4 -4
|
@@ -1,49 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Stacking scale for stacking WITHIN a single context.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* ## What belongs here, and what does not
|
|
5
|
+
*
|
|
6
|
+
* These values order elements against their own siblings — a badge over its
|
|
7
|
+
* avatar, a floating control over the panel it sits on, a sheet's drag handle
|
|
8
|
+
* over the sheet body. That is a local decision with a local answer, so a
|
|
9
|
+
* constant is the right tool.
|
|
10
|
+
*
|
|
11
|
+
* **Which OVERLAY SURFACE is on top of which is NOT on this scale**, and must
|
|
12
|
+
* never be added back to it. That question is answered by open order — the
|
|
13
|
+
* surface the user opened last wins — and is owned by `src/overlay/stack.ts`.
|
|
14
|
+
*
|
|
15
|
+
* This file used to carry rungs for that too (`dropdown` 40/41, `overlay`
|
|
16
|
+
* 50/60, `tooltip` 70, `toast` 80, `fullscreen` 90/91), plus
|
|
17
|
+
* `createOverlayZIndex` / `createDropdownZIndex` / `Z_INDEX_LAYER_STEP` to
|
|
18
|
+
* offset them. Because a rung is fixed per COMPONENT KIND, it decided stacking
|
|
19
|
+
* by what a surface WAS rather than by when it opened, so some pairings were
|
|
20
|
+
* permanently inverted: a confirm dialog (50/60) opened from inside an open
|
|
21
|
+
* bottom sheet (999999) rendered underneath it and could not be pressed or
|
|
22
|
+
* dismissed, and a menu (40/41) opened from inside a dialog landed behind that
|
|
23
|
+
* dialog. Those rungs and helpers are gone; reintroducing one reintroduces the
|
|
24
|
+
* bug.
|
|
7
25
|
*/
|
|
8
26
|
export declare const Z_INDEX: {
|
|
27
|
+
/** Default resting level. */
|
|
9
28
|
readonly base: 0;
|
|
29
|
+
/** One step above an immediate sibling — a badge over its avatar. */
|
|
10
30
|
readonly raised: 1;
|
|
31
|
+
/** A control floating over the content it belongs to. */
|
|
11
32
|
readonly floating: 10;
|
|
12
|
-
|
|
33
|
+
/** A dropdown's INLINE trigger wrapper, still in the app's document flow. */
|
|
13
34
|
readonly dropdown: 40;
|
|
14
|
-
|
|
15
|
-
readonly overlayBackdrop: 50;
|
|
16
|
-
readonly overlaySurface: 60;
|
|
17
|
-
readonly tooltip: 70;
|
|
18
|
-
readonly toast: 80;
|
|
19
|
-
readonly fullscreen: 90;
|
|
20
|
-
readonly fullscreenControl: 91;
|
|
35
|
+
/** A bottom sheet's drag handle, over the sheet's own body. */
|
|
21
36
|
readonly sheetHandle: 100;
|
|
37
|
+
/**
|
|
38
|
+
* The document-level portal container itself (`#bloom-portal-root`). Overlay
|
|
39
|
+
* surfaces render INSIDE it and order among themselves by open order — none
|
|
40
|
+
* of them may claim this value, which is what made the sheet unbeatable.
|
|
41
|
+
*/
|
|
22
42
|
readonly portalRoot: 999999;
|
|
23
43
|
};
|
|
24
|
-
export declare const Z_INDEX_LAYER_STEP = 10;
|
|
25
|
-
export declare function createDropdownZIndex(layer?: number): {
|
|
26
|
-
readonly root: number;
|
|
27
|
-
readonly surface: number;
|
|
28
|
-
};
|
|
29
|
-
export declare function createOverlayZIndex(layer?: number): {
|
|
30
|
-
readonly backdrop: number;
|
|
31
|
-
readonly surface: number;
|
|
32
|
-
};
|
|
33
44
|
export declare const zIndex: {
|
|
45
|
+
/** Default resting level. */
|
|
34
46
|
readonly base: 0;
|
|
47
|
+
/** One step above an immediate sibling — a badge over its avatar. */
|
|
35
48
|
readonly raised: 1;
|
|
49
|
+
/** A control floating over the content it belongs to. */
|
|
36
50
|
readonly floating: 10;
|
|
37
|
-
|
|
51
|
+
/** A dropdown's INLINE trigger wrapper, still in the app's document flow. */
|
|
38
52
|
readonly dropdown: 40;
|
|
39
|
-
|
|
40
|
-
readonly overlayBackdrop: 50;
|
|
41
|
-
readonly overlaySurface: 60;
|
|
42
|
-
readonly tooltip: 70;
|
|
43
|
-
readonly toast: 80;
|
|
44
|
-
readonly fullscreen: 90;
|
|
45
|
-
readonly fullscreenControl: 91;
|
|
53
|
+
/** A bottom sheet's drag handle, over the sheet's own body. */
|
|
46
54
|
readonly sheetHandle: 100;
|
|
55
|
+
/**
|
|
56
|
+
* The document-level portal container itself (`#bloom-portal-root`). Overlay
|
|
57
|
+
* surfaces render INSIDE it and order among themselves by open order — none
|
|
58
|
+
* of them may claim this value, which is what made the sheet unbeatable.
|
|
59
|
+
*/
|
|
47
60
|
readonly portalRoot: 999999;
|
|
48
61
|
};
|
|
49
62
|
//# sourceMappingURL=z-index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"z-index.d.ts","sourceRoot":"","sources":["../../../../src/styles/z-index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"z-index.d.ts","sourceRoot":"","sources":["../../../../src/styles/z-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,OAAO;IAClB,6BAA6B;;IAE7B,qEAAqE;;IAErE,yDAAyD;;IAEzD,6EAA6E;;IAE7E,+DAA+D;;IAE/D;;;;OAIG;;CAEK,CAAC;AAEX,eAAO,MAAM,MAAM;IAlBjB,6BAA6B;;IAE7B,qEAAqE;;IAErE,yDAAyD;;IAEzD,6EAA6E;;IAE7E,+DAA+D;;IAE/D;;;;OAIG;;CAIwB,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;AAgCxD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe;;;;;;
|
|
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;;;;;;uBA8FhB;YAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;SAAE;;;EAcrE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToastHost.d.ts","sourceRoot":"","sources":["../../../../src/toast/ToastHost.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ToastHost.d.ts","sourceRoot":"","sources":["../../../../src/toast/ToastHost.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,qBAAqB,EAAE,EAAE,cAAc,2CAwB5E;yBAxBe,SAAS"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/tooltip/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/tooltip/index.tsx"],"names":[],"mappings":"AAqEA;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,EACzC,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,2CAEA;yBANe,0BAA0B;;;AAS1C,wBAAgB,OAAO,CAAC,EACtB,QAAQ,EACR,QAAmB,EACnB,OAAO,EAAE,cAAc,EACvB,eAAe,GAChB,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CAC7C,2CAsCA;AAED,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,2CAuBzE;AAED,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;CACf,kDA0BA;AAgND,eAAO,MAAM,iBAAiB;;6CAAmC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ZoomableImageGallery.d.ts","sourceRoot":"","sources":["../../../../src/zoomable-image-gallery/ZoomableImageGallery.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6E,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ZoomableImageGallery.d.ts","sourceRoot":"","sources":["../../../../src/zoomable-image-gallery/ZoomableImageGallery.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6E,MAAM,OAAO,CAAC;AAmElG,OAAO,KAAK,EAGV,0BAA0B,EAC1B,yBAAyB,EAC1B,MAAM,SAAS,CAAC;AA87BjB,eAAO,MAAM,oBAAoB,8GAA4B,CAAC;AA0K9D,eAAe,oBAAoB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/bloom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.73.0",
|
|
4
|
+
"packageManager": "bun@1.3.14",
|
|
4
5
|
"description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
|
|
5
6
|
"main": "lib/commonjs/index.js",
|
|
6
7
|
"module": "lib/module/index.js",
|
|
@@ -52,9 +52,17 @@ describe('ToastHost platform split', () => {
|
|
|
52
52
|
expect(host).not.toMatch(/'fixed' as/);
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
it('web/default host
|
|
56
|
-
const host =
|
|
57
|
-
|
|
55
|
+
it('web/default host pins toasts above the whole overlay stack', () => {
|
|
56
|
+
const host = code('toast/ToastHost.tsx');
|
|
57
|
+
// A toast is a notification, not a modal surface: it must stay visible over
|
|
58
|
+
// whatever is open, including a surface opened AFTER it. So it opts out of
|
|
59
|
+
// the open-order stack via `OverlayRoot`'s `zIndex` prop rather than taking
|
|
60
|
+
// a rank (see `src/overlay/stack.ts`).
|
|
61
|
+
expect(host).toMatch(/<OverlayRoot zIndex=\{TOAST_LAYER_Z\}/);
|
|
62
|
+
// And it must not ALSO carry a hand-picked depth in its stylesheet: a fixed
|
|
63
|
+
// number there is exactly what left toasts stranded behind an open bottom
|
|
64
|
+
// sheet, and it would fight the prop above.
|
|
65
|
+
expect(host).not.toMatch(/zIndex: Z_INDEX\./);
|
|
58
66
|
});
|
|
59
67
|
|
|
60
68
|
it('native host imports neither react-native-screens nor the portal (D6)', () => {
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The overlay stacking rule: a surface opened LATER paints above one opened
|
|
7
|
+
* earlier.
|
|
8
|
+
*
|
|
9
|
+
* Jest cannot see the bug this guards — the losing surface renders perfectly
|
|
10
|
+
* and its styles are all valid; it is simply painted underneath. So these tests
|
|
11
|
+
* pin the two halves Jest CAN decide:
|
|
12
|
+
*
|
|
13
|
+
* 1. the authority in `src/overlay/stack.ts` hands out ascending depths, and
|
|
14
|
+
* keeps doing so across the interleavings that actually occur;
|
|
15
|
+
* 2. every overlay surface in `src/` gets its depth FROM that authority
|
|
16
|
+
* rather than from a constant of its own — the property whose absence
|
|
17
|
+
* caused the bug in the first place.
|
|
18
|
+
*
|
|
19
|
+
* The hit test that proves the user-visible outcome lives in
|
|
20
|
+
* `scripts/verify-overlay-stacking.mjs`, driven against a real browser.
|
|
21
|
+
*/
|
|
22
|
+
import { readFileSync } from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
|
|
25
|
+
import { createElement } from 'react';
|
|
26
|
+
import { act } from 'react';
|
|
27
|
+
import { createRoot, type Root } from 'react-dom/client';
|
|
28
|
+
|
|
29
|
+
// The point of the DOM half is what react-native-web actually emits, so it runs
|
|
30
|
+
// against the REAL RNW — the repo-wide `react-native` mock renders no host
|
|
31
|
+
// elements and would make every depth assertion below vacuous.
|
|
32
|
+
jest.mock('react-native', () => jest.requireActual('react-native-web'));
|
|
33
|
+
|
|
34
|
+
import { OverlayRoot } from '../overlay';
|
|
35
|
+
import {
|
|
36
|
+
acquireOverlayRank,
|
|
37
|
+
layerForRank,
|
|
38
|
+
OVERLAY_STACK_BASE,
|
|
39
|
+
OVERLAY_STACK_MAX_RANK,
|
|
40
|
+
registerOverlayRank,
|
|
41
|
+
releaseOverlayRank,
|
|
42
|
+
resetOverlayStack,
|
|
43
|
+
TOAST_LAYER_Z,
|
|
44
|
+
} from '../overlay/stack';
|
|
45
|
+
|
|
46
|
+
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
resetOverlayStack();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('the stacking authority', () => {
|
|
53
|
+
it('gives a later opener a higher depth than an earlier one', () => {
|
|
54
|
+
const first = layerForRank(acquireOverlayRank());
|
|
55
|
+
const second = layerForRank(acquireOverlayRank());
|
|
56
|
+
const third = layerForRank(acquireOverlayRank());
|
|
57
|
+
|
|
58
|
+
expect(second.root).toBeGreaterThan(first.root);
|
|
59
|
+
expect(third.root).toBeGreaterThan(second.root);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('puts a surface above its OWN backdrop without reaching the next surface', () => {
|
|
63
|
+
const lower = layerForRank(acquireOverlayRank());
|
|
64
|
+
const upper = layerForRank(acquireOverlayRank());
|
|
65
|
+
|
|
66
|
+
expect(lower.surface).toBeGreaterThan(lower.backdrop);
|
|
67
|
+
// The band is what keeps a surface's own parts from colliding with the
|
|
68
|
+
// floor of the surface above it.
|
|
69
|
+
expect(lower.surface).toBeLessThan(upper.backdrop);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('keeps ordering when surfaces close out of order', () => {
|
|
73
|
+
// A opens, B opens, A closes, C opens. C is newest and must outrank B.
|
|
74
|
+
const a = acquireOverlayRank();
|
|
75
|
+
registerOverlayRank(a);
|
|
76
|
+
const b = acquireOverlayRank();
|
|
77
|
+
registerOverlayRank(b);
|
|
78
|
+
|
|
79
|
+
releaseOverlayRank(a);
|
|
80
|
+
|
|
81
|
+
const c = acquireOverlayRank();
|
|
82
|
+
registerOverlayRank(c);
|
|
83
|
+
|
|
84
|
+
expect(layerForRank(c).root).toBeGreaterThan(layerForRank(b).root);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('starts over once every surface has closed, so depths stay small', () => {
|
|
88
|
+
const a = acquireOverlayRank();
|
|
89
|
+
registerOverlayRank(a);
|
|
90
|
+
releaseOverlayRank(a);
|
|
91
|
+
|
|
92
|
+
expect(layerForRank(acquireOverlayRank()).root).toBe(layerForRank(a).root);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('cannot lose a rank handed out but not yet registered', () => {
|
|
96
|
+
// The one interleaving the reset could otherwise break: a surface takes a
|
|
97
|
+
// rank during render, and before its effect registers it the last open
|
|
98
|
+
// surface unmounts and resets the counter. The newcomer must still land
|
|
99
|
+
// ABOVE the one that is on its way up.
|
|
100
|
+
const open = acquireOverlayRank();
|
|
101
|
+
registerOverlayRank(open);
|
|
102
|
+
|
|
103
|
+
const rendering = acquireOverlayRank(); // taken, effect not run yet
|
|
104
|
+
releaseOverlayRank(open); // live set empties -> reset
|
|
105
|
+
registerOverlayRank(rendering); // effect finally runs
|
|
106
|
+
|
|
107
|
+
const next = acquireOverlayRank();
|
|
108
|
+
expect(next).toBeGreaterThan(rendering);
|
|
109
|
+
expect(layerForRank(next).root).toBeGreaterThan(layerForRank(rendering).root);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('saturates instead of climbing into the toast layer', () => {
|
|
113
|
+
const runaway = layerForRank(OVERLAY_STACK_MAX_RANK + 5_000);
|
|
114
|
+
expect(runaway.surface).toBeLessThan(TOAST_LAYER_Z);
|
|
115
|
+
expect(runaway.root).toBe(layerForRank(OVERLAY_STACK_MAX_RANK).root);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('keeps every rank below the toast layer', () => {
|
|
119
|
+
// Toasts are notifications, not modal surfaces: they stay visible over
|
|
120
|
+
// whatever is open, including a surface opened after them.
|
|
121
|
+
expect(layerForRank(1).root).toBeGreaterThanOrEqual(OVERLAY_STACK_BASE);
|
|
122
|
+
expect(layerForRank(OVERLAY_STACK_MAX_RANK).surface).toBeLessThan(TOAST_LAYER_Z);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('OverlayRoot', () => {
|
|
127
|
+
function render(node: ReturnType<typeof createElement>): {
|
|
128
|
+
root: Root;
|
|
129
|
+
container: HTMLElement;
|
|
130
|
+
} {
|
|
131
|
+
const container = document.createElement('div');
|
|
132
|
+
document.body.appendChild(container);
|
|
133
|
+
const root = createRoot(container);
|
|
134
|
+
act(() => {
|
|
135
|
+
root.render(node);
|
|
136
|
+
});
|
|
137
|
+
return { root, container };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const depthOf = (container: HTMLElement, testID: string) => {
|
|
141
|
+
const el = container.querySelector<HTMLElement>(`[data-testid="${testID}"]`);
|
|
142
|
+
if (!el) throw new Error(`no element with testID ${testID}`);
|
|
143
|
+
return Number(getComputedStyle(el).zIndex);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
it('paints a surface mounted later above one mounted earlier', () => {
|
|
147
|
+
const first = render(createElement(OverlayRoot, { testID: 'first', children: null }));
|
|
148
|
+
const second = render(createElement(OverlayRoot, { testID: 'second', children: null }));
|
|
149
|
+
|
|
150
|
+
const firstZ = depthOf(first.container, 'first');
|
|
151
|
+
const secondZ = depthOf(second.container, 'second');
|
|
152
|
+
|
|
153
|
+
// Vacuity floor: both must have emitted a real depth, or "second > first"
|
|
154
|
+
// would be comparing two NaNs into a silent pass.
|
|
155
|
+
expect(Number.isFinite(firstZ)).toBe(true);
|
|
156
|
+
expect(Number.isFinite(secondZ)).toBe(true);
|
|
157
|
+
expect(secondZ).toBeGreaterThan(firstZ);
|
|
158
|
+
|
|
159
|
+
act(() => {
|
|
160
|
+
first.root.unmount();
|
|
161
|
+
second.root.unmount();
|
|
162
|
+
});
|
|
163
|
+
first.container.remove();
|
|
164
|
+
second.container.remove();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('pins to the given depth, and takes no rank, when told to', () => {
|
|
168
|
+
const pinned = render(
|
|
169
|
+
createElement(OverlayRoot, { testID: 'pinned', zIndex: TOAST_LAYER_Z, children: null }),
|
|
170
|
+
);
|
|
171
|
+
expect(depthOf(pinned.container, 'pinned')).toBe(TOAST_LAYER_Z);
|
|
172
|
+
|
|
173
|
+
// Having opted out, it must not have consumed a rank either — the next
|
|
174
|
+
// surface to open is still the FIRST one in the stack.
|
|
175
|
+
const ranked = render(createElement(OverlayRoot, { testID: 'ranked', children: null }));
|
|
176
|
+
expect(depthOf(ranked.container, 'ranked')).toBe(layerForRank(1).root);
|
|
177
|
+
|
|
178
|
+
act(() => {
|
|
179
|
+
pinned.root.unmount();
|
|
180
|
+
ranked.root.unmount();
|
|
181
|
+
});
|
|
182
|
+
pinned.container.remove();
|
|
183
|
+
ranked.container.remove();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('no surface picks its own depth', () => {
|
|
188
|
+
const SRC = path.join(__dirname, '..');
|
|
189
|
+
const read = (rel: string) => readFileSync(path.join(SRC, rel), 'utf8');
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Every file in `src/` that presents an overlay surface. Enumerated
|
|
193
|
+
* explicitly rather than globbed: a glob that silently stops matching is the
|
|
194
|
+
* shape of check that passes forever while covering nothing.
|
|
195
|
+
*/
|
|
196
|
+
const SURFACES = [
|
|
197
|
+
'dialog/Dialog.tsx',
|
|
198
|
+
'dialog/Dialog.web.tsx',
|
|
199
|
+
'bottom-sheet/index.web.tsx',
|
|
200
|
+
'menu/index.web.tsx',
|
|
201
|
+
'select/index.web.tsx',
|
|
202
|
+
'context-menu/index.web.tsx',
|
|
203
|
+
'popover/index.web.tsx',
|
|
204
|
+
'tooltip/index.tsx',
|
|
205
|
+
'zoomable-image-gallery/ZoomableImageGallery.tsx',
|
|
206
|
+
'prompt-input/PromptInput.tsx',
|
|
207
|
+
'avatar-group/AvatarGroup.web.tsx',
|
|
208
|
+
'toast/ToastHost.tsx',
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
/** Strips comments, so prose ABOUT the old constants never trips the scan. */
|
|
212
|
+
const code = (rel: string) =>
|
|
213
|
+
read(rel)
|
|
214
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
215
|
+
.replace(/^\s*\/\/.*$/gm, '')
|
|
216
|
+
.replace(/\{\s*\/\*[\s\S]*?\*\/\s*\}/g, '');
|
|
217
|
+
|
|
218
|
+
it.each(SURFACES)('%s claims no overlay-ordering constant', (rel) => {
|
|
219
|
+
const body = code(rel);
|
|
220
|
+
|
|
221
|
+
// The rungs that used to decide which SURFACE was on top. Their absence is
|
|
222
|
+
// the property that makes open order the only authority.
|
|
223
|
+
expect(body).not.toMatch(
|
|
224
|
+
/Z_INDEX\.(overlayBackdrop|overlaySurface|dropdownSurface|tooltip|toast|fullscreen|fullscreenControl|portalRoot)\b/,
|
|
225
|
+
);
|
|
226
|
+
expect(body).not.toMatch(/createOverlayZIndex|createDropdownZIndex|Z_INDEX_LAYER_STEP/);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('the scale no longer offers an overlay-ordering rung to reach for', () => {
|
|
230
|
+
const scale = code('styles/z-index.ts');
|
|
231
|
+
for (const gone of [
|
|
232
|
+
'overlayBackdrop',
|
|
233
|
+
'overlaySurface',
|
|
234
|
+
'dropdownSurface',
|
|
235
|
+
'tooltip',
|
|
236
|
+
'toast',
|
|
237
|
+
'fullscreen',
|
|
238
|
+
'fullscreenControl',
|
|
239
|
+
'createOverlayZIndex',
|
|
240
|
+
'createDropdownZIndex',
|
|
241
|
+
'Z_INDEX_LAYER_STEP',
|
|
242
|
+
]) {
|
|
243
|
+
expect(scale).not.toContain(gone);
|
|
244
|
+
}
|
|
245
|
+
// Vacuity floor: the module must still export the within-context scale, or
|
|
246
|
+
// the loop above would pass against an empty file.
|
|
247
|
+
expect(scale).toMatch(/export const Z_INDEX = \{/);
|
|
248
|
+
expect(scale).toMatch(/portalRoot: 999999/);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('the WEB tooltip is deliberately NOT in this list', () => {
|
|
252
|
+
// It is the one tooltip that is not portaled: the bubble is absolutely
|
|
253
|
+
// positioned against its own trigger and renders inline, so it cannot be
|
|
254
|
+
// lifted over another surface and has no rank to take. The NATIVE tooltip
|
|
255
|
+
// does portal, and IS in the list above. If this ever starts portaling,
|
|
256
|
+
// move it into `SURFACES`.
|
|
257
|
+
expect(code('tooltip/index.web.tsx')).not.toMatch(/from '\.\.\/portal/);
|
|
258
|
+
expect(code('tooltip/index.tsx')).toMatch(/from '\.\.\/portal/);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('every surface routes its depth through OverlayRoot', () => {
|
|
262
|
+
// `Dialog.web`'s side placement renders `SheetSurface`, which mounts its
|
|
263
|
+
// own `OverlayRoot`; the centred branch mounts one directly. Both files are
|
|
264
|
+
// covered by the membership check below.
|
|
265
|
+
const missing = SURFACES.filter((rel) => !code(rel).includes('OverlayRoot'));
|
|
266
|
+
expect(missing).toEqual([]);
|
|
267
|
+
// Vacuity floor on the list itself.
|
|
268
|
+
expect(SURFACES.length).toBeGreaterThanOrEqual(12);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { memo, useCallback, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { Pressable, StyleSheet, View } from 'react-native';
|
|
3
3
|
|
|
4
|
+
import { OverlayRoot } from '../overlay';
|
|
4
5
|
import { Portal } from '../portal';
|
|
5
|
-
import { Z_INDEX } from '../styles/z-index';
|
|
6
6
|
import { WEB_POSITION_FIXED } from '../styles/web-view-style';
|
|
7
7
|
import { UserHoverCard } from '../user-hover-card';
|
|
8
8
|
import {
|
|
@@ -97,35 +97,40 @@ const AvatarGroupWebComponent: React.FC<AvatarGroupProps> = (props) => {
|
|
|
97
97
|
<AvatarGroupBase {...props} hoverHandlers={hoverHandlers} />
|
|
98
98
|
{hoverCard && hover && (
|
|
99
99
|
<Portal>
|
|
100
|
-
{/*
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
100
|
+
{/* The hover card stacks by when it opened, like every other Bloom
|
|
101
|
+
overlay (`src/overlay/stack.ts`). `box-none` keeps the area
|
|
102
|
+
around it click-through. */}
|
|
103
|
+
<OverlayRoot>
|
|
104
|
+
{/*
|
|
105
|
+
A Pressable hosts the hover-bridge: `onHoverIn`/`onHoverOut` are
|
|
106
|
+
typed in React Native (and map to mouseenter/mouseleave on
|
|
107
|
+
react-native-web), so the card stays open while the cursor is over
|
|
108
|
+
it without any web-only DOM-prop type suppression.
|
|
109
|
+
*/}
|
|
110
|
+
<Pressable
|
|
111
|
+
onHoverIn={cancelClose}
|
|
112
|
+
onHoverOut={scheduleClose}
|
|
113
|
+
accessibilityRole="none"
|
|
114
|
+
pointerEvents="auto"
|
|
115
|
+
style={[
|
|
116
|
+
webStyles.floating,
|
|
117
|
+
{ top: hover.top, left: hover.left, width: CARD_WIDTH },
|
|
118
|
+
]}
|
|
119
|
+
>
|
|
120
|
+
<UserHoverCard
|
|
121
|
+
avatar={hover.item.uri ?? undefined}
|
|
122
|
+
displayName={getItemName(hover.item)}
|
|
123
|
+
username={hover.item.username}
|
|
124
|
+
onPressProfile={
|
|
125
|
+
onPressItem
|
|
126
|
+
? () => onPressItem(hover.item, hover.index)
|
|
127
|
+
: undefined
|
|
128
|
+
}
|
|
129
|
+
action={renderItemAction?.(hover.item, hover.index)}
|
|
130
|
+
style={webStyles.card}
|
|
131
|
+
/>
|
|
132
|
+
</Pressable>
|
|
133
|
+
</OverlayRoot>
|
|
129
134
|
</Portal>
|
|
130
135
|
)}
|
|
131
136
|
</>
|
|
@@ -135,7 +140,6 @@ const AvatarGroupWebComponent: React.FC<AvatarGroupProps> = (props) => {
|
|
|
135
140
|
const webStyles = StyleSheet.create({
|
|
136
141
|
floating: {
|
|
137
142
|
position: WEB_POSITION_FIXED,
|
|
138
|
-
zIndex: Z_INDEX.tooltip,
|
|
139
143
|
},
|
|
140
144
|
card: {
|
|
141
145
|
width: CARD_WIDTH,
|
|
@@ -4,7 +4,6 @@ import { StyleSheet } from 'react-native';
|
|
|
4
4
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
5
5
|
import { OverlayRoot } from '../overlay';
|
|
6
6
|
import { Portal } from '../portal/index.web';
|
|
7
|
-
import { Z_INDEX } from '../styles/z-index';
|
|
8
7
|
import { WEB_POSITION_FIXED } from '../styles/web-view-style';
|
|
9
8
|
import {
|
|
10
9
|
BottomSheetBase,
|
|
@@ -54,16 +53,22 @@ const BottomSheet = forwardRef((props: BottomSheetProps, ref: React.ForwardedRef
|
|
|
54
53
|
BottomSheet.displayName = 'BottomSheet';
|
|
55
54
|
|
|
56
55
|
const webStyles = StyleSheet.create({
|
|
56
|
+
// Fixed, full-viewport box the sheet's `StyleSheet.absoluteFill` body
|
|
57
|
+
// anchors to. The pointer-events opt-in and the STACKING both live in
|
|
58
|
+
// `OverlayRoot`.
|
|
59
|
+
//
|
|
60
|
+
// This used to also pin `zIndex: Z_INDEX.portalRoot` (999999) here. Inside
|
|
61
|
+
// the portal root that number does not mean "the portal layer" — it means
|
|
62
|
+
// "above every other portaled surface", which is how a confirm dialog
|
|
63
|
+
// opened from inside an open sheet ended up rendering underneath it,
|
|
64
|
+
// unreachable, no matter which one opened last. Stacking is the overlay
|
|
65
|
+
// stack's job now (`src/overlay/stack.ts`); do not reintroduce a constant.
|
|
57
66
|
rootView: {
|
|
58
|
-
// Fixed, full-viewport box the sheet's `StyleSheet.absoluteFill` body
|
|
59
|
-
// anchors to. The pointer-events opt-in lives in `OverlayRoot`; this
|
|
60
|
-
// only adds the stacking context.
|
|
61
67
|
position: WEB_POSITION_FIXED,
|
|
62
68
|
top: 0,
|
|
63
69
|
left: 0,
|
|
64
70
|
right: 0,
|
|
65
71
|
bottom: 0,
|
|
66
|
-
zIndex: Z_INDEX.portalRoot,
|
|
67
72
|
},
|
|
68
73
|
});
|
|
69
74
|
|
|
@@ -18,10 +18,9 @@ import { Pressable, StyleSheet, View } from 'react-native';
|
|
|
18
18
|
|
|
19
19
|
import { useTheme } from '../theme/use-theme';
|
|
20
20
|
import { Text } from '../typography';
|
|
21
|
-
import { Backdrop } from '../overlay';
|
|
21
|
+
import { Backdrop, OverlayRoot } from '../overlay';
|
|
22
22
|
import { Portal } from '../portal/index.web';
|
|
23
23
|
import { useInteractionState } from '../hooks/useInteractionState';
|
|
24
|
-
import { createOverlayZIndex } from '../styles/z-index';
|
|
25
24
|
import { WEB_POSITION_FIXED } from '../styles/web-view-style';
|
|
26
25
|
import {
|
|
27
26
|
resolveDropdownPlacement,
|
|
@@ -40,7 +39,6 @@ import type {
|
|
|
40
39
|
ItemContextValue,
|
|
41
40
|
} from './types';
|
|
42
41
|
|
|
43
|
-
const contextMenuZIndex = createOverlayZIndex();
|
|
44
42
|
const VIEWPORT_GUTTER = 8;
|
|
45
43
|
|
|
46
44
|
// ---------------------------------------------------------------------------
|
|
@@ -211,31 +209,38 @@ export function ContextMenuContent({ children, style }: ContextMenuContentProps)
|
|
|
211
209
|
|
|
212
210
|
return (
|
|
213
211
|
<Portal>
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
<
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
212
|
+
{/* `OverlayRoot` takes this surface's place in the open-order overlay
|
|
213
|
+
stack, so it paints above anything opened before it (see
|
|
214
|
+
`src/overlay/stack.ts`). It is `box-none`, so the area outside the
|
|
215
|
+
panel stays click-through and the backdrop below still takes its own
|
|
216
|
+
presses. */}
|
|
217
|
+
<OverlayRoot>
|
|
218
|
+
<Backdrop
|
|
219
|
+
style={styles.backdrop}
|
|
220
|
+
onPress={close}
|
|
221
|
+
accessibilityLabel="Close context menu"
|
|
222
|
+
/>
|
|
223
|
+
<View
|
|
224
|
+
ref={attachMenu}
|
|
225
|
+
style={[
|
|
226
|
+
styles.dropdown,
|
|
227
|
+
{
|
|
228
|
+
// The raw click point is the pre-measurement anchor; the layout
|
|
229
|
+
// effect above replaces it before the browser paints.
|
|
230
|
+
top: placement?.top ?? position.y,
|
|
231
|
+
left: placement?.left ?? position.x,
|
|
232
|
+
backgroundColor: theme.isDark
|
|
233
|
+
? theme.colors.backgroundSecondary
|
|
234
|
+
: theme.colors.background,
|
|
235
|
+
borderColor: theme.colors.borderLight,
|
|
236
|
+
...bloomShadowStyle('m'),
|
|
237
|
+
},
|
|
238
|
+
style,
|
|
239
|
+
]}
|
|
240
|
+
>
|
|
241
|
+
{children}
|
|
242
|
+
</View>
|
|
243
|
+
</OverlayRoot>
|
|
239
244
|
</Portal>
|
|
240
245
|
);
|
|
241
246
|
}
|
|
@@ -374,13 +379,11 @@ const styles = StyleSheet.create({
|
|
|
374
379
|
left: 0,
|
|
375
380
|
right: 0,
|
|
376
381
|
bottom: 0,
|
|
377
|
-
zIndex: contextMenuZIndex.backdrop,
|
|
378
382
|
// Opt back in from the Portal root's `pointer-events: none`.
|
|
379
383
|
pointerEvents: 'auto',
|
|
380
384
|
},
|
|
381
385
|
dropdown: {
|
|
382
386
|
position: WEB_POSITION_FIXED,
|
|
383
|
-
zIndex: contextMenuZIndex.surface,
|
|
384
387
|
borderRadius: 8,
|
|
385
388
|
padding: 4,
|
|
386
389
|
borderWidth: 1,
|