arlo-react-native 0.1.2 → 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 +666 -137
- 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,47 +674,108 @@ function renderDefaultComponent(component, context, registry) {
|
|
|
209
674
|
return null;
|
|
210
675
|
}
|
|
211
676
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const onSnapshotChangeRef = (0, react_1.useRef)(onSnapshotChange);
|
|
215
|
-
(0, react_1.useEffect)(() => {
|
|
216
|
-
handlersRef.current = handlers;
|
|
217
|
-
onSnapshotChangeRef.current = onSnapshotChange;
|
|
218
|
-
});
|
|
677
|
+
// ─── Main Renderer ──────────────────────────────────────────────────────────
|
|
678
|
+
function ArloFlowRenderer({ session, handlers, componentRenderers, registry, iconRenderer, autoStart = true, emptyState = null, unsupportedComponent, unsupportedScreen, onSnapshotChange, }) {
|
|
219
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]);
|
|
220
686
|
(0, react_1.useEffect)(() => {
|
|
221
687
|
const nextSnapshot = session.getSnapshot();
|
|
222
|
-
|
|
223
|
-
|
|
688
|
+
(0, react_1.startTransition)(() => {
|
|
689
|
+
setSnapshot(nextSnapshot);
|
|
690
|
+
});
|
|
691
|
+
savedOnSnapshotChange.current?.(nextSnapshot);
|
|
224
692
|
if (autoStart && session.getSnapshot().status === "idle") {
|
|
225
693
|
const effect = session.start();
|
|
226
694
|
const startedSnapshot = session.getSnapshot();
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
695
|
+
(0, react_1.startTransition)(() => {
|
|
696
|
+
setSnapshot(startedSnapshot);
|
|
697
|
+
});
|
|
698
|
+
savedOnSnapshotChange.current?.(startedSnapshot);
|
|
699
|
+
void (0, arlo_sdk_1.applyFlowSessionEffect)(session, effect, savedHandlers.current);
|
|
230
700
|
}
|
|
231
701
|
}, [autoStart, session]);
|
|
232
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]);
|
|
233
704
|
const context = {
|
|
234
705
|
session,
|
|
235
706
|
snapshot,
|
|
236
707
|
handlers,
|
|
708
|
+
iconRenderer,
|
|
237
709
|
onValueChange: (fieldKey, value) => {
|
|
238
710
|
const nextSnapshot = session.setValue(fieldKey, value);
|
|
239
711
|
setSnapshot(nextSnapshot);
|
|
240
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
|
+
}
|
|
241
717
|
},
|
|
242
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
|
+
}
|
|
243
725
|
const effect = session.pressButton(componentId);
|
|
244
726
|
const immediateSnapshot = session.getSnapshot();
|
|
245
727
|
setSnapshot(immediateSnapshot);
|
|
246
728
|
onSnapshotChange?.(immediateSnapshot);
|
|
247
|
-
await (0, arlo_sdk_1.applyFlowSessionEffect)(session, effect,
|
|
729
|
+
await (0, arlo_sdk_1.applyFlowSessionEffect)(session, effect, currentHandlers);
|
|
248
730
|
const finalSnapshot = session.getSnapshot();
|
|
249
731
|
setSnapshot(finalSnapshot);
|
|
250
732
|
onSnapshotChange?.(finalSnapshot);
|
|
251
733
|
},
|
|
252
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
|
+
};
|
|
253
779
|
if (!snapshot.currentScreen) {
|
|
254
780
|
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: emptyState });
|
|
255
781
|
}
|
|
@@ -265,63 +791,49 @@ function ArloFlowRenderer({ session, handlers, componentRenderers, registry, aut
|
|
|
265
791
|
}
|
|
266
792
|
if (importedPreviewScreen) {
|
|
267
793
|
const previewComponents = [...(importedPreviewScreen.components ?? [])].sort((a, b) => a.order - b.order);
|
|
794
|
+
const previewTheme = getThemeForScreen(importedPreviewScreen);
|
|
268
795
|
const previewContext = {
|
|
269
796
|
...context,
|
|
270
797
|
onValueChange: () => undefined,
|
|
271
798
|
onPressButton: async () => undefined,
|
|
272
799
|
};
|
|
273
|
-
return (
|
|
274
|
-
styles.container,
|
|
275
|
-
getScreenContainerStyle(importedPreviewScreen),
|
|
276
|
-
], children: previewComponents.map((component) => {
|
|
277
|
-
const customRenderer = componentRenderers?.[component.type];
|
|
278
|
-
const content = customRenderer
|
|
279
|
-
? customRenderer(component, previewContext)
|
|
280
|
-
: renderDefaultComponent(component, previewContext, registry);
|
|
281
|
-
if (content === null) {
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
return (0, jsx_runtime_1.jsx)(react_native_1.View, { children: content }, component.id);
|
|
285
|
-
}) }));
|
|
800
|
+
return renderAutoLayoutScreen(importedPreviewScreen, previewComponents, previewContext, previewTheme);
|
|
286
801
|
}
|
|
287
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] }) })) }));
|
|
288
803
|
}
|
|
289
804
|
if (snapshot.currentScreen.layoutMode === "absolute") {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const content = customRenderer
|
|
296
|
-
? customRenderer(component, context)
|
|
297
|
-
: renderDefaultComponent(component, context, registry);
|
|
298
|
-
if (content === null) {
|
|
299
|
-
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));
|
|
300
|
-
}
|
|
301
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: getComponentWrapperStyle(component, true), children: content }, component.id));
|
|
302
|
-
}) }));
|
|
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)) }) }));
|
|
303
810
|
}
|
|
304
|
-
return (
|
|
305
|
-
styles.container,
|
|
306
|
-
getScreenContainerStyle(snapshot.currentScreen),
|
|
307
|
-
], children: sortedComponents.map((component) => {
|
|
308
|
-
const customRenderer = componentRenderers?.[component.type];
|
|
309
|
-
const content = customRenderer
|
|
310
|
-
? customRenderer(component, context)
|
|
311
|
-
: renderDefaultComponent(component, context, registry);
|
|
312
|
-
if (content === null) {
|
|
313
|
-
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));
|
|
314
|
-
}
|
|
315
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: getComponentWrapperStyle(component, false), children: content }, component.id));
|
|
316
|
-
}) }));
|
|
811
|
+
return renderAutoLayoutScreen(snapshot.currentScreen, sortedComponents, context, theme);
|
|
317
812
|
}
|
|
813
|
+
// ─── Styles ─────────────────────────────────────────────────────────────────
|
|
318
814
|
const styles = react_native_1.StyleSheet.create({
|
|
319
815
|
container: {
|
|
320
816
|
flexGrow: 1,
|
|
321
|
-
|
|
817
|
+
alignItems: "stretch",
|
|
322
818
|
},
|
|
323
819
|
componentBlock: {
|
|
820
|
+
alignSelf: "stretch",
|
|
821
|
+
},
|
|
822
|
+
autoLayoutRoot: {
|
|
823
|
+
flexGrow: 1,
|
|
824
|
+
width: "100%",
|
|
825
|
+
alignSelf: "stretch",
|
|
826
|
+
},
|
|
827
|
+
autoLayoutMain: {
|
|
828
|
+
flexGrow: 1,
|
|
829
|
+
width: "100%",
|
|
830
|
+
alignSelf: "stretch",
|
|
831
|
+
gap: 16,
|
|
832
|
+
},
|
|
833
|
+
autoLayoutBottom: {
|
|
324
834
|
width: "100%",
|
|
835
|
+
alignSelf: "stretch",
|
|
836
|
+
gap: 12,
|
|
325
837
|
},
|
|
326
838
|
absoluteContainer: {
|
|
327
839
|
flex: 1,
|
|
@@ -332,24 +844,16 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
332
844
|
gap: 8,
|
|
333
845
|
},
|
|
334
846
|
fieldLabel: {
|
|
335
|
-
color: "#f3f3f5",
|
|
336
847
|
fontSize: 14,
|
|
337
848
|
fontWeight: "600",
|
|
338
849
|
},
|
|
339
850
|
input: {
|
|
340
851
|
borderWidth: 1,
|
|
341
|
-
borderColor: "#2c2c34",
|
|
342
852
|
borderRadius: 14,
|
|
343
853
|
paddingHorizontal: 14,
|
|
344
854
|
paddingVertical: 12,
|
|
345
|
-
color: "#ffffff",
|
|
346
|
-
backgroundColor: "#141419",
|
|
347
|
-
},
|
|
348
|
-
inputError: {
|
|
349
|
-
borderColor: "#f36b8d",
|
|
350
855
|
},
|
|
351
856
|
fieldError: {
|
|
352
|
-
color: "#f59cb3",
|
|
353
857
|
fontSize: 12,
|
|
354
858
|
fontWeight: "500",
|
|
355
859
|
},
|
|
@@ -357,13 +861,30 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
357
861
|
minHeight: 52,
|
|
358
862
|
alignItems: "center",
|
|
359
863
|
justifyContent: "center",
|
|
360
|
-
|
|
361
|
-
|
|
864
|
+
},
|
|
865
|
+
buttonContent: {
|
|
866
|
+
flexDirection: "row",
|
|
867
|
+
alignItems: "center",
|
|
868
|
+
justifyContent: "center",
|
|
362
869
|
},
|
|
363
870
|
buttonText: {
|
|
364
871
|
fontSize: 16,
|
|
365
872
|
fontWeight: "700",
|
|
366
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
|
+
},
|
|
367
888
|
optionGroup: {
|
|
368
889
|
flexDirection: "row",
|
|
369
890
|
flexWrap: "wrap",
|
|
@@ -374,37 +895,45 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
374
895
|
paddingVertical: 10,
|
|
375
896
|
borderRadius: 999,
|
|
376
897
|
borderWidth: 1,
|
|
377
|
-
borderColor: "#30303a",
|
|
378
|
-
backgroundColor: "#15151b",
|
|
379
|
-
},
|
|
380
|
-
optionPillSelected: {
|
|
381
|
-
backgroundColor: "#ffffff",
|
|
382
|
-
borderColor: "#ffffff",
|
|
383
898
|
},
|
|
384
899
|
optionPillText: {
|
|
385
|
-
color: "#f1f1f3",
|
|
386
900
|
fontSize: 14,
|
|
387
901
|
fontWeight: "600",
|
|
388
902
|
},
|
|
389
|
-
|
|
390
|
-
|
|
903
|
+
sliderField: {
|
|
904
|
+
gap: 6,
|
|
391
905
|
},
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
borderColor: "#2b2b34",
|
|
397
|
-
padding: 14,
|
|
398
|
-
gap: 12,
|
|
906
|
+
sliderTouchArea: {
|
|
907
|
+
height: 28,
|
|
908
|
+
justifyContent: "center",
|
|
909
|
+
position: "relative",
|
|
399
910
|
},
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
911
|
+
sliderTrack: {
|
|
912
|
+
height: 6,
|
|
913
|
+
borderRadius: 999,
|
|
914
|
+
overflow: "hidden",
|
|
915
|
+
},
|
|
916
|
+
sliderFill: {
|
|
917
|
+
position: "absolute",
|
|
918
|
+
left: 0,
|
|
919
|
+
top: 11,
|
|
920
|
+
height: 6,
|
|
921
|
+
borderRadius: 999,
|
|
404
922
|
},
|
|
405
|
-
|
|
923
|
+
sliderThumb: {
|
|
924
|
+
position: "absolute",
|
|
925
|
+
top: 5,
|
|
926
|
+
width: 18,
|
|
927
|
+
height: 18,
|
|
928
|
+
borderRadius: 999,
|
|
929
|
+
borderWidth: 3,
|
|
930
|
+
},
|
|
931
|
+
sliderLabels: {
|
|
406
932
|
flexDirection: "row",
|
|
407
|
-
|
|
933
|
+
justifyContent: "space-between",
|
|
934
|
+
},
|
|
935
|
+
sliderLabel: {
|
|
936
|
+
fontSize: 11,
|
|
408
937
|
},
|
|
409
938
|
progressTrack: {
|
|
410
939
|
width: "100%",
|