@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.
- package/README.md +18 -3
- package/dist/BlarProvider.d.ts +2 -0
- package/dist/actions/scrolling.d.ts +3 -1
- package/dist/actions/select.d.ts +16 -0
- package/dist/actions/text-input.d.ts +1 -0
- package/dist/actions/types.d.ts +3 -0
- package/dist/agent/compose.d.ts +1 -0
- package/dist/agent-mode/keyboard-guard.d.ts +13 -0
- package/dist/backend/app-info.d.ts +1 -0
- package/dist/fiber/text.d.ts +1 -0
- package/dist/snapshot/capture.d.ts +1 -0
- package/dist/snapshot/view.d.ts +3 -0
- package/dist/snapshot/visibility.d.ts +1 -1
- package/dist/snapshot/wrappers.d.ts +0 -2
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/BlarProvider.tsx +13 -0
- package/src/actions/click.ts +2 -1
- package/src/actions/executor.ts +8 -2
- package/src/actions/landing.ts +2 -2
- package/src/actions/parse.ts +2 -0
- package/src/actions/scrolling.ts +3 -20
- package/src/actions/select.ts +176 -0
- package/src/actions/text-input.ts +30 -1
- package/src/actions/types.ts +1 -0
- package/src/agent/compose.ts +2 -0
- package/src/agent/controller.ts +3 -1
- package/src/agent-mode/keyboard-guard.ts +33 -0
- package/src/backend/app-info.ts +3 -0
- package/src/chat/ChatSheet.tsx +29 -9
- package/src/chat/ChatView.tsx +1 -1
- package/src/chat/widget-bundle.generated.ts +2 -2
- package/src/fiber/text.ts +2 -0
- package/src/snapshot/capture.ts +9 -6
- package/src/snapshot/collect.ts +1 -2
- package/src/snapshot/render.ts +4 -2
- package/src/snapshot/view.ts +34 -0
- package/src/snapshot/visibility.ts +11 -10
- package/src/snapshot/wrappers.ts +0 -13
- package/src/version.ts +1 -1
package/src/agent/compose.ts
CHANGED
|
@@ -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);
|
package/src/agent/controller.ts
CHANGED
|
@@ -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:
|
|
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
|
+
}
|
package/src/backend/app-info.ts
CHANGED
|
@@ -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(),
|
package/src/chat/ChatSheet.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from 'react';
|
|
2
|
-
import { Animated, BackHandler, Dimensions, Easing,
|
|
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
|
-
<
|
|
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
|
-
</
|
|
85
|
+
</Animated.View>
|
|
66
86
|
</View>
|
|
67
87
|
);
|
|
68
88
|
}
|
package/src/chat/ChatView.tsx
CHANGED
|
@@ -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
|
|
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.
|