@sonata-innovations/fiber-fbre 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +508 -0
- package/dist/fiber-fbre.cjs +1 -0
- package/dist/fiber-fbre.css +1 -0
- package/dist/fiber-fbre.js +2113 -0
- package/dist/index.d.ts +237 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
# @sonata-innovations/fiber-fbre v2
|
|
2
|
+
|
|
3
|
+
Fiber Render Engine — consumes Flow JSON and renders data collection forms. Handles conditional logic, validation, screen transitions, and outputs collected FlowData back to the parent application.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Local Mode
|
|
8
|
+
|
|
9
|
+
Pass a Flow object directly:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { FBRE } from "@sonata-innovations/fiber-fbre";
|
|
13
|
+
import "@sonata-innovations/fiber-fbre/styles";
|
|
14
|
+
|
|
15
|
+
function App() {
|
|
16
|
+
return (
|
|
17
|
+
<FBRE
|
|
18
|
+
flow={myFlow}
|
|
19
|
+
screenIndex={0}
|
|
20
|
+
onFlowComplete={(data) => console.log(data)}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Remote Mode
|
|
27
|
+
|
|
28
|
+
Fetch a published flow from a Fiber API:
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { FBRE } from "@sonata-innovations/fiber-fbre";
|
|
32
|
+
import "@sonata-innovations/fiber-fbre/styles";
|
|
33
|
+
|
|
34
|
+
function App() {
|
|
35
|
+
return (
|
|
36
|
+
<FBRE
|
|
37
|
+
flowId="your-flow-id"
|
|
38
|
+
apiEndpoint="https://your-api.example.com/api/v1"
|
|
39
|
+
apiKey="your-api-key"
|
|
40
|
+
onFlowComplete={(data) => console.log(data)}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Server-Driven Mode
|
|
47
|
+
|
|
48
|
+
Session-based rendering where the server evaluates conditions and validation:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { FBRE } from "@sonata-innovations/fiber-fbre";
|
|
52
|
+
import "@sonata-innovations/fiber-fbre/styles";
|
|
53
|
+
|
|
54
|
+
function App() {
|
|
55
|
+
return (
|
|
56
|
+
<FBRE
|
|
57
|
+
sessionEndpoint="https://your-api.example.com/api/v1/public/sessions"
|
|
58
|
+
flowId="your-flow-id"
|
|
59
|
+
onFlowComplete={(data) => console.log(data)}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm install @sonata-innovations/fiber-fbre
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then import the CSS in your app entry point:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import "@sonata-innovations/fiber-fbre/styles";
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Peer dependencies:** `react ^18.0.0 || ^19.0.0`, `react-dom ^18.0.0 || ^19.0.0`
|
|
78
|
+
|
|
79
|
+
**Runtime dependencies:** `zustand`, `@sonata-innovations/fiber-types`, `@sonata-innovations/fiber-shared`
|
|
80
|
+
|
|
81
|
+
## API
|
|
82
|
+
|
|
83
|
+
### `<FBRE />` Props
|
|
84
|
+
|
|
85
|
+
FBRE supports three rendering modes, selected by which props you pass. The modes are mutually exclusive.
|
|
86
|
+
|
|
87
|
+
#### Local Mode
|
|
88
|
+
|
|
89
|
+
Render a Flow object you already have in memory.
|
|
90
|
+
|
|
91
|
+
| Prop | Type | Required | Description |
|
|
92
|
+
|------|------|----------|-------------|
|
|
93
|
+
| `flow` | `Flow` | Yes | Flow JSON object |
|
|
94
|
+
| `data` | `FlowData` | No | Pre-populated form data |
|
|
95
|
+
| `screenIndex` | `number` | No | Initial screen index (default `0`) |
|
|
96
|
+
| `theme` | `ThemeConfig` | No | Override theme settings (merged over `flow.config.theme`) |
|
|
97
|
+
| `navigation` | `NavigationConfig` | No | Override navigation settings (merged over `flow.config.navigation`) |
|
|
98
|
+
| `controls` | `ControlsConfig` | No | Override controls settings (merged over `flow.config.controls`) |
|
|
99
|
+
| `context` | `Record<string, string \| boolean \| number>` | No | External context values for condition evaluation and calculations |
|
|
100
|
+
| `storeRef` | `MutableRefObject<StoreApi<FBREStoreState> \| null>` | No | Ref to access the Zustand store |
|
|
101
|
+
| `onFlowComplete` | `(data: FlowData) => void` | Yes | Called when the user completes the flow |
|
|
102
|
+
| `onScreenChange` | `(index: number, data: FlowData) => void` | No | Called on screen navigation |
|
|
103
|
+
| `onScreenValidationChange` | `(index: number, data: any) => void` | No | Called when screen validity changes |
|
|
104
|
+
|
|
105
|
+
#### Remote Mode
|
|
106
|
+
|
|
107
|
+
Fetch a published flow from a Fiber API by ID.
|
|
108
|
+
|
|
109
|
+
| Prop | Type | Required | Description |
|
|
110
|
+
|------|------|----------|-------------|
|
|
111
|
+
| `flowId` | `string` | Yes | ID of the published flow |
|
|
112
|
+
| `apiEndpoint` | `string` | Yes | Base URL of the Fiber API |
|
|
113
|
+
| `apiKey` | `string` | No | API key for authentication |
|
|
114
|
+
| `data` | `FlowData` | No | Pre-populated form data |
|
|
115
|
+
| `screenIndex` | `number` | No | Initial screen index (default `0`) |
|
|
116
|
+
| `theme` | `ThemeConfig` | No | Override theme settings |
|
|
117
|
+
| `navigation` | `NavigationConfig` | No | Override navigation settings |
|
|
118
|
+
| `controls` | `ControlsConfig` | No | Override controls settings |
|
|
119
|
+
| `context` | `Record<string, string \| boolean \| number>` | No | External context values for condition evaluation and calculations |
|
|
120
|
+
| `storeRef` | `MutableRefObject<StoreApi<FBREStoreState> \| null>` | No | Ref to access the Zustand store |
|
|
121
|
+
| `onFlowComplete` | `(data: FlowData) => void` | Yes | Called when the user completes the flow |
|
|
122
|
+
| `onScreenChange` | `(index: number, data: FlowData) => void` | No | Called on screen navigation |
|
|
123
|
+
| `onScreenValidationChange` | `(index: number, data: any) => void` | No | Called when screen validity changes |
|
|
124
|
+
|
|
125
|
+
#### Server-Driven Mode
|
|
126
|
+
|
|
127
|
+
Session-based rendering. The server evaluates conditions and validation; the client renders one screen at a time.
|
|
128
|
+
|
|
129
|
+
| Prop | Type | Required | Description |
|
|
130
|
+
|------|------|----------|-------------|
|
|
131
|
+
| `sessionEndpoint` | `string` | Yes | Session API base URL |
|
|
132
|
+
| `flowId` | `string` | Yes | ID of the flow to start a session for |
|
|
133
|
+
| `apiKey` | `string` | No | API key for authentication |
|
|
134
|
+
| `theme` | `ThemeConfig` | No | Override theme settings |
|
|
135
|
+
| `context` | `Record<string, string \| boolean \| number>` | No | External context values for condition evaluation and calculations |
|
|
136
|
+
| `onFlowComplete` | `(data: FlowData) => void` | Yes | Called when the user completes the flow |
|
|
137
|
+
| `onScreenChange` | `(screenNumber: number) => void` | No | Called on screen navigation (note: receives screen number, not index + data) |
|
|
138
|
+
|
|
139
|
+
### Imperative Access
|
|
140
|
+
|
|
141
|
+
Access the store directly for programmatic control:
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { useFBREStore, useFBREStoreApi, useFBREApi } from "@sonata-innovations/fiber-fbre";
|
|
145
|
+
|
|
146
|
+
// Inside a child of <FBRE />
|
|
147
|
+
const flowData = useFBREStore((s) => s.getFlowData());
|
|
148
|
+
const storeApi = useFBREStoreApi();
|
|
149
|
+
const data = storeApi.getState().getFlowData();
|
|
150
|
+
|
|
151
|
+
// In remote mode — access API config, flowId, tenantId
|
|
152
|
+
const apiContext = useFBREApi();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Events
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
import { addFBREEventListener, removeFBREEventListener } from "@sonata-innovations/fiber-fbre";
|
|
159
|
+
|
|
160
|
+
const handler = (id, data) => console.log("file uploaded:", id, data);
|
|
161
|
+
addFBREEventListener("file-upload", handler);
|
|
162
|
+
removeFBREEventListener("file-upload", handler);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Exports
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
// Components
|
|
169
|
+
import { FBRE } from "@sonata-innovations/fiber-fbre";
|
|
170
|
+
|
|
171
|
+
// Store hooks
|
|
172
|
+
import { useFBREStore, useFBREStoreApi, useFBREApi } from "@sonata-innovations/fiber-fbre";
|
|
173
|
+
|
|
174
|
+
// Event system
|
|
175
|
+
import { addFBREEventListener, removeFBREEventListener } from "@sonata-innovations/fiber-fbre";
|
|
176
|
+
|
|
177
|
+
// Error classes
|
|
178
|
+
import { ApiError, TimeoutError } from "@sonata-innovations/fiber-fbre";
|
|
179
|
+
|
|
180
|
+
// Types
|
|
181
|
+
import type {
|
|
182
|
+
FBREProps,
|
|
183
|
+
FBRELocalProps,
|
|
184
|
+
FBRERemoteProps,
|
|
185
|
+
FBREServerDrivenModeProps,
|
|
186
|
+
FBREStoreState,
|
|
187
|
+
FBREApiConfig,
|
|
188
|
+
Flow,
|
|
189
|
+
FlowScreen,
|
|
190
|
+
FlowConfiguration,
|
|
191
|
+
ThemeConfig,
|
|
192
|
+
NavigationConfig,
|
|
193
|
+
ControlsConfig,
|
|
194
|
+
FlowMetadata,
|
|
195
|
+
Component,
|
|
196
|
+
ComponentProperties,
|
|
197
|
+
ComponentDisplayProperties,
|
|
198
|
+
ComponentDividerProperties,
|
|
199
|
+
ComponentInputProperties,
|
|
200
|
+
ComponentOptionProperties,
|
|
201
|
+
ComponentSliderProperties,
|
|
202
|
+
ComponentSwitchProperties,
|
|
203
|
+
ComponentRatingProperties,
|
|
204
|
+
ComponentFileUploadProperties,
|
|
205
|
+
ComponentGroupProperties,
|
|
206
|
+
FlowData,
|
|
207
|
+
ScreenData,
|
|
208
|
+
ComponentData,
|
|
209
|
+
FileUploadData,
|
|
210
|
+
FileUploadBase64Data,
|
|
211
|
+
FileUploadS3Data,
|
|
212
|
+
FlowConditionConfig,
|
|
213
|
+
ConditionOperator,
|
|
214
|
+
ConditionRule,
|
|
215
|
+
ConditionGroup,
|
|
216
|
+
ConditionDependency,
|
|
217
|
+
} from "@sonata-innovations/fiber-fbre";
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Flow JSON Schema
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
Flow
|
|
224
|
+
├── uuid: string
|
|
225
|
+
├── metadata: { name?, description?, ...}
|
|
226
|
+
├── config?: { theme?: { color?, darkMode?, style? }, navigation?: { transition?, allowInvalidTransition? }, controls?: { show?, layout?, showStepper? }, summary? }
|
|
227
|
+
└── screens: FlowScreen[]
|
|
228
|
+
├── uuid: string
|
|
229
|
+
├── label?: string
|
|
230
|
+
├── conditions?: FlowConditionConfig
|
|
231
|
+
└── components: Component[]
|
|
232
|
+
├── uuid: string
|
|
233
|
+
├── type: string
|
|
234
|
+
├── properties: { ... }
|
|
235
|
+
├── conditions?: FlowConditionConfig
|
|
236
|
+
└── components?: Component[] (groups and repeaters)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Component Types
|
|
240
|
+
|
|
241
|
+
| Type | Category | Description |
|
|
242
|
+
|------|----------|-------------|
|
|
243
|
+
| `header` | Display | Heading text |
|
|
244
|
+
| `text` | Display | Rich text with markup support |
|
|
245
|
+
| `divider` | Display | Horizontal rule with optional label |
|
|
246
|
+
| `callout` | Display | Styled alert/info box with variant coloring (info, success, warning, neutral) |
|
|
247
|
+
| `table` | Display | Static comparison/data table with optional column highlighting |
|
|
248
|
+
| `inputText` | Input | Single-line text field |
|
|
249
|
+
| `inputTextArea` | Input | Multi-line text field |
|
|
250
|
+
| `inputNumber` | Input | Numeric input (supports decimal restriction and start adornment) |
|
|
251
|
+
| `dropDown` | Selection | Native select dropdown |
|
|
252
|
+
| `dropDownMulti` | Selection | Multi-select with tag chips |
|
|
253
|
+
| `checkbox` | Selection | Checkbox group |
|
|
254
|
+
| `radio` | Selection | Radio button group |
|
|
255
|
+
| `toggleSwitch` | Selection | On/off toggle |
|
|
256
|
+
| `yesNo` | Selection | Two large tappable buttons for binary yes/no |
|
|
257
|
+
| `cardSelect` | Selection | Card-based visual selection with image/icon support |
|
|
258
|
+
| `date` | Date & Time | Calendar popup date picker |
|
|
259
|
+
| `time` | Date & Time | Hour/minute time selector |
|
|
260
|
+
| `dateTime` | Date & Time | Combined date + time picker |
|
|
261
|
+
| `dateRange` | Date & Time | Two date pickers for start/end |
|
|
262
|
+
| `timeRange` | Date & Time | Two time pickers for start/end |
|
|
263
|
+
| `dateTimeRange` | Date & Time | Two datetime pickers for start/end |
|
|
264
|
+
| `fileUpload` | Interactive | File picker with size validation |
|
|
265
|
+
| `rating` | Interactive | Star rating with half-star precision |
|
|
266
|
+
| `slider` | Interactive | Range slider with value label |
|
|
267
|
+
| `colorPicker` | Interactive | Saturation/hue picker with hex input |
|
|
268
|
+
| `computed` | Display | Calculated field with formula-based computed values |
|
|
269
|
+
| `signature` | Interactive | Signature pad — draw on canvas, type name, or both |
|
|
270
|
+
| `group` | Container | Component container with layout grid, collapsible |
|
|
271
|
+
| `repeater` | Container | Iterable component container (add/remove iterations, pre-populated rows) |
|
|
272
|
+
|
|
273
|
+
### Conditions
|
|
274
|
+
|
|
275
|
+
Components and screens can be conditionally shown/hidden using `FlowConditionConfig`:
|
|
276
|
+
|
|
277
|
+
```json
|
|
278
|
+
{
|
|
279
|
+
"action": "show",
|
|
280
|
+
"when": {
|
|
281
|
+
"logic": "and",
|
|
282
|
+
"rules": [
|
|
283
|
+
{
|
|
284
|
+
"source": "other-component-uuid",
|
|
285
|
+
"operator": "equals",
|
|
286
|
+
"value": "yes"
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Logic:** `"and"` (all rules must match) or `"or"` (any rule matches).
|
|
294
|
+
|
|
295
|
+
**Context rules:** Set `sourceType: "context"` on a rule to reference an external context value instead of a component. In this case, `source` is a context key (not a component UUID).
|
|
296
|
+
|
|
297
|
+
**Operators:**
|
|
298
|
+
|
|
299
|
+
| Category | Operators |
|
|
300
|
+
|----------|-----------|
|
|
301
|
+
| Equality | `equals`, `notEquals` |
|
|
302
|
+
| String | `contains`, `notContains`, `startsWith`, `endsWith` |
|
|
303
|
+
| Presence | `isEmpty`, `isNotEmpty` |
|
|
304
|
+
| Numeric | `greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual` |
|
|
305
|
+
| Set (single) | `isOneOf`, `isNotOneOf` |
|
|
306
|
+
| Set (multi) | `includesAny`, `includesAll`, `includesNone` |
|
|
307
|
+
| Boolean | `isTrue`, `isFalse` |
|
|
308
|
+
|
|
309
|
+
Condition results are stored separately from the Flow JSON in a `conditionResults` map (keyed by target UUID). Hidden components are excluded from FlowData output.
|
|
310
|
+
|
|
311
|
+
### Validation
|
|
312
|
+
|
|
313
|
+
Components support a rules-based validation system via `properties.validation`:
|
|
314
|
+
|
|
315
|
+
```json
|
|
316
|
+
{
|
|
317
|
+
"validation": {
|
|
318
|
+
"rules": [
|
|
319
|
+
{ "type": "required" },
|
|
320
|
+
{ "type": "email" },
|
|
321
|
+
{ "type": "minLength", "params": { "min": 5 } }
|
|
322
|
+
]
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**17 built-in validators:** `required`, `email`, `phone`, `url`, `minLength`, `maxLength`, `exactLength`, `minValue`, `maxValue`, `pattern`, `minSelected`, `maxSelected`, `fileType`, `fileSize`, `contains`, `excludes`, `matchesField`
|
|
328
|
+
|
|
329
|
+
Each validator has a default error message. Custom messages can be set per rule via the `message` field.
|
|
330
|
+
|
|
331
|
+
**Error display:** The first failing message is shown below the field with an "(and N more)" count when multiple rules fail.
|
|
332
|
+
|
|
333
|
+
**Cross-field validation:** `matchesField` enables bidirectional re-evaluation — changing either field re-validates the other.
|
|
334
|
+
|
|
335
|
+
**Migration:** Legacy `required` and `regex` flat properties are automatically converted to validation rules on load.
|
|
336
|
+
|
|
337
|
+
Validation errors are stored in `validationErrors: Record<string, string[]>` on the store (keyed by component UUID). Hidden components (by conditions) do not block screen validity.
|
|
338
|
+
|
|
339
|
+
### Calculations
|
|
340
|
+
|
|
341
|
+
Flows can include formula-based computed values via `FlowCalculation`:
|
|
342
|
+
|
|
343
|
+
```json
|
|
344
|
+
{
|
|
345
|
+
"calculations": [
|
|
346
|
+
{
|
|
347
|
+
"uuid": "calc-uuid",
|
|
348
|
+
"formula": "{{comp-a-uuid}} + {{comp-b-uuid}} * 0.1",
|
|
349
|
+
"label": "Total"
|
|
350
|
+
}
|
|
351
|
+
]
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
- Formulas reference component values via `{uuid}` tokens and support standard arithmetic (`+`, `-`, `*`, `/`)
|
|
356
|
+
- Aggregation functions: `SUM()`, `COUNT()`, `AVG()`, `MIN()`, `MAX()` over repeater iterations
|
|
357
|
+
- Scalar `MIN(expr, ...)` / `MAX(expr, ...)` for capping values (e.g. `MIN({discount}, 25)`)
|
|
358
|
+
- `IF(condition, then, else)` for conditional formulas (e.g. tiered pricing)
|
|
359
|
+
- Comparison operators: `>`, `<`, `>=`, `<=`, `==`, `!=` (return 1/0)
|
|
360
|
+
- Option metadata is accessible for option-based components
|
|
361
|
+
- Results update reactively when source values change
|
|
362
|
+
- Access results via `getCalculationResult(uuid)` on the store
|
|
363
|
+
|
|
364
|
+
### Markup
|
|
365
|
+
|
|
366
|
+
Text components and `detail` fields support inline markup:
|
|
367
|
+
|
|
368
|
+
- `[b]bold[/b]` → **bold**
|
|
369
|
+
- `[i]italic[/i]` → *italic*
|
|
370
|
+
- `[l href="url"]link[/l]` → [link](url)
|
|
371
|
+
|
|
372
|
+
## Theming
|
|
373
|
+
|
|
374
|
+
FBRE uses CSS custom properties. Override on the container:
|
|
375
|
+
|
|
376
|
+
```css
|
|
377
|
+
.fbre-container {
|
|
378
|
+
--fbre-theme-color: #1976d2;
|
|
379
|
+
--fbre-theme-light: #e3f2fd;
|
|
380
|
+
--fbre-theme-dark: #1565c0;
|
|
381
|
+
--fbre-error: #d32f2f;
|
|
382
|
+
--fbre-text: #212121;
|
|
383
|
+
--fbre-text-secondary: #666;
|
|
384
|
+
--fbre-border: #ccc;
|
|
385
|
+
--fbre-bg: #fff;
|
|
386
|
+
--fbre-radius: 4px;
|
|
387
|
+
--fbre-font: "Segoe UI", system-ui, sans-serif;
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
`theme.color` from `flow.config.theme.color` is applied automatically via inline style.
|
|
392
|
+
|
|
393
|
+
### Dark Mode
|
|
394
|
+
|
|
395
|
+
Enable dark mode via the `theme` prop or config:
|
|
396
|
+
|
|
397
|
+
```tsx
|
|
398
|
+
// Via theme prop (takes precedence)
|
|
399
|
+
<FBRE flow={myFlow} theme={{ darkMode: true }} onFlowComplete={handleComplete} />
|
|
400
|
+
|
|
401
|
+
// Via flow config
|
|
402
|
+
const flow = {
|
|
403
|
+
...myFlow,
|
|
404
|
+
config: { ...myFlow.config, theme: { darkMode: true } }
|
|
405
|
+
};
|
|
406
|
+
<FBRE flow={flow} onFlowComplete={handleComplete} />
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Dark mode applies `data-mode="dark"` on the container and overrides all CSS custom properties with a dark palette. Custom `theme.color` values are preserved — the inline style takes precedence over the dark mode default.
|
|
410
|
+
|
|
411
|
+
### Screen Transitions
|
|
412
|
+
|
|
413
|
+
Animate screen changes by setting `navigation.transition` in the flow config:
|
|
414
|
+
|
|
415
|
+
```json
|
|
416
|
+
{
|
|
417
|
+
"config": {
|
|
418
|
+
"navigation": { "transition": "slideFade" }
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
| Type | Effect | Direction-aware? |
|
|
424
|
+
|------|--------|-----------------|
|
|
425
|
+
| `"none"` | Instant swap (default) | — |
|
|
426
|
+
| `"slide"` | Full horizontal slide | Yes |
|
|
427
|
+
| `"fade"` | Crossfade | No |
|
|
428
|
+
| `"slideFade"` | 30px slide + opacity | Yes |
|
|
429
|
+
| `"rise"` | Vertical rise/sink | Yes |
|
|
430
|
+
| `"scaleFade"` | Scale + opacity | No |
|
|
431
|
+
|
|
432
|
+
Direction-aware transitions reverse when navigating backward. Buttons are disabled during animation to prevent overlapping transitions.
|
|
433
|
+
|
|
434
|
+
Customize timing via CSS custom properties:
|
|
435
|
+
|
|
436
|
+
```css
|
|
437
|
+
.fbre-container {
|
|
438
|
+
--fbre-transition-duration: 250ms;
|
|
439
|
+
--fbre-transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
`@media (prefers-reduced-motion: reduce)` sets duration to `0ms` automatically.
|
|
444
|
+
|
|
445
|
+
## Architecture
|
|
446
|
+
|
|
447
|
+
| Concern | Implementation |
|
|
448
|
+
|---------|---------------|
|
|
449
|
+
| UI framework | Zero-dependency custom CSS |
|
|
450
|
+
| Styling | CSS custom properties + BEM (`fbre-` prefix) |
|
|
451
|
+
| Display detection | `isDisplayComponent()` checks type against constant set (`header`, `text`, `divider`, `callout`, `table`) |
|
|
452
|
+
| State | Zustand store factory, one store per `<FBRE />` instance via React Context |
|
|
453
|
+
| Re-renders | Zustand selectors — granular, automatic |
|
|
454
|
+
| Build | Vite library mode |
|
|
455
|
+
| Components | Registry pattern (`Map<string, FC>`), one file per type |
|
|
456
|
+
| Dependencies | react, zustand, @sonata-innovations/fiber-types, @sonata-innovations/fiber-shared |
|
|
457
|
+
|
|
458
|
+
## FlowData Output
|
|
459
|
+
|
|
460
|
+
`getFlowData()` returns collected form data. Display-only components (`header`, `text`, `divider`, `callout`, `table`) are excluded automatically based on their type.
|
|
461
|
+
|
|
462
|
+
```
|
|
463
|
+
FlowData
|
|
464
|
+
├── uuid: string
|
|
465
|
+
├── metadata: Record<string, string>
|
|
466
|
+
└── screens: ScreenData[]
|
|
467
|
+
├── uuid: string
|
|
468
|
+
├── label?: string
|
|
469
|
+
└── components: ComponentData[]
|
|
470
|
+
├── uuid: string
|
|
471
|
+
├── type: string
|
|
472
|
+
├── label?: string (omitted when empty)
|
|
473
|
+
├── value: string | string[] | number | number[] | boolean | null | FileUploadData
|
|
474
|
+
└── components?: ComponentData[][] (groups and repeaters)
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
## Build
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
npm run build
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Output:
|
|
484
|
+
|
|
485
|
+
```
|
|
486
|
+
dist/
|
|
487
|
+
├── index.d.ts # Bundled type declarations
|
|
488
|
+
├── fiber-fbre.js # ES module
|
|
489
|
+
├── fiber-fbre.cjs # CommonJS
|
|
490
|
+
└── fiber-fbre.css # All styles
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
## Responsive Sizing
|
|
494
|
+
|
|
495
|
+
FBRE uses CSS container queries to automatically scale controls in narrow containers (under 400px). Buttons, stepper dots, and toggle switches shrink proportionally. No configuration needed — just embed in a smaller container and the compact sizing kicks in.
|
|
496
|
+
|
|
497
|
+
## Multiple Instances
|
|
498
|
+
|
|
499
|
+
Each `<FBRE />` creates its own isolated Zustand store. Multiple instances can run simultaneously on the same page without state conflicts.
|
|
500
|
+
|
|
501
|
+
```tsx
|
|
502
|
+
<FBRE flow={flowA} onFlowComplete={handleA} />
|
|
503
|
+
<FBRE flow={flowB} onFlowComplete={handleB} />
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## AI-Assisted Development
|
|
507
|
+
|
|
508
|
+
For AI integration reference, see [AI Integration Guide](../documentation/ai-integration-guide.md) and [llms.txt](./llms.txt).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("react/jsx-runtime"),w=require("react"),G=require("zustand"),Je=require("@sonata-innovations/fiber-types"),U=require("@sonata-innovations/fiber-shared"),Ye=new Map,We=e=>Ye.get(e),Xe=new Set(["header","text","divider","callout","table"]),ue=e=>Xe.has(e),Ke=(e,s)=>{var o,t,n,r;if(s.includes(":")){const i=s.split(":"),a=e.components[i[0]];return(n=(t=(o=a==null?void 0:a.addedComponents)==null?void 0:o[parseInt(i[1])])==null?void 0:t[parseInt(i[2])])==null?void 0:n.value}return(r=e.components[s])==null?void 0:r.value},T=(e,s)=>{var t;const o=(t=e.properties)==null?void 0:t.validation;return!o||!o.rules||o.rules.length===0?[]:U.validateValue(e.value,o,s?n=>Ke(s,n):void 0)},Qe=e=>{var o;const s=(o=e.properties)==null?void 0:o.validation;return!s||!s.rules?!1:s.rules.some(t=>t.type==="required")},z=e=>{var o,t;const s={...e};return Array.isArray((o=s.properties)==null?void 0:o.options)&&(s.properties={...s.properties,options:Je.normalizeOptions(s.properties.options)}),e.type==="computed"?(s.value=null,s.valid=!0):!ue(e.type)&&e.type!=="group"&&e.type!=="repeater"&&(s.value=null,s.valid=!Qe(e)),(e.type==="checkbox"||e.type==="dropDownMulti")&&(s.value=[]),e.type==="radio"&&"options"in e.properties&&e.properties.options.length>0&&(s.value=e.properties.options[0].value),e.type==="toggleSwitch"&&(s.value=!1),e.type==="colorPicker"&&((t=e.properties)!=null&&t.defaultValue)&&(s.value=e.properties.defaultValue),e.type==="slider"&&(s.value="initValue"in e.properties?e.properties.initValue:"min"in e.properties?e.properties.min:0),s},oe=(e,s,o)=>{var t;for(const n of o.rules)if(n.type==="matchesField"&&((t=n.params)!=null&&t.field)){const r=n.params.field;e[r]||(e[r]=[]),e[r].push({target:s,config:o})}},X=(e,s)=>{const o=e.screens[s];return o?!o.components.some(n=>{var a;const r=e.components[n.uuid];if(!r)return!1;if(r.type==="group"||r.type==="repeater")return(a=r.addedComponents)==null?void 0:a.some(c=>c.some(u=>{const l=u.conditions&&"when"in u.conditions?u.conditions:void 0;return l&&U.isHiddenByCondition(l,e.conditionResults[u.uuid])?!1:!u.valid}));const i=r.conditions&&"when"in r.conditions?r.conditions:void 0;return U.isHiddenByCondition(i,e.conditionResults[r.uuid])?!1:r&&"valid"in r&&!r.valid}):!0};function Ze(e){const s={};for(const o of e){const t=U.extractFormulaReferences(o.formula);for(const n of t)s[n]||(s[n]=[]),s[n].includes(o.uuid)||s[n].push(o.uuid)}return s}function Ue(e){const s=new Map(e.map(i=>[i.uuid,i])),o=new Set(e.map(i=>i.uuid)),t=new Set,n=[];function r(i,a){if(t.has(i)||a.has(i))return;a.add(i);const c=s.get(i);if(!c)return;const u=U.extractFormulaReferences(c.formula);for(const l of u)o.has(l)&&r(l,a);a.delete(i),t.add(i),n.push(c)}for(const i of e)r(i.uuid,new Set);return n}function Ae(e,s){return{resolveValue:o=>{if(o in s)return s[o];const t=J(e,o);if(!t)return null;const n=t.value;if(n==null||n==="")return null;const r=Number(n);return isNaN(r)?null:r},resolveOptionMetadata:(o,t)=>{var l;const n=J(e,o);if(!n)return null;const r=n.value;if(!r)return null;const i=(l=n.properties)==null?void 0:l.options;if(!Array.isArray(i))return null;const a=i.find(d=>d.value===r||d.label===r);if(!(a!=null&&a.metadata))return null;const c=a.metadata[t];if(c==null)return null;const u=Number(c);return isNaN(u)?null:u},resolveRepeaterValues:o=>{for(const t of Object.keys(e.components)){const n=e.components[t];if(n.type!=="repeater"||!n.addedComponents||!n.components)continue;const r=n.components.findIndex(a=>a.uuid===o);if(r===-1)continue;const i=[];for(const a of n.addedComponents){const c=a[r];if(!c)continue;const u=c.value;if(u==null||u==="")continue;const l=Number(u);isNaN(l)||i.push(l)}return i.length>0?i:null}return null}}}function ye(e){const s=e.calculations??[];if(s.length===0)return{};const o=Ue(s),t={},n=Ae(e,t);for(const r of o)t[r.uuid]=U.evaluateFormula(r.formula,n);return t}function me(e,s){const o=e.calculationsByDependency,t=e.calculations??[];if(t.length===0||!o)return null;const n=new Set,r=[s];for(;r.length>0;){const l=r.shift(),d=o[l];if(d)for(const f of d)n.has(f)||(n.add(f),r.push(f))}if(n.size===0)return null;const i=Ue(t),a={...e.calculationResults},c=Ae(e,a);let u=!1;for(const l of i)if(n.has(l.uuid)){const d=U.evaluateFormula(l.formula,c);a[l.uuid]!==d&&(a[l.uuid]=d,u=!0)}return u?a:null}function en(e){const s={},o=t=>{var r;if(t.type!=="computed"||!((r=t.properties)!=null&&r.formula))return;const n=U.extractFormulaReferences(t.properties.formula);for(const i of n)s[i]||(s[i]=[]),s[i].includes(t.uuid)||s[i].push(t.uuid)};for(const t of Object.values(e))if(o(t),(t.type==="repeater"||t.type==="group")&&t.components)for(const n of t.components)o(n);return s}function Le(e,s,o,t){var c,u;const n=e.components[o],r=((c=n==null?void 0:n.components)==null?void 0:c.map(l=>l.uuid))??[],i=(u=n==null?void 0:n.addedComponents)==null?void 0:u[t],a=new Map;if(i)for(let l=0;l<i.length;l++){const d=r[l];d&&a.set(d,i[l])}return{resolveValue:l=>{if(l in s)return s[l];const d=a.get(l);if(d){const g=d.value;if(g==null||g==="")return null;const h=Number(g);return isNaN(h)?null:h}const f=J(e,l);if(!f)return null;const m=f.value;if(m==null||m==="")return null;const y=Number(m);return isNaN(y)?null:y},resolveOptionMetadata:(l,d)=>{var x;const m=a.get(l)??J(e,l);if(!m)return null;const y=m.value;if(!y)return null;const g=(x=m.properties)==null?void 0:x.options;if(!Array.isArray(g))return null;const h=g.find(b=>b.value===y||b.label===y);if(!(h!=null&&h.metadata))return null;const v=h.metadata[d];if(v==null)return null;const C=Number(v);return isNaN(C)?null:C},resolveRepeaterValues:l=>{for(const d of Object.keys(e.components)){const f=e.components[d];if(f.type!=="repeater"||!f.addedComponents||!f.components)continue;const m=f.components.findIndex(g=>g.uuid===l);if(m===-1)continue;const y=[];for(const g of f.addedComponents){const h=g[m];if(!h)continue;const v=h.value;if(v==null||v==="")continue;const C=Number(v);isNaN(C)||y.push(C)}return y.length>0?y:null}return null}}}function de(e,s){return{resolveValue:o=>{if(o in s)return s[o];const t=J(e,o);if(!t)return null;const n=t.value;if(n==null||n==="")return null;const r=Number(n);return isNaN(r)?null:r},resolveOptionMetadata:(o,t)=>{var l;const n=J(e,o);if(!n)return null;const r=n.value;if(!r)return null;const i=(l=n.properties)==null?void 0:l.options;if(!Array.isArray(i))return null;const a=i.find(d=>d.value===r||d.label===r);if(!(a!=null&&a.metadata))return null;const c=a.metadata[t];if(c==null)return null;const u=Number(c);return isNaN(u)?null:u},resolveRepeaterValues:o=>{for(const t of Object.keys(e.components)){const n=e.components[t];if(n.type!=="repeater"||!n.addedComponents||!n.components)continue;const r=n.components.findIndex(a=>a.uuid===o);if(r===-1)continue;const i=[];for(const a of n.addedComponents){const c=a[r];if(!c)continue;const u=c.value;if(u==null||u==="")continue;const l=Number(u);isNaN(l)||i.push(l)}return i.length>0?i:null}return null}}}function ge(e){var o,t;const s=e.calculationResults??{};for(const n of Object.values(e.components))if(n.type==="computed"&&((o=n.properties)!=null&&o.formula)){const r=de(e,s),i=U.evaluateFormula(n.properties.formula,r);n.value=i}for(const n of Object.values(e.components))if((n.type==="repeater"||n.type==="group")&&n.addedComponents&&n.components)for(let r=0;r<n.addedComponents.length;r++){const i=n.addedComponents[r];for(let a=0;a<i.length;a++){const c=i[a],u=n.components[a];if((u==null?void 0:u.type)==="computed"&&((t=u.properties)!=null&&t.formula)){const l=n.type==="repeater"?Le(e,s,n.uuid,r):de(e,s),d=U.evaluateFormula(u.properties.formula,l);c.value=d}}}}function nn(e,s){var i,a;const o=e.computedByDependency;if(!o)return!1;const t=o[s];if(!t||t.length===0)return!1;const n=e.calculationResults??{};let r=!1;for(const c of t){const u=e.components[c];if(u&&u.type==="computed"&&((i=u.properties)!=null&&i.formula)){const l=de(e,n),d=U.evaluateFormula(u.properties.formula,l);u.value!==d&&(e.components[c]={...u,value:d},r=!0);continue}for(const l of Object.values(e.components))if((l.type==="repeater"||l.type==="group")&&l.components&&l.addedComponents){const d=l.components.findIndex(m=>m.uuid===c);if(d===-1)continue;const f=l.components[d];if((f==null?void 0:f.type)!=="computed"||!((a=f.properties)!=null&&a.formula))continue;for(let m=0;m<l.addedComponents.length;m++){const y=l.addedComponents[m][d];if(!y)continue;const g=l.type==="repeater"?Le(e,n,l.uuid,m):de(e,n),h=U.evaluateFormula(f.properties.formula,g);y.value!==h&&(l.addedComponents[m][d]={...y,value:h},r=!0)}}}return r}const Pe=(e,s)=>{var o,t;if(s.includes(":")){const n=s.split(":"),r=e[n[0]];return(t=(o=r==null?void 0:r.addedComponents)==null?void 0:o[parseInt(n[1])])==null?void 0:t[parseInt(n[2])]}return e[s]},ae=(e,s)=>{for(const o of e.screenOrder)if(e.screens[o].components.some(n=>n.uuid===s))return o;return null},tn=(e,s)=>{if(!s.includes(":"))return null;const o=s.split(":");if(o.length!==3)return null;const t=o[0],n=parseInt(o[2]),r=e.components[t];if(!r||r.type!=="repeater"||!r.components)return null;const i=r.components[n];return(i==null?void 0:i.uuid)??null},ke=(e,s,o,t,n)=>{const r=s(),i=r.groupComponentMap[o]??o,a=rn(e,s,i),c={components:r.components,groupComponentMap:r.groupComponentMap},u=T(t,c);t.valid=u.length===0;const l={...r.validationErrors,[o]:u},d=r.validationsByDependency[o];if(d)for(const x of d){const b=J(r,x.target);if(b){const R=T(b,c);b.valid=R.length===0,l[x.target]=R}}const f=ae(r,n),m={components:{...r.components},validationErrors:l};if(a&&(m.conditionResults=a),f){const x={...r,...m};m.screenValidity={...r.screenValidity,[f]:X(x,f)}}const y={...r,...m},g=o.includes(":")?tn(y,o):null,h=[],v=x=>{if(nn(y,x)){const b=y.computedByDependency[x]??[];for(const R of b)h.includes(R)||(h.push(R),v(R))}};v(i),g&&g!==i&&v(g);let C=me(y,i);if(g&&g!==i){const x=C?{...y,calculationResults:C}:y,b=me(x,g);b&&(C=b)}for(const x of h){const b=C?{...y,calculationResults:C}:y,R=me(b,x);R&&(C=R)}C&&(m.calculationResults=C),e(m)},on=(e,s)=>({updateComponentValue:(o,t)=>{var i,a;const n=s();if(o.includes(":")){const c=o.split(":"),u=c[0],l=parseInt(c[1]),d=parseInt(c[2]),f=n.components[u];if(!((a=(i=f==null?void 0:f.addedComponents)==null?void 0:i[l])!=null&&a[d]))return;const m=f.addedComponents[l][d].value!==t;if(f.addedComponents[l][d]={...f.addedComponents[l][d],value:t},m){ke(e,s,o,f.addedComponents[l][d],u);return}e({components:{...n.components}});return}const r=n.components[o];if(r){if(r.type==="group"||r.type==="repeater"){e({components:{...n.components}});return}t!==r.value&&(r.value=t,ke(e,s,o,r,o))}},addGroupIteration:o=>{const t=s(),n=t.components[o];if(!n||n.type!=="group"||!n.components)return;n.addedComponents||(n.addedComponents=[]);const r={...t.conditionsByDependency},i=n.components.map((u,l)=>{const d=z(u);if(d.uuid=[o,n.addedComponents.length,l].join(":"),t.groupComponentMap[d.uuid]=u.uuid,d.conditions&&Object.keys(d.conditions).length>0){const m="when"in d.conditions?d.conditions:null;m&&(d.conditions=m,W(r,d.uuid,m,"component"))}const f=T(d);return d.valid=f.length===0,d});n.addedComponents.push(i);const a=ae(t,o),c={...t.screenValidity};return a&&(c[a]=X({...t},a)),e({components:{...t.components},groupComponentMap:{...t.groupComponentMap},conditionsByDependency:r,screenValidity:c}),n.addedComponents},addRepeaterIteration:o=>{const t=s(),n=t.components[o];if(!n||n.type!=="repeater"||!n.components)return;n.addedComponents||(n.addedComponents=[]);const r={...t.conditionsByDependency},i=n.components.map((d,f)=>{const m=z(d);if(m.uuid=[o,n.addedComponents.length,f].join(":"),m.conditions&&Object.keys(m.conditions).length>0){const g="when"in m.conditions?m.conditions:null;g&&(m.conditions=g,W(r,m.uuid,g,"component"))}const y=T(m);return m.valid=y.length===0,m});n.addedComponents.push(i);const a=ae(t,o),c={...t.screenValidity};a&&(c[a]=X({...t},a));const u={...t,components:{...t.components}};ge(u);const l=ye(u);return e({components:{...t.components},conditionsByDependency:r,screenValidity:c,calculationResults:l}),n.addedComponents},removeRepeaterIteration:(o,t)=>{var g;const n=s(),r=n.components[o];if(!r||r.type!=="repeater"||!r.addedComponents)return;const i=((g=r.properties)==null?void 0:g.minIterations)??1;if(r.addedComponents.length<=i)return;const a=r.addedComponents[t],c=new Set(a.map(h=>h.uuid));r.addedComponents.splice(t,1);const u={...n.conditionsByDependency},l={...n.validationErrors};for(const h of c)delete l[h],delete u[h];for(const h of Object.keys(u)){const v=u[h],C=v.filter(x=>!c.has(x.target));C.length===0?delete u[h]:C.length!==v.length&&(u[h]=C)}for(let h=t;h<r.addedComponents.length;h++)r.addedComponents[h].forEach((v,C)=>{const x=v.uuid,b=[o,h,C].join(":");if(x!==b){v.uuid=b,x in l&&(l[b]=l[x],delete l[x]),x in u&&(u[b]=u[x],delete u[x]);for(const R of Object.values(u))for(const _ of R)_.target===x&&(_.target=b)}});const d=ae(n,o),f={...n.screenValidity};d&&(f[d]=X({...n},d));const m={...n,components:{...n.components}};ge(m);const y=ye(m);e({components:{...n.components},conditionsByDependency:u,validationErrors:l,screenValidity:f,calculationResults:y})}}),J=(e,s)=>Pe(e.components,s),W=(e,s,o,t)=>{const n=new Set;for(const i of o.when.rules)n.add(i.source);const r={target:s,type:t,config:o};for(const i of n)e[i]||(e[i]=[]),e[i].push(r)},sn=(e,s)=>{var o,t;return s.includes(":")?(o=J(e,s))==null?void 0:o.value:(t=e.components[s])==null?void 0:t.value},Q=(e,s)=>U.evaluateConditionConfig(s,(o,t)=>(t==null?void 0:t.sourceType)==="context"?e.externalContext[t.source]:sn(e,o)),rn=(e,s,o)=>{const t=s(),n=t.conditionsByDependency[o];if(!n||n.length===0)return null;let r=!1;const i={...t.conditionResults};for(const a of n){const c=Q(t,a.config);i[a.target]!==c&&(i[a.target]=c,r=!0)}return r?i:null},cn=e=>{const s={},o=new Set;for(const t of Object.values(e.conditionsByDependency))for(const n of t)o.has(n.target)||(o.add(n.target),s[n.target]=Q(e,n.config));return s},ve=e=>{if(!e||ue(e.type))return null;const s="label"in e.properties?e.properties.label:"",o={uuid:e.uuid,type:e.type,value:e.value};return s&&(o.label=s),o},Ve=e=>{if(e.conditions&&"when"in e.conditions)return e.conditions},an=e=>{const s={uuid:e.flowUUID,metadata:e.metadata,screens:[]},o=t=>{const n=e.screens[t];if(!n)return!1;const r=n.conditions&&"when"in n.conditions?n.conditions:void 0;return U.isHiddenByCondition(r,e.conditionResults[t])};return s.screens=e.screenOrder.filter(t=>!o(t)).map(t=>{const n=e.screens[t],r={uuid:t,components:n.components.map(i=>{const a=e.components[i.uuid];if(!a)return null;const c=Ve(a);if(U.isHiddenByCondition(c,e.conditionResults[i.uuid]))return null;if((a.type==="group"||a.type==="repeater")&&a.addedComponents){const u=ve(a);return u?{...u,components:a.addedComponents.map(l=>l.filter(d=>{const f=Ve(d);return!U.isHiddenByCondition(f,e.conditionResults[d.uuid])}).map(d=>ve(d)).filter(d=>d!==null))}:null}return ve(a)}).filter(i=>i!==null)};return n.label&&(r.label=n.label),r}),s},pe=w.createContext(null);function P(e){const s=w.useContext(pe);if(!s)throw new Error("useFBREStore must be used within an FBRE provider");return G.useStore(s,e)}const be=()=>{const e=w.useContext(pe);if(!e)throw new Error("useFBREStoreApi must be used within an FBRE provider");return e},re=e=>!e||Object.keys(e).length===0?null:"when"in e?e:null,ln=[],un=()=>G.createStore((e,s)=>({flowUUID:"",metadata:{},config:{},externalContext:{},screenOrder:[],screens:{},screenValidity:{},components:{},groupComponentMap:{},templateComponentMap:{},conditionsByDependency:{},conditionResults:{},validationErrors:{},validationsByDependency:{},calculations:[],calculationResults:{},calculationsByDependency:{},computedByDependency:{},loadFlow:(o,t,n)=>{var _;const r={},i=[],a={},c={},u={},l={},d={},f={},m={};o.screens.forEach(E=>{const O=re(E.conditions),F=O?{...E,conditions:O}:{...E};r[E.uuid]=F,i.push(E.uuid),O&&W(l,E.uuid,O,"screen"),E.components.forEach(S=>{var k;const D=re(S.conditions),B=z(S);if(D&&(B.conditions=D),a[S.uuid]=B,D&&W(l,S.uuid,D,"component"),(k=B.properties)!=null&&k.validation&&oe(d,S.uuid,B.properties.validation),(S.type==="group"||S.type==="repeater")&&S.components){const $=a[S.uuid];$.addedComponents||($.addedComponents=[]);const q=S.properties??{},j=S.type==="repeater"?q.initialData:void 0,N=S.type==="repeater"?q.minIterations:void 0,M=j?j.length:S.type==="repeater"&&N&&N>1?N:1;for(let V=0;V<M;V++){const K=S.components.map((Y,fe)=>{var te;const A=z(Y);A.uuid=[S.uuid,$.addedComponents.length,fe].join(":"),j&&j[V]&&Y.uuid in j[V]&&(A.value=j[V][Y.uuid]),S.type==="group"&&(c[A.uuid]=Y.uuid),V===0&&(u[Y.uuid]=A.uuid);const ee=re(A.conditions);ee&&(A.conditions=ee,W(l,A.uuid,ee,"component")),(te=A.properties)!=null&&te.validation&&oe(d,A.uuid,A.properties.validation);const ne=T(A);return A.valid=ne.length===0,ne.length>0&&(f[A.uuid]=ne),A});$.addedComponents.push(K)}}})}),t&&t.screens.forEach(E=>{const O=i.indexOf(E.uuid);O<0||E.components.forEach(F=>{const S=a[F.uuid];if(S)if((S.type==="group"||S.type==="repeater")&&F.components&&S.addedComponents){if(F.components.length>1){const D=S.addedComponents.length,B=F.components.length-D,H=o.screens[O].components.find(k=>k.uuid===S.uuid);for(let k=0;k<B;k++)if(H!=null&&H.components){const $=H.components.map((q,j)=>{var K;const N=z(q);N.uuid=[S.uuid,S.addedComponents.length,j].join(":"),S.type==="group"&&(c[N.uuid]=q.uuid),u[q.uuid]||(u[q.uuid]=N.uuid);const M=re(N.conditions);M&&(N.conditions=M,W(l,N.uuid,M,"component")),(K=N.properties)!=null&&K.validation&&oe(d,N.uuid,N.properties.validation);const V=T(N);return N.valid=V.length===0,V.length>0&&(f[N.uuid]=V),N});S.addedComponents.push($)}}F.components.forEach(D=>{D.forEach(B=>{for(const H of S.addedComponents){const k=H.find($=>$.uuid===B.uuid);if(k){k.value=B.value;const $=T(k);k.valid=$.length===0,f[k.uuid]=$;break}}})})}else{S.value=F.value;const D=T(S);S.valid=D.length===0,f[S.uuid]=D}})});for(const E of Object.keys(a)){const O=a[E];if(!(O.type==="group"||O.type==="repeater"||!((_=O.properties)!=null&&_.validation))&&!(E in f)){const F=T(O);O.valid=F.length===0,F.length>0&&(f[E]=F)}}const y={...s(),externalContext:n??{},screens:r,screenOrder:i,components:a,groupComponentMap:c,templateComponentMap:u,conditionsByDependency:l,conditionResults:{},validationErrors:f,validationsByDependency:d},g=cn(y);y.conditionResults=g,i.forEach(E=>{m[E]=X(y,E)});const h=en(a),v={...y,computedByDependency:h,calculationResults:{},screenValidity:m};ge(v);const C=o.calculations??[],x=Ze(C),b={...v,calculations:C},R=ye(b);e({flowUUID:o.uuid,metadata:o.metadata,config:o.config??{},externalContext:n??{},screens:r,screenOrder:i,components:a,groupComponentMap:c,templateComponentMap:u,conditionsByDependency:l,conditionResults:g,screenValidity:m,validationErrors:f,validationsByDependency:d,calculations:C,calculationResults:R,calculationsByDependency:x,computedByDependency:h})},updateContext:o=>{const t=s(),n=t.externalContext,r=[];for(const u of Object.keys(o))n[u]!==o[u]&&r.push(u);for(const u of Object.keys(n))!(u in o)&&!r.includes(u)&&r.push(u);if(r.length===0)return;const i={...t.conditionResults};let a=!1;const c=new Set;for(const u of r){const l=t.conditionsByDependency[u];if(!(!l||l.length===0))for(const d of l){if(c.has(d.target))continue;c.add(d.target);const f={...t,externalContext:o},m=Q(f,d.config);i[d.target]!==m&&(i[d.target]=m,a=!0)}}if(a){const u={...t,conditionResults:i},l={};for(const d of t.screenOrder)l[d]=X(u,d);e({externalContext:o,conditionResults:i,screenValidity:l})}else e({externalContext:o})},...on(e,s),getFlowData:()=>an(s()),getScreenByIndex:o=>{const t=s();return o<0||o>=t.screenOrder.length?null:t.screens[t.screenOrder[o]]},getScreenValidity:o=>{const t=s();return o<0||o>=t.screenOrder.length?!0:t.screenValidity[t.screenOrder[o]]??!0},getValidationErrors:o=>s().validationErrors[o]??ln,evaluateFlowValidation:()=>{const o=s(),t={};let n=!0;return o.screenOrder.forEach(r=>{const i=X(o,r);t[r]=i,i||(n=!1)}),e({screenValidity:t}),n},getMaxScreenCount:()=>s().screenOrder.length-1,getCalculationResult:o=>s().calculationResults[o]??null})),dn=({uuid:e})=>{var a;const s=P(c=>c.components[e]),o=P(c=>c.conditionResults[e]);if(!s)return null;const t=s.conditions&&"when"in s.conditions?s.conditions:void 0;if(U.isHiddenByCondition(t,o))return null;const n=We(s.type);if(!n)return console.warn(`FBRE: No renderer for component type "${s.type}"`),null;const r=(a=s.properties)==null?void 0:a.width,i=r&&r!=="full"?`fbre-component fbre-width-${r}`:"fbre-component";return p.jsx("div",{className:i,"data-uuid":e,children:p.jsx(n,{uuid:e})})},Ce=({screenIndex:e})=>{const s=P(o=>{const t=o.screenOrder[e];return t?o.screens[t]:null});return s?p.jsx("div",{className:"fbre-screen",children:s.components.map(o=>p.jsx(dn,{uuid:o.uuid},o.uuid))}):null},Oe=({size:e=14})=>p.jsx("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",children:p.jsx("polyline",{points:"4,12 9.5,17.5 20,7"})}),pn=({size:e=12})=>p.jsx("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"currentColor",children:p.jsx("path",{d:"M7 10l5 5 5-5z"})}),fn=({size:e=16})=>p.jsx("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"currentColor",children:p.jsx("path",{d:"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"})}),mn=({size:e=16})=>p.jsx("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"currentColor",children:p.jsx("path",{d:"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"})}),Me=({position:e,screenIndex:s,maxScreenIndex:o,allowInvalidTransition:t,screenValidity:n,summaryEnabled:r,summaryActive:i,atMaxScreen:a,nextButtonLabel:c,backButtonLabel:u,completionLoading:l,nextScreen:d,prevScreen:f,onFlowComplete:m})=>{const y=l??!1;if(e==="back"){const x=s===0&&!i||y;return p.jsxs("button",{type:"button",className:"fbre-btn fbre-btn--secondary",disabled:x,onClick:f,children:[p.jsx(fn,{}),u||"Back"]})}const g=r?a&&i:a,h=r?a?i?"Done":"Review":"Next":a?"Done":"Next";if(o===0){const x=r?i?"Complete":"Review":"Complete",b=y||!n;return p.jsxs("button",{type:"button",className:"fbre-btn fbre-btn--primary",disabled:b,onClick:()=>{r&&!i?d():m()},children:[c||x,y?p.jsx("span",{className:"fbre-btn__spinner"}):p.jsx(Oe,{size:16})]})}const v=y||!t&&!n||h==="Done"&&!n,C=()=>{g?m():d()};return p.jsxs("button",{type:"button",className:"fbre-btn fbre-btn--primary",disabled:v,onClick:C,children:[c||h,y?p.jsx("span",{className:"fbre-btn__spinner"}):g?p.jsx(Oe,{size:16}):p.jsx(mn,{})]})},vn=({current:e,total:s})=>{if(s<=1)return null;const o=(e+1)/s*100;return s<=6?p.jsx("div",{className:"fbre-stepper fbre-stepper--dots",children:Array.from({length:s},(t,n)=>p.jsx("div",{className:`fbre-stepper__dot${n===e?" active":n<e?" completed":""}`},n))}):p.jsx("div",{className:"fbre-stepper fbre-stepper--bar",children:p.jsx("div",{className:"fbre-stepper__fill",style:{width:`${o}%`}})})},hn=({current:e,total:s})=>s<=1?null:p.jsxs("div",{className:"fbre-stepper-text",children:["Step ",e+1," of ",s]}),yn=e=>{const s=e.controlsLayout==="stacked",o=e.showStepper!==!1&&(e.stepperStyle??"default")==="default";return p.jsxs("div",{className:s?"fbre-controls fbre-controls--stacked":"fbre-controls",children:[e.screenIndex>0||e.summaryActive?p.jsx(Me,{position:"back",...e}):p.jsx("div",{}),o&&e.maxScreenIndex>0&&p.jsx(vn,{current:e.screenIndex,total:e.maxScreenIndex+1}),p.jsx(Me,{position:"next",...e})]})},$e=({component:e})=>{const{uuid:s,label:o,type:t,value:n,components:r}=e,i=()=>{if(n!=null)switch(t){case"fileUpload":return n.name;case"inputText":case"inputTextArea":case"inputNumber":case"dropDown":case"radio":case"rating":case"slider":return n.toString();case"computed":return typeof n=="number"?n.toString():"—";case"toggleSwitch":return n?"Yes":"No";case"checkbox":case"dropDownMulti":return n.length===0?"No options selected":n.join(", ")}return"No response given"};if(t==="signature"&&n){const u=typeof n=="string"&&n.startsWith("data:");return p.jsxs("div",{className:"fbre-summary__item",children:[p.jsx("div",{className:"fbre-summary__item-label",children:o}),p.jsx("div",{className:"fbre-summary__item-value",children:u?p.jsx("img",{src:n,alt:"Signature",style:{maxWidth:"100%",maxHeight:80}}):p.jsx("span",{style:{fontFamily:'"Brush Script MT", "Segoe Script", "Dancing Script", cursive',fontSize:24},children:n})})]})}const a=()=>r?r.map((u,l)=>p.jsx("div",{children:u.map(d=>p.jsx($e,{component:d},d.uuid))},`${s}-${l}`)):null,c=n==null||typeof n=="string"&&n.length===0||Array.isArray(n)&&n.length===0;return p.jsxs("div",{className:`fbre-summary__item${t==="group"||t==="repeater"?" fbre-summary__item--group":""}`,children:[p.jsx("div",{className:"fbre-summary__item-label",children:o}),t!=="group"&&t!=="repeater"&&p.jsx("div",{className:`fbre-summary__item-value${c?" fbre-summary__item-value--empty":""}`,children:i()}),(t==="group"||t==="repeater")&&a()]})},gn=/\$\{([^}]+)\}/g,Cn=(e,s)=>e.replace(gn,(o,t)=>{const n=s.resolveCalculation(t);if(n!==null)return n;const r=s.resolveComponentValue(t);return r!==null?r:""});function bn(){const e=P(n=>n.components),s=P(n=>n.templateComponentMap),o=P(n=>n.calculations),t=P(n=>n.calculationResults);return w.useMemo(()=>{const n=new Map(o.map(i=>[i.uuid,i])),r=i=>{var d,f;const a=e[i];if(a)return a;const c=s[i];if(!c)return;const u=c.split(":"),l=e[u[0]];return(f=(d=l==null?void 0:l.addedComponents)==null?void 0:d[parseInt(u[1])])==null?void 0:f[parseInt(u[2])]};return{resolveComponentValue:i=>{const a=r(i);if(!a)return null;const c=a.value;return c==null||c===""?null:Array.isArray(c)?c.join(", "):String(c)},resolveCalculation:i=>{const a=n.get(i);if(!a)return null;const c=t[i]??null;return U.formatCalculationResult(c,a.format,a.decimalPlaces,a.currencySymbol)}}},[e,s,o,t])}const xn=({screen:e,showToggle:s})=>{const o=bn(),[t,n]=w.useState(!0);return p.jsxs("div",{children:[p.jsxs("div",{className:"fbre-summary__screen-header",onClick:()=>s&&n(!t),children:[e.label&&p.jsx("span",{className:"fbre-summary__screen-label",children:Cn(e.label,o)}),s&&p.jsx("span",{className:`fbre-summary__screen-toggle${t?"":" collapsed"}`,children:p.jsx(pn,{size:20})})]}),p.jsx("div",{className:`fbre-summary__screen-body${t?"":" collapsed"}`,children:e.components.map(r=>p.jsx($e,{component:r},r.uuid))})]})},_e=()=>{const e=be(),s=P(n=>n.components),o=w.useMemo(()=>e.getState().getFlowData(),[e,s]),t=o.screens.length;return p.jsxs("div",{className:"fbre-summary",children:[p.jsx("h2",{className:"fbre-summary__title",children:"Review"}),p.jsx("div",{className:"fbre-summary__body",children:o.screens.map(n=>p.jsx(xn,{screen:n,showToggle:t>1},n.uuid))})]})},He=(e,s,o)=>s?!U.isHiddenByCondition(s,o[e]):!0,Fe=(e,s)=>{const o=e.screenOrder.length-1;let t=s+1;for(;t<=o;){const n=e.screenOrder[t],r=e.screens[n],i=r!=null&&r.conditions&&"when"in r.conditions?r.conditions:void 0;if(He(n,i,e.conditionResults))return t;t++}return null},Sn=(e,s)=>{let o=s-1;for(;o>=0;){const t=e.screenOrder[o],n=e.screens[t],r=n!=null&&n.conditions&&"when"in n.conditions?n.conditions:void 0;if(He(t,r,e.conditionResults))return o;o--}return 0},le=new Map,wn=(e,s)=>{le.has(e)||le.set(e,new Set),le.get(e).add(s)},jn=(e,s)=>{var o;(o=le.get(e))==null||o.delete(s)},qe=w.createContext(null);function Rn(){return w.useContext(qe)}class se extends Error{constructor(s,o,t,n){super(s),this.name="ApiError",this.status=o,this.code=t,this.validationErrors=n}}class Ge extends se{constructor(s,o){super(`Request timed out after ${o}ms: ${s}`,0,"TIMEOUT"),this.name="TimeoutError"}}const En=new Set([502,503,504]),ie=2,Nn=[500,1500];function In(e){return new Promise(s=>setTimeout(s,e))}async function Z(e,s,o={}){const n=`${e.apiEndpoint||""}${s}`,r=e.timeout??3e4,i={...o.body?{"Content-Type":"application/json"}:{},...o.headers};e.apiKey&&(i.Authorization=`Bearer ${e.apiKey}`);let a;for(let c=0;c<=ie;c++){c>0&&await In(Nn[c-1]);try{const u=new AbortController,l=setTimeout(()=>u.abort(),r);let d;try{d=await fetch(n,{...o,headers:i,signal:u.signal})}finally{clearTimeout(l)}if(!d.ok){const f=await d.json().catch(()=>({})),m=new se(f.error||`HTTP ${d.status}`,d.status,f.code,f.validationErrors);if(c<ie&&En.has(d.status)){a=m;continue}throw m}return d.json()}catch(u){if(u instanceof se)throw u;if(u instanceof DOMException&&u.name==="AbortError"){const l=new Ge(n,r);if(c<ie){a=l;continue}throw l}if(u instanceof TypeError&&c<ie){a=u;continue}throw u}}throw a}async function Dn(e,s){return Z(e,`/api/v1/public/flows/${s}`)}async function Bn(e,s,o){const t={flowId:s};return o&&Object.keys(o).length>0&&(t.context=o),Z(e,"/api/v1/public/sessions",{method:"POST",body:JSON.stringify(t)})}async function kn(e,s,o){return Z(e,`/api/v1/public/sessions/${s}/next`,{method:"POST",body:JSON.stringify({data:o})})}async function Vn(e,s,o){return Z(e,`/api/v1/public/sessions/${s}/prev`,{method:"POST",body:JSON.stringify({data:o})})}async function On(e,s,o){return Z(e,`/api/v1/public/sessions/${s}/complete`,{method:"POST",body:JSON.stringify({data:o})})}async function Mn(e,s){return Z(e,`/api/v1/public/sessions/${s}`)}const _n=({screenIndex:e,onFlowComplete:s,onScreenChange:o})=>{var te,xe,Se,we,je,Re,Ee,Ne,Ie,De,Be;const t=P(I=>I.config),n=P(I=>I.screenOrder.length),r=P(I=>Object.keys(I.components).length>0),i=be(),a=Math.max(0,Math.min(e,n-1)),[c,u]=w.useState(a),[l,d]=w.useState(!!(t!=null&&t.summary&&e>=n)),f=((te=t==null?void 0:t.navigation)==null?void 0:te.transition)??"none",m=f!=="none",[y,g]=w.useState(null),[h,v]=w.useState(!1),[C,x]=w.useState("forward"),[b,R]=w.useState(!1),_=w.useRef(null);w.useEffect(()=>{const I=Math.max(0,Math.min(e,n-1));u(I)},[e]);const E=P(I=>I.screenOrder[c]),O=P(I=>E?I.screenValidity[E]??!0:!0),F=P(I=>{var L;return E?(L=I.screens[E])==null?void 0:L.nextButtonLabel:void 0}),S=P(I=>{var L;return E?(L=I.screens[E])==null?void 0:L.backButtonLabel:void 0}),D=w.useCallback(I=>{o&&o(I,i.getState().getFlowData())},[o,i]),B=w.useCallback(I=>{g(c),v(l),x(I),R(!0),_.current&&(_.current.scrollTop=0)},[c,l]),H=w.useCallback(()=>{R(!1),g(null)},[]),k=w.useCallback(()=>{if(!b)if(l)m&&B("back"),d(!1),D(c);else{const I=i.getState(),L=Sn(I,c);L!==c&&(m&&B("back"),u(L),D(L))}},[b,l,c,m,B,D,i]),$=w.useCallback(()=>{if(b)return;const I=i.getState(),L=Fe(I,c);L!==null?(m&&B("forward"),u(L),D(L)):t!=null&&t.summary&&(m&&B("forward"),d(!0),D(c+1))},[b,c,t,m,B,D,i]),[q,j]=w.useState(!1),[N,M]=w.useState(null),V=w.useCallback(()=>{M(null);const I=s(i.getState().getFlowData());I&&typeof I.then=="function"&&(j(!0),I.then(()=>{j(!1)}).catch(L=>{j(!1),M(L instanceof Error?L.message:String(L))}))},[s,i]),K=P(I=>Fe(I,c)===null),Y=((xe=t==null?void 0:t.theme)==null?void 0:xe.darkMode)??!1,fe=((Se=t==null?void 0:t.controls)==null?void 0:Se.showStepper)!==!1&&((we=t==null?void 0:t.controls)==null?void 0:we.stepperStyle)==="text",A={};(je=t==null?void 0:t.theme)!=null&&je.color&&(A["--fbre-theme-color"]=t.theme.color);const ee=()=>h?p.jsx(_e,{}):y!==null?p.jsx(Ce,{screenIndex:y}):null,ne=b?"fbre-screen-wrapper fbre-screen-wrapper--transitioning":"fbre-screen-wrapper";return p.jsxs("div",{className:"fbre-container","data-style":((Re=t==null?void 0:t.theme)==null?void 0:Re.style)??"clean","data-mode":Y?"dark":"light","data-transition":m?f:void 0,style:Object.keys(A).length>0?A:void 0,children:[fe&&p.jsx(hn,{current:c,total:n}),p.jsxs("div",{className:ne,ref:_,children:[b&&p.jsx("div",{className:"fbre-screen--exiting","data-direction":C,onAnimationEnd:H,children:ee()}),p.jsxs("div",{className:b?"fbre-screen--entering":void 0,"data-direction":b?C:void 0,children:[!l&&p.jsx(Ce,{screenIndex:c}),l&&p.jsx(_e,{})]})]}),N&&p.jsx("div",{className:"fbre-completion-error",children:N}),((Ee=t==null?void 0:t.controls)==null?void 0:Ee.show)!==!1&&r&&p.jsx(yn,{screenIndex:c,maxScreenIndex:n-1,allowInvalidTransition:(Ne=t==null?void 0:t.navigation)==null?void 0:Ne.allowInvalidTransition,screenValidity:O,summaryEnabled:(t==null?void 0:t.summary)??!1,summaryActive:l,atMaxScreen:K,nextButtonLabel:F,backButtonLabel:S,completionLoading:q,showStepper:(Ie=t==null?void 0:t.controls)==null?void 0:Ie.showStepper,stepperStyle:(De=t==null?void 0:t.controls)==null?void 0:De.stepperStyle,controlsLayout:(Be=t==null?void 0:t.controls)==null?void 0:Be.layout,nextScreen:$,prevScreen:k,onFlowComplete:V})]})},Fn=[],Tn=()=>G.createStore((e,s)=>({flowUUID:"",metadata:{},config:{},externalContext:{},screenOrder:[],screens:{},screenValidity:{},components:{},groupComponentMap:{},templateComponentMap:{},conditionsByDependency:{},conditionResults:{},validationErrors:{},validationsByDependency:{},calculations:[],calculationResults:{},calculationsByDependency:{},computedByDependency:{},sessionId:"",navigation:{canGoBack:!1,canGoForward:!1,screenNumber:1,totalScreens:1,progress:0,isLastScreen:!0},loading:!1,error:"",clearError:()=>{e({error:""})},loadFlow:()=>{},updateContext:o=>{e({externalContext:o})},updateComponentValue:(o,t)=>{var f,m;const n=s();if(o.includes(":")){const y=o.split(":"),g=y[0],h=parseInt(y[1]),v=parseInt(y[2]),C=n.components[g];if(!((m=(f=C==null?void 0:C.addedComponents)==null?void 0:f[h])!=null&&m[v]))return;C.addedComponents[h][v]={...C.addedComponents[h][v],value:t};const x=n.groupComponentMap[o]??o,b=n.conditionsByDependency[x];let R=n.conditionResults;if(b&&b.length>0){R={...n.conditionResults};for(const S of b)R[S.target]=Q({...n},S.config)}const _={components:n.components,groupComponentMap:n.groupComponentMap},E=T(C.addedComponents[h][v],_);C.addedComponents[h][v].valid=E.length===0;const O={...n.validationErrors,[o]:E},F=n.validationsByDependency[o];if(F)for(const S of F){const D=Te(n,S.target);if(D){const B=T(D,_);D.valid=B.length===0,O[S.target]=B}}e({components:{...n.components},conditionResults:R,validationErrors:O});return}const r=n.components[o];if(!r)return;if(r.type==="group"||r.type==="repeater"){e({components:{...n.components}});return}if(t===r.value)return;r.value=t;const i=n.conditionsByDependency[o];let a=n.conditionResults;if(i&&i.length>0){a={...n.conditionResults};for(const y of i)a[y.target]=Q({...n},y.config)}const c={components:n.components,groupComponentMap:n.groupComponentMap},u=T(r,c);r.valid=u.length===0;const l={...n.validationErrors,[o]:u},d=n.validationsByDependency[o];if(d)for(const y of d){const g=Te(n,y.target);if(g){const h=T(g,c);g.valid=h.length===0,l[y.target]=h}}e({components:{...n.components},conditionResults:a,validationErrors:l})},addGroupIteration:o=>{const t=s(),n=t.components[o];if(!n||n.type!=="group"||!n.components)return;n.addedComponents||(n.addedComponents=[]);const r=n.components.map((i,a)=>{const c=z(i);c.uuid=[o,n.addedComponents.length,a].join(":"),t.groupComponentMap[c.uuid]=i.uuid;const u=T(c);return c.valid=u.length===0,c});return n.addedComponents.push(r),e({components:{...t.components},groupComponentMap:{...t.groupComponentMap}}),n.addedComponents},addRepeaterIteration:o=>{const t=s(),n=t.components[o];if(!n||n.type!=="repeater"||!n.components)return;n.addedComponents||(n.addedComponents=[]);const r=n.components.map((i,a)=>{const c=z(i);c.uuid=[o,n.addedComponents.length,a].join(":");const u=T(c);return c.valid=u.length===0,c});return n.addedComponents.push(r),e({components:{...t.components}}),n.addedComponents},removeRepeaterIteration:(o,t)=>{const n=s(),r=n.components[o];if(!(!r||r.type!=="repeater"||!r.addedComponents||r.addedComponents.length<=1)){r.addedComponents.splice(t,1);for(let i=t;i<r.addedComponents.length;i++)r.addedComponents[i].forEach((a,c)=>{a.uuid=[o,i,c].join(":")});e({components:{...n.components}})}},getFlowData:()=>({uuid:"",metadata:{},screens:[]}),getScreenByIndex:o=>{const t=s();if(o!==0)return null;const n=t.screenOrder[0];return n?t.screens[n]:null},getScreenValidity:o=>{const t=s();if(o!==0)return!0;const n=t.screenOrder[0];return n?t.screenValidity[n]??!0:!0},getValidationErrors:o=>s().validationErrors[o]??Fn,evaluateFlowValidation:()=>{const o=s(),t=o.screenOrder[0];if(!t)return!0;const n=o.screens[t];if(!n)return!0;let r=!0;const i={components:o.components,groupComponentMap:o.groupComponentMap},a={...o.validationErrors};for(const u of n.components){const l=o.components[u.uuid];if(!l)continue;if((l.type==="group"||l.type==="repeater")&&l.addedComponents){for(const m of l.addedComponents)for(const y of m){const g=y.conditions&&"when"in y.conditions?y.conditions:void 0;if(g){const v=o.conditionResults[y.uuid]??!1;if(g.action==="show"&&!v||g.action==="hide"&&v)continue}const h=T(y,i);y.valid=h.length===0,a[y.uuid]=h,h.length>0&&(r=!1)}continue}const d=l.conditions&&"when"in l.conditions?l.conditions:void 0;if(d){const m=o.conditionResults[l.uuid]??!1;if(d.action==="show"&&!m||d.action==="hide"&&m)continue}const f=T(l,i);l.valid=f.length===0,a[l.uuid]=f,f.length>0&&(r=!1)}const c={...o.screenValidity,[t]:r};return e({components:{...o.components},validationErrors:a,screenValidity:c}),r},getMaxScreenCount:()=>0,getCalculationResult:o=>s().calculationResults[o]??null})),ce=(e,s)=>{var y,g;const o={},t={},n={},r={},i={},a={},c=s.screen.uuid;for(const h of s.screen.components){const v=z(h);if(o[v.uuid]=v,(v.type==="group"||v.type==="repeater")&&h.components){v.addedComponents||(v.addedComponents=[]);const C=h.components.map((x,b)=>{var _;const R=z(x);if(R.uuid=[v.uuid,0,b].join(":"),v.type==="group"&&(t[R.uuid]=x.uuid),n[x.uuid]=R.uuid,(_=R.properties)!=null&&_.validation){const E=R.properties.validation;E.rules&&E.rules.length>0&&oe(i,R.uuid,E)}return R});v.addedComponents.push(C)}if((y=v.properties)!=null&&y.validation){const C=v.properties.validation;C.rules&&C.rules.length>0&&oe(i,v.uuid,C)}}for(const{targetUUID:h,config:v}of s.conditions)W(r,h,v,h===c?"screen":"component");if(s.previousData)for(const h of s.previousData){const v=o[h.uuid];v&&(v.value=h.value)}const u={components:o};for(const h of Object.keys(o)){const v=o[h];if(v.type==="group"||v.type==="repeater"){if(v.addedComponents)for(const x of v.addedComponents)for(const b of x){const R=T(b,u);b.valid=R.length===0,R.length>0&&(a[b.uuid]=R)}continue}if(!((g=v.properties)!=null&&g.validation))continue;const C=T(v,u);v.valid=C.length===0,C.length>0&&(a[h]=C)}const l={},d={components:o},f=new Set;for(const h of Object.values(r))for(const v of h)f.has(v.target)||(f.add(v.target),l[v.target]=Q(d,v.config));let m=!0;for(const h of s.screen.components){const v=o[h.uuid];if(v)if((v.type==="group"||v.type==="repeater")&&v.addedComponents)for(const C of v.addedComponents){for(const x of C)if(!x.valid){m=!1;break}if(!m)break}else v.valid===!1&&(m=!1)}e.setState({screenOrder:[c],screens:{[c]:{uuid:c,label:s.screen.label,components:s.screen.components,nextButtonLabel:s.screen.nextButtonLabel,backButtonLabel:s.screen.backButtonLabel}},screenValidity:{[c]:m},components:o,groupComponentMap:t,templateComponentMap:n,conditionsByDependency:r,conditionResults:l,validationErrors:a,validationsByDependency:i,calculations:[],calculationResults:{},calculationsByDependency:{},computedByDependency:{},sessionId:s.sessionId,navigation:s.navigation,loading:!1,error:"",externalContext:s.context??{},config:s.config??{}})},he=e=>{var r;const s=e.getState(),o=s.screenOrder[0];if(!o)return[];const t=s.screens[o];if(!t)return[];const n=[];for(const i of t.components){const a=s.components[i.uuid];if(!a||ue(a.type))continue;const c={uuid:a.uuid,type:a.type,value:a.value};(r=a.properties)!=null&&r.label&&(c.label=a.properties.label),(a.type==="group"||a.type==="repeater")&&a.addedComponents&&(c.components=a.addedComponents.map(u=>u.filter(l=>!ue(l.type)).map(l=>({uuid:l.uuid,type:l.type,value:l.value})))),n.push(c)}return n},Te=(e,s)=>Pe(e.components,s),Un=6e4,An=({sessionEndpoint:e,flowId:s,apiKey:o,theme:t,context:n,onFlowComplete:r,onScreenChange:i})=>{const a=w.useRef(null);a.current||(a.current=Tn());const c=a.current,u=G.useStore(c,j=>j.loading),l=G.useStore(c,j=>j.error),d=G.useStore(c,j=>j.navigation),f=G.useStore(c,j=>j.config),m=G.useStore(c,j=>j.screenOrder),y=G.useStore(c,j=>j.screens),[g,h]=w.useState(""),v=w.useRef(null),C=w.useRef(Date.now()),x=w.useMemo(()=>({apiEndpoint:e,apiKey:o}),[e,o]),b=w.useCallback(j=>{h(j),v.current&&clearTimeout(v.current),v.current=setTimeout(()=>h(""),8e3)},[]),R=w.useCallback(()=>{h(""),v.current&&(clearTimeout(v.current),v.current=null)},[]),_=w.useCallback(async()=>{c.setState({loading:!0,error:""});try{const j=await Bn(x,s,n);ce(c,j)}catch(j){c.setState({loading:!1,error:j.message})}},[s,e,o,n]);w.useEffect(()=>{_()},[_]),w.useEffect(()=>{const j=async()=>{if(document.visibilityState!=="visible")return;const N=c.getState();if(!(!N.sessionId||Date.now()-C.current<Un))try{const V=await Mn(x,N.sessionId);ce(c,V)}catch(V){V instanceof se&&V.status===404&&c.setState({loading:!1,error:"Session expired. Please start over.",screenOrder:[],screens:{}})}};return document.addEventListener("visibilitychange",j),()=>document.removeEventListener("visibilitychange",j)},[e,o]);const E=w.useCallback(async()=>{const j=c.getState();if(!(j.loading||!j.evaluateFlowValidation())){c.setState({loading:!0}),c.getState().clearError(),R();try{const M=he(c),V=await kn(x,j.sessionId,M);ce(c,V),C.current=Date.now(),i==null||i(V.navigation.screenNumber)}catch(M){c.setState({loading:!1}),b(M.message)}}},[x,c,i,b,R]),O=w.useCallback(async()=>{const j=c.getState();if(!j.loading){c.setState({loading:!0}),c.getState().clearError(),R();try{const N=he(c),M=await Vn(x,j.sessionId,N);ce(c,M),C.current=Date.now(),i==null||i(M.navigation.screenNumber)}catch(N){c.setState({loading:!1}),b(N.message)}}},[x,c,i,b,R]),F=w.useCallback(async()=>{const j=c.getState();if(!(j.loading||!j.evaluateFlowValidation())){c.setState({loading:!0}),c.getState().clearError(),R();try{const M=he(c),V=await On(x,j.sessionId,M);C.current=Date.now(),r(V)}catch(M){c.setState({loading:!1}),b(M.message)}}},[x,c,r,b,R]),S={...f==null?void 0:f.theme,...t},D=S.darkMode??!1,B={};S.color&&(B["--fbre-theme-color"]=S.color);const H=m[0],k=H?y[H]:void 0,$=k==null?void 0:k.nextButtonLabel,q=k==null?void 0:k.backButtonLabel;return u&&m.length===0?p.jsx("div",{className:"fbre-container",children:p.jsxs("div",{className:"fbre-remote-loading",children:[p.jsx("div",{className:"fbre-spinner"}),p.jsx("p",{children:"Loading form..."})]})}):l&&m.length===0?p.jsx("div",{className:"fbre-container",children:p.jsxs("div",{className:"fbre-remote-error",children:[p.jsx("p",{children:"Failed to load form"}),p.jsx("p",{className:"fbre-remote-error__detail",children:l}),p.jsx("button",{type:"button",className:"fbre-btn fbre-btn--next",onClick:_,style:{marginTop:12},children:"Retry"})]})}):m.length===0?null:p.jsx(pe.Provider,{value:c,children:p.jsxs("div",{className:"fbre-container","data-style":S.style??"clean","data-mode":D?"dark":"light",style:Object.keys(B).length>0?B:void 0,children:[p.jsx("div",{className:"fbre-screen-wrapper",children:p.jsx(Ce,{screenIndex:0})}),p.jsx(Ln,{navigation:d,loading:u,onPrev:O,onNext:E,onComplete:F,nextButtonLabel:$,backButtonLabel:q}),p.jsxs("div",{className:`fbre-toast${g?" visible":""}`,children:[p.jsx("span",{children:g}),p.jsx("button",{type:"button",className:"fbre-toast__close",onClick:R,"aria-label":"Dismiss",children:"×"})]})]})})},Ln=({navigation:e,loading:s,onPrev:o,onNext:t,onComplete:n,nextButtonLabel:r,backButtonLabel:i})=>p.jsxs("div",{className:"fbre-controls",children:[p.jsx("div",{className:"fbre-controls__left",children:e.canGoBack&&p.jsx("button",{type:"button",className:"fbre-btn fbre-btn--back",onClick:o,disabled:s,children:i||"Back"})}),p.jsx("div",{className:"fbre-controls__center",children:p.jsxs("span",{className:"fbre-controls__progress",children:[e.screenNumber," / ",e.totalScreens]})}),p.jsx("div",{className:"fbre-controls__right",children:e.isLastScreen?p.jsx("button",{type:"button",className:"fbre-btn fbre-btn--complete",onClick:n,disabled:s,children:s?"Submitting...":r||"Complete"}):p.jsx("button",{type:"button",className:"fbre-btn fbre-btn--next",onClick:t,disabled:s,children:s?"Loading...":r||"Next"})})]});function Pn(e,s,o,t){return!s&&!o&&!t?e:{...e,...s&&{theme:{...e.theme,...s}},...o&&{navigation:{...e.navigation,...o}},...t&&{controls:{...e.controls,...t}}}}const ze=({flow:e,data:s,theme:o,navigation:t,controls:n,screenIndex:r,context:i,storeRef:a,onFlowComplete:c,onScreenChange:u,onScreenValidationChange:l,apiCtx:d})=>{const f=w.useRef(null);f.current||(f.current=un(),f.current.getState().loadFlow(e,s,i)),a&&(a.current=f.current),w.useEffect(()=>{f.current&&f.current.getState().loadFlow(e,s,i)},[e.uuid]),w.useEffect(()=>{if(f.current){const g=f.current.getState(),h=Pn(g.config,o,t,n);h!==g.config&&f.current.setState({config:h})}},[o,t,n]);const m=i?JSON.stringify(i):"";w.useEffect(()=>{f.current&&i&&f.current.getState().updateContext(i)},[m]);const y=p.jsx(pe.Provider,{value:f.current,children:p.jsx(_n,{screenIndex:r??0,onFlowComplete:c,onScreenChange:u,onScreenValidationChange:l})});return d?p.jsx(qe.Provider,{value:d,children:y}):y},$n=({flowId:e,apiEndpoint:s,apiKey:o,data:t,theme:n,navigation:r,controls:i,screenIndex:a,context:c,storeRef:u,onFlowComplete:l,onScreenChange:d,onScreenValidationChange:f})=>{const[m,y]=w.useState(null),[g,h]=w.useState(""),[v,C]=w.useState(null);return w.useEffect(()=>{y(null),h("");const x={apiEndpoint:s,apiKey:o};Dn(x,e).then(b=>{y(b.data),C({config:x,flowId:b.id,flowVersionId:b.versionId,tenantId:b.tenantId})}).catch(b=>h(b.message))},[e,s,o]),g?p.jsx("div",{className:"fbre-container",children:p.jsxs("div",{className:"fbre-remote-error",children:[p.jsx("p",{children:"Failed to load form"}),p.jsx("p",{className:"fbre-remote-error__detail",children:g})]})}):!m||!v?p.jsx("div",{className:"fbre-container",children:p.jsxs("div",{className:"fbre-remote-loading",children:[p.jsx("div",{className:"fbre-spinner"}),p.jsx("p",{children:"Loading form..."})]})}):p.jsx(ze,{flow:m,data:t,theme:n,navigation:r,controls:i,screenIndex:a,context:c,storeRef:u,onFlowComplete:l,onScreenChange:d,onScreenValidationChange:f,apiCtx:v})},Hn=e=>"sessionEndpoint"in e?p.jsx(An,{...e}):"flowId"in e&&e.flowId?p.jsx($n,{...e}):p.jsx(ze,{...e});exports.ApiError=se;exports.FBRE=Hn;exports.TimeoutError=Ge;exports.addFBREEventListener=wn;exports.removeFBREEventListener=jn;exports.useFBREApi=Rn;exports.useFBREStore=P;exports.useFBREStoreApi=be;
|