@oxy.so/bloom 4.24.1 → 4.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/docs/adoption-matrix.mdx +6 -6
  2. package/docs/ai-chat.mdx +1 -1
  3. package/docs/context-menu.mdx +2 -0
  4. package/lib/commonjs/ai-chat/AiChatCodePanel.js +2 -2
  5. package/lib/commonjs/ai-chat/AiChatCodePanel.js.map +1 -1
  6. package/lib/commonjs/ai-chat/AiChatContainer.js +60 -7
  7. package/lib/commonjs/ai-chat/AiChatContainer.js.map +1 -1
  8. package/lib/commonjs/ai-chat/AiChatShell.js +3 -3
  9. package/lib/commonjs/ai-chat/AiChatShell.js.map +1 -1
  10. package/lib/commonjs/ai-chat/use-panel-interaction.js +6 -1
  11. package/lib/commonjs/ai-chat/use-panel-interaction.js.map +1 -1
  12. package/lib/commonjs/bottom-sheet/BottomSheetBase.js +6 -2
  13. package/lib/commonjs/bottom-sheet/BottomSheetBase.js.map +1 -1
  14. package/lib/commonjs/bottom-sheet/context.js +29 -0
  15. package/lib/commonjs/bottom-sheet/context.js.map +1 -0
  16. package/lib/commonjs/composer-panel/ComposerPanelBase.js +32 -11
  17. package/lib/commonjs/composer-panel/ComposerPanelBase.js.map +1 -1
  18. package/lib/commonjs/composer-panel/ComposerPanelStatusTab.js +2 -2
  19. package/lib/commonjs/composer-panel/ComposerPanelStatusTab.js.map +1 -1
  20. package/lib/commonjs/composer-panel/ComposerPillBase.js +3 -1
  21. package/lib/commonjs/composer-panel/ComposerPillBase.js.map +1 -1
  22. package/lib/commonjs/composer-panel/ComposerPopover.js +13 -5
  23. package/lib/commonjs/composer-panel/ComposerPopover.js.map +1 -1
  24. package/lib/commonjs/composer-panel/ComposerStatusBarBase.js +3 -3
  25. package/lib/commonjs/composer-panel/ComposerStatusBarBase.js.map +1 -1
  26. package/lib/commonjs/composer-panel/ModelPickerBase.js +16 -4
  27. package/lib/commonjs/composer-panel/ModelPickerBase.js.map +1 -1
  28. package/lib/commonjs/composer-panel/PermissionMenu.js +19 -3
  29. package/lib/commonjs/composer-panel/PermissionMenu.js.map +1 -1
  30. package/lib/commonjs/context-menu/ContextMenu.js +10 -2
  31. package/lib/commonjs/context-menu/ContextMenu.js.map +1 -1
  32. package/lib/commonjs/dialog/DialogBottomSheet.js +27 -6
  33. package/lib/commonjs/dialog/DialogBottomSheet.js.map +1 -1
  34. package/lib/commonjs/floating/LongPressArea.js +107 -0
  35. package/lib/commonjs/floating/LongPressArea.js.map +1 -0
  36. package/lib/commonjs/settings-modal/SettingsModal.js +8 -1
  37. package/lib/commonjs/settings-modal/SettingsModal.js.map +1 -1
  38. package/lib/commonjs/settings-modal/use-keyboard-reveal.js +76 -0
  39. package/lib/commonjs/settings-modal/use-keyboard-reveal.js.map +1 -0
  40. package/lib/commonjs/sidebar/SidebarItem.js +5 -1
  41. package/lib/commonjs/sidebar/SidebarItem.js.map +1 -1
  42. package/lib/module/ai-chat/AiChatCodePanel.js +2 -2
  43. package/lib/module/ai-chat/AiChatCodePanel.js.map +1 -1
  44. package/lib/module/ai-chat/AiChatContainer.js +60 -7
  45. package/lib/module/ai-chat/AiChatContainer.js.map +1 -1
  46. package/lib/module/ai-chat/AiChatShell.js +3 -3
  47. package/lib/module/ai-chat/AiChatShell.js.map +1 -1
  48. package/lib/module/ai-chat/use-panel-interaction.js +7 -2
  49. package/lib/module/ai-chat/use-panel-interaction.js.map +1 -1
  50. package/lib/module/bottom-sheet/BottomSheetBase.js +6 -2
  51. package/lib/module/bottom-sheet/BottomSheetBase.js.map +1 -1
  52. package/lib/module/bottom-sheet/context.js +25 -0
  53. package/lib/module/bottom-sheet/context.js.map +1 -0
  54. package/lib/module/composer-panel/ComposerPanelBase.js +33 -12
  55. package/lib/module/composer-panel/ComposerPanelBase.js.map +1 -1
  56. package/lib/module/composer-panel/ComposerPanelStatusTab.js +2 -2
  57. package/lib/module/composer-panel/ComposerPanelStatusTab.js.map +1 -1
  58. package/lib/module/composer-panel/ComposerPillBase.js +3 -1
  59. package/lib/module/composer-panel/ComposerPillBase.js.map +1 -1
  60. package/lib/module/composer-panel/ComposerPopover.js +13 -5
  61. package/lib/module/composer-panel/ComposerPopover.js.map +1 -1
  62. package/lib/module/composer-panel/ComposerStatusBarBase.js +3 -3
  63. package/lib/module/composer-panel/ComposerStatusBarBase.js.map +1 -1
  64. package/lib/module/composer-panel/ModelPickerBase.js +16 -4
  65. package/lib/module/composer-panel/ModelPickerBase.js.map +1 -1
  66. package/lib/module/composer-panel/PermissionMenu.js +19 -3
  67. package/lib/module/composer-panel/PermissionMenu.js.map +1 -1
  68. package/lib/module/context-menu/ContextMenu.js +10 -2
  69. package/lib/module/context-menu/ContextMenu.js.map +1 -1
  70. package/lib/module/dialog/DialogBottomSheet.js +25 -4
  71. package/lib/module/dialog/DialogBottomSheet.js.map +1 -1
  72. package/lib/module/floating/LongPressArea.js +100 -0
  73. package/lib/module/floating/LongPressArea.js.map +1 -0
  74. package/lib/module/settings-modal/SettingsModal.js +8 -1
  75. package/lib/module/settings-modal/SettingsModal.js.map +1 -1
  76. package/lib/module/settings-modal/use-keyboard-reveal.js +72 -0
  77. package/lib/module/settings-modal/use-keyboard-reveal.js.map +1 -0
  78. package/lib/module/sidebar/SidebarItem.js +5 -1
  79. package/lib/module/sidebar/SidebarItem.js.map +1 -1
  80. package/lib/typescript/commonjs/ai-chat/AiChatCodePanel.d.ts +1 -1
  81. package/lib/typescript/commonjs/ai-chat/AiChatShell.d.ts +1 -1
  82. package/lib/typescript/commonjs/ai-chat/use-panel-interaction.d.ts +5 -1
  83. package/lib/typescript/commonjs/ai-chat/use-panel-interaction.d.ts.map +1 -1
  84. package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
  85. package/lib/typescript/commonjs/bottom-sheet/context.d.ts +5 -0
  86. package/lib/typescript/commonjs/bottom-sheet/context.d.ts.map +1 -0
  87. package/lib/typescript/commonjs/composer-panel/ComposerPanelBase.d.ts.map +1 -1
  88. package/lib/typescript/commonjs/composer-panel/ComposerPanelStatusTab.d.ts +1 -1
  89. package/lib/typescript/commonjs/composer-panel/ComposerPillBase.d.ts.map +1 -1
  90. package/lib/typescript/commonjs/composer-panel/ComposerPopover.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/composer-panel/ComposerStatusBarBase.d.ts +2 -2
  92. package/lib/typescript/commonjs/composer-panel/ModelPickerBase.d.ts +1 -1
  93. package/lib/typescript/commonjs/composer-panel/ModelPickerBase.d.ts.map +1 -1
  94. package/lib/typescript/commonjs/composer-panel/PermissionMenu.d.ts +3 -1
  95. package/lib/typescript/commonjs/composer-panel/PermissionMenu.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/context-menu/ContextMenu.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/dialog/DialogBottomSheet.d.ts.map +1 -1
  98. package/lib/typescript/commonjs/floating/LongPressArea.d.ts +34 -0
  99. package/lib/typescript/commonjs/floating/LongPressArea.d.ts.map +1 -0
  100. package/lib/typescript/commonjs/settings-modal/SettingsModal.d.ts.map +1 -1
  101. package/lib/typescript/commonjs/settings-modal/use-keyboard-reveal.d.ts +21 -0
  102. package/lib/typescript/commonjs/settings-modal/use-keyboard-reveal.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/sidebar/SidebarItem.d.ts.map +1 -1
  104. package/lib/typescript/module/ai-chat/AiChatCodePanel.d.ts +1 -1
  105. package/lib/typescript/module/ai-chat/AiChatShell.d.ts +1 -1
  106. package/lib/typescript/module/ai-chat/use-panel-interaction.d.ts +5 -1
  107. package/lib/typescript/module/ai-chat/use-panel-interaction.d.ts.map +1 -1
  108. package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
  109. package/lib/typescript/module/bottom-sheet/context.d.ts +5 -0
  110. package/lib/typescript/module/bottom-sheet/context.d.ts.map +1 -0
  111. package/lib/typescript/module/composer-panel/ComposerPanelBase.d.ts.map +1 -1
  112. package/lib/typescript/module/composer-panel/ComposerPanelStatusTab.d.ts +1 -1
  113. package/lib/typescript/module/composer-panel/ComposerPillBase.d.ts.map +1 -1
  114. package/lib/typescript/module/composer-panel/ComposerPopover.d.ts.map +1 -1
  115. package/lib/typescript/module/composer-panel/ComposerStatusBarBase.d.ts +2 -2
  116. package/lib/typescript/module/composer-panel/ModelPickerBase.d.ts +1 -1
  117. package/lib/typescript/module/composer-panel/ModelPickerBase.d.ts.map +1 -1
  118. package/lib/typescript/module/composer-panel/PermissionMenu.d.ts +3 -1
  119. package/lib/typescript/module/composer-panel/PermissionMenu.d.ts.map +1 -1
  120. package/lib/typescript/module/context-menu/ContextMenu.d.ts.map +1 -1
  121. package/lib/typescript/module/dialog/DialogBottomSheet.d.ts.map +1 -1
  122. package/lib/typescript/module/floating/LongPressArea.d.ts +34 -0
  123. package/lib/typescript/module/floating/LongPressArea.d.ts.map +1 -0
  124. package/lib/typescript/module/settings-modal/SettingsModal.d.ts.map +1 -1
  125. package/lib/typescript/module/settings-modal/use-keyboard-reveal.d.ts +21 -0
  126. package/lib/typescript/module/settings-modal/use-keyboard-reveal.d.ts.map +1 -0
  127. package/lib/typescript/module/sidebar/SidebarItem.d.ts.map +1 -1
  128. package/package.json +1 -1
  129. package/src/ai-chat/AiChatCodePanel.tsx +2 -2
  130. package/src/ai-chat/AiChatContainer.tsx +44 -5
  131. package/src/ai-chat/AiChatShell.tsx +3 -3
  132. package/src/ai-chat/use-panel-interaction.ts +7 -2
  133. package/src/bottom-sheet/BottomSheetBase.tsx +4 -1
  134. package/src/bottom-sheet/context.ts +23 -0
  135. package/src/composer-panel/ComposerPanelBase.tsx +38 -13
  136. package/src/composer-panel/ComposerPanelStatusTab.tsx +2 -2
  137. package/src/composer-panel/ComposerPillBase.tsx +3 -1
  138. package/src/composer-panel/ComposerPopover.tsx +13 -5
  139. package/src/composer-panel/ComposerStatusBarBase.tsx +3 -3
  140. package/src/composer-panel/ModelPickerBase.tsx +14 -5
  141. package/src/composer-panel/PermissionMenu.tsx +17 -4
  142. package/src/context-menu/ContextMenu.tsx +7 -2
  143. package/src/dialog/DialogBottomSheet.tsx +16 -4
  144. package/src/floating/LongPressArea.tsx +114 -0
  145. package/src/settings-modal/SettingsModal.tsx +9 -2
  146. package/src/settings-modal/use-keyboard-reveal.ts +62 -0
  147. package/src/sidebar/SidebarItem.tsx +5 -1
