@blario/react-native 0.1.4 → 0.1.6

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 (40) hide show
  1. package/README.md +18 -3
  2. package/dist/BlarProvider.d.ts +2 -0
  3. package/dist/actions/scrolling.d.ts +3 -1
  4. package/dist/actions/select.d.ts +16 -0
  5. package/dist/actions/text-input.d.ts +1 -0
  6. package/dist/actions/types.d.ts +3 -0
  7. package/dist/agent/compose.d.ts +1 -0
  8. package/dist/agent-mode/keyboard-guard.d.ts +13 -0
  9. package/dist/backend/app-info.d.ts +1 -0
  10. package/dist/fiber/text.d.ts +1 -0
  11. package/dist/snapshot/capture.d.ts +1 -0
  12. package/dist/snapshot/view.d.ts +3 -0
  13. package/dist/snapshot/visibility.d.ts +1 -1
  14. package/dist/snapshot/wrappers.d.ts +0 -2
  15. package/dist/version.d.ts +1 -1
  16. package/package.json +1 -1
  17. package/src/BlarProvider.tsx +13 -0
  18. package/src/actions/click.ts +2 -1
  19. package/src/actions/executor.ts +8 -2
  20. package/src/actions/landing.ts +2 -2
  21. package/src/actions/parse.ts +2 -0
  22. package/src/actions/scrolling.ts +3 -20
  23. package/src/actions/select.ts +176 -0
  24. package/src/actions/text-input.ts +30 -1
  25. package/src/actions/types.ts +1 -0
  26. package/src/agent/compose.ts +2 -0
  27. package/src/agent/controller.ts +3 -1
  28. package/src/agent-mode/keyboard-guard.ts +33 -0
  29. package/src/backend/app-info.ts +3 -0
  30. package/src/chat/ChatSheet.tsx +29 -9
  31. package/src/chat/ChatView.tsx +1 -1
  32. package/src/chat/widget-bundle.generated.ts +2 -2
  33. package/src/fiber/text.ts +2 -0
  34. package/src/snapshot/capture.ts +9 -6
  35. package/src/snapshot/collect.ts +1 -2
  36. package/src/snapshot/render.ts +4 -2
  37. package/src/snapshot/view.ts +34 -0
  38. package/src/snapshot/visibility.ts +11 -10
  39. package/src/snapshot/wrappers.ts +0 -13
  40. package/src/version.ts +1 -1
@@ -20,6 +20,7 @@ export interface AgentEnvironment {
20
20
  transport: AgentTransport;
21
21
  getAnchorFiber: () => Fiber | null;
22
22
  getViewport: () => Size;
23
+ getWindow: () => Size;
23
24
  getNavigationRef: () => NavigationRefLike | null;
24
25
  getCaptureTarget: () => unknown;
25
26
  reading: ReadingPort;
@@ -40,6 +41,7 @@ export function createAgentController(environment: AgentEnvironment): AgentContr
40
41
  getAnchorFiber: environment.getAnchorFiber,
41
42
  registry,
42
43
  getViewport: environment.getViewport,
44
+ getWindow: environment.getWindow,
43
45
  getExtraRoutes: (): string[] => navigation.listRefRoutePaths(),
44
46
  });
45
47
  const commitWatch = new CommitWatch(environment.getAnchorFiber, environment.onCommit);
@@ -14,6 +14,8 @@ import { handleElementContext } from './handlers/element-context.ts';
14
14
  import { handleScreenshot } from './handlers/screenshot.ts';
15
15
  import { handleTextDom, readTextDom } from './handlers/text-dom.ts';
16
16
 
