@blario/react-native 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +378 -0
- package/dist/BlarProvider.d.ts +74 -0
- package/dist/actions/batched.d.ts +1 -0
- package/dist/actions/click.d.ts +4 -0
- package/dist/actions/commit-watch.d.ts +15 -0
- package/dist/actions/events.d.ts +35 -0
- package/dist/actions/executor.d.ts +29 -0
- package/dist/actions/highlight.d.ts +4 -0
- package/dist/actions/interaction-guards.d.ts +9 -0
- package/dist/actions/landing.d.ts +11 -0
- package/dist/actions/navigation-driver.d.ts +22 -0
- package/dist/actions/parse.d.ts +4 -0
- package/dist/actions/result.d.ts +4 -0
- package/dist/actions/scrolling.d.ts +12 -0
- package/dist/actions/services.d.ts +20 -0
- package/dist/actions/settle.d.ts +13 -0
- package/dist/actions/switch.d.ts +3 -0
- package/dist/actions/target.d.ts +25 -0
- package/dist/actions/text-input.d.ts +9 -0
- package/dist/actions/types.d.ts +59 -0
- package/dist/agent/batch-commands.d.ts +17 -0
- package/dist/agent/compose.d.ts +23 -0
- package/dist/agent/controller.d.ts +28 -0
- package/dist/agent/frame-runner.d.ts +9 -0
- package/dist/agent/frames.d.ts +180 -0
- package/dist/agent/handlers/agent-command.d.ts +5 -0
- package/dist/agent/handlers/batch.d.ts +5 -0
- package/dist/agent/handlers/consent-screenshot.d.ts +4 -0
- package/dist/agent/handlers/context.d.ts +16 -0
- package/dist/agent/handlers/dom-state.d.ts +4 -0
- package/dist/agent/handlers/element-context.d.ts +2 -0
- package/dist/agent/handlers/screenshot.d.ts +3 -0
- package/dist/agent/handlers/text-dom.d.ts +5 -0
- package/dist/agent/stage.d.ts +13 -0
- package/dist/agent-mode/action-description.d.ts +3 -0
- package/dist/agent-mode/bubbles.d.ts +10 -0
- package/dist/agent-mode/colors.d.ts +12 -0
- package/dist/agent-mode/controller.d.ts +92 -0
- package/dist/agent-mode/cursor-follow.d.ts +12 -0
- package/dist/agent-mode/cursor.d.ts +37 -0
- package/dist/agent-mode/frames.d.ts +36 -0
- package/dist/agent-mode/haptics.d.ts +6 -0
- package/dist/agent-mode/keep-awake.d.ts +19 -0
- package/dist/agent-mode/layer-stacking.d.ts +7 -0
- package/dist/agent-mode/markdown.d.ts +19 -0
- package/dist/agent-mode/panel-geometry.d.ts +6 -0
- package/dist/agent-mode/resume.d.ts +13 -0
- package/dist/agent-mode/run-memory.d.ts +19 -0
- package/dist/agent-mode/spotlight-controller.d.ts +68 -0
- package/dist/agent-mode/spotlight.d.ts +6 -0
- package/dist/agent-mode/state.d.ts +9 -0
- package/dist/agent-mode/status.d.ts +11 -0
- package/dist/agent-mode/timers.d.ts +6 -0
- package/dist/agent-mode/ui/AgentDock.d.ts +37 -0
- package/dist/agent-mode/ui/AgentLayer.d.ts +19 -0
- package/dist/agent-mode/ui/AgentLayerHost.d.ts +8 -0
- package/dist/agent-mode/ui/AgentPanel.d.ts +19 -0
- package/dist/agent-mode/ui/AgentTap.d.ts +10 -0
- package/dist/agent-mode/ui/MarkdownText.d.ts +8 -0
- package/dist/agent-mode/ui/MessageBubbles.d.ts +9 -0
- package/dist/agent-mode/ui/PendingOverlay.d.ts +5 -0
- package/dist/agent-mode/ui/SpotlightLayer.d.ts +9 -0
- package/dist/agent-mode/ui/SpotlightOverlay.d.ts +7 -0
- package/dist/agent-mode/ui/StatusCard.d.ts +14 -0
- package/dist/agent-mode/ui/TargetPulse.d.ts +9 -0
- package/dist/agent-mode/ui/TouchShield.d.ts +5 -0
- package/dist/agent-mode/ui/WarningIcon.d.ts +6 -0
- package/dist/agent-mode/ui/full-window-overlay.d.ts +5 -0
- package/dist/agent-mode/ui/layer-host.d.ts +2 -0
- package/dist/backend/api-client.d.ts +38 -0
- package/dist/backend/api-types.d.ts +25 -0
- package/dist/backend/app-info.d.ts +14 -0
- package/dist/backend/backend-transport.d.ts +70 -0
- package/dist/backend/backoff.d.ts +9 -0
- package/dist/backend/base64.d.ts +1 -0
- package/dist/backend/errors.d.ts +17 -0
- package/dist/backend/foreground.d.ts +1 -0
- package/dist/backend/health.d.ts +17 -0
- package/dist/backend/identity-proof.d.ts +23 -0
- package/dist/backend/identity.d.ts +27 -0
- package/dist/backend/message-metadata.d.ts +19 -0
- package/dist/backend/presence.d.ts +20 -0
- package/dist/backend/response-cache.d.ts +9 -0
- package/dist/backend/session-change.d.ts +2 -0
- package/dist/backend/session-socket.d.ts +49 -0
- package/dist/backend/session.d.ts +39 -0
- package/dist/backend/socket-url.d.ts +9 -0
- package/dist/backend/storage.d.ts +13 -0
- package/dist/backend/tab-id.d.ts +2 -0
- package/dist/blar.d.ts +37 -0
- package/dist/chat/ChatLayer.d.ts +29 -0
- package/dist/chat/ChatSheet.d.ts +11 -0
- package/dist/chat/ChatView.d.ts +19 -0
- package/dist/chat/Launcher.d.ts +14 -0
- package/dist/chat/bridge.d.ts +22 -0
- package/dist/chat/chat-coordinator.d.ts +57 -0
- package/dist/chat/chat-state.d.ts +20 -0
- package/dist/chat/navigation.d.ts +5 -0
- package/dist/chat/options.d.ts +22 -0
- package/dist/chat/webview.d.ts +10 -0
- package/dist/chat/widget-bundle.generated.d.ts +2 -0
- package/dist/chat/widget-shell.d.ts +24 -0
- package/dist/confirmation/port.d.ts +12 -0
- package/dist/fiber/locate.d.ts +7 -0
- package/dist/fiber/narrow.d.ts +5 -0
- package/dist/fiber/reader.d.ts +10 -0
- package/dist/fiber/text.d.ts +4 -0
- package/dist/fiber/types.d.ts +21 -0
- package/dist/host/border-radius.d.ts +1 -0
- package/dist/host/instance.d.ts +9 -0
- package/dist/host/measure.d.ts +3 -0
- package/dist/host/rect.d.ts +22 -0
- package/dist/host/visible-area.d.ts +2 -0
- package/dist/index.d.ts +10 -0
- package/dist/nudge/NudgeBubble.d.ts +15 -0
- package/dist/nudge/nudge.d.ts +23 -0
- package/dist/optional/expo-haptics.d.ts +1 -0
- package/dist/optional/expo-keep-awake.d.ts +1 -0
- package/dist/optional/react-native-haptic-feedback.d.ts +1 -0
- package/dist/optional/react-native-keep-awake.d.ts +1 -0
- package/dist/overlay/AgentOverlay.d.ts +5 -0
- package/dist/screenshot/capture-backend.d.ts +6 -0
- package/dist/screenshot/screenshotter.d.ts +18 -0
- package/dist/screenshot/skia.d.ts +35 -0
- package/dist/screenshot/view-shot.d.ts +2 -0
- package/dist/snapshot/capture.d.ts +36 -0
- package/dist/snapshot/classify.d.ts +11 -0
- package/dist/snapshot/collect.d.ts +27 -0
- package/dist/snapshot/element-context.d.ts +9 -0
- package/dist/snapshot/icon.d.ts +2 -0
- package/dist/snapshot/kinds.d.ts +3 -0
- package/dist/snapshot/modals.d.ts +2 -0
- package/dist/snapshot/navigation.d.ts +14 -0
- package/dist/snapshot/ordering.d.ts +2 -0
- package/dist/snapshot/properties.d.ts +14 -0
- package/dist/snapshot/registry.d.ts +16 -0
- package/dist/snapshot/render.d.ts +10 -0
- package/dist/snapshot/route-summary.d.ts +3 -0
- package/dist/snapshot/scroll.d.ts +15 -0
- package/dist/snapshot/text-layout.d.ts +8 -0
- package/dist/snapshot/view.d.ts +30 -0
- package/dist/snapshot/visibility.d.ts +17 -0
- package/dist/snapshot/wrappers.d.ts +11 -0
- package/dist/telemetry/SdkErrorBoundary.d.ts +15 -0
- package/dist/telemetry/build-info.generated.d.ts +2 -0
- package/dist/telemetry/envelope.d.ts +53 -0
- package/dist/telemetry/reporter.d.ts +33 -0
- package/dist/telemetry/scrub.d.ts +1 -0
- package/dist/telemetry/sdk-reporter.d.ts +5 -0
- package/dist/transport/connection-options.d.ts +18 -0
- package/dist/transport/relay-transport.d.ts +17 -0
- package/dist/transport/transport.d.ts +7 -0
- package/dist/ui/insets.d.ts +8 -0
- package/dist/ui/strings.d.ts +33 -0
- package/dist/ui/useInsets.d.ts +2 -0
- package/dist/version.d.ts +1 -0
- package/package.json +84 -3
- package/src/BlarProvider.tsx +462 -0
- package/src/actions/batched.ts +16 -0
- package/src/actions/click.ts +54 -0
- package/src/actions/commit-watch.ts +86 -0
- package/src/actions/events.ts +58 -0
- package/src/actions/executor.ts +147 -0
- package/src/actions/highlight.ts +13 -0
- package/src/actions/interaction-guards.ts +44 -0
- package/src/actions/landing.ts +26 -0
- package/src/actions/navigation-driver.ts +131 -0
- package/src/actions/parse.ts +82 -0
- package/src/actions/result.ts +14 -0
- package/src/actions/scrolling.ts +112 -0
- package/src/actions/services.ts +24 -0
- package/src/actions/settle.ts +40 -0
- package/src/actions/switch.ts +31 -0
- package/src/actions/target.ts +77 -0
- package/src/actions/text-input.ts +82 -0
- package/src/actions/types.ts +52 -0
- package/src/agent/batch-commands.ts +33 -0
- package/src/agent/compose.ts +67 -0
- package/src/agent/controller.ts +106 -0
- package/src/agent/frame-runner.ts +27 -0
- package/src/agent/frames.ts +193 -0
- package/src/agent/handlers/agent-command.ts +74 -0
- package/src/agent/handlers/batch.ts +72 -0
- package/src/agent/handlers/consent-screenshot.ts +44 -0
- package/src/agent/handlers/context.ts +17 -0
- package/src/agent/handlers/dom-state.ts +19 -0
- package/src/agent/handlers/element-context.ts +9 -0
- package/src/agent/handlers/screenshot.ts +24 -0
- package/src/agent/handlers/text-dom.ts +17 -0
- package/src/agent/stage.ts +22 -0
- package/src/agent-mode/action-description.ts +25 -0
- package/src/agent-mode/bubbles.ts +31 -0
- package/src/agent-mode/colors.ts +40 -0
- package/src/agent-mode/controller.ts +459 -0
- package/src/agent-mode/cursor-follow.ts +43 -0
- package/src/agent-mode/cursor.ts +118 -0
- package/src/agent-mode/frames.ts +70 -0
- package/src/agent-mode/haptics.ts +49 -0
- package/src/agent-mode/keep-awake.ts +107 -0
- package/src/agent-mode/layer-stacking.ts +16 -0
- package/src/agent-mode/markdown.ts +84 -0
- package/src/agent-mode/panel-geometry.ts +27 -0
- package/src/agent-mode/resume.ts +16 -0
- package/src/agent-mode/run-memory.ts +65 -0
- package/src/agent-mode/spotlight-controller.ts +255 -0
- package/src/agent-mode/spotlight.ts +14 -0
- package/src/agent-mode/state.ts +36 -0
- package/src/agent-mode/status.ts +13 -0
- package/src/agent-mode/timers.ts +11 -0
- package/src/agent-mode/ui/AgentDock.tsx +285 -0
- package/src/agent-mode/ui/AgentLayer.tsx +123 -0
- package/src/agent-mode/ui/AgentLayerHost.tsx +37 -0
- package/src/agent-mode/ui/AgentPanel.tsx +187 -0
- package/src/agent-mode/ui/AgentTap.tsx +190 -0
- package/src/agent-mode/ui/MarkdownText.tsx +55 -0
- package/src/agent-mode/ui/MessageBubbles.tsx +58 -0
- package/src/agent-mode/ui/PendingOverlay.tsx +32 -0
- package/src/agent-mode/ui/SpotlightLayer.tsx +60 -0
- package/src/agent-mode/ui/SpotlightOverlay.tsx +35 -0
- package/src/agent-mode/ui/StatusCard.tsx +137 -0
- package/src/agent-mode/ui/TargetPulse.tsx +74 -0
- package/src/agent-mode/ui/TouchShield.tsx +22 -0
- package/src/agent-mode/ui/WarningIcon.tsx +32 -0
- package/src/agent-mode/ui/full-window-overlay.ts +16 -0
- package/src/agent-mode/ui/layer-host.ts +11 -0
- package/src/backend/api-client.ts +210 -0
- package/src/backend/api-types.ts +56 -0
- package/src/backend/app-info.ts +40 -0
- package/src/backend/backend-transport.ts +338 -0
- package/src/backend/backoff.ts +16 -0
- package/src/backend/base64.ts +25 -0
- package/src/backend/errors.ts +32 -0
- package/src/backend/foreground.ts +10 -0
- package/src/backend/health.ts +64 -0
- package/src/backend/identity-proof.ts +151 -0
- package/src/backend/identity.ts +31 -0
- package/src/backend/message-metadata.ts +28 -0
- package/src/backend/presence.ts +95 -0
- package/src/backend/response-cache.ts +45 -0
- package/src/backend/session-change.ts +14 -0
- package/src/backend/session-socket.ts +292 -0
- package/src/backend/session.ts +188 -0
- package/src/backend/socket-url.ts +25 -0
- package/src/backend/storage.ts +49 -0
- package/src/backend/tab-id.ts +24 -0
- package/src/blar.ts +100 -0
- package/src/chat/ChatLayer.tsx +109 -0
- package/src/chat/ChatSheet.tsx +82 -0
- package/src/chat/ChatView.tsx +127 -0
- package/src/chat/Launcher.tsx +72 -0
- package/src/chat/bridge.ts +56 -0
- package/src/chat/chat-coordinator.ts +189 -0
- package/src/chat/chat-state.ts +35 -0
- package/src/chat/navigation.ts +15 -0
- package/src/chat/options.ts +39 -0
- package/src/chat/webview.ts +23 -0
- package/src/chat/widget-bundle.generated.ts +2 -0
- package/src/chat/widget-shell.ts +170 -0
- package/src/confirmation/port.ts +15 -0
- package/src/fiber/locate.ts +41 -0
- package/src/fiber/narrow.ts +15 -0
- package/src/fiber/reader.ts +98 -0
- package/src/fiber/text.ts +52 -0
- package/src/fiber/types.ts +24 -0
- package/src/host/border-radius.ts +11 -0
- package/src/host/instance.ts +30 -0
- package/src/host/measure.ts +25 -0
- package/src/host/rect.ts +45 -0
- package/src/host/visible-area.ts +5 -0
- package/src/index.ts +10 -0
- package/src/nudge/NudgeBubble.tsx +117 -0
- package/src/nudge/nudge.ts +100 -0
- package/src/optional/expo-haptics.ts +11 -0
- package/src/optional/expo-keep-awake.ts +11 -0
- package/src/optional/react-native-haptic-feedback.ts +11 -0
- package/src/optional/react-native-keep-awake.ts +11 -0
- package/src/overlay/AgentOverlay.tsx +16 -0
- package/src/screenshot/capture-backend.ts +7 -0
- package/src/screenshot/screenshotter.ts +39 -0
- package/src/screenshot/skia.ts +107 -0
- package/src/screenshot/view-shot.ts +41 -0
- package/src/snapshot/capture.ts +163 -0
- package/src/snapshot/classify.ts +40 -0
- package/src/snapshot/collect.ts +136 -0
- package/src/snapshot/element-context.ts +66 -0
- package/src/snapshot/icon.ts +21 -0
- package/src/snapshot/kinds.ts +10 -0
- package/src/snapshot/modals.ts +21 -0
- package/src/snapshot/navigation.ts +47 -0
- package/src/snapshot/ordering.ts +26 -0
- package/src/snapshot/properties.ts +71 -0
- package/src/snapshot/registry.ts +33 -0
- package/src/snapshot/render.ts +140 -0
- package/src/snapshot/route-summary.ts +15 -0
- package/src/snapshot/scroll.ts +90 -0
- package/src/snapshot/text-layout.ts +59 -0
- package/src/snapshot/view.ts +87 -0
- package/src/snapshot/visibility.ts +63 -0
- package/src/snapshot/wrappers.ts +69 -0
- package/src/telemetry/SdkErrorBoundary.tsx +28 -0
- package/src/telemetry/build-info.generated.ts +2 -0
- package/src/telemetry/envelope.ts +83 -0
- package/src/telemetry/reporter.ts +170 -0
- package/src/telemetry/scrub.ts +10 -0
- package/src/telemetry/sdk-reporter.ts +37 -0
- package/src/transport/connection-options.ts +65 -0
- package/src/transport/relay-transport.ts +82 -0
- package/src/transport/transport.ts +8 -0
- package/src/ui/insets.ts +31 -0
- package/src/ui/strings.ts +102 -0
- package/src/ui/useInsets.ts +10 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
export interface ShellIdentity {
|
|
2
|
+
externalId: string;
|
|
3
|
+
email?: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
phone?: string;
|
|
6
|
+
contactCompany?: string;
|
|
7
|
+
contactCompanyExternalId?: string;
|
|
8
|
+
country?: string;
|
|
9
|
+
customFields?: Record<string, string | number | boolean>;
|
|
10
|
+
identityToken?: string;
|
|
11
|
+
identitySignature?: string;
|
|
12
|
+
providesProof: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface WidgetShellOptions {
|
|
16
|
+
bundle: string | null;
|
|
17
|
+
scriptUrl: string | null;
|
|
18
|
+
publishableKey: string;
|
|
19
|
+
apiBaseUrl: string;
|
|
20
|
+
language: 'es' | 'en';
|
|
21
|
+
tabId: string;
|
|
22
|
+
primaryColor: string | null;
|
|
23
|
+
identity: ShellIdentity | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const MESSAGE_CREATE_PATH = '/api/ai/contact-sessions/\\\\d+/widget/messages/create/';
|
|
27
|
+
|
|
28
|
+
function safeJson(value: unknown): string {
|
|
29
|
+
return JSON.stringify(value).replace(/</g, '\\u003c').replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function escapeScriptText(text: string): string {
|
|
33
|
+
return text.replace(/<\/(script)/gi, '<\\/$1').replace(/<!--/g, '<\\!--');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const PAGE_PRELUDE = `
|
|
37
|
+
(function () {
|
|
38
|
+
var boot = JSON.parse(document.getElementById('blar-boot').textContent);
|
|
39
|
+
try { sessionStorage.setItem('blar_tab_id', boot.tabId); } catch (error) {}
|
|
40
|
+
|
|
41
|
+
function post(message) {
|
|
42
|
+
if (window.ReactNativeWebView) window.ReactNativeWebView.postMessage(JSON.stringify(message));
|
|
43
|
+
}
|
|
44
|
+
window.__blarPost = post;
|
|
45
|
+
|
|
46
|
+
var pending = {};
|
|
47
|
+
var counter = 0;
|
|
48
|
+
function callNative(name, args) {
|
|
49
|
+
return new Promise(function (resolve, reject) {
|
|
50
|
+
var id = 'c' + (++counter);
|
|
51
|
+
var timer = setTimeout(function () { delete pending[id]; reject(new Error('native call timed out: ' + name)); }, 8000);
|
|
52
|
+
pending[id] = { resolve: resolve, reject: reject, timer: timer };
|
|
53
|
+
post({ kind: 'call', id: id, name: name, args: args || {} });
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
window.__blarCallNative = callNative;
|
|
57
|
+
window.__blarNative = {
|
|
58
|
+
resolve: function (id, value, error) {
|
|
59
|
+
var entry = pending[id];
|
|
60
|
+
if (!entry) return;
|
|
61
|
+
clearTimeout(entry.timer);
|
|
62
|
+
delete pending[id];
|
|
63
|
+
if (error) entry.reject(new Error(error)); else entry.resolve(value);
|
|
64
|
+
},
|
|
65
|
+
event: function (name, payload) {
|
|
66
|
+
window.dispatchEvent(new CustomEvent('blar-native', { detail: { name: name, payload: payload } }));
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
var nativeFetch = window.fetch.bind(window);
|
|
71
|
+
var messageCreate = new RegExp(boot.messageCreatePath + '$');
|
|
72
|
+
window.fetch = function (input, init) {
|
|
73
|
+
var url = typeof input === 'string' ? input : (input && input.url) || '';
|
|
74
|
+
var method = ((init && init.method) || (input && input.method) || 'GET').toUpperCase();
|
|
75
|
+
if (method !== 'POST' || !messageCreate.test(url.split('?')[0]) || !init || typeof init.body !== 'string') {
|
|
76
|
+
return nativeFetch(input, init);
|
|
77
|
+
}
|
|
78
|
+
return callNative('metadata', { includeTextDom: true }).then(function (metadata) {
|
|
79
|
+
var body = JSON.parse(init.body);
|
|
80
|
+
body.metadata = Object.assign({}, body.metadata || {}, metadata || {});
|
|
81
|
+
return nativeFetch(input, Object.assign({}, init, { body: JSON.stringify(body) }));
|
|
82
|
+
}, function () {
|
|
83
|
+
return nativeFetch(input, init);
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
['error', 'unhandledrejection'].forEach(function (type) {
|
|
88
|
+
window.addEventListener(type, function (event) {
|
|
89
|
+
var reason = type === 'error' ? (event.message || '') : String(event.reason && event.reason.message || event.reason);
|
|
90
|
+
post({ kind: 'log', level: 'error', text: type + ': ' + reason });
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
})();
|
|
94
|
+
`;
|
|
95
|
+
|
|
96
|
+
const PAGE_START = `
|
|
97
|
+
(function () {
|
|
98
|
+
var boot = JSON.parse(document.getElementById('blar-boot').textContent);
|
|
99
|
+
var identity = boot.identity;
|
|
100
|
+
var widgetIdentity = null;
|
|
101
|
+
if (identity) {
|
|
102
|
+
widgetIdentity = {
|
|
103
|
+
externalId: identity.externalId, email: identity.email, name: identity.name, phone: identity.phone,
|
|
104
|
+
contactCompany: identity.contactCompany, contactCompanyExternalId: identity.contactCompanyExternalId,
|
|
105
|
+
customFields: identity.country ? Object.assign({ country: identity.country }, identity.customFields || {}) : identity.customFields,
|
|
106
|
+
identityToken: identity.identityToken, identitySignature: identity.identitySignature
|
|
107
|
+
};
|
|
108
|
+
if (identity.providesProof) {
|
|
109
|
+
widgetIdentity.getProof = function () { return window.__blarCallNative('proof', {}); };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
var config = {
|
|
113
|
+
publishableKey: boot.publishableKey, apiBaseUrl: boot.apiBaseUrl, language: boot.language,
|
|
114
|
+
messagesOnly: true, autoOpen: true, startHidden: true, enableSound: false, showBranding: false,
|
|
115
|
+
agentExecution: false,
|
|
116
|
+
onClose: function () { window.__blarPost({ kind: 'closed' }); },
|
|
117
|
+
onSessionChange: function (sessionId) {
|
|
118
|
+
window.__blarPost({ kind: 'session', sessionId: typeof sessionId === 'number' ? sessionId : null });
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
if (boot.primaryColor) config.primaryColor = boot.primaryColor;
|
|
122
|
+
if (widgetIdentity) config.identity = widgetIdentity;
|
|
123
|
+
var widget = window.initSupportWidget(config);
|
|
124
|
+
window.__blarWidget = widget;
|
|
125
|
+
window.addEventListener('blar-native', function (event) {
|
|
126
|
+
var detail = event.detail || {};
|
|
127
|
+
var payload = detail.payload || {};
|
|
128
|
+
if (detail.name === 'open') {
|
|
129
|
+
Promise.resolve(widget.openChat()).catch(function () {});
|
|
130
|
+
} else if (detail.name === 'openSession' && typeof payload.sessionId === 'number') {
|
|
131
|
+
Promise.resolve(widget.openSession(payload.sessionId)).catch(function () {});
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
Promise.resolve(widget.openChat()).catch(function () {});
|
|
135
|
+
if (widgetIdentity) {
|
|
136
|
+
Promise.resolve(widget.setUser(widgetIdentity)).catch(function (error) {
|
|
137
|
+
window.__blarPost({ kind: 'log', level: 'error', text: 'setUser failed: ' + (error && error.message) });
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
window.__blarPost({ kind: 'ready' });
|
|
141
|
+
})();
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
export function buildWidgetShell(options: WidgetShellOptions): string {
|
|
145
|
+
const boot = {
|
|
146
|
+
publishableKey: options.publishableKey,
|
|
147
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
148
|
+
language: options.language,
|
|
149
|
+
tabId: options.tabId,
|
|
150
|
+
primaryColor: options.primaryColor,
|
|
151
|
+
identity: options.identity,
|
|
152
|
+
messageCreatePath: MESSAGE_CREATE_PATH,
|
|
153
|
+
};
|
|
154
|
+
const widgetScript = options.scriptUrl !== null
|
|
155
|
+
? `<script src="${options.scriptUrl}"></script>`
|
|
156
|
+
: `<script>${escapeScriptText(options.bundle ?? '')}</script>`;
|
|
157
|
+
return [
|
|
158
|
+
'<!doctype html>',
|
|
159
|
+
'<html><head><meta charset="utf-8">',
|
|
160
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content">',
|
|
161
|
+
'<style>html,body{margin:0;height:100%;background:transparent;overflow:hidden}</style>',
|
|
162
|
+
'</head><body>',
|
|
163
|
+
`<script id="blar-boot" type="application/json">${safeJson(boot)}</script>`,
|
|
164
|
+
`<script>${PAGE_PRELUDE}</script>`,
|
|
165
|
+
widgetScript,
|
|
166
|
+
`<script>${PAGE_START}</script>`,
|
|
167
|
+
'</body></html>',
|
|
168
|
+
].join('\n');
|
|
169
|
+
}
|
|
170
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TargetView } from '../actions/target.ts';
|
|
2
|
+
import type { AgentAction, RiskLevel } from '../actions/types.ts';
|
|
3
|
+
|
|
4
|
+
export interface ConfirmationRequest {
|
|
5
|
+
riskLevel: RiskLevel;
|
|
6
|
+
action: AgentAction;
|
|
7
|
+
explanation: string | null;
|
|
8
|
+
target: TargetView | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type ConfirmationOutcome = 'confirmed' | 'declined' | 'timed_out';
|
|
12
|
+
|
|
13
|
+
export interface ConfirmationPort {
|
|
14
|
+
confirm: (request: ConfirmationRequest) => Promise<ConfirmationOutcome>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { currentHostRoot } from './reader.ts';
|
|
2
|
+
import type { Fiber } from './types.ts';
|
|
3
|
+
|
|
4
|
+
export interface LocatedFiber {
|
|
5
|
+
fiber: Fiber;
|
|
6
|
+
ancestors: Fiber[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isSameFiber(candidate: Fiber, target: Fiber): boolean {
|
|
10
|
+
return candidate === target || candidate === target.alternate;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function locateCurrent(start: Fiber, target: Fiber): LocatedFiber | null {
|
|
14
|
+
const ancestors: Fiber[] = [];
|
|
15
|
+
const visit = (node: Fiber): Fiber | null => {
|
|
16
|
+
if (isSameFiber(node, target)) {
|
|
17
|
+
return node;
|
|
18
|
+
}
|
|
19
|
+
ancestors.push(node);
|
|
20
|
+
let child = node.child;
|
|
21
|
+
while (child !== null) {
|
|
22
|
+
const found = visit(child);
|
|
23
|
+
if (found !== null) {
|
|
24
|
+
return found;
|
|
25
|
+
}
|
|
26
|
+
child = child.sibling;
|
|
27
|
+
}
|
|
28
|
+
ancestors.pop();
|
|
29
|
+
return null;
|
|
30
|
+
};
|
|
31
|
+
const fiber = visit(start);
|
|
32
|
+
return fiber === null ? null : { fiber, ancestors: [...ancestors] };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function currentRootFiber(anchor: Fiber): Fiber | null {
|
|
36
|
+
const hostRoot = currentHostRoot(anchor);
|
|
37
|
+
if (hostRoot === null) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return locateCurrent(hostRoot, anchor)?.fiber ?? null;
|
|
41
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type UnknownRecord = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
export type UnknownFunction = (...args: unknown[]) => unknown;
|
|
4
|
+
|
|
5
|
+
export function isRecord(value: unknown): value is UnknownRecord {
|
|
6
|
+
return typeof value === 'object' && value !== null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function isFunction(value: unknown): value is UnknownFunction {
|
|
10
|
+
return typeof value === 'function';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function nonEmptyString(value: unknown): string | null {
|
|
14
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
15
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { isRecord } from './narrow.ts';
|
|
2
|
+
import { type Fiber, type FiberRoot, FiberTag, type Props } from './types.ts';
|
|
3
|
+
|
|
4
|
+
const NO_PROPS: Props = {};
|
|
5
|
+
|
|
6
|
+
interface NamedType {
|
|
7
|
+
displayName?: unknown;
|
|
8
|
+
name?: unknown;
|
|
9
|
+
render?: unknown;
|
|
10
|
+
type?: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function propsOf(fiber: Fiber): Props {
|
|
14
|
+
const props = fiber.memoizedProps;
|
|
15
|
+
return isRecord(props) ? props : NO_PROPS;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function childrenOf(fiber: Fiber): Fiber[] {
|
|
19
|
+
const children: Fiber[] = [];
|
|
20
|
+
let child = fiber.child;
|
|
21
|
+
while (child !== null) {
|
|
22
|
+
children.push(child);
|
|
23
|
+
child = child.sibling;
|
|
24
|
+
}
|
|
25
|
+
return children;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function nameOfType(type: unknown): string {
|
|
29
|
+
if (typeof type === 'string') {
|
|
30
|
+
return type;
|
|
31
|
+
}
|
|
32
|
+
if (typeof type !== 'function' && !isRecord(type)) {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
const named = type as NamedType;
|
|
36
|
+
if (typeof named.displayName === 'string' && named.displayName.length > 0) {
|
|
37
|
+
return named.displayName;
|
|
38
|
+
}
|
|
39
|
+
if (typeof named.name === 'string' && named.name.length > 0) {
|
|
40
|
+
return named.name;
|
|
41
|
+
}
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function componentName(fiber: Fiber): string {
|
|
46
|
+
const direct = nameOfType(fiber.type);
|
|
47
|
+
if (direct.length > 0 || !isRecord(fiber.type)) {
|
|
48
|
+
return direct;
|
|
49
|
+
}
|
|
50
|
+
const wrapped = fiber.type as NamedType;
|
|
51
|
+
return nameOfType(wrapped.render) || nameOfType(wrapped.type);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function isHostComponent(fiber: Fiber): boolean {
|
|
55
|
+
return fiber.tag === FiberTag.HostComponent;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isHostText(fiber: Fiber): boolean {
|
|
59
|
+
return fiber.tag === FiberTag.HostText;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function hostRootOf(fiber: Fiber): Fiber | null {
|
|
63
|
+
let node: Fiber | null = fiber;
|
|
64
|
+
while (node !== null && node.tag !== FiberTag.HostRoot) {
|
|
65
|
+
node = node.return;
|
|
66
|
+
}
|
|
67
|
+
return node;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function fiberRootOf(fiber: Fiber): FiberRoot | null {
|
|
71
|
+
const hostRoot = hostRootOf(fiber);
|
|
72
|
+
const root = hostRoot === null ? null : hostRoot.stateNode;
|
|
73
|
+
return isRecord(root) && 'current' in root ? (root as unknown as FiberRoot) : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function currentHostRoot(fiber: Fiber): Fiber | null {
|
|
77
|
+
const hostRoot = hostRootOf(fiber);
|
|
78
|
+
if (hostRoot === null) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const root = hostRoot.stateNode as FiberRoot | null;
|
|
82
|
+
return root !== null && isRecord(root) ? root.current : null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function firstHostDescendant(fiber: Fiber): Fiber | null {
|
|
86
|
+
if (isHostComponent(fiber)) {
|
|
87
|
+
return fiber;
|
|
88
|
+
}
|
|
89
|
+
let child = fiber.child;
|
|
90
|
+
while (child !== null) {
|
|
91
|
+
const host = firstHostDescendant(child);
|
|
92
|
+
if (host !== null) {
|
|
93
|
+
return host;
|
|
94
|
+
}
|
|
95
|
+
child = child.sibling;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { childrenOf, isHostText } from './reader.ts';
|
|
2
|
+
import type { Fiber } from './types.ts';
|
|
3
|
+
|
|
4
|
+
const PRIVATE_USE_GLYPHS = /[-\u{F0000}-\u{10FFFF}]/gu;
|
|
5
|
+
|
|
6
|
+
export function normalizeText(value: string): string {
|
|
7
|
+
return value.replace(PRIVATE_USE_GLYPHS, '').replace(/\s+/g, ' ').trim();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function collectText(fiber: Fiber, limit: number): string {
|
|
11
|
+
const parts: string[] = [];
|
|
12
|
+
let length = 0;
|
|
13
|
+
const visit = (node: Fiber): void => {
|
|
14
|
+
if (length >= limit) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (isHostText(node)) {
|
|
18
|
+
const text = typeof node.memoizedProps === 'string' ? normalizeText(node.memoizedProps) : '';
|
|
19
|
+
if (text.length > 0) {
|
|
20
|
+
parts.push(text);
|
|
21
|
+
length += text.length + 1;
|
|
22
|
+
}
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
for (const child of childrenOf(node)) {
|
|
26
|
+
visit(child);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
visit(fiber);
|
|
30
|
+
return parts.join(' ');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function precedingText(ancestors: Fiber[], fiber: Fiber, limit: number): string {
|
|
34
|
+
let branch = fiber;
|
|
35
|
+
for (let depth = ancestors.length - 1; depth >= Math.max(0, ancestors.length - 4); depth -= 1) {
|
|
36
|
+
const parent = ancestors[depth];
|
|
37
|
+
if (parent === undefined) {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
const siblings = childrenOf(parent);
|
|
41
|
+
const index = siblings.findIndex((sibling) => sibling === branch);
|
|
42
|
+
for (let position = index - 1; position >= 0; position -= 1) {
|
|
43
|
+
const sibling = siblings[position];
|
|
44
|
+
const text = sibling === undefined ? '' : collectText(sibling, limit);
|
|
45
|
+
if (text.length > 0) {
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
branch = parent;
|
|
50
|
+
}
|
|
51
|
+
return '';
|
|
52
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type Props = Readonly<Record<string, unknown>>;
|
|
2
|
+
|
|
3
|
+
export interface Fiber {
|
|
4
|
+
tag: number;
|
|
5
|
+
key: string | null;
|
|
6
|
+
type: unknown;
|
|
7
|
+
memoizedProps: unknown;
|
|
8
|
+
stateNode: unknown;
|
|
9
|
+
child: Fiber | null;
|
|
10
|
+
sibling: Fiber | null;
|
|
11
|
+
return: Fiber | null;
|
|
12
|
+
alternate: Fiber | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface FiberRoot {
|
|
16
|
+
current: Fiber;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const FiberTag = {
|
|
20
|
+
ClassComponent: 1,
|
|
21
|
+
HostRoot: 3,
|
|
22
|
+
HostComponent: 5,
|
|
23
|
+
HostText: 6,
|
|
24
|
+
} as const;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { isRecord } from '../fiber/narrow.ts';
|
|
2
|
+
|
|
3
|
+
export function borderRadiusOf(style: unknown): number {
|
|
4
|
+
if (Array.isArray(style)) {
|
|
5
|
+
return style.reduce<number>((radius, entry) => {
|
|
6
|
+
const nested = borderRadiusOf(entry);
|
|
7
|
+
return nested > 0 ? nested : radius;
|
|
8
|
+
}, 0);
|
|
9
|
+
}
|
|
10
|
+
return isRecord(style) && typeof style.borderRadius === 'number' ? style.borderRadius : 0;
|
|
11
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isFunction, isRecord } from '../fiber/narrow.ts';
|
|
2
|
+
import type { Fiber } from '../fiber/types.ts';
|
|
3
|
+
|
|
4
|
+
export type MeasureCallback = (x: number, y: number, width: number, height: number) => void;
|
|
5
|
+
|
|
6
|
+
export interface HostInstance {
|
|
7
|
+
measureInWindow: (callback: MeasureCallback) => void;
|
|
8
|
+
setNativeProps?: (props: Record<string, unknown>) => void;
|
|
9
|
+
focus?: () => void;
|
|
10
|
+
blur?: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isHostInstance(value: unknown): value is HostInstance {
|
|
14
|
+
return isRecord(value) && isFunction(value.measureInWindow);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function hostInstanceOf(host: Fiber): HostInstance | null {
|
|
18
|
+
const node = host.stateNode;
|
|
19
|
+
if (isHostInstance(node)) {
|
|
20
|
+
return node;
|
|
21
|
+
}
|
|
22
|
+
if (!isRecord(node) || !isRecord(node.canonical)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const canonical = node.canonical;
|
|
26
|
+
if (isHostInstance(canonical.publicInstance)) {
|
|
27
|
+
return canonical.publicInstance;
|
|
28
|
+
}
|
|
29
|
+
return isHostInstance(canonical) ? canonical : null;
|
|
30
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Fiber } from '../fiber/types.ts';
|
|
2
|
+
import { hostInstanceOf } from './instance.ts';
|
|
3
|
+
import type { Rect } from './rect.ts';
|
|
4
|
+
|
|
5
|
+
const MEASURE_TIMEOUT_MS = 400;
|
|
6
|
+
|
|
7
|
+
export function measureHost(host: Fiber): Promise<Rect | null> {
|
|
8
|
+
const instance = hostInstanceOf(host);
|
|
9
|
+
if (instance === null) {
|
|
10
|
+
return Promise.resolve(null);
|
|
11
|
+
}
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
const timer = setTimeout(() => resolve(null), MEASURE_TIMEOUT_MS);
|
|
14
|
+
try {
|
|
15
|
+
instance.measureInWindow((x, y, width, height) => {
|
|
16
|
+
clearTimeout(timer);
|
|
17
|
+
const finite = [x, y, width, height].every((value) => Number.isFinite(value));
|
|
18
|
+
resolve(finite ? { x, y, width, height } : null);
|
|
19
|
+
});
|
|
20
|
+
} catch {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
resolve(null);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
package/src/host/rect.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface Rect {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface Point {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Size {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function intersects(first: Rect, second: Rect): boolean {
|
|
19
|
+
return (
|
|
20
|
+
first.x < second.x + second.width &&
|
|
21
|
+
first.x + first.width > second.x &&
|
|
22
|
+
first.y < second.y + second.height &&
|
|
23
|
+
first.y + first.height > second.y
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isVisibleIn(rect: Rect, viewport: Size): boolean {
|
|
28
|
+
return rect.width > 0 && rect.height > 0 && intersects(rect, { x: 0, y: 0, ...viewport });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function centerOf(rect: Rect): Point {
|
|
32
|
+
return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface Band {
|
|
36
|
+
top: number;
|
|
37
|
+
bottom: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function scrollDeltaIntoBand(target: Rect, band: Band): number {
|
|
41
|
+
if (target.height > band.bottom - band.top) {
|
|
42
|
+
return target.y - band.top;
|
|
43
|
+
}
|
|
44
|
+
return target.y + target.height / 2 - (band.top + band.bottom) / 2;
|
|
45
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { BlarProvider } from './BlarProvider.tsx';
|
|
2
|
+
export type { BlarProviderProps } from './BlarProvider.tsx';
|
|
3
|
+
export { Blar } from './blar.ts';
|
|
4
|
+
export type { ChatControls, MessageMetadataOptions } from './blar.ts';
|
|
5
|
+
export type { ChatOptions, LauncherOptions } from './chat/options.ts';
|
|
6
|
+
export type { Language, UiStringOverrides, UiStrings } from './ui/strings.ts';
|
|
7
|
+
export type { NavigationRefLike } from './actions/navigation-driver.ts';
|
|
8
|
+
export type { BlarIdentity, BlarIdentityProofInput, CustomFields } from './backend/identity.ts';
|
|
9
|
+
export type { MessageMetadata } from './backend/message-metadata.ts';
|
|
10
|
+
export { SDK_VERSION } from './version.ts';
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Image, Pressable, StyleSheet, Text, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import type { LauncherPlacement } from '../chat/Launcher.tsx';
|
|
5
|
+
import type { UiStrings } from '../ui/strings.ts';
|
|
6
|
+
import { useInsets } from '../ui/useInsets.ts';
|
|
7
|
+
import { initialsOf, type NudgeView } from './nudge.ts';
|
|
8
|
+
|
|
9
|
+
export interface NudgeBubbleProps {
|
|
10
|
+
nudge: NudgeView;
|
|
11
|
+
strings: UiStrings;
|
|
12
|
+
color: string;
|
|
13
|
+
placement: LauncherPlacement;
|
|
14
|
+
launcherVisible: boolean;
|
|
15
|
+
onAccept: () => void;
|
|
16
|
+
onDecline: () => void;
|
|
17
|
+
onLater: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const LAUNCHER_SIZE = 56;
|
|
21
|
+
const GAP = 12;
|
|
22
|
+
|
|
23
|
+
export function NudgeBubble({ nudge, strings, color, placement, launcherVisible, onAccept, onDecline, onLater }: NudgeBubbleProps): React.JSX.Element {
|
|
24
|
+
const insets = useInsets();
|
|
25
|
+
const horizontal = placement.side === 'left' ? { left: placement.offsetX } : { right: placement.offsetX };
|
|
26
|
+
const lift = launcherVisible ? LAUNCHER_SIZE + GAP : 0;
|
|
27
|
+
return (
|
|
28
|
+
<View style={[styles.card, horizontal, { bottom: insets.bottom + placement.offsetY + lift }]} testID="blar-nudge-bubble">
|
|
29
|
+
<View style={styles.header}>
|
|
30
|
+
{nudge.sender === null ? null : <Avatar name={nudge.sender.name} url={nudge.sender.avatarUrl} color={color} />}
|
|
31
|
+
<View style={styles.headerText}>
|
|
32
|
+
{nudge.sender === null ? null : <Text style={styles.name}>{nudge.sender.name}</Text>}
|
|
33
|
+
{nudge.sender?.subtitle === null || nudge.sender === null ? null : <Text style={styles.subtitle}>{nudge.sender.subtitle}</Text>}
|
|
34
|
+
</View>
|
|
35
|
+
<Pressable accessibilityLabel={strings.nudgeDismiss} hitSlop={10} onPress={onDecline}>
|
|
36
|
+
<Text style={styles.close}>×</Text>
|
|
37
|
+
</Pressable>
|
|
38
|
+
</View>
|
|
39
|
+
<Text style={styles.message}>{nudge.bubbleMessage}</Text>
|
|
40
|
+
{nudge.progress === null ? null : <ProgressBar total={nudge.progress.total} completed={nudge.progress.completed} color={color} />}
|
|
41
|
+
{nudge.continuation ? (
|
|
42
|
+
<Pressable accessibilityRole="button" style={[styles.primary, { backgroundColor: color }]} onPress={onAccept}>
|
|
43
|
+
<Text style={styles.primaryText}>{nudge.ctaLabel ?? strings.nudgeAccept}</Text>
|
|
44
|
+
</Pressable>
|
|
45
|
+
) : (
|
|
46
|
+
<View>
|
|
47
|
+
<Text style={styles.prompt}>{strings.nudgePrompt}</Text>
|
|
48
|
+
<View style={styles.actions}>
|
|
49
|
+
<Pressable accessibilityRole="button" style={[styles.primary, styles.action, { backgroundColor: color }]} onPress={onAccept}>
|
|
50
|
+
<Text style={styles.primaryText}>{strings.nudgeAccept}</Text>
|
|
51
|
+
</Pressable>
|
|
52
|
+
<Pressable accessibilityRole="button" style={[styles.secondary, styles.action]} onPress={onDecline}>
|
|
53
|
+
<Text style={styles.secondaryText}>{strings.nudgeDecline}</Text>
|
|
54
|
+
</Pressable>
|
|
55
|
+
<Pressable accessibilityRole="button" style={[styles.secondary, styles.action]} onPress={onLater}>
|
|
56
|
+
<Text style={styles.secondaryText}>{strings.nudgeLater}</Text>
|
|
57
|
+
</Pressable>
|
|
58
|
+
</View>
|
|
59
|
+
</View>
|
|
60
|
+
)}
|
|
61
|
+
</View>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function Avatar({ name, url, color }: { name: string; url: string | null; color: string }): React.JSX.Element {
|
|
66
|
+
return url === null ? (
|
|
67
|
+
<View style={[styles.avatar, { backgroundColor: color }]}>
|
|
68
|
+
<Text style={styles.avatarText}>{initialsOf(name)}</Text>
|
|
69
|
+
</View>
|
|
70
|
+
) : (
|
|
71
|
+
<Image source={{ uri: url }} style={styles.avatar} />
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function ProgressBar({ total, completed, color }: { total: number; completed: number; color: string }): React.JSX.Element {
|
|
76
|
+
const segments = Array.from({ length: Math.max(0, total) }, (_, index) => index < completed);
|
|
77
|
+
return (
|
|
78
|
+
<View style={styles.progress}>
|
|
79
|
+
{segments.map((done, index) => (
|
|
80
|
+
<View key={index} style={[styles.segment, { backgroundColor: done ? color : '#e5e7eb' }]} />
|
|
81
|
+
))}
|
|
82
|
+
</View>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const styles = StyleSheet.create({
|
|
87
|
+
card: {
|
|
88
|
+
position: 'absolute',
|
|
89
|
+
width: 300,
|
|
90
|
+
maxWidth: '90%',
|
|
91
|
+
backgroundColor: '#ffffff',
|
|
92
|
+
borderRadius: 16,
|
|
93
|
+
padding: 14,
|
|
94
|
+
gap: 10,
|
|
95
|
+
elevation: 10,
|
|
96
|
+
shadowColor: '#000000',
|
|
97
|
+
shadowOpacity: 0.25,
|
|
98
|
+
shadowRadius: 10,
|
|
99
|
+
},
|
|
100
|
+
header: { flexDirection: 'row', alignItems: 'center', gap: 10 },
|
|
101
|
+
headerText: { flex: 1 },
|
|
102
|
+
avatar: { width: 36, height: 36, borderRadius: 18, alignItems: 'center', justifyContent: 'center' },
|
|
103
|
+
avatarText: { color: '#ffffff', fontWeight: '700' },
|
|
104
|
+
name: { fontWeight: '700', color: '#111827' },
|
|
105
|
+
subtitle: { color: '#6b7280', fontSize: 12 },
|
|
106
|
+
close: { fontSize: 22, color: '#9ca3af', lineHeight: 22 },
|
|
107
|
+
message: { color: '#111827', fontSize: 15 },
|
|
108
|
+
progress: { flexDirection: 'row', gap: 4 },
|
|
109
|
+
segment: { flex: 1, height: 4, borderRadius: 2 },
|
|
110
|
+
prompt: { color: '#374151', fontSize: 13, marginBottom: 6 },
|
|
111
|
+
actions: { flexDirection: 'row', gap: 8 },
|
|
112
|
+
action: { flex: 1 },
|
|
113
|
+
primary: { borderRadius: 10, paddingVertical: 10, alignItems: 'center' },
|
|
114
|
+
primaryText: { color: '#ffffff', fontWeight: '700' },
|
|
115
|
+
secondary: { borderRadius: 10, paddingVertical: 10, alignItems: 'center', backgroundColor: '#f3f4f6' },
|
|
116
|
+
secondaryText: { color: '#111827', fontWeight: '600' },
|
|
117
|
+
});
|