@@ -0,0 +1,114 @@
1
+ /**
2
+ * A long-press target that never becomes the touch RESPONDER — native only,
3
+ * the trigger `ContextMenuTrigger` renders around content it does not own.
4
+ *
5
+ * ── WHY NOT A `Pressable` ───────────────────────────────────────────────────
6
+ *
7
+ * On Android the view holding the JS responder intercepts every later MOVE of
8
+ * that touch natively (`JSResponderHandler.onInterceptTouchEvent` returns true
9
+ * for the responder's own id). A `Pressable` claims the responder on touch
10
+ * start, so a horizontal `ScrollView` INSIDE it never sees the drag: a turn
11
+ * wrapped in the context menu's trigger could not scroll its code block
12
+ * sideways (Pixel 8a, Android 16; the same block scrolled without the wrapper).
13
+ * A vertical scroller ABOVE the trigger kept working, because a parent
14
+ * intercepts before its child — which is why only the inner one broke.
15
+ *
16
+ * So the hold is timed off the RAW touch events, which every ancestor receives
17
+ * without claiming anything: a scroller that takes the drag cancels the touch,
18
+ * and a finger that travels past the slop or lifts early cancels the timer.
19
+ * Taps keep going to whatever the content itself makes pressable.
20
+ *
21
+ * Assistive tech reaches the menu through the `longpress` accessibility action
22
+ * (TalkBack's double-tap-and-hold, VoiceOver's actions rotor).
23
+ */
24
+ import React, { useCallback, useEffect, useRef } from 'react';
25
+ import {
26
+ View,
27
+ type AccessibilityActionEvent,
28
+ type GestureResponderEvent,
29
+ } from 'react-native';
30
+
31
+ import type { TriggerHandleProps } from './types';
32
+
33
+ /** `Pressable`'s own default, so a context menu opens on the platform's usual hold. */
34
+ export const LONG_PRESS_MS = 500;
35
+ /** How far the finger may drift and still be holding, in dp (`Pressability`'s deactivation distance). */
36
+ export const LONG_PRESS_SLOP = 10;
37
+
38
+ export type LongPressAreaProps = Partial<TriggerHandleProps> & {
39
+ children?: React.ReactNode;
40
+ };
41
+
42
+ export function LongPressArea({
43
+ children,
44
+ onLongPress,
45
+ // A press is the content's own business: the area never claims one.
46
+ onPress: _onPress,
47
+ disabled,
48
+ accessibilityLabel,
49
+ accessibilityRole,
50
+ ...aria
51
+ }: LongPressAreaProps) {
52
+ const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
53
+ const origin = useRef<{ x: number; y: number } | null>(null);
54
+ const onLongPressRef = useRef(onLongPress);
55
+ onLongPressRef.current = onLongPress;
56
+
57
+ const cancel = useCallback(() => {
58
+ if (timer.current !== null) clearTimeout(timer.current);
59
+ timer.current = null;
60
+ origin.current = null;
61
+ }, []);
62
+ useEffect(() => cancel, [cancel]);
63
+
64
+ const onTouchStart = useCallback(
65
+ (event: GestureResponderEvent) => {
66
+ cancel();
67
+ // A second finger is a pinch or a scroll, not a hold.
68
+ if (disabled || !onLongPressRef.current || event.nativeEvent.touches?.length > 1) return;
69
+ origin.current = { x: event.nativeEvent.pageX, y: event.nativeEvent.pageY };
70
+ timer.current = setTimeout(() => {
71
+ timer.current = null;
72
+ origin.current = null;
73
+ onLongPressRef.current?.(event);
74
+ }, LONG_PRESS_MS);
75
+ },
76
+ [cancel, disabled],
77
+ );
78
+ const onTouchMove = useCallback(
79
+ (event: GestureResponderEvent) => {
80
+ const start = origin.current;
81
+ if (!start) return;
82
+ const dx = event.nativeEvent.pageX - start.x;
83
+ const dy = event.nativeEvent.pageY - start.y;
84
+ if (dx * dx + dy * dy > LONG_PRESS_SLOP * LONG_PRESS_SLOP) cancel();
85
+ },
86
+ [cancel],
87
+ );
88
+ const onAccessibilityAction = useCallback(
89
+ (event: AccessibilityActionEvent) => {
90
+ if (event.nativeEvent.actionName === 'longpress' && !disabled) {
91
+ onLongPressRef.current?.(event as unknown as GestureResponderEvent);
92
+ }
93
+ },
94
+ [disabled],
95
+ );
96
+
97
+ return (
98
+ <View
99
+ {...aria}
100
+ // One node, as the `Pressable` it replaces was: the hold is its action.
101
+ accessible
102
+ accessibilityLabel={accessibilityLabel}
103
+ accessibilityRole={accessibilityRole}
104
+ accessibilityState={{ disabled: !!disabled }}
105
+ accessibilityActions={disabled ? undefined : [{ name: 'longpress', label: accessibilityLabel }]}
106
+ onAccessibilityAction={onAccessibilityAction}
107
+ onTouchStart={onTouchStart}
108
+ onTouchMove={onTouchMove}
109
+ onTouchEnd={cancel}
110
+ onTouchCancel={cancel}>
111
+ {children}
112
+ </View>
113
+ );
114
+ }
@@ -6,7 +6,7 @@ import React, {
6
6
  useRef,
7
7
  useState,
8
8
  } from 'react';
