arlo-react-native 0.1.1 → 0.1.3
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/dist/ArloFlowRenderer.d.ts +1 -1
- package/dist/ArloFlowRenderer.d.ts.map +1 -1
- package/dist/ArloFlowRenderer.js +667 -132
- package/dist/ArloFlowRenderer.js.map +1 -1
- package/dist/ArloPresenterRenderer.d.ts +1 -1
- package/dist/ArloPresenterRenderer.d.ts.map +1 -1
- package/dist/ArloPresenterRenderer.js +3 -2
- package/dist/ArloPresenterRenderer.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -3
package/dist/ArloFlowRenderer.js
CHANGED
|
@@ -4,7 +4,135 @@ exports.ArloFlowRenderer = ArloFlowRenderer;
|
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const react_native_1 = require("react-native");
|
|
7
|
+
const react_native_safe_area_context_1 = require("react-native-safe-area-context");
|
|
8
|
+
const SafeView = react_native_safe_area_context_1.SafeAreaView;
|
|
7
9
|
const arlo_sdk_1 = require("arlo-sdk");
|
|
10
|
+
const LIGHT_THEME = {
|
|
11
|
+
isLight: true,
|
|
12
|
+
textPrimary: "#111111",
|
|
13
|
+
textSecondary: "#555555",
|
|
14
|
+
inputBackground: "#ffffff",
|
|
15
|
+
inputBorder: "#d4d4d8",
|
|
16
|
+
inputText: "#111111",
|
|
17
|
+
pillBackground: "#ffffff",
|
|
18
|
+
pillBorder: "#d4d4d8",
|
|
19
|
+
pillText: "#111111",
|
|
20
|
+
pillSelectedBackground: "#111111",
|
|
21
|
+
pillSelectedBorder: "#111111",
|
|
22
|
+
pillSelectedText: "#ffffff",
|
|
23
|
+
sliderCardBackground: "#f4f4f5",
|
|
24
|
+
sliderCardBorder: "#e4e4e7",
|
|
25
|
+
sliderValueText: "#111111",
|
|
26
|
+
progressTrackBackground: "#e4e4e7",
|
|
27
|
+
progressFillColor: "#111111",
|
|
28
|
+
indicatorActive: "#111111",
|
|
29
|
+
indicatorInactive: "#d4d4d8",
|
|
30
|
+
placeholderText: "#a1a1aa",
|
|
31
|
+
errorText: "#dc2626",
|
|
32
|
+
errorBorder: "#f87171",
|
|
33
|
+
};
|
|
34
|
+
const DARK_THEME = {
|
|
35
|
+
isLight: false,
|
|
36
|
+
textPrimary: "#ffffff",
|
|
37
|
+
textSecondary: "#a1a1aa",
|
|
38
|
+
inputBackground: "#141419",
|
|
39
|
+
inputBorder: "#2c2c34",
|
|
40
|
+
inputText: "#ffffff",
|
|
41
|
+
pillBackground: "#15151b",
|
|
42
|
+
pillBorder: "#30303a",
|
|
43
|
+
pillText: "#f1f1f3",
|
|
44
|
+
pillSelectedBackground: "#ffffff",
|
|
45
|
+
pillSelectedBorder: "#ffffff",
|
|
46
|
+
pillSelectedText: "#111111",
|
|
47
|
+
sliderCardBackground: "#15151b",
|
|
48
|
+
sliderCardBorder: "#2b2b34",
|
|
49
|
+
sliderValueText: "#ffffff",
|
|
50
|
+
progressTrackBackground: "#26262b",
|
|
51
|
+
progressFillColor: "#ffffff",
|
|
52
|
+
indicatorActive: "#ffffff",
|
|
53
|
+
indicatorInactive: "#4b4b55",
|
|
54
|
+
placeholderText: "#7a7a85",
|
|
55
|
+
errorText: "#f59cb3",
|
|
56
|
+
errorBorder: "#f36b8d",
|
|
57
|
+
};
|
|
58
|
+
function findInteractiveComponent(screen, fieldKey) {
|
|
59
|
+
if (!screen) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return (screen.components.find((component) => {
|
|
63
|
+
if (component.type !== "TEXT_INPUT" &&
|
|
64
|
+
component.type !== "SINGLE_SELECT" &&
|
|
65
|
+
component.type !== "MULTI_SELECT" &&
|
|
66
|
+
component.type !== "SLIDER") {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return component.props.fieldKey === fieldKey;
|
|
70
|
+
}) ?? null);
|
|
71
|
+
}
|
|
72
|
+
function findButtonComponent(screen, componentId) {
|
|
73
|
+
if (!screen) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return (screen.components.find((component) => component.type === "BUTTON" && component.id === componentId) ?? null);
|
|
77
|
+
}
|
|
78
|
+
function emitAnalyticsEvent(handlers, session, snapshot, event) {
|
|
79
|
+
if (!handlers?.onAnalyticsEvent) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
void handlers.onAnalyticsEvent({
|
|
83
|
+
session,
|
|
84
|
+
snapshot,
|
|
85
|
+
event,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Compute relative luminance of a color.
|
|
90
|
+
* Returns a value between 0 (black) and 1 (white).
|
|
91
|
+
*/
|
|
92
|
+
function getRelativeLuminance(color) {
|
|
93
|
+
if (!color || color === "transparent")
|
|
94
|
+
return 1;
|
|
95
|
+
const hex = color;
|
|
96
|
+
// Handle rgba/rgb
|
|
97
|
+
if (color.startsWith("rgb")) {
|
|
98
|
+
const matches = color.match(/\d+(\.\d+)?/g);
|
|
99
|
+
if (!matches || matches.length < 3)
|
|
100
|
+
return 1;
|
|
101
|
+
const r = parseInt(matches[0], 10) / 255;
|
|
102
|
+
const g = parseInt(matches[1], 10) / 255;
|
|
103
|
+
const b = parseInt(matches[2], 10) / 255;
|
|
104
|
+
if (matches.length >= 4 && parseFloat(matches[3]) === 0)
|
|
105
|
+
return 1;
|
|
106
|
+
const toLinear = (c) => (c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
|
|
107
|
+
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
|
|
108
|
+
}
|
|
109
|
+
// Handle named colors (basic)
|
|
110
|
+
if (color === "white")
|
|
111
|
+
return 1;
|
|
112
|
+
if (color === "black")
|
|
113
|
+
return 0;
|
|
114
|
+
let clean = hex.replace("#", "");
|
|
115
|
+
if (clean.length === 3) {
|
|
116
|
+
clean = clean[0] + clean[0] + clean[1] + clean[1] + clean[2] + clean[2];
|
|
117
|
+
}
|
|
118
|
+
if (clean.length < 6)
|
|
119
|
+
return 1;
|
|
120
|
+
const r = parseInt(clean.slice(0, 2), 16) / 255;
|
|
121
|
+
const g = parseInt(clean.slice(2, 4), 16) / 255;
|
|
122
|
+
const b = parseInt(clean.slice(4, 6), 16) / 255;
|
|
123
|
+
if (isNaN(r) || isNaN(g) || isNaN(b))
|
|
124
|
+
return 1;
|
|
125
|
+
const toLinear = (c) => (c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
|
|
126
|
+
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
|
|
127
|
+
}
|
|
128
|
+
function getThemeForScreen(screen) {
|
|
129
|
+
const bg = screen.style?.backgroundColor;
|
|
130
|
+
if (!bg || bg === "transparent")
|
|
131
|
+
return LIGHT_THEME;
|
|
132
|
+
const luminance = getRelativeLuminance(bg);
|
|
133
|
+
return luminance > 0.5 ? LIGHT_THEME : DARK_THEME;
|
|
134
|
+
}
|
|
135
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
8
136
|
const IMPORTED_SCREEN_KEYS = new Set([
|
|
9
137
|
"__arlo_imported_code__",
|
|
10
138
|
"__arlo_imported_figma__",
|
|
@@ -24,19 +152,133 @@ function getImportedPreviewScreen(screen) {
|
|
|
24
152
|
: null;
|
|
25
153
|
}
|
|
26
154
|
function getScreenContainerStyle(screen) {
|
|
155
|
+
const padding = getScreenPadding(screen);
|
|
27
156
|
return {
|
|
28
|
-
backgroundColor: screen.style?.backgroundColor ?? "#
|
|
29
|
-
paddingTop:
|
|
30
|
-
paddingBottom:
|
|
31
|
-
|
|
157
|
+
backgroundColor: screen.style?.backgroundColor ?? "#FFFFFF",
|
|
158
|
+
paddingTop: padding.top,
|
|
159
|
+
paddingBottom: padding.bottom,
|
|
160
|
+
paddingRight: padding.right,
|
|
161
|
+
paddingLeft: padding.left,
|
|
32
162
|
justifyContent: screen.style?.justifyContent ?? "flex-start",
|
|
33
163
|
alignItems: screen.style?.alignItems ?? "stretch",
|
|
34
164
|
};
|
|
35
165
|
}
|
|
36
|
-
function
|
|
166
|
+
function getScreenPadding(screen) {
|
|
167
|
+
const p = screen.style?.padding;
|
|
168
|
+
const pV = screen.style?.paddingVertical ?? p ?? (screen.layoutMode === "absolute" ? 0 : 24);
|
|
169
|
+
const pH = screen.style?.paddingHorizontal ?? p ?? (screen.layoutMode === "absolute" ? 0 : 24);
|
|
170
|
+
return {
|
|
171
|
+
top: screen.style?.paddingTop ?? pV,
|
|
172
|
+
right: screen.style?.paddingRight ?? pH,
|
|
173
|
+
bottom: screen.style?.paddingBottom ?? pV,
|
|
174
|
+
left: screen.style?.paddingLeft ?? pH,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function splitAutoLayoutComponents(components) {
|
|
178
|
+
return {
|
|
179
|
+
main: components.filter((component) => {
|
|
180
|
+
const props = component.props;
|
|
181
|
+
return props.position !== "bottom";
|
|
182
|
+
}),
|
|
183
|
+
bottom: components.filter((component) => {
|
|
184
|
+
const props = component.props;
|
|
185
|
+
return props.position === "bottom";
|
|
186
|
+
}),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/** Components that size to their own content rather than stretching to fill width */
|
|
190
|
+
const SELF_SIZING_TYPES = new Set(["ICON_LIBRARY", "ICON", "PAGE_INDICATOR"]);
|
|
191
|
+
function getSpacing(props, prefix) {
|
|
192
|
+
const top = props[`${prefix}Top`];
|
|
193
|
+
const right = props[`${prefix}Right`];
|
|
194
|
+
const bottom = props[`${prefix}Bottom`];
|
|
195
|
+
const left = props[`${prefix}Left`];
|
|
196
|
+
if (typeof top === "number" ||
|
|
197
|
+
typeof right === "number" ||
|
|
198
|
+
typeof bottom === "number" ||
|
|
199
|
+
typeof left === "number") {
|
|
200
|
+
return {
|
|
201
|
+
top: typeof top === "number" ? top : 0,
|
|
202
|
+
right: typeof right === "number" ? right : 0,
|
|
203
|
+
bottom: typeof bottom === "number" ? bottom : 0,
|
|
204
|
+
left: typeof left === "number" ? left : 0,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const vertical = props[`${prefix}Vertical`];
|
|
208
|
+
const horizontal = props[`${prefix}Horizontal`];
|
|
209
|
+
return {
|
|
210
|
+
top: typeof vertical === "number" ? vertical : 0,
|
|
211
|
+
right: typeof horizontal === "number" ? horizontal : 0,
|
|
212
|
+
bottom: typeof vertical === "number" ? vertical : 0,
|
|
213
|
+
left: typeof horizontal === "number" ? horizontal : 0,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function getComponentMarginStyle(component) {
|
|
217
|
+
const props = component.props;
|
|
218
|
+
const margin = getSpacing(props, "margin");
|
|
219
|
+
return {
|
|
220
|
+
marginTop: margin.top || undefined,
|
|
221
|
+
marginRight: margin.right || undefined,
|
|
222
|
+
marginBottom: margin.bottom || undefined,
|
|
223
|
+
marginLeft: margin.left || undefined,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function getComponentWidthMode(component) {
|
|
227
|
+
const props = component.props;
|
|
228
|
+
if (props.widthMode === "fill" || props.widthMode === "fit" || props.widthMode === "fixed") {
|
|
229
|
+
return props.widthMode;
|
|
230
|
+
}
|
|
231
|
+
if (component.type === "BUTTON" || SELF_SIZING_TYPES.has(component.type)) {
|
|
232
|
+
return "fit";
|
|
233
|
+
}
|
|
234
|
+
return "fill";
|
|
235
|
+
}
|
|
236
|
+
function getFixedComponentWidth(component) {
|
|
237
|
+
const props = component.props;
|
|
238
|
+
if (typeof props.width === "number")
|
|
239
|
+
return props.width;
|
|
240
|
+
if (typeof props.fixedWidth === "number")
|
|
241
|
+
return props.fixedWidth;
|
|
242
|
+
if (component.type === "BUTTON")
|
|
243
|
+
return 300;
|
|
244
|
+
if (component.type === "TEXT")
|
|
245
|
+
return 200;
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
function getAutoLayoutWrapperStyle(component, parentAlignItems = "stretch") {
|
|
249
|
+
const widthMode = getComponentWidthMode(component);
|
|
250
|
+
if (widthMode === "fill") {
|
|
251
|
+
return {
|
|
252
|
+
alignSelf: "stretch",
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const alignSelf = parentAlignItems === "center"
|
|
256
|
+
? "center"
|
|
257
|
+
: parentAlignItems === "flex-end"
|
|
258
|
+
? "flex-end"
|
|
259
|
+
: "flex-start";
|
|
260
|
+
if (widthMode === "fixed") {
|
|
261
|
+
return {
|
|
262
|
+
width: getFixedComponentWidth(component),
|
|
263
|
+
alignSelf,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
alignSelf,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function getComponentWrapperStyle(component, isAbsoluteScreen, parentAlignItems = "stretch") {
|
|
37
271
|
const layout = component.layout;
|
|
272
|
+
const blockStyle = SELF_SIZING_TYPES.has(component.type) ? {} : styles.componentBlock;
|
|
273
|
+
const marginStyle = getComponentMarginStyle(component);
|
|
38
274
|
if (!layout) {
|
|
39
|
-
return isAbsoluteScreen
|
|
275
|
+
return isAbsoluteScreen
|
|
276
|
+
? { position: "absolute", zIndex: component.order }
|
|
277
|
+
: {
|
|
278
|
+
...blockStyle,
|
|
279
|
+
...marginStyle,
|
|
280
|
+
...getAutoLayoutWrapperStyle(component, parentAlignItems),
|
|
281
|
+
};
|
|
40
282
|
}
|
|
41
283
|
const baseStyle = {
|
|
42
284
|
display: layout.visible === false ? "none" : "flex",
|
|
@@ -44,15 +286,17 @@ function getComponentWrapperStyle(component, isAbsoluteScreen) {
|
|
|
44
286
|
};
|
|
45
287
|
if (!isAbsoluteScreen && layout.position !== "absolute") {
|
|
46
288
|
return {
|
|
47
|
-
...
|
|
289
|
+
...blockStyle,
|
|
290
|
+
...marginStyle,
|
|
291
|
+
...getAutoLayoutWrapperStyle(component, parentAlignItems),
|
|
48
292
|
...baseStyle,
|
|
49
293
|
};
|
|
50
294
|
}
|
|
51
295
|
return {
|
|
52
296
|
...baseStyle,
|
|
53
297
|
position: "absolute",
|
|
54
|
-
left: layout.x ?? 0,
|
|
55
|
-
top: layout.y ?? 0,
|
|
298
|
+
left: layout.x ?? marginStyle.marginLeft ?? 0,
|
|
299
|
+
top: layout.y ?? marginStyle.marginTop ?? 0,
|
|
56
300
|
width: layout.width,
|
|
57
301
|
height: layout.height,
|
|
58
302
|
transform: layout.rotation ? [{ rotate: `${layout.rotation}deg` }] : undefined,
|
|
@@ -67,15 +311,48 @@ function coerceStringArrayValue(value) {
|
|
|
67
311
|
function coerceNumberValue(value, fallback) {
|
|
68
312
|
return typeof value === "number" ? value : fallback;
|
|
69
313
|
}
|
|
314
|
+
function renderFallbackIcon(name, size, color) {
|
|
315
|
+
const normalized = name.toLowerCase().replace(/[-_]/g, "");
|
|
316
|
+
let glyph = "\u2022";
|
|
317
|
+
if (normalized.includes("chevronleft") || normalized === "back")
|
|
318
|
+
glyph = "\u2039";
|
|
319
|
+
else if (normalized.includes("arrowleft") || normalized.endsWith("left"))
|
|
320
|
+
glyph = "\u2190";
|
|
321
|
+
else if (normalized.includes("chevronright") || normalized === "next")
|
|
322
|
+
glyph = "\u203A";
|
|
323
|
+
else if (normalized.includes("arrowright") || normalized.endsWith("right"))
|
|
324
|
+
glyph = "\u2192";
|
|
325
|
+
else if (normalized === "x" || normalized.includes("close"))
|
|
326
|
+
glyph = "\u2715";
|
|
327
|
+
else if (normalized.includes("check"))
|
|
328
|
+
glyph = "\u2713";
|
|
329
|
+
else if (normalized.includes("plus"))
|
|
330
|
+
glyph = "+";
|
|
331
|
+
else if (normalized.includes("minus"))
|
|
332
|
+
glyph = "\u2212";
|
|
333
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
|
|
334
|
+
fontSize: size,
|
|
335
|
+
color,
|
|
336
|
+
textAlign: "center",
|
|
337
|
+
includeFontPadding: false,
|
|
338
|
+
lineHeight: size,
|
|
339
|
+
}, children: glyph }));
|
|
340
|
+
}
|
|
70
341
|
function getFieldError(snapshot, fieldKey) {
|
|
71
342
|
return snapshot.validationErrorsByField[fieldKey] ?? null;
|
|
72
343
|
}
|
|
73
|
-
|
|
344
|
+
// ─── Default Components ─────────────────────────────────────────────────────
|
|
345
|
+
function DefaultTextComponent({ component, theme, parentAlignItems = "stretch", }) {
|
|
346
|
+
const defaultTextAlign = parentAlignItems === "center"
|
|
347
|
+
? "center"
|
|
348
|
+
: parentAlignItems === "flex-end"
|
|
349
|
+
? "right"
|
|
350
|
+
: "left";
|
|
74
351
|
return ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
|
|
75
|
-
color: component.props.color ??
|
|
352
|
+
color: component.props.color ?? theme.textPrimary,
|
|
76
353
|
fontSize: component.props.fontSize ?? 16,
|
|
77
354
|
fontWeight: component.props.fontWeight ?? "normal",
|
|
78
|
-
textAlign: component.props.textAlign ??
|
|
355
|
+
textAlign: component.props.textAlign ?? defaultTextAlign,
|
|
79
356
|
lineHeight: component.props.lineHeight && component.props.fontSize
|
|
80
357
|
? component.props.lineHeight * component.props.fontSize
|
|
81
358
|
: undefined,
|
|
@@ -83,117 +360,305 @@ function DefaultTextComponent({ component }) {
|
|
|
83
360
|
}, children: component.props.content }));
|
|
84
361
|
}
|
|
85
362
|
function DefaultImageComponent({ component }) {
|
|
86
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri: component.props.src }, accessibilityLabel: component.props.alt, resizeMode: component.props.resizeMode ?? "cover", style: {
|
|
363
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri: component.props.src }, alt: component.props.alt ?? "", accessibilityLabel: component.props.alt, resizeMode: component.props.resizeMode ?? "cover", style: {
|
|
87
364
|
width: component.props.width ?? "100%",
|
|
88
365
|
height: component.props.height ?? 220,
|
|
89
366
|
borderRadius: component.props.borderRadius ?? 0,
|
|
90
367
|
} }));
|
|
91
368
|
}
|
|
92
|
-
function
|
|
369
|
+
function DefaultIconLibraryComponent({ component, iconRenderer, }) {
|
|
370
|
+
const props = component.props;
|
|
371
|
+
const iconName = props.iconName ?? "";
|
|
372
|
+
const size = props.size ?? 24;
|
|
373
|
+
const color = props.color ?? "#ffffff";
|
|
374
|
+
const bgColor = props.backgroundColor ?? undefined;
|
|
375
|
+
const width = props.width ?? size + 16;
|
|
376
|
+
const height = props.height ?? size + 16;
|
|
377
|
+
const opacity = props.opacity ?? 1;
|
|
378
|
+
const borderRadius = Math.min(width, height) / 2;
|
|
379
|
+
const iconContent = iconRenderer
|
|
380
|
+
? iconRenderer(iconName, size, color)
|
|
381
|
+
: renderFallbackIcon(iconName, size, color);
|
|
382
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: {
|
|
383
|
+
width,
|
|
384
|
+
height,
|
|
385
|
+
borderRadius,
|
|
386
|
+
backgroundColor: bgColor,
|
|
387
|
+
opacity,
|
|
388
|
+
alignItems: "center",
|
|
389
|
+
justifyContent: "center",
|
|
390
|
+
paddingVertical: props.paddingVertical ?? 0,
|
|
391
|
+
paddingHorizontal: props.paddingHorizontal ?? 0,
|
|
392
|
+
marginVertical: props.marginVertical ?? 0,
|
|
393
|
+
marginHorizontal: props.marginHorizontal ?? 0,
|
|
394
|
+
}, children: iconContent }));
|
|
395
|
+
}
|
|
396
|
+
function DefaultButtonComponent({ component, onPress, iconRenderer, }) {
|
|
397
|
+
const props = component.props;
|
|
398
|
+
const padding = getSpacing(props, "padding");
|
|
399
|
+
const showIcon = props.showIcon === true;
|
|
400
|
+
const iconName = props.iconName ?? "";
|
|
401
|
+
const iconPosition = props.iconPosition ?? "left";
|
|
402
|
+
const iconSize = props.iconSize ?? 20;
|
|
403
|
+
const textColor = props.textColor ?? component.props.style?.textColor ?? "#ffffff";
|
|
404
|
+
const iconColor = props.iconColor ?? textColor;
|
|
405
|
+
const iconSpacing = props.iconSpacing ?? 8;
|
|
406
|
+
const fontSize = props.fontSize ?? 16;
|
|
407
|
+
const fontWeight = props.fontWeight ?? "600";
|
|
408
|
+
const widthMode = getComponentWidthMode(component);
|
|
409
|
+
const heightMode = props.heightMode === "fit" || props.heightMode === "fill" || props.heightMode === "fixed"
|
|
410
|
+
? props.heightMode
|
|
411
|
+
: "fit";
|
|
412
|
+
const backgroundColor = props.backgroundColor ?? component.props.style?.backgroundColor ?? "#007AFF";
|
|
413
|
+
const borderRadius = props.shape === "circle"
|
|
414
|
+
? 999
|
|
415
|
+
: props.shape === "pill"
|
|
416
|
+
? 999
|
|
417
|
+
: props.shape === "rounded"
|
|
418
|
+
? 16
|
|
419
|
+
: props.borderRadius ?? component.props.style?.borderRadius ?? 14;
|
|
420
|
+
const borderColor = props.borderColor ?? component.props.style?.borderColor ?? "transparent";
|
|
421
|
+
const borderWidth = props.borderWidth ?? component.props.style?.borderWidth ?? 0;
|
|
422
|
+
const fixedHeight = typeof props.height === "number"
|
|
423
|
+
? props.height
|
|
424
|
+
: typeof props.fixedHeight === "number"
|
|
425
|
+
? props.fixedHeight
|
|
426
|
+
: 48;
|
|
427
|
+
const iconElement = showIcon && iconName
|
|
428
|
+
? (iconRenderer
|
|
429
|
+
? iconRenderer(iconName, iconSize, iconColor)
|
|
430
|
+
: renderFallbackIcon(iconName, iconSize, iconColor))
|
|
431
|
+
: null;
|
|
432
|
+
const isIconOnly = showIcon && iconPosition === "only";
|
|
433
|
+
const isCircularIconOnlyButton = props.shape === "circle" && isIconOnly;
|
|
93
434
|
return ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: () => {
|
|
94
435
|
void onPress();
|
|
95
436
|
}, style: [
|
|
96
437
|
styles.button,
|
|
97
438
|
{
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
439
|
+
width: isCircularIconOnlyButton ? fixedHeight : widthMode === "fit" ? undefined : "100%",
|
|
440
|
+
minHeight: isCircularIconOnlyButton ? fixedHeight : heightMode === "fit" ? 48 : undefined,
|
|
441
|
+
height: isCircularIconOnlyButton ? fixedHeight : heightMode === "fixed" ? fixedHeight : undefined,
|
|
442
|
+
backgroundColor,
|
|
443
|
+
borderRadius,
|
|
444
|
+
borderColor,
|
|
445
|
+
borderWidth,
|
|
446
|
+
paddingTop: isCircularIconOnlyButton ? 0 : padding.top || 14,
|
|
447
|
+
paddingRight: isCircularIconOnlyButton ? 0 : padding.right || 16,
|
|
448
|
+
paddingBottom: isCircularIconOnlyButton ? 0 : padding.bottom || 14,
|
|
449
|
+
paddingLeft: isCircularIconOnlyButton ? 0 : padding.left || 16,
|
|
102
450
|
},
|
|
103
|
-
], children: (0, jsx_runtime_1.
|
|
104
|
-
styles.
|
|
451
|
+
], children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
|
|
452
|
+
styles.buttonContent,
|
|
105
453
|
{
|
|
106
|
-
|
|
454
|
+
width: widthMode === "fit" ? undefined : "100%",
|
|
455
|
+
gap: isIconOnly ? 0 : iconSpacing,
|
|
107
456
|
},
|
|
108
|
-
], children:
|
|
457
|
+
], children: [iconElement && (iconPosition === "left" || isIconOnly) ? iconElement : null, !isIconOnly ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
|
|
458
|
+
styles.buttonText,
|
|
459
|
+
{
|
|
460
|
+
color: textColor,
|
|
461
|
+
fontSize,
|
|
462
|
+
fontWeight,
|
|
463
|
+
textAlign: props.textAlign ?? "center",
|
|
464
|
+
},
|
|
465
|
+
], children: component.props.label })) : null, iconElement && iconPosition === "right" ? iconElement : null] }) }));
|
|
109
466
|
}
|
|
110
|
-
function DefaultTextInputComponent({ component, context, }) {
|
|
467
|
+
function DefaultTextInputComponent({ component, context, theme, parentAlignItems = "stretch", }) {
|
|
111
468
|
const value = coerceStringValue(context.snapshot.values[component.props.fieldKey]);
|
|
112
469
|
const error = getFieldError(context.snapshot, component.props.fieldKey);
|
|
113
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.fieldGroup, children: [component.props.label ? (0, jsx_runtime_1.jsx)(react_native_1.Text, { style:
|
|
470
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.fieldGroup, children: [component.props.label ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
|
|
471
|
+
styles.fieldLabel,
|
|
472
|
+
{
|
|
473
|
+
color: theme.textSecondary,
|
|
474
|
+
textAlign: parentAlignItems === "center"
|
|
475
|
+
? "center"
|
|
476
|
+
: parentAlignItems === "flex-end"
|
|
477
|
+
? "right"
|
|
478
|
+
: "left",
|
|
479
|
+
},
|
|
480
|
+
], children: component.props.label })) : null, (0, jsx_runtime_1.jsx)(react_native_1.TextInput, { value: value, onChangeText: (nextValue) => context.onValueChange(component.props.fieldKey, nextValue), placeholder: component.props.placeholder, placeholderTextColor: theme.placeholderText, keyboardType: component.props.keyboardType === "email"
|
|
114
481
|
? "email-address"
|
|
115
482
|
: component.props.keyboardType === "numeric"
|
|
116
483
|
? "numeric"
|
|
117
484
|
: component.props.keyboardType === "phone"
|
|
118
485
|
? "phone-pad"
|
|
119
|
-
: "default", maxLength: component.props.maxLength, style: [
|
|
486
|
+
: "default", maxLength: component.props.maxLength, style: [
|
|
487
|
+
styles.input,
|
|
488
|
+
{
|
|
489
|
+
width: "100%",
|
|
490
|
+
backgroundColor: theme.inputBackground,
|
|
491
|
+
borderColor: error ? theme.errorBorder : theme.inputBorder,
|
|
492
|
+
color: theme.inputText,
|
|
493
|
+
},
|
|
494
|
+
] }), error ? (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.fieldError, { color: theme.errorText }], children: error }) : null] }));
|
|
120
495
|
}
|
|
121
|
-
function OptionPill({ label, selected, onPress, }) {
|
|
122
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: onPress, style: [
|
|
496
|
+
function OptionPill({ label, selected, theme, onPress, }) {
|
|
497
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: onPress, style: [
|
|
498
|
+
styles.optionPill,
|
|
499
|
+
{
|
|
500
|
+
backgroundColor: selected ? theme.pillSelectedBackground : theme.pillBackground,
|
|
501
|
+
borderColor: selected ? theme.pillSelectedBorder : theme.pillBorder,
|
|
502
|
+
},
|
|
503
|
+
], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
|
|
504
|
+
styles.optionPillText,
|
|
505
|
+
{ color: selected ? theme.pillSelectedText : theme.pillText },
|
|
506
|
+
], children: label }) }));
|
|
123
507
|
}
|
|
124
|
-
function DefaultSingleSelectComponent({ component, context, }) {
|
|
508
|
+
function DefaultSingleSelectComponent({ component, context, theme, parentAlignItems = "stretch", }) {
|
|
125
509
|
const value = coerceStringValue(context.snapshot.values[component.props.fieldKey]);
|
|
126
510
|
const error = getFieldError(context.snapshot, component.props.fieldKey);
|
|
127
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.fieldGroup, children: [component.props.label ? (
|
|
511
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.fieldGroup, children: [component.props.label ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
|
|
512
|
+
styles.fieldLabel,
|
|
513
|
+
{
|
|
514
|
+
color: theme.textSecondary,
|
|
515
|
+
textAlign: parentAlignItems === "center"
|
|
516
|
+
? "center"
|
|
517
|
+
: parentAlignItems === "flex-end"
|
|
518
|
+
? "right"
|
|
519
|
+
: "left",
|
|
520
|
+
},
|
|
521
|
+
], children: component.props.label })) : null, (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.selectList, children: component.props.options.map((option) => ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: [
|
|
522
|
+
styles.selectOption,
|
|
523
|
+
{
|
|
524
|
+
borderColor: value === option.id ? theme.textPrimary : theme.inputBorder,
|
|
525
|
+
backgroundColor: value === option.id ? theme.textPrimary : theme.inputBackground,
|
|
526
|
+
},
|
|
527
|
+
], onPress: () => context.onValueChange(component.props.fieldKey, option.id), children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
|
|
528
|
+
styles.selectOptionText,
|
|
529
|
+
{ color: value === option.id ? "#ffffff" : theme.textPrimary },
|
|
530
|
+
], children: option.label }) }, option.id))) }), error ? (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.fieldError, { color: theme.errorText }], children: error }) : null] }));
|
|
128
531
|
}
|
|
129
|
-
function DefaultMultiSelectComponent({ component, context, }) {
|
|
532
|
+
function DefaultMultiSelectComponent({ component, context, theme, parentAlignItems = "stretch", }) {
|
|
130
533
|
const values = coerceStringArrayValue(context.snapshot.values[component.props.fieldKey]);
|
|
131
534
|
const error = getFieldError(context.snapshot, component.props.fieldKey);
|
|
132
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.fieldGroup, children: [component.props.label ? (0, jsx_runtime_1.jsx)(react_native_1.Text, { style:
|
|
535
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.fieldGroup, children: [component.props.label ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
|
|
536
|
+
styles.fieldLabel,
|
|
537
|
+
{
|
|
538
|
+
color: theme.textSecondary,
|
|
539
|
+
textAlign: parentAlignItems === "center"
|
|
540
|
+
? "center"
|
|
541
|
+
: parentAlignItems === "flex-end"
|
|
542
|
+
? "right"
|
|
543
|
+
: "left",
|
|
544
|
+
},
|
|
545
|
+
], children: component.props.label })) : null, (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.optionGroup, children: component.props.options.map((option) => {
|
|
133
546
|
const selected = values.includes(option.id);
|
|
134
|
-
return ((0, jsx_runtime_1.jsx)(OptionPill, { label: option.label, selected: selected, onPress: () => {
|
|
547
|
+
return ((0, jsx_runtime_1.jsx)(OptionPill, { label: option.label, selected: selected, theme: theme, onPress: () => {
|
|
135
548
|
const nextValues = selected
|
|
136
549
|
? values.filter((value) => value !== option.id)
|
|
137
550
|
: [...values, option.id];
|
|
138
551
|
context.onValueChange(component.props.fieldKey, nextValues);
|
|
139
552
|
} }, option.id));
|
|
140
|
-
}) }), error ? (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.fieldError, children: error }) : null] }));
|
|
553
|
+
}) }), error ? (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.fieldError, { color: theme.errorText }], children: error }) : null] }));
|
|
141
554
|
}
|
|
142
|
-
function DefaultSliderComponent({ component, context, }) {
|
|
555
|
+
function DefaultSliderComponent({ component, context, theme, parentAlignItems = "stretch", }) {
|
|
556
|
+
const [trackWidth, setTrackWidth] = (0, react_1.useState)(0);
|
|
143
557
|
const currentValue = coerceNumberValue(context.snapshot.values[component.props.fieldKey], component.props.defaultValue ?? component.props.min);
|
|
144
558
|
const step = component.props.step ?? 1;
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
|
|
559
|
+
const clampedValue = Math.min(component.props.max, Math.max(component.props.min, currentValue));
|
|
560
|
+
const range = component.props.max - component.props.min;
|
|
561
|
+
const progress = range <= 0 ? 0 : ((clampedValue - component.props.min) / range) * 100;
|
|
562
|
+
const thumbSize = 18;
|
|
563
|
+
const thumbLeft = trackWidth <= 0
|
|
564
|
+
? 0
|
|
565
|
+
: Math.min(Math.max((progress / 100) * trackWidth - thumbSize / 2, 0), Math.max(trackWidth - thumbSize, 0));
|
|
566
|
+
const updateFromLocation = (locationX) => {
|
|
567
|
+
if (trackWidth <= 0 || range <= 0)
|
|
568
|
+
return;
|
|
569
|
+
const ratio = Math.min(Math.max(locationX / trackWidth, 0), 1);
|
|
570
|
+
const rawValue = component.props.min + ratio * range;
|
|
571
|
+
const steppedValue = Math.round((rawValue - component.props.min) / step) * step + component.props.min;
|
|
572
|
+
const nextValue = Math.min(component.props.max, Math.max(component.props.min, Number(steppedValue.toFixed(4))));
|
|
573
|
+
context.onValueChange(component.props.fieldKey, nextValue);
|
|
574
|
+
};
|
|
575
|
+
const handleTrackLayout = (event) => {
|
|
576
|
+
setTrackWidth(event.nativeEvent.layout.width);
|
|
577
|
+
};
|
|
578
|
+
const handleTrackInteraction = (event) => {
|
|
579
|
+
updateFromLocation(event.nativeEvent.locationX);
|
|
580
|
+
};
|
|
581
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.fieldGroup, children: [component.props.label ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
|
|
582
|
+
styles.fieldLabel,
|
|
583
|
+
{
|
|
584
|
+
color: theme.textSecondary,
|
|
585
|
+
textAlign: parentAlignItems === "center"
|
|
586
|
+
? "center"
|
|
587
|
+
: parentAlignItems === "flex-end"
|
|
588
|
+
? "right"
|
|
589
|
+
: "left",
|
|
590
|
+
},
|
|
591
|
+
], children: component.props.label })) : null, (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.sliderField, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { onLayout: handleTrackLayout, onStartShouldSetResponder: () => true, onResponderGrant: handleTrackInteraction, onResponderMove: handleTrackInteraction, style: styles.sliderTouchArea, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
592
|
+
styles.sliderTrack,
|
|
593
|
+
{
|
|
594
|
+
backgroundColor: theme.progressTrackBackground,
|
|
595
|
+
},
|
|
596
|
+
] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
597
|
+
styles.sliderFill,
|
|
598
|
+
{
|
|
599
|
+
width: `${progress}%`,
|
|
600
|
+
backgroundColor: "#3B82F6",
|
|
601
|
+
},
|
|
602
|
+
] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
603
|
+
styles.sliderThumb,
|
|
604
|
+
{
|
|
605
|
+
left: thumbLeft,
|
|
606
|
+
backgroundColor: "#3B82F6",
|
|
607
|
+
borderColor: theme.inputBackground,
|
|
608
|
+
},
|
|
609
|
+
] })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.sliderLabels, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.sliderLabel, { color: theme.placeholderText }], children: String(component.props.minLabel ?? component.props.min) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.sliderLabel, { color: theme.placeholderText }], children: String(component.props.maxLabel ?? component.props.max) })] })] })] }));
|
|
148
610
|
}
|
|
149
|
-
function DefaultProgressBarComponent({ snapshot, component, }) {
|
|
611
|
+
function DefaultProgressBarComponent({ snapshot, component, theme, }) {
|
|
150
612
|
const progress = snapshot.totalScreens > 1
|
|
151
613
|
? ((snapshot.currentScreenIndex + 1) / snapshot.totalScreens) * 100
|
|
152
614
|
: 100;
|
|
153
615
|
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
154
616
|
styles.progressTrack,
|
|
155
617
|
{
|
|
156
|
-
backgroundColor: component.props.backgroundColor ??
|
|
618
|
+
backgroundColor: component.props.backgroundColor ?? theme.progressTrackBackground,
|
|
157
619
|
height: component.props.height ?? 6,
|
|
158
620
|
},
|
|
159
621
|
], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: {
|
|
160
622
|
width: `${progress}%`,
|
|
161
|
-
backgroundColor: component.props.color ??
|
|
623
|
+
backgroundColor: component.props.color ?? theme.progressFillColor,
|
|
162
624
|
height: "100%",
|
|
163
625
|
borderRadius: 999,
|
|
164
626
|
} }) }));
|
|
165
627
|
}
|
|
166
|
-
function DefaultPageIndicatorComponent({ snapshot, component, }) {
|
|
628
|
+
function DefaultPageIndicatorComponent({ snapshot, component, theme, }) {
|
|
167
629
|
const size = component.props.size ?? 8;
|
|
168
630
|
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.pageIndicatorRow, children: Array.from({ length: snapshot.totalScreens }).map((_, index) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: {
|
|
169
631
|
width: size,
|
|
170
632
|
height: size,
|
|
171
633
|
borderRadius: size / 2,
|
|
172
634
|
backgroundColor: index === snapshot.currentScreenIndex
|
|
173
|
-
? component.props.activeColor ??
|
|
174
|
-
: component.props.inactiveColor ??
|
|
635
|
+
? component.props.activeColor ?? theme.indicatorActive
|
|
636
|
+
: component.props.inactiveColor ?? theme.indicatorInactive,
|
|
175
637
|
} }, index))) }));
|
|
176
638
|
}
|
|
177
|
-
|
|
639
|
+
// ─── Render Dispatch ────────────────────────────────────────────────────────
|
|
640
|
+
function renderDefaultComponent(component, context, theme, registry, iconRenderer, parentAlignItems = "stretch") {
|
|
178
641
|
switch (component.type) {
|
|
179
642
|
case "TEXT":
|
|
180
|
-
return (0, jsx_runtime_1.jsx)(DefaultTextComponent, { component: component });
|
|
643
|
+
return ((0, jsx_runtime_1.jsx)(DefaultTextComponent, { component: component, theme: theme, parentAlignItems: parentAlignItems }));
|
|
181
644
|
case "IMAGE":
|
|
182
645
|
return (0, jsx_runtime_1.jsx)(DefaultImageComponent, { component: component });
|
|
646
|
+
case "ICON_LIBRARY":
|
|
647
|
+
return (0, jsx_runtime_1.jsx)(DefaultIconLibraryComponent, { component: component, iconRenderer: iconRenderer });
|
|
183
648
|
case "BUTTON":
|
|
184
|
-
return (0, jsx_runtime_1.jsx)(DefaultButtonComponent, { component: component, onPress: () => context.onPressButton(component.id) });
|
|
649
|
+
return ((0, jsx_runtime_1.jsx)(DefaultButtonComponent, { component: component, onPress: () => context.onPressButton(component.id), iconRenderer: iconRenderer }));
|
|
185
650
|
case "TEXT_INPUT":
|
|
186
|
-
return (0, jsx_runtime_1.jsx)(DefaultTextInputComponent, { component: component, context: context });
|
|
651
|
+
return ((0, jsx_runtime_1.jsx)(DefaultTextInputComponent, { component: component, context: context, theme: theme, parentAlignItems: parentAlignItems }));
|
|
187
652
|
case "SINGLE_SELECT":
|
|
188
|
-
return (0, jsx_runtime_1.jsx)(DefaultSingleSelectComponent, { component: component, context: context });
|
|
653
|
+
return ((0, jsx_runtime_1.jsx)(DefaultSingleSelectComponent, { component: component, context: context, theme: theme, parentAlignItems: parentAlignItems }));
|
|
189
654
|
case "MULTI_SELECT":
|
|
190
|
-
return (0, jsx_runtime_1.jsx)(DefaultMultiSelectComponent, { component: component, context: context });
|
|
655
|
+
return ((0, jsx_runtime_1.jsx)(DefaultMultiSelectComponent, { component: component, context: context, theme: theme, parentAlignItems: parentAlignItems }));
|
|
191
656
|
case "SLIDER":
|
|
192
|
-
return (0, jsx_runtime_1.jsx)(DefaultSliderComponent, { component: component, context: context });
|
|
657
|
+
return ((0, jsx_runtime_1.jsx)(DefaultSliderComponent, { component: component, context: context, theme: theme, parentAlignItems: parentAlignItems }));
|
|
193
658
|
case "PROGRESS_BAR":
|
|
194
|
-
return (0, jsx_runtime_1.jsx)(DefaultProgressBarComponent, { component: component, snapshot: context.snapshot });
|
|
659
|
+
return (0, jsx_runtime_1.jsx)(DefaultProgressBarComponent, { component: component, snapshot: context.snapshot, theme: theme });
|
|
195
660
|
case "PAGE_INDICATOR":
|
|
196
|
-
return (0, jsx_runtime_1.jsx)(DefaultPageIndicatorComponent, { component: component, snapshot: context.snapshot });
|
|
661
|
+
return (0, jsx_runtime_1.jsx)(DefaultPageIndicatorComponent, { component: component, snapshot: context.snapshot, theme: theme });
|
|
197
662
|
case "CUSTOM_COMPONENT": {
|
|
198
663
|
const registered = registry?.getComponent(component.props.registryKey);
|
|
199
664
|
return registered
|
|
@@ -209,41 +674,108 @@ function renderDefaultComponent(component, context, registry) {
|
|
|
209
674
|
return null;
|
|
210
675
|
}
|
|
211
676
|
}
|
|
212
|
-
|
|
677
|
+
// ─── Main Renderer ──────────────────────────────────────────────────────────
|
|
678
|
+
function ArloFlowRenderer({ session, handlers, componentRenderers, registry, iconRenderer, autoStart = true, emptyState = null, unsupportedComponent, unsupportedScreen, onSnapshotChange, }) {
|
|
213
679
|
const [snapshot, setSnapshot] = (0, react_1.useState)(() => session.getSnapshot());
|
|
680
|
+
const savedOnSnapshotChange = (0, react_1.useRef)(onSnapshotChange);
|
|
681
|
+
const savedHandlers = (0, react_1.useRef)(handlers);
|
|
682
|
+
(0, react_1.useEffect)(() => {
|
|
683
|
+
savedOnSnapshotChange.current = onSnapshotChange;
|
|
684
|
+
savedHandlers.current = handlers;
|
|
685
|
+
}, [onSnapshotChange, handlers]);
|
|
214
686
|
(0, react_1.useEffect)(() => {
|
|
215
687
|
const nextSnapshot = session.getSnapshot();
|
|
216
|
-
|
|
217
|
-
|
|
688
|
+
(0, react_1.startTransition)(() => {
|
|
689
|
+
setSnapshot(nextSnapshot);
|
|
690
|
+
});
|
|
691
|
+
savedOnSnapshotChange.current?.(nextSnapshot);
|
|
218
692
|
if (autoStart && session.getSnapshot().status === "idle") {
|
|
219
693
|
const effect = session.start();
|
|
220
694
|
const startedSnapshot = session.getSnapshot();
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
695
|
+
(0, react_1.startTransition)(() => {
|
|
696
|
+
setSnapshot(startedSnapshot);
|
|
697
|
+
});
|
|
698
|
+
savedOnSnapshotChange.current?.(startedSnapshot);
|
|
699
|
+
void (0, arlo_sdk_1.applyFlowSessionEffect)(session, effect, savedHandlers.current);
|
|
224
700
|
}
|
|
225
|
-
}, [autoStart,
|
|
701
|
+
}, [autoStart, session]);
|
|
226
702
|
const sortedComponents = (0, react_1.useMemo)(() => [...(snapshot.currentScreen?.components ?? [])].sort((a, b) => a.order - b.order), [snapshot.currentScreen]);
|
|
703
|
+
const theme = (0, react_1.useMemo)(() => (snapshot.currentScreen ? getThemeForScreen(snapshot.currentScreen) : LIGHT_THEME), [snapshot.currentScreen]);
|
|
227
704
|
const context = {
|
|
228
705
|
session,
|
|
229
706
|
snapshot,
|
|
230
707
|
handlers,
|
|
708
|
+
iconRenderer,
|
|
231
709
|
onValueChange: (fieldKey, value) => {
|
|
232
710
|
const nextSnapshot = session.setValue(fieldKey, value);
|
|
233
711
|
setSnapshot(nextSnapshot);
|
|
234
712
|
onSnapshotChange?.(nextSnapshot);
|
|
713
|
+
const component = findInteractiveComponent(nextSnapshot.currentScreen, fieldKey);
|
|
714
|
+
if (component) {
|
|
715
|
+
emitAnalyticsEvent(handlers, session, nextSnapshot, (0, arlo_sdk_1.createComponentInteractionAnalyticsEvent)(nextSnapshot, component, value));
|
|
716
|
+
}
|
|
235
717
|
},
|
|
236
718
|
onPressButton: async (componentId) => {
|
|
719
|
+
const currentSnapshot = session.getSnapshot();
|
|
720
|
+
const currentHandlers = handlers;
|
|
721
|
+
const buttonComponent = findButtonComponent(currentSnapshot.currentScreen, componentId);
|
|
722
|
+
if (buttonComponent) {
|
|
723
|
+
emitAnalyticsEvent(currentHandlers, session, currentSnapshot, (0, arlo_sdk_1.createButtonPressedAnalyticsEvent)(currentSnapshot, buttonComponent));
|
|
724
|
+
}
|
|
237
725
|
const effect = session.pressButton(componentId);
|
|
238
726
|
const immediateSnapshot = session.getSnapshot();
|
|
239
727
|
setSnapshot(immediateSnapshot);
|
|
240
728
|
onSnapshotChange?.(immediateSnapshot);
|
|
241
|
-
await (0, arlo_sdk_1.applyFlowSessionEffect)(session, effect,
|
|
729
|
+
await (0, arlo_sdk_1.applyFlowSessionEffect)(session, effect, currentHandlers);
|
|
242
730
|
const finalSnapshot = session.getSnapshot();
|
|
243
731
|
setSnapshot(finalSnapshot);
|
|
244
732
|
onSnapshotChange?.(finalSnapshot);
|
|
245
733
|
},
|
|
246
734
|
};
|
|
735
|
+
const renderComponentNode = (component, renderContext, renderTheme, isAbsoluteScreen, parentAlignItems = "stretch") => {
|
|
736
|
+
const customRenderer = componentRenderers?.[component.type];
|
|
737
|
+
const content = customRenderer
|
|
738
|
+
? customRenderer(component, renderContext)
|
|
739
|
+
: renderDefaultComponent(component, renderContext, renderTheme, registry, iconRenderer, parentAlignItems);
|
|
740
|
+
if (content === null) {
|
|
741
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.unsupported, children: unsupportedComponent ? (unsupportedComponent(component)) : ((0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.unsupportedText, children: ["Unsupported component: ", component.type] })) }, component.id));
|
|
742
|
+
}
|
|
743
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: getComponentWrapperStyle(component, isAbsoluteScreen, parentAlignItems), children: content }, component.id));
|
|
744
|
+
};
|
|
745
|
+
const renderAutoLayoutScreen = (screen, components, renderContext, renderTheme) => {
|
|
746
|
+
const padding = getScreenPadding(screen);
|
|
747
|
+
const backgroundColor = screen.style?.backgroundColor ?? "#FFFFFF";
|
|
748
|
+
const justifyContent = screen.style?.justifyContent ?? "flex-start";
|
|
749
|
+
const alignItems = screen.style?.alignItems ?? "stretch";
|
|
750
|
+
const { main, bottom } = splitAutoLayoutComponents(components);
|
|
751
|
+
return ((0, jsx_runtime_1.jsx)(SafeView, { style: { flex: 1, backgroundColor }, children: (0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { contentInsetAdjustmentBehavior: "automatic", automaticallyAdjustKeyboardInsets: true, keyboardShouldPersistTaps: "handled", contentContainerStyle: [
|
|
752
|
+
styles.container,
|
|
753
|
+
{
|
|
754
|
+
backgroundColor,
|
|
755
|
+
},
|
|
756
|
+
], children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.autoLayoutRoot, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
757
|
+
styles.autoLayoutMain,
|
|
758
|
+
{
|
|
759
|
+
backgroundColor,
|
|
760
|
+
justifyContent,
|
|
761
|
+
alignItems,
|
|
762
|
+
paddingTop: padding.top,
|
|
763
|
+
paddingRight: padding.right,
|
|
764
|
+
paddingBottom: bottom.length > 0 ? 12 : padding.bottom,
|
|
765
|
+
paddingLeft: padding.left,
|
|
766
|
+
},
|
|
767
|
+
], children: main.map((component) => renderComponentNode(component, renderContext, renderTheme, false, alignItems)) }), bottom.length > 0 ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
768
|
+
styles.autoLayoutBottom,
|
|
769
|
+
{
|
|
770
|
+
backgroundColor,
|
|
771
|
+
alignItems,
|
|
772
|
+
paddingTop: 12,
|
|
773
|
+
paddingRight: padding.right,
|
|
774
|
+
paddingBottom: padding.bottom,
|
|
775
|
+
paddingLeft: padding.left,
|
|
776
|
+
},
|
|
777
|
+
], children: bottom.map((component) => renderComponentNode(component, renderContext, renderTheme, false, alignItems)) })) : null] }) }) }));
|
|
778
|
+
};
|
|
247
779
|
if (!snapshot.currentScreen) {
|
|
248
780
|
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: emptyState });
|
|
249
781
|
}
|
|
@@ -259,63 +791,49 @@ function ArloFlowRenderer({ session, handlers, componentRenderers, registry, aut
|
|
|
259
791
|
}
|
|
260
792
|
if (importedPreviewScreen) {
|
|
261
793
|
const previewComponents = [...(importedPreviewScreen.components ?? [])].sort((a, b) => a.order - b.order);
|
|
794
|
+
const previewTheme = getThemeForScreen(importedPreviewScreen);
|
|
262
795
|
const previewContext = {
|
|
263
796
|
...context,
|
|
264
797
|
onValueChange: () => undefined,
|
|
265
798
|
onPressButton: async () => undefined,
|
|
266
799
|
};
|
|
267
|
-
return (
|
|
268
|
-
styles.container,
|
|
269
|
-
getScreenContainerStyle(importedPreviewScreen),
|
|
270
|
-
], children: previewComponents.map((component) => {
|
|
271
|
-
const customRenderer = componentRenderers?.[component.type];
|
|
272
|
-
const content = customRenderer
|
|
273
|
-
? customRenderer(component, previewContext)
|
|
274
|
-
: renderDefaultComponent(component, previewContext, registry);
|
|
275
|
-
if (content === null) {
|
|
276
|
-
return null;
|
|
277
|
-
}
|
|
278
|
-
return (0, jsx_runtime_1.jsx)(react_native_1.View, { children: content }, component.id);
|
|
279
|
-
}) }));
|
|
800
|
+
return renderAutoLayoutScreen(importedPreviewScreen, previewComponents, previewContext, previewTheme);
|
|
280
801
|
}
|
|
281
802
|
return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: unsupportedScreen ? (unsupportedScreen(snapshot.currentScreen)) : ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.unsupported, children: (0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.unsupportedText, children: ["Unsupported screen: ", snapshot.currentScreen.customScreenKey] }) })) }));
|
|
282
803
|
}
|
|
283
804
|
if (snapshot.currentScreen.layoutMode === "absolute") {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const content = customRenderer
|
|
290
|
-
? customRenderer(component, context)
|
|
291
|
-
: renderDefaultComponent(component, context, registry);
|
|
292
|
-
if (content === null) {
|
|
293
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.unsupported, children: unsupportedComponent ? (unsupportedComponent(component)) : ((0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.unsupportedText, children: ["Unsupported component: ", component.type] })) }, component.id));
|
|
294
|
-
}
|
|
295
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: getComponentWrapperStyle(component, true), children: content }, component.id));
|
|
296
|
-
}) }));
|
|
805
|
+
const bgColor = snapshot.currentScreen.style?.backgroundColor ?? "#FFFFFF";
|
|
806
|
+
return ((0, jsx_runtime_1.jsx)(SafeView, { style: { flex: 1, backgroundColor: bgColor }, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
|
|
807
|
+
styles.absoluteContainer,
|
|
808
|
+
getScreenContainerStyle(snapshot.currentScreen),
|
|
809
|
+
], children: sortedComponents.map((component) => renderComponentNode(component, context, theme, true)) }) }));
|
|
297
810
|
}
|
|
298
|
-
return (
|
|
299
|
-
styles.container,
|
|
300
|
-
getScreenContainerStyle(snapshot.currentScreen),
|
|
301
|
-
], children: sortedComponents.map((component) => {
|
|
302
|
-
const customRenderer = componentRenderers?.[component.type];
|
|
303
|
-
const content = customRenderer
|
|
304
|
-
? customRenderer(component, context)
|
|
305
|
-
: renderDefaultComponent(component, context, registry);
|
|
306
|
-
if (content === null) {
|
|
307
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.unsupported, children: unsupportedComponent ? (unsupportedComponent(component)) : ((0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.unsupportedText, children: ["Unsupported component: ", component.type] })) }, component.id));
|
|
308
|
-
}
|
|
309
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: getComponentWrapperStyle(component, false), children: content }, component.id));
|
|
310
|
-
}) }));
|
|
811
|
+
return renderAutoLayoutScreen(snapshot.currentScreen, sortedComponents, context, theme);
|
|
311
812
|
}
|
|
813
|
+
// ─── Styles ─────────────────────────────────────────────────────────────────
|
|
312
814
|
const styles = react_native_1.StyleSheet.create({
|
|
313
815
|
container: {
|
|
314
816
|
flexGrow: 1,
|
|
315
|
-
|
|
817
|
+
alignItems: "stretch",
|
|
316
818
|
},
|
|
317
819
|
componentBlock: {
|
|
820
|
+
alignSelf: "stretch",
|
|
821
|
+
},
|
|
822
|
+
autoLayoutRoot: {
|
|
823
|
+
flexGrow: 1,
|
|
318
824
|
width: "100%",
|
|
825
|
+
alignSelf: "stretch",
|
|
826
|
+
},
|
|
827
|
+
autoLayoutMain: {
|
|
828
|
+
flexGrow: 1,
|
|
829
|
+
width: "100%",
|
|
830
|
+
alignSelf: "stretch",
|
|
831
|
+
gap: 16,
|
|
832
|
+
},
|
|
833
|
+
autoLayoutBottom: {
|
|
834
|
+
width: "100%",
|
|
835
|
+
alignSelf: "stretch",
|
|
836
|
+
gap: 12,
|
|
319
837
|
},
|
|
320
838
|
absoluteContainer: {
|
|
321
839
|
flex: 1,
|
|
@@ -326,24 +844,16 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
326
844
|
gap: 8,
|
|
327
845
|
},
|
|
328
846
|
fieldLabel: {
|
|
329
|
-
color: "#f3f3f5",
|
|
330
847
|
fontSize: 14,
|
|
331
848
|
fontWeight: "600",
|
|
332
849
|
},
|
|
333
850
|
input: {
|
|
334
851
|
borderWidth: 1,
|
|
335
|
-
borderColor: "#2c2c34",
|
|
336
852
|
borderRadius: 14,
|
|
337
853
|
paddingHorizontal: 14,
|
|
338
854
|
paddingVertical: 12,
|
|
339
|
-
color: "#ffffff",
|
|
340
|
-
backgroundColor: "#141419",
|
|
341
|
-
},
|
|
342
|
-
inputError: {
|
|
343
|
-
borderColor: "#f36b8d",
|
|
344
855
|
},
|
|
345
856
|
fieldError: {
|
|
346
|
-
color: "#f59cb3",
|
|
347
857
|
fontSize: 12,
|
|
348
858
|
fontWeight: "500",
|
|
349
859
|
},
|
|
@@ -351,13 +861,30 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
351
861
|
minHeight: 52,
|
|
352
862
|
alignItems: "center",
|
|
353
863
|
justifyContent: "center",
|
|
354
|
-
|
|
355
|
-
|
|
864
|
+
},
|
|
865
|
+
buttonContent: {
|
|
866
|
+
flexDirection: "row",
|
|
867
|
+
alignItems: "center",
|
|
868
|
+
justifyContent: "center",
|
|
356
869
|
},
|
|
357
870
|
buttonText: {
|
|
358
871
|
fontSize: 16,
|
|
359
872
|
fontWeight: "700",
|
|
360
873
|
},
|
|
874
|
+
selectList: {
|
|
875
|
+
gap: 8,
|
|
876
|
+
},
|
|
877
|
+
selectOption: {
|
|
878
|
+
width: "100%",
|
|
879
|
+
borderWidth: 1,
|
|
880
|
+
borderRadius: 16,
|
|
881
|
+
paddingHorizontal: 16,
|
|
882
|
+
paddingVertical: 12,
|
|
883
|
+
},
|
|
884
|
+
selectOptionText: {
|
|
885
|
+
fontSize: 14,
|
|
886
|
+
fontWeight: "600",
|
|
887
|
+
},
|
|
361
888
|
optionGroup: {
|
|
362
889
|
flexDirection: "row",
|
|
363
890
|
flexWrap: "wrap",
|
|
@@ -368,37 +895,45 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
368
895
|
paddingVertical: 10,
|
|
369
896
|
borderRadius: 999,
|
|
370
897
|
borderWidth: 1,
|
|
371
|
-
borderColor: "#30303a",
|
|
372
|
-
backgroundColor: "#15151b",
|
|
373
|
-
},
|
|
374
|
-
optionPillSelected: {
|
|
375
|
-
backgroundColor: "#ffffff",
|
|
376
|
-
borderColor: "#ffffff",
|
|
377
898
|
},
|
|
378
899
|
optionPillText: {
|
|
379
|
-
color: "#f1f1f3",
|
|
380
900
|
fontSize: 14,
|
|
381
901
|
fontWeight: "600",
|
|
382
902
|
},
|
|
383
|
-
|
|
384
|
-
|
|
903
|
+
sliderField: {
|
|
904
|
+
gap: 6,
|
|
385
905
|
},
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
borderColor: "#2b2b34",
|
|
391
|
-
padding: 14,
|
|
392
|
-
gap: 12,
|
|
906
|
+
sliderTouchArea: {
|
|
907
|
+
height: 28,
|
|
908
|
+
justifyContent: "center",
|
|
909
|
+
position: "relative",
|
|
393
910
|
},
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
911
|
+
sliderTrack: {
|
|
912
|
+
height: 6,
|
|
913
|
+
borderRadius: 999,
|
|
914
|
+
overflow: "hidden",
|
|
398
915
|
},
|
|
399
|
-
|
|
916
|
+
sliderFill: {
|
|
917
|
+
position: "absolute",
|
|
918
|
+
left: 0,
|
|
919
|
+
top: 11,
|
|
920
|
+
height: 6,
|
|
921
|
+
borderRadius: 999,
|
|
922
|
+
},
|
|
923
|
+
sliderThumb: {
|
|
924
|
+
position: "absolute",
|
|
925
|
+
top: 5,
|
|
926
|
+
width: 18,
|
|
927
|
+
height: 18,
|
|
928
|
+
borderRadius: 999,
|
|
929
|
+
borderWidth: 3,
|
|
930
|
+
},
|
|
931
|
+
sliderLabels: {
|
|
400
932
|
flexDirection: "row",
|
|
401
|
-
|
|
933
|
+
justifyContent: "space-between",
|
|
934
|
+
},
|
|
935
|
+
sliderLabel: {
|
|
936
|
+
fontSize: 11,
|
|
402
937
|
},
|
|
403
938
|
progressTrack: {
|
|
404
939
|
width: "100%",
|