@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.
Files changed (150) hide show
  1. package/lib/commonjs/avatar-group/AvatarGroup.web.js +22 -21
  2. package/lib/commonjs/avatar-group/AvatarGroup.web.js.map +1 -1
  3. package/lib/commonjs/bottom-sheet/index.web.js +11 -6
  4. package/lib/commonjs/bottom-sheet/index.web.js.map +1 -1
  5. package/lib/commonjs/context-menu/index.web.js +20 -22
  6. package/lib/commonjs/context-menu/index.web.js.map +1 -1
  7. package/lib/commonjs/dialog/Dialog.js +66 -70
  8. package/lib/commonjs/dialog/Dialog.js.map +1 -1
  9. package/lib/commonjs/dialog/Dialog.web.js +49 -51
  10. package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
  11. package/lib/commonjs/fonts/font-data.web.js +22 -0
  12. package/lib/commonjs/fonts/font-data.web.js.map +1 -0
  13. package/lib/commonjs/menu/index.web.js +15 -11
  14. package/lib/commonjs/menu/index.web.js.map +1 -1
  15. package/lib/commonjs/overlay/index.js +133 -7
  16. package/lib/commonjs/overlay/index.js.map +1 -1
  17. package/lib/commonjs/overlay/stack.js +170 -0
  18. package/lib/commonjs/overlay/stack.js.map +1 -0
  19. package/lib/commonjs/overlay/use-overlay-layer.js +37 -0
  20. package/lib/commonjs/overlay/use-overlay-layer.js.map +1 -0
  21. package/lib/commonjs/popover/index.web.js +31 -34
  22. package/lib/commonjs/popover/index.web.js.map +1 -1
  23. package/lib/commonjs/prompt-input/PromptInput.js +33 -29
  24. package/lib/commonjs/prompt-input/PromptInput.js.map +1 -1
  25. package/lib/commonjs/select/index.web.js +20 -22
  26. package/lib/commonjs/select/index.web.js.map +1 -1
  27. package/lib/commonjs/styles/z-index.js +33 -34
  28. package/lib/commonjs/styles/z-index.js.map +1 -1
  29. package/lib/commonjs/surfaces/SurfaceHost.js +3 -6
  30. package/lib/commonjs/surfaces/SurfaceHost.js.map +1 -1
  31. package/lib/commonjs/toast/ToastHost.js +9 -5
  32. package/lib/commonjs/toast/ToastHost.js.map +1 -1
  33. package/lib/commonjs/tooltip/index.js +9 -6
  34. package/lib/commonjs/tooltip/index.js.map +1 -1
  35. package/lib/commonjs/tooltip/index.web.js +8 -2
  36. package/lib/commonjs/tooltip/index.web.js.map +1 -1
  37. package/lib/commonjs/zoomable-image-gallery/ZoomableImageGallery.js +5 -5
  38. package/lib/commonjs/zoomable-image-gallery/ZoomableImageGallery.js.map +1 -1
  39. package/lib/module/avatar-group/AvatarGroup.web.js +21 -20
  40. package/lib/module/avatar-group/AvatarGroup.web.js.map +1 -1
  41. package/lib/module/bottom-sheet/index.web.js +11 -6
  42. package/lib/module/bottom-sheet/index.web.js.map +1 -1
  43. package/lib/module/context-menu/index.web.js +21 -23
  44. package/lib/module/context-menu/index.web.js.map +1 -1
  45. package/lib/module/dialog/Dialog.js +67 -71
  46. package/lib/module/dialog/Dialog.js.map +1 -1
  47. package/lib/module/dialog/Dialog.web.js +50 -52
  48. package/lib/module/dialog/Dialog.web.js.map +1 -1
  49. package/lib/module/fonts/font-data.web.js +18 -0
  50. package/lib/module/fonts/font-data.web.js.map +1 -0
  51. package/lib/module/menu/index.web.js +16 -12
  52. package/lib/module/menu/index.web.js.map +1 -1
  53. package/lib/module/overlay/index.js +99 -8
  54. package/lib/module/overlay/index.js.map +1 -1
  55. package/lib/module/overlay/stack.js +161 -0
  56. package/lib/module/overlay/stack.js.map +1 -0
  57. package/lib/module/overlay/use-overlay-layer.js +33 -0
  58. package/lib/module/overlay/use-overlay-layer.js.map +1 -0
  59. package/lib/module/popover/index.web.js +32 -34
  60. package/lib/module/popover/index.web.js.map +1 -1
  61. package/lib/module/prompt-input/PromptInput.js +33 -29
  62. package/lib/module/prompt-input/PromptInput.js.map +1 -1
  63. package/lib/module/select/index.web.js +21 -23
  64. package/lib/module/select/index.web.js.map +1 -1
  65. package/lib/module/styles/z-index.js +32 -30
  66. package/lib/module/styles/z-index.js.map +1 -1
  67. package/lib/module/surfaces/SurfaceHost.js +3 -6
  68. package/lib/module/surfaces/SurfaceHost.js.map +1 -1
  69. package/lib/module/toast/ToastHost.js +10 -6
  70. package/lib/module/toast/ToastHost.js.map +1 -1
  71. package/lib/module/tooltip/index.js +9 -6
  72. package/lib/module/tooltip/index.js.map +1 -1
  73. package/lib/module/tooltip/index.web.js +8 -2
  74. package/lib/module/tooltip/index.web.js.map +1 -1
  75. package/lib/module/zoomable-image-gallery/ZoomableImageGallery.js +5 -5
  76. package/lib/module/zoomable-image-gallery/ZoomableImageGallery.js.map +1 -1
  77. package/lib/typescript/commonjs/avatar-group/AvatarGroup.web.d.ts.map +1 -1
  78. package/lib/typescript/commonjs/bottom-sheet/index.web.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/context-menu/index.web.d.ts.map +1 -1
  80. package/lib/typescript/commonjs/dialog/Dialog.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/dialog/types.d.ts +0 -10
  83. package/lib/typescript/commonjs/dialog/types.d.ts.map +1 -1
  84. package/lib/typescript/commonjs/fonts/font-data.web.d.ts +5 -0
  85. package/lib/typescript/commonjs/fonts/font-data.web.d.ts.map +1 -0
  86. package/lib/typescript/commonjs/menu/index.web.d.ts.map +1 -1
  87. package/lib/typescript/commonjs/overlay/index.d.ts +31 -6
  88. package/lib/typescript/commonjs/overlay/index.d.ts.map +1 -1
  89. package/lib/typescript/commonjs/overlay/stack.d.ts +122 -0
  90. package/lib/typescript/commonjs/overlay/stack.d.ts.map +1 -0
  91. package/lib/typescript/commonjs/overlay/use-overlay-layer.d.ts +7 -0
  92. package/lib/typescript/commonjs/overlay/use-overlay-layer.d.ts.map +1 -0
  93. package/lib/typescript/commonjs/popover/index.web.d.ts.map +1 -1
  94. package/lib/typescript/commonjs/prompt-input/PromptInput.d.ts.map +1 -1
  95. package/lib/typescript/commonjs/select/index.web.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/styles/z-index.d.ts +42 -29
  97. package/lib/typescript/commonjs/styles/z-index.d.ts.map +1 -1
  98. package/lib/typescript/commonjs/surfaces/SurfaceHost.d.ts.map +1 -1
  99. package/lib/typescript/commonjs/toast/ToastHost.d.ts.map +1 -1
  100. package/lib/typescript/commonjs/tooltip/index.d.ts.map +1 -1
  101. package/lib/typescript/commonjs/zoomable-image-gallery/ZoomableImageGallery.d.ts.map +1 -1
  102. package/lib/typescript/module/avatar-group/AvatarGroup.web.d.ts.map +1 -1
  103. package/lib/typescript/module/bottom-sheet/index.web.d.ts.map +1 -1
  104. package/lib/typescript/module/context-menu/index.web.d.ts.map +1 -1
  105. package/lib/typescript/module/dialog/Dialog.d.ts.map +1 -1
  106. package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
  107. package/lib/typescript/module/dialog/types.d.ts +0 -10
  108. package/lib/typescript/module/dialog/types.d.ts.map +1 -1
  109. package/lib/typescript/module/fonts/font-data.web.d.ts +5 -0
  110. package/lib/typescript/module/fonts/font-data.web.d.ts.map +1 -0
  111. package/lib/typescript/module/menu/index.web.d.ts.map +1 -1
  112. package/lib/typescript/module/overlay/index.d.ts +31 -6
  113. package/lib/typescript/module/overlay/index.d.ts.map +1 -1
  114. package/lib/typescript/module/overlay/stack.d.ts +122 -0
  115. package/lib/typescript/module/overlay/stack.d.ts.map +1 -0
  116. package/lib/typescript/module/overlay/use-overlay-layer.d.ts +7 -0
  117. package/lib/typescript/module/overlay/use-overlay-layer.d.ts.map +1 -0
  118. package/lib/typescript/module/popover/index.web.d.ts.map +1 -1
  119. package/lib/typescript/module/prompt-input/PromptInput.d.ts.map +1 -1
  120. package/lib/typescript/module/select/index.web.d.ts.map +1 -1
  121. package/lib/typescript/module/styles/z-index.d.ts +42 -29
  122. package/lib/typescript/module/styles/z-index.d.ts.map +1 -1
  123. package/lib/typescript/module/surfaces/SurfaceHost.d.ts.map +1 -1
  124. package/lib/typescript/module/toast/ToastHost.d.ts.map +1 -1
  125. package/lib/typescript/module/tooltip/index.d.ts.map +1 -1
  126. package/lib/typescript/module/zoomable-image-gallery/ZoomableImageGallery.d.ts.map +1 -1
  127. package/package.json +2 -1
  128. package/src/__tests__/ToastHostWebFork.test.ts +11 -3
  129. package/src/__tests__/overlay-stack-order.test.tsx +270 -0
  130. package/src/avatar-group/AvatarGroup.web.tsx +35 -31
  131. package/src/bottom-sheet/index.web.tsx +10 -5
  132. package/src/context-menu/index.web.tsx +33 -30
  133. package/src/dialog/Dialog.tsx +9 -18
  134. package/src/dialog/Dialog.web.tsx +62 -60
  135. package/src/dialog/types.ts +0 -10
  136. package/src/fonts/font-data.web.ts +15 -0
  137. package/src/menu/index.web.tsx +33 -22
  138. package/src/overlay/OverlayStacking.stories.tsx +207 -0
  139. package/src/overlay/index.tsx +114 -9
  140. package/src/overlay/stack.ts +166 -0
  141. package/src/overlay/use-overlay-layer.ts +39 -0
  142. package/src/popover/index.web.tsx +47 -44
  143. package/src/prompt-input/PromptInput.tsx +36 -29
  144. package/src/select/index.web.tsx +35 -32
  145. package/src/styles/z-index.ts +32 -34
  146. package/src/surfaces/SurfaceHost.tsx +3 -10
  147. package/src/toast/ToastHost.tsx +9 -6
  148. package/src/tooltip/index.tsx +14 -7
  149. package/src/tooltip/index.web.tsx +8 -2
  150. package/src/zoomable-image-gallery/ZoomableImageGallery.tsx +4 -4