9
- import { Pressable, StyleSheet, View, useWindowDimensions } from 'react-native';
9
+ import { Pressable, StyleSheet, View, useWindowDimensions, type ScrollView } from 'react-native';
10
10
  import Animated, {
11
11
  Easing,
12
12
  useAnimatedStyle,
@@ -37,6 +37,7 @@ import {
37
37
  import { ModalPortal } from './modal-portal';
38
38
  import type { SettingsPalette } from './palette';
39
39
  import type { SettingsModalProps, SettingsNavGroup, SettingsNavItem } from './types';
40
+ import { useKeyboardReveal } from './use-keyboard-reveal';
40
41
  import { IS_WEB, useSettingsWebCss } from './web-css';
41
42
 
42
43
  /**
@@ -722,9 +723,15 @@ function PageScroller({
722
723
  const onScroll = useAnimatedScrollHandler((event) => {
723
724
  scrollY.value = Math.max(0, event.contentOffset.y);
724
725
  }, [scrollY]);
726
+ const scrollRef = useRef<ScrollView>(null);
727
+ // The keyboard slides over an edge-to-edge modal: pad by it, and bring the
728
+ // focused field up above it.
729
+ const keyboardHeight = useKeyboardReveal(scrollRef, scrollY, !IS_WEB);
725
730
  return (
726
731
  <Animated.ScrollView
732
+ ref={scrollRef}
727
733
  style={styles.scroll}
734
+ keyboardShouldPersistTaps="handled"
728
735
  stickyHeaderIndices={IS_WEB ? undefined : [0]}
729
736
  onScroll={onScroll}
730
737
  scrollEventThrottle={16}
@@ -737,7 +744,7 @@ function PageScroller({
737
744
  style={{
738
745
  paddingLeft: inset,
739
746
  paddingRight: inset,
740
- paddingBottom: inset,
747
+ paddingBottom: inset + keyboardHeight,
741
748
  paddingTop: insetTop,
742
749
  }}
743
750
  >
@@ -0,0 +1,62 @@
1
+ import { useEffect, useRef, useState, type RefObject } from 'react';
2
+ import { Keyboard, Platform, TextInput, type KeyboardEvent, type ScrollView } from 'react-native';
3
+
4
+ /** Clearance kept between the focused field and the keyboard's top, px. */
5
+ export const KEYBOARD_REVEAL_MARGIN = 16;
6
+
7
+ type Measurable = { measureInWindow?: (cb: (x: number, y: number, width: number, height: number) => void) => void };
8
+
9
+ /**
10
+ * Keep a settings page's focused field above the software keyboard — native
11
+ * only; on web the browser scrolls a focused field into view itself.
12
+ *
13
+ * The settings modal draws edge to edge, so the keyboard slides OVER it rather
14
+ * than resizing it: a field low on a page (Personalization's last input, a
15
+ * Pixel 8a at Android 16) stayed behind the keyboard with nothing to scroll it
16
+ * out. This returns the keyboard's height for the page to pad its content by —
17
+ * so there is room to scroll — and, when the keyboard shows, scrolls the page
18
+ * by exactly what the focused field sits below the keyboard's top.
19
+ *
20
+ * `offset` is the page's current scroll offset (its scroll handler keeps it).
21
+ */
22
+ export function useKeyboardReveal(
23
+ scrollRef: RefObject<ScrollView | null>,
24
+ offset: { value: number },
25
+ enabled: boolean,
26
+ ): number {
27
+ const [keyboard, setKeyboard] = useState<{ height: number; overlap: number }>({ height: 0, overlap: 0 });
28
+ const offsetRef = useRef(offset);
29
+ offsetRef.current = offset;
30
+
31
+ useEffect(() => {
32
+ if (!enabled || typeof Keyboard?.addListener !== 'function') return;
33
+ const reveal = (event: KeyboardEvent) => {
34
+ const { height, screenY } = event.endCoordinates;
35
+ const field = TextInput.State?.currentlyFocusedInput?.() as Measurable | null | undefined;
36
+ if (!field || typeof field.measureInWindow !== 'function') {
37
+ setKeyboard({ height, overlap: 0 });
38
+ return;
39
+ }
40
+ field.measureInWindow((_x, y, _width, fieldHeight) => {
41
+ setKeyboard({ height, overlap: Math.max(0, y + fieldHeight + KEYBOARD_REVEAL_MARGIN - screenY) });
42
+ });
43
+ };
44
+ // iOS announces the keyboard before it moves; Android only once it is up.
45
+ const show = Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow', reveal);
46
+ const hide = Keyboard.addListener(Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide', () =>
47
+ setKeyboard({ height: 0, overlap: 0 }),
48
+ );
49
+ return () => {
50
+ show.remove();
51
+ hide.remove();
52
+ };
53
+ }, [enabled]);
54
+
55
+ // After the commit that pads the page by the keyboard: before it, the scroll
56
+ // range ends where the keyboard begins and the scroll would be clamped short.
57
+ useEffect(() => {
58
+ if (keyboard.overlap > 0) scrollRef.current?.scrollTo({ y: offsetRef.current.value + keyboard.overlap, animated: true });
59
+ }, [keyboard, scrollRef]);
60
+
61
+ return enabled ? keyboard.height : 0;
62
+ }
@@ -67,7 +67,11 @@ const SidebarItemComponent: React.FC<SidebarItemProps> = ({
67
67
  // Not clipped: the collapsed badge hangs off the square's top-end corner.
68
68
  // The content clips itself instead, so the label can't spill mid-morph.
69
69
  overflow: 'visible',
70
- height: metrics.row.square,
70
+ // Collapsed it is a square. Expanded it is at LEAST the square: at the
71
+ // default type scale the label's line fits exactly, and at the largest
72
+ // system font a fixed height clipped it top and bottom (Android 16).
73
+ height: collapsed ? metrics.row.square : undefined,
74
+ minHeight: metrics.row.square,
71
75
  paddingLeft: metrics.row.padding,
72
76
  paddingRight: metrics.row.padding,
73
77
  paddingTop: metrics.row.padding,