17
+ const MESSAGE_TEXT_DOM_MAX_CHARS = 100_000;
18
+
17
19
  export interface ControllerDependencies {
18
20
  transport: AgentTransport;
19
21
  commitWatch: CommitWatch;
@@ -44,7 +46,7 @@ export class AgentController {
44
46
 
45
47
  async readMessageMetadata(options: { includeTextDom: boolean; tabId: string }): Promise<MessageMetadata> {
46
48
  const snapshot = await this.context.capturer.capture('peek');
47
- const textDom = options.includeTextDom ? await readTextDom(this.context, { viewportOnly: false, maxChars: 0 }) : null;
49
+ const textDom = options.includeTextDom ? await readTextDom(this.context, { viewportOnly: false, maxChars: MESSAGE_TEXT_DOM_MAX_CHARS }) : null;
48
50
  return buildMessageMetadata({
49
51
  screen: snapshot.screen,
50
52
  tabId: options.tabId,
@@ -0,0 +1,33 @@
1
+ export interface KeyboardPort {
2
+ addListener: (event: 'keyboardWillShow' | 'keyboardDidShow', listener: () => void) => { remove: () => void };
3
+ dismiss: () => void;
4
+ }
5
+
6
+ // Nobody types during a run, and an open keyboard only covers what the agent is doing: an input that focuses itself would
7
+ // otherwise leave it up. Android has no keyboardWillShow, so it hides a beat later. The agent's own typing never opens it
8
+ // (focusWithoutKeyboard); ending the run closes the field it left focused, so the next tap opens a normal keyboard.
9
+ export class KeyboardGuard {
10
+ private subscriptions: Array<{ remove: () => void }> = [];
11
+
12
+ constructor(
13
+ private readonly keyboard: KeyboardPort,
14
+ private readonly onRelease: () => void = () => undefined,
15
+ ) {}
16
+
17
+ setActive(active: boolean): void {
18
+ if (!active) {
19
+ if (this.subscriptions.length > 0) {
20
+ this.subscriptions.splice(0).forEach((subscription) => subscription.remove());
21
+ this.keyboard.dismiss();
22
+ }
23
+ this.onRelease();
24
+ return;
25
+ }
26
+ if (this.subscriptions.length > 0) {
27
+ return;
28
+ }
29
+ const dismiss = (): void => this.keyboard.dismiss();
30
+ this.subscriptions = [this.keyboard.addListener('keyboardWillShow', dismiss), this.keyboard.addListener('keyboardDidShow', dismiss)];
31
+ dismiss();
32
+ }
33
+ }
@@ -1,9 +1,11 @@
1
1
  import { Platform } from 'react-native';
2
2
 
3
3
  import { isRecord } from '../fiber/narrow.ts';
4
+ import { SDK_VERSION } from '../version.ts';
4
5
 
5
6
  export interface AppInfo {
6
7
  platform: 'react-native';
8
+ sdk_version: string;
7
9
  os: string;
8
10
  os_version: string;
9
11
  rn_version: string;
@@ -32,6 +34,7 @@ export function detectNewArchitecture(markers: GlobalMarkers): boolean {
32
34
  export function readAppInfo(): AppInfo {
33
35
  return {
34
36
  platform: 'react-native',
37
+ sdk_version: SDK_VERSION,
35
38
  os: Platform.OS,
36
39
  os_version: String(Platform.Version),
37
40
  rn_version: readReactNativeVersion(),
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useRef } from 'react';
2
- import { Animated, BackHandler, Dimensions, Easing, KeyboardAvoidingView, Platform, Pressable, StyleSheet, View } from 'react-native';
2
+ import { Animated, BackHandler, Dimensions, Easing, Keyboard, Platform, Pressable, StyleSheet, View } from 'react-native';
3
3
 
4
4
  import { useInsets } from '../ui/useInsets.ts';
5
5
  import type { UiStrings } from '../ui/strings.ts';
@@ -16,12 +16,38 @@ export interface ChatSheetProps {
16
16
  const TOP_GAP = 12;
17
17
  const ANIMATION_MS = 240;
18
18
 
19
+ // iOS lifts the sheet over the keyboard while the chat is open. Not KeyboardAvoidingView: it reacts to every keyboard in
20
+ // the app even with the chat closed, and its LayoutAnimation applies to the host's whole next commit, which aborted a
21
+ // debug build as soon as a keyboard opened while the host mounted views. On Android the window resizes by itself.
22
+ function useKeyboardInset(active: boolean, homeIndicator: number): Animated.Value {
23
+ const inset = useRef(new Animated.Value(0)).current;
24
+ useEffect(() => {
25
+ if (!active || Platform.OS !== 'ios') {
26
+ inset.setValue(0);
27
+ return undefined;
28
+ }
29
+ const subscription = Keyboard.addListener('keyboardWillChangeFrame', (event) => {
30
+ const covered = Dimensions.get('window').height - event.endCoordinates.screenY;
31
+ Animated.timing(inset, {
32
+ // The keyboard covers the home-indicator inset, so the sheet's bottom padding sits under it, not above it.
33
+ toValue: Math.max(0, covered - homeIndicator),
34
+ duration: event.duration,
35
+ easing: Easing.out(Easing.cubic),
36
+ useNativeDriver: false,
37
+ }).start();
38
+ });
39
+ return () => subscription.remove();
40
+ }, [active, homeIndicator, inset]);
41
+ return inset;
42
+ }
43
+
19
44
  export function ChatSheet({ mode, mounted, strings, onClose, children }: ChatSheetProps): React.JSX.Element | null {
20
45
  const insets = useInsets();
21
46
  const windowHeight = Dimensions.get('window').height;
22
47
  const sheetHeight = windowHeight - insets.top - TOP_GAP;
23
48
  const progress = useRef(new Animated.Value(mode === 'open' ? 1 : 0)).current;
24
49
  const isOpen = mode === 'open';
50
+ const keyboardInset = useKeyboardInset(isOpen, insets.bottom);
25
51
 
26
52
  useEffect(() => {
27
53
  Animated.timing(progress, { toValue: isOpen ? 1 : 0, duration: ANIMATION_MS, easing: Easing.out(Easing.cubic), useNativeDriver: true }).start();
@@ -48,13 +74,7 @@ export function ChatSheet({ mode, mounted, strings, onClose, children }: ChatShe
48
74
  <Animated.View style={[styles.backdrop, { opacity: backdropOpacity }]}>
49
75
  <Pressable accessibilityLabel={strings.chatClose} style={StyleSheet.absoluteFill} onPress={onClose} />
50
76
  </Animated.View>
51
- <KeyboardAvoidingView
52
- style={[styles.container, { paddingTop: insets.top + TOP_GAP }]}
53
- behavior={Platform.OS === 'ios' ? 'padding' : undefined}
54
- // The keyboard covers the home-indicator inset, so the sheet's bottom padding sits under it, not above it.
55
- keyboardVerticalOffset={-insets.bottom}
56
- pointerEvents="box-none"
57
- >
77
+ <Animated.View style={[styles.container, { paddingTop: insets.top + TOP_GAP, paddingBottom: keyboardInset }]} pointerEvents="box-none">
58
78
  <Animated.View
59
79
  accessibilityElementsHidden={!isOpen}
60
80
  importantForAccessibility={isOpen ? 'yes' : 'no-hide-descendants'}
@@ -62,7 +82,7 @@ export function ChatSheet({ mode, mounted, strings, onClose, children }: ChatShe
62
82
  >
63
83
  {children}
64
84
  </Animated.View>
65
- </KeyboardAvoidingView>
85
+ </Animated.View>
66
86
  </View>
67
87
  );
68
88
  }
@@ -107,7 +107,7 @@ export function ChatView({ shell, unavailableText, getMetadata, getProof, onClos
107
107
  mixedContentMode="always"
108
108
  setSupportMultipleWindows={false}
109
109
  keyboardDisplayRequiresUserAction={false}
110
- // The sheet's KeyboardAvoidingView already lifts the chat above the keyboard. WKWebView would also scroll its
110
+ // The sheet already lifts the chat above the keyboard. WKWebView would also scroll its
111
111
  // own page up to reveal the composer, pushing the widget out of the sheet; with scrolling off it resets that
112
112
  // offset (the message list is an overflow container and keeps scrolling). The form bar (^ v Done) is for
113
113
  // multi-field forms, not a one-field composer.