@@ -10,9 +10,8 @@ import React, {
10
10
  import { Pressable, StyleSheet, View } from 'react-native';
11
11
 
12
12
  import { useTheme } from '../theme/use-theme';
13
- import { Backdrop } from '../overlay';
13
+ import { Backdrop, OverlayRoot } from '../overlay';
14
14
  import { Portal } from '../portal/index.web';
15
- import { createOverlayZIndex } from '../styles/z-index';
16
15
  import { WEB_POSITION_FIXED } from '../styles/web-view-style';
17
16
  import { bloomShadowStyle } from '../design-tokens/shadows';
18
17
  import { PopoverContext, usePopoverContext } from './context';
@@ -36,7 +35,6 @@ export type {
36
35
 
37
36
  export { usePopoverContext };
38
37
 
39
- const popoverZIndex = createOverlayZIndex();
40
38
 
41
39
  /**
42
40
  * Web self-contained control. Unlike native (which proxies a `BottomSheet`
@@ -262,45 +260,52 @@ export function PopoverContent({
262
260
 
263
261
  return (
264
262
  <Portal>
265
- {dismissible ? (
266
- <Backdrop
267
- style={styles.backdrop}
268
- onPress={() => control.close()}
269
- accessibilityLabel="Dismiss popover"
270
- />
271
- ) : null}
272
- <View
273
- ref={panelRef}
274
- accessibilityRole="none"
275
- aria-label={label}
276
- testID={testID}
277
- onLayout={(e) =>
278
- setPanelSize({
279
- width: e.nativeEvent.layout.width,
280
- height: e.nativeEvent.layout.height,
281
- })
282
- }
283
- style={[
284
- styles.panel,
285
- {
286
- left: position.left,
287
- top: position.top,
288
- minWidth: resolvedMinWidth,
289
- maxWidth,
290
- backgroundColor: theme.isDark
291
- ? theme.colors.backgroundSecondary
292
- : theme.colors.background,
293
- borderColor: theme.colors.borderLight,
294
- // Design-system overlay elevation (`shadow-m`) as a `boxShadow` —
295
- // RN-Web deprecated the `shadow*` style props.
296
- ...bloomShadowStyle('m'),
297
- // Hide until measured to avoid a one-frame jump.
298
- opacity: panelSize ? 1 : 0,
299
- },
300
- style,
301
- ]}>
302
- {children}
303
- </View>
263
+ {/* `OverlayRoot` takes this surface's place in the open-order overlay
264
+ stack, so it paints above anything opened before it (see
265
+ `src/overlay/stack.ts`). It is `box-none`, so the area outside the
266
+ panel stays click-through and the backdrop below still takes its own
267
+ presses. */}
268
+ <OverlayRoot>
269
+ {dismissible ? (
270
+ <Backdrop
271
+ style={styles.backdrop}
272
+ onPress={() => control.close()}
273
+ accessibilityLabel="Dismiss popover"
274
+ />
275
+ ) : null}
276
+ <View
277
+ ref={panelRef}
278
+ accessibilityRole="none"
279
+ aria-label={label}
280
+ testID={testID}
281
+ onLayout={(e) =>
282
+ setPanelSize({
283
+ width: e.nativeEvent.layout.width,
284
+ height: e.nativeEvent.layout.height,
285
+ })
286
+ }
287
+ style={[
288
+ styles.panel,
289
+ {
290
+ left: position.left,
291
+ top: position.top,
292
+ minWidth: resolvedMinWidth,
293
+ maxWidth,
294
+ backgroundColor: theme.isDark
295
+ ? theme.colors.backgroundSecondary
296
+ : theme.colors.background,
297
+ borderColor: theme.colors.borderLight,
298
+ // Design-system overlay elevation (`shadow-m`) as a `boxShadow` —
299
+ // RN-Web deprecated the `shadow*` style props.
300
+ ...bloomShadowStyle('m'),
301
+ // Hide until measured to avoid a one-frame jump.
302
+ opacity: panelSize ? 1 : 0,
303
+ },
304
+ style,
305
+ ]}>
306
+ {children}
307
+ </View>
308
+ </OverlayRoot>
304
309
  </Portal>
305
310
  );
306
311
  }
@@ -315,12 +320,10 @@ const styles = StyleSheet.create({
315
320
  left: 0,
316
321
  right: 0,
317
322
  bottom: 0,
318
- zIndex: popoverZIndex.backdrop,
319
323
  pointerEvents: 'auto',
320
324
  },
321
325
  panel: {
322
326
  position: WEB_POSITION_FIXED,
323
- zIndex: popoverZIndex.surface,
324
327
  borderRadius: 12,
325
328
  borderWidth: 1,
326
329
  paddingVertical: 4,
@@ -10,6 +10,7 @@ import {
10
10
  } from 'react-native';
11
11
 
12
12
  import { useTheme } from '../theme/use-theme';
13
+ import { OverlayRoot } from '../overlay';
13
14
  import { Portal } from '../portal';
14
15
  import { Z_INDEX } from '../styles/z-index';
15
16
  import { PromptInputContext, type Attachment } from './context';
@@ -230,36 +231,42 @@ export function PromptInput({
230
231
 
231
232
  {showFullscreen && (
232
233
  <Portal>
233
- <View
234
- style={[
235
- StyleSheet.absoluteFill,
236
- {
237
- zIndex: Z_INDEX.fullscreen,
238
- backgroundColor: theme.colors.background,
239
- // Opt back in from the Portal root's `pointer-events: none`
240
- // (web only — harmless on native).
241
- pointerEvents: 'auto',
242
- },
243
- ]}
244
- >
245
- <Pressable
246
- onPress={() => setShowFullscreen(false)}
247
- style={{
248
- position: 'absolute',
249
- top: 16,
250
- right: 16,
251
- zIndex: Z_INDEX.fullscreenControl,
252
- padding: 8,
253
- backgroundColor: theme.colors.background,
254
- borderRadius: 9999,
255
- }}
234
+ {/* `OverlayRoot` takes the fullscreen editor's place in the
235
+ open-order overlay stack (`src/overlay/stack.ts`). It mounts with
236
+ `showFullscreen`, so the rank tracks opening. */}
237
+ <OverlayRoot>
238
+ <View
239
+ style={[
240
+ StyleSheet.absoluteFill,
241
+ {
242
+ backgroundColor: theme.colors.background,
243
+ // Opt back in from the Portal root's `pointer-events: none`
244
+ // (web only — harmless on native).
245
+ pointerEvents: 'auto',
246
+ },
247
+ ]}
256
248
  >
257
- {collapseIcon ?? (
258
- <Text style={{ fontSize: 18, color: theme.colors.text }}>⤡</Text>
259
- )}
260
- </Pressable>
261
- <View style={{ flex: 1 }}>{content}</View>
262
- </View>
249
+ <Pressable
250
+ onPress={() => setShowFullscreen(false)}
251
+ style={{
252
+ position: 'absolute',
253
+ top: 16,
254
+ right: 16,
255
+ // Above the editor's own body; where the editor sits relative
256
+ // to OTHER surfaces is the overlay stack's call.
257
+ zIndex: Z_INDEX.raised,
258
+ padding: 8,
259
+ backgroundColor: theme.colors.background,
260
+ borderRadius: 9999,
261
+ }}
262
+ >
263
+ {collapseIcon ?? (
264
+ <Text style={{ fontSize: 18, color: theme.colors.text }}>⤡</Text>
265
+ )}
266
+ </Pressable>
267
+ <View style={{ flex: 1 }}>{content}</View>
268
+ </View>
269
+ </OverlayRoot>
263
270
  </Portal>
264
271
  )}
265
272
  </PromptInputContext.Provider>
@@ -12,9 +12,8 @@ import { Pressable, StyleSheet, View, type ViewStyle } from 'react-native';
12
12
 
13
13
  import { useTheme } from '../theme/use-theme';
14
14
  import { Text } from '../typography';
15
- import { Backdrop } from '../overlay';
15
+ import { Backdrop, OverlayRoot } from '../overlay';
16
16
  import { Portal } from '../portal/index.web';
17
- import { createOverlayZIndex } from '../styles/z-index';
18
17
  import { WEB_POSITION_FIXED } from '../styles/web-view-style';
19
18
  import { resolveDropdownPlacement } from '../overlay/dropdown-placement';
20
19
  import { bloomShadowStyle } from '../design-tokens/shadows';
@@ -39,7 +38,6 @@ import type {
39
38
 
40
39
  export { useSelectItemContext };
41
40
 
42
- const selectZIndex = createOverlayZIndex();
43
41
  const VIEWPORT_GUTTER = 8;
44
42
  const SELECT_OFFSET = 6;
45
43
 
@@ -259,33 +257,40 @@ export function SelectContent<T>({
259
257
 
260
258
  return (
261
259
  <Portal>
262
- <Backdrop
263
- style={styles.backdrop}
264
- onPress={ctx.close}
265
- accessibilityLabel="Close selection"
266
- />
267
- <View
268
- ref={attachDropdown}
269
- accessibilityRole="list"
270
- accessibilityLabel={label}
271
- style={[
272
- styles.dropdown,
273
- {
274
- backgroundColor: theme.isDark
275
- ? theme.colors.backgroundSecondary
276
- : theme.colors.background,
277
- borderColor: theme.colors.borderLight,
278
- ...bloomShadowStyle('m'),
279
- },
280
- position,
281
- ]}
282
- >
283
- {items.map((item, index) => (
284
- <React.Fragment key={valueExtractor(item)}>
285
- {renderItem(item, index, ctx.value)}
286
- </React.Fragment>
287
- ))}
288
- </View>
260
+ {/* `OverlayRoot` takes this surface's place in the open-order overlay
261
+ stack, so it paints above anything opened before it (see
262
+ `src/overlay/stack.ts`). It is `box-none`, so the area outside the
263
+ panel stays click-through and the backdrop below still takes its own
264
+ presses. */}
265
+ <OverlayRoot>
266
+ <Backdrop
267
+ style={styles.backdrop}
268
+ onPress={ctx.close}
269
+ accessibilityLabel="Close selection"
270
+ />
271
+ <View
272
+ ref={attachDropdown}
273
+ accessibilityRole="list"
274
+ accessibilityLabel={label}
275
+ style={[
276
+ styles.dropdown,
277
+ {
278
+ backgroundColor: theme.isDark
279
+ ? theme.colors.backgroundSecondary
280
+ : theme.colors.background,
281
+ borderColor: theme.colors.borderLight,
282
+ ...bloomShadowStyle('m'),
283
+ },
284
+ position,
285
+ ]}
286
+ >
287
+ {items.map((item, index) => (
288
+ <React.Fragment key={valueExtractor(item)}>
289
+ {renderItem(item, index, ctx.value)}
290
+ </React.Fragment>
291
+ ))}
292
+ </View>
293
+ </OverlayRoot>
289
294
  </Portal>
290
295
  );
291
296
  }
@@ -415,7 +420,6 @@ const styles = StyleSheet.create({
415
420
  left: 0,
416
421
  right: 0,
417
422
  bottom: 0,
418
- zIndex: selectZIndex.backdrop,
419
423
  // Opt back in from the Portal root's `pointer-events: none`.
420
424
  pointerEvents: 'auto',
421
425
  },
@@ -428,7 +432,6 @@ const styles = StyleSheet.create({
428
432
  position: WEB_POSITION_FIXED,
429
433
  top: 0,
430
434
  left: 0,
431
- zIndex: selectZIndex.surface,
432
435
  borderRadius: 8,
433
436
  borderWidth: 1,
434
437
  overflow: 'hidden',
@@ -1,47 +1,45 @@
1
1
  /**
2
- * Shared stacking scale for Bloom primitives.
2
+ * Stacking scale for stacking WITHIN a single context.
3
3
  *
4
- * Keep component layers on this scale instead of introducing local magic
5
- * numbers. App-specific surfaces can still sit above Bloom's inline layers,
6
- * while portaled overlays use `portalRoot` as the document-level host.
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 const Z_INDEX = {
27
+ /** Default resting level. */
9
28
  base: 0,
29
+ /** One step above an immediate sibling — a badge over its avatar. */
10
30
  raised: 1,
31
+ /** A control floating over the content it belongs to. */
11
32
  floating: 10,
12
- sticky: 20,
33
+ /** A dropdown's INLINE trigger wrapper, still in the app's document flow. */
13
34
  dropdown: 40,
14
- dropdownSurface: 41,
15
- overlayBackdrop: 50,
16
- overlaySurface: 60,
17
- tooltip: 70,
18
- toast: 80,
19
- fullscreen: 90,
20
- fullscreenControl: 91,
35
+ /** A bottom sheet's drag handle, over the sheet's own body. */
21
36
  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
  portalRoot: 999999,
23
43
  } as const;
24
44
 
25
- export const Z_INDEX_LAYER_STEP = 10;
26
-
27
- function layerOffset(layer = 0) {
28
- return Math.max(0, Math.floor(layer)) * Z_INDEX_LAYER_STEP;
29
- }
30
-
31
- export function createDropdownZIndex(layer = 0) {
32
- const offset = layerOffset(layer);
33
- return {
34
- root: Z_INDEX.dropdown + offset,
35
- surface: Z_INDEX.dropdownSurface + offset,
36
- } as const;
37
- }
38
-
39
- export function createOverlayZIndex(layer = 0) {
40
- const offset = layerOffset(layer);
41
- return {
42
- backdrop: Z_INDEX.overlayBackdrop + offset,
43
- surface: Z_INDEX.overlaySurface + offset,
44
- } as const;
45
- }
46
-
47
45
  export const zIndex = Z_INDEX;
@@ -67,13 +67,7 @@ export function createSurfaceHost(Dialog: DialogComponent) {
67
67
  * dismiss REQUEST and has no post-exit callback, so it could never tell the
68
68
  * store WHEN to splice the entry after the exit animation.
69
69
  */
70
- function SurfaceLayer({
71
- entry,
72
- index,
73
- }: {
74
- entry: SurfaceEntry;
75
- index: number;
76
- }) {
70
+ function SurfaceLayer({ entry }: { entry: SurfaceEntry }) {
77
71
  const control = useDialogControl();
78
72
  const { id, status } = entry;
79
73
 
@@ -125,7 +119,6 @@ export function createSurfaceHost(Dialog: DialogComponent) {
125
119
  <Dialog
126
120
  control={control}
127
121
  onClose={handleClose}
128
- layer={index}
129
122
  startOpen={status !== 'closing'}
130
123
  {...placement}
131
124
  >
@@ -144,8 +137,8 @@ export function createSurfaceHost(Dialog: DialogComponent) {
144
137
 
145
138
  return (
146
139
  <>
147
- {stack.map((entry, index) => (
148
- <SurfaceLayer key={entry.id} entry={entry} index={index} />
140
+ {stack.map((entry) => (
141
+ <SurfaceLayer key={entry.id} entry={entry} />
149
142
  ))}
150
143
  </>
151
144
  );
@@ -26,10 +26,9 @@ import * as React from 'react';
26
26
  import { StyleSheet } from 'react-native';
27
27
  import { GestureHandlerRootView } from 'react-native-gesture-handler';
28
28
 
29
- import { OverlayRoot } from '../overlay';
29
+ import { OverlayRoot, TOAST_LAYER_Z } from '../overlay';
30
30
  import { Portal } from '../portal/index.web';
31
31
  import { WEB_POSITION_FIXED } from '../styles/web-view-style';
32
- import { Z_INDEX } from '../styles/z-index';
33
32
  import type { ToastHostProps } from './types';
34
33
 
35
34
  export function ToastHost({ children, ToasterOverlayWrapper }: ToastHostProps) {
@@ -37,7 +36,7 @@ export function ToastHost({ children, ToasterOverlayWrapper }: ToastHostProps) {
37
36
  // `OverlayRoot` carries the pointer-events opt-in as a PROP; as a style
38
37
  // entry it never reached the DOM, so the rows inherited the portal root's
39
38
  // `pointer-events: none` and no toast could be pressed or swiped away.
40
- <OverlayRoot style={styles.host}>
39
+ <OverlayRoot zIndex={TOAST_LAYER_Z} style={styles.host}>
41
40
  {/* Also `box-none`: as the direct child of a box-none root it would
42
41
  otherwise be handed `pointer-events: auto` and, spanning the whole
43
42
  viewport, block the app underneath while a toast is up. */}
@@ -68,8 +67,12 @@ const styles = StyleSheet.create({
68
67
  left: 0,
69
68
  right: 0,
70
69
  bottom: 0,
71
- // Inside `#bloom-portal-root` (which owns the document layer at 999999),
72
- // this keeps toasts above Dialog's surface (60) and tooltips (70).
73
- zIndex: Z_INDEX.toast,
70
+ // The depth is `TOAST_LAYER_Z`, applied by `OverlayRoot` via its `zIndex`
71
+ // prop a toast is a notification, not a modal surface, so it is pinned
72
+ // above the whole open-order overlay stack rather than taking a rank in it
73
+ // (see `src/overlay/stack.ts`). It must not be set here as well: a style
74
+ // z-index would fight the prop and, being a fixed number again, would land
75
+ // back under any surface that outranked it — which is how toasts were
76
+ // already being lost behind an open bottom sheet.
74
77
  },
75
78
  });
@@ -18,6 +18,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
18
18
 
19
19
  import { useTheme } from '../theme/use-theme';
20
20
  import { atoms as a } from '../styles';
21
+ import { OverlayRoot } from '../overlay';
21
22
  import { Portal } from '../portal';
22
23
  import {
23
24
  ARROW_HALF_SIZE,
@@ -173,13 +174,19 @@ export function TooltipContent({
173
174
 
174
175
  return (
175
176
  <Portal>
176
- <Bubble
177
- label={label}
178
- position={position}
179
- targetMeasurements={targetMeasurements}
180
- requestClose={requestClose}>
181
- {children}
182
- </Bubble>
177
+ {/* `OverlayRoot` takes the tooltip's place in the open-order overlay
178
+ stack, so a tooltip opened over another surface paints above it (see
179
+ `src/overlay/stack.ts`). It mounts past the `visible` guard, so the
180
+ rank tracks opening. */}
181
+ <OverlayRoot>
182
+ <Bubble
183
+ label={label}
184
+ position={position}
185
+ targetMeasurements={targetMeasurements}
186
+ requestClose={requestClose}>
187
+ {children}
188
+ </Bubble>
189
+ </OverlayRoot>
183
190
  </Portal>
184
191
  );
185
192
  }
@@ -3,8 +3,8 @@ import { Pressable, StyleSheet, View } from 'react-native';
3
3
 
4
4
  import { useTheme } from '../theme/use-theme';
5
5
  import { atoms as a, flatten } from '../styles';
6
- import { Z_INDEX } from '../styles/z-index';
7
6
  import { WEB_POSITION_FIXED } from '../styles/web-view-style';
7
+ import { Z_INDEX } from '../styles/z-index';
8
8
  import {
9
9
  ARROW_SIZE,
10
10
  BUBBLE_MAX_WIDTH,
@@ -156,6 +156,12 @@ const webStyles = StyleSheet.create({
156
156
  left: 0,
157
157
  right: 0,
158
158
  bottom: 0,
159
- zIndex: Z_INDEX.tooltip,
159
+ // In-flow depth, not an overlay-ordering one: the WEB tooltip is not
160
+ // portaled (unlike the native one) — the bubble is absolutely positioned
161
+ // against its trigger and renders inline. So this only lifts the
162
+ // outside-press catcher over adjacent content, and stays below the bubble's
163
+ // own `a.z_50`. Which SURFACE is on top is never decided here; see
164
+ // `src/overlay/stack.ts`.
165
+ zIndex: Z_INDEX.floating,
160
166
  },
161
167
  });
@@ -36,7 +36,6 @@ import {
36
36
  ArrowRight_Stroke2_Corner0_Rounded,
37
37
  ArrowOutOfBox_Stroke2_Corner0_Rounded,
38
38
  } from '../icons';
39
- import { Z_INDEX } from '../styles/z-index';
40
39
  import { WEB_POSITION_FIXED, type WebCssStyle } from '../styles/web-view-style';
41
40
  import {
42
41
  getAspectRatio,
@@ -1039,14 +1038,15 @@ const styles = StyleSheet.create({
1039
1038
  web: {
1040
1039
  // Full-screen fixed overlay (web), like Bloom's Portal and Dialog.web.
1041
1040
  // Only ever applied on web (this is the `web` arm of Platform.select),
1042
- // so `WEB_POSITION_FIXED` is honest here; the fullscreen z-index token
1043
- // keeps the viewer above the app chrome.
1041
+ // so `WEB_POSITION_FIXED` is honest here. The depth is NOT set here:
1042
+ // `OverlayRoot` above assigns it from the open-order overlay stack, so
1043
+ // a viewer opened over another surface paints above it and one opened
1044
+ // under a later surface goes behind (see `src/overlay/stack.ts`).
1044
1045
  position: WEB_POSITION_FIXED,
1045
1046
  top: 0,
1046
1047
  left: 0,
1047
1048
  right: 0,
1048
1049
  bottom: 0,
1049
- zIndex: Z_INDEX.fullscreen,
1050
1050
  },
1051
1051
  default: {
1052
1052
  // Mounted through the Bloom Portal `Outlet` (which renders its content