@sonata-innovations/fiber-fbre 3.3.0 → 3.4.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 CHANGED
@@ -1,6 +1,6 @@
1
- # @sonata-innovations/fiber-fbre v2
1
+ # @sonata-innovations/fiber-fbre
2
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.
3
+ Fiber Render Engine — consumes Flow JSON and renders data collection forms. Handles conditional logic, validation, calculations, screen transitions, theming, and outputs collected FlowData back to the parent application. Renders 30 component types across standard and conversational modes; zero UI-framework dependency (custom CSS only).
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -68,7 +68,7 @@ function App() {
68
68
  npm install @sonata-innovations/fiber-fbre
69
69
  ```
70
70
 
71
- Then import the CSS in your app entry point:
71
+ Then import the CSS **once** in your app entry point — without it the form renders unstyled:
72
72
 
73
73
  ```ts
74
74
  import "@sonata-innovations/fiber-fbre/styles";
@@ -78,6 +78,22 @@ import "@sonata-innovations/fiber-fbre/styles";
78
78
 
79
79
  **Runtime dependencies:** `zustand`, `@sonata-innovations/fiber-types`, `@sonata-innovations/fiber-shared`
80
80
 
81
+ ## Documentation
82
+
83
+ This README covers evaluation and day-to-day API lookup. Deeper guides ship **inside the package** under `docs/` (and an `AGENTS.md` routing guide at the package root), and are browsable in the public docs mirror at [github.com/sonata-innovations/fiber-docs](https://github.com/sonata-innovations/fiber-docs).
84
+
85
+ | Topic | In package | Mirror |
86
+ |-------|-----------|--------|
87
+ | Concepts overview | `docs/fiber-concepts.md` | [fiber-concepts.md](https://github.com/sonata-innovations/fiber-docs/blob/main/fiber-concepts.md) |
88
+ | FBRE integration guide | `docs/integration/fbre.md` | [integration/fbre.md](https://github.com/sonata-innovations/fiber-docs/blob/main/integration/fbre.md) |
89
+ | Theming reference | `docs/features/fbre-theming.md` | [features/fbre-theming.md](https://github.com/sonata-innovations/fiber-docs/blob/main/features/fbre-theming.md) |
90
+ | Confirmation screen | `docs/features/confirmation-screen.md` | [features/confirmation-screen.md](https://github.com/sonata-innovations/fiber-docs/blob/main/features/confirmation-screen.md) |
91
+
92
+ Schema references (Flow input and FlowData output) ship in the **`@sonata-innovations/fiber-types`** package under `docs/schema/`, and are mirrored at:
93
+
94
+ - [schema/flow-schema.md](https://github.com/sonata-innovations/fiber-docs/blob/main/schema/flow-schema.md) · [schema/flow-quick-reference.md](https://github.com/sonata-innovations/fiber-docs/blob/main/schema/flow-quick-reference.md)
95
+ - [schema/flow-data-schema.md](https://github.com/sonata-innovations/fiber-docs/blob/main/schema/flow-data-schema.md) · [schema/flow-schema.json](https://github.com/sonata-innovations/fiber-docs/blob/main/schema/flow-schema.json)
96
+
81
97
  ## API
82
98
 
83
99
  ### `<FBRE />` Props
@@ -91,7 +107,7 @@ Render a Flow object you already have in memory.
91
107
  | Prop | Type | Required | Description |
92
108
  |------|------|----------|-------------|
93
109
  | `flow` | `Flow` | Yes | Flow JSON object |
94
- | `data` | `FlowData` | No | Pre-populated form data |
110
+ | `data` | `FlowData` | No | Pre-populated form data (not reactive — read once at mount) |
95
111
  | `mode` | `FlowModeType` | No | Override form mode (`"standard"` \| `"conversational"`) |
96
112
  | `screenIndex` | `number` | No | Initial screen index (default `0`) |
97
113
  | `theme` | `ThemeConfig` | No | Override theme settings (merged over `flow.config.theme`) |
@@ -101,7 +117,6 @@ Render a Flow object you already have in memory.
101
117
  | `storeRef` | `MutableRefObject<StoreApi<FBREStoreState> \| null>` | No | Ref to access the Zustand store |
102
118
  | `onFlowComplete` | `(data: FlowData) => void \| ConfirmationResult \| Promise<void \| ConfirmationResult>` | Yes | Called when the user completes the flow. Return/resolve a `ConfirmationResult` to override the configured confirmation message; a rejected Promise surfaces a completion error |
103
119
  | `onScreenChange` | `(index: number, data: FlowData) => void` | No | Called on screen navigation |
104
- | `onScreenValidationChange` | `(index: number, data: any) => void` | No | Called when screen validity changes |
105
120
 
106
121
  #### Remote Mode
107
122
 
@@ -112,7 +127,7 @@ Fetch a published flow from a Fiber API by ID.
112
127
  | `flowId` | `string` | Yes | ID of the published flow |
113
128
  | `apiEndpoint` | `string` | Yes | Base URL of the Fiber API |
114
129
  | `apiKey` | `string` | No | API key for authentication |
115
- | `data` | `FlowData` | No | Pre-populated form data |
130
+ | `data` | `FlowData` | No | Pre-populated form data (not reactive) |
116
131
  | `mode` | `FlowModeType` | No | Override form mode (`"standard"` \| `"conversational"`) |
117
132
  | `screenIndex` | `number` | No | Initial screen index (default `0`) |
118
133
  | `theme` | `ThemeConfig` | No | Override theme settings |
@@ -120,13 +135,12 @@ Fetch a published flow from a Fiber API by ID.
120
135
  | `controls` | `ControlsConfig` | No | Override controls settings |
121
136
  | `context` | `Record<string, string \| boolean \| number>` | No | External context values for condition evaluation and calculations |
122
137
  | `storeRef` | `MutableRefObject<StoreApi<FBREStoreState> \| null>` | No | Ref to access the Zustand store |
123
- | `onFlowComplete` | `(data: FlowData) => void \| ConfirmationResult \| Promise<void \| ConfirmationResult>` | Yes | Called when the user completes the flow. Return/resolve a `ConfirmationResult` to override the configured confirmation message; a rejected Promise surfaces a completion error |
138
+ | `onFlowComplete` | `(data: FlowData) => void \| ConfirmationResult \| Promise<void \| ConfirmationResult>` | Yes | Called when the user completes the flow (see Local Mode) |
124
139
  | `onScreenChange` | `(index: number, data: FlowData) => void` | No | Called on screen navigation |
125
- | `onScreenValidationChange` | `(index: number, data: any) => void` | No | Called when screen validity changes |
126
140
 
127
141
  #### Server-Driven Mode
128
142
 
129
- Session-based rendering. The server evaluates conditions and validation; the client renders one screen at a time.
143
+ Session-based rendering. The server evaluates conditions and validation; the client renders one screen at a time. On completion it resets the submit button and shows a terminal confirmation (from `config.confirmation`, else a generic "Thank you"); `onFlowComplete` still fires with the server result if you want to drive your own post-completion UI.
130
144
 
131
145
  | Prop | Type | Required | Description |
132
146
  |------|------|----------|-------------|
@@ -137,7 +151,7 @@ Session-based rendering. The server evaluates conditions and validation; the cli
137
151
  | `theme` | `ThemeConfig` | No | Override theme settings |
138
152
  | `context` | `Record<string, string \| boolean \| number>` | No | External context values for condition evaluation and calculations |
139
153
  | `onFlowComplete` | `(data: FlowData) => void` | Yes | Called when the user completes the flow |
140
- | `onScreenChange` | `(screenNumber: number) => void` | No | Called on screen navigation (note: receives screen number, not index + data) |
154
+ | `onScreenChange` | `(screenNumber: number) => void` | No | Called on screen navigation (receives screen number, not index + data) |
141
155
 
142
156
  ### Imperative Access
143
157
 
@@ -155,6 +169,8 @@ const data = storeApi.getState().getFlowData();
155
169
  const apiContext = useFBREApi();
156
170
  ```
157
171
 
172
+ `getMaxScreenIndex()` on the store returns the maximum screen **index** (screen count − 1). (Renamed from `getMaxScreenCount`, whose name implied a count.)
173
+
158
174
  ### Events
159
175
 
160
176
  ```tsx
@@ -165,10 +181,12 @@ addFBREEventListener("file-upload", handler);
165
181
  removeFBREEventListener("file-upload", handler);
166
182
  ```
167
183
 
184
+ Note: the `file-upload` event bus is **module-global** — a listener fires for uploads from every `<FBRE />` instance on the page, not just one. Filter by the emitted component id if you run multiple instances.
185
+
168
186
  ### Exports
169
187
 
170
188
  ```ts
171
- // Components
189
+ // Component
172
190
  import { FBRE } from "@sonata-innovations/fiber-fbre";
173
191
 
174
192
  // Store hooks
@@ -180,7 +198,7 @@ import { addFBREEventListener, removeFBREEventListener } from "@sonata-innovatio
180
198
  // Error classes
181
199
  import { ApiError, TimeoutError } from "@sonata-innovations/fiber-fbre";
182
200
 
183
- // Types
201
+ // Types — props, store, and the full Flow/FlowData/Component type surface
184
202
  import type {
185
203
  FBREProps,
186
204
  FBRELocalProps,
@@ -196,356 +214,62 @@ import type {
196
214
  ControlsConfig,
197
215
  ConfirmationConfig,
198
216
  ConfirmationResult,
199
- FlowMetadata,
200
217
  Component,
201
218
  ComponentProperties,
202
- ComponentDisplayProperties,
203
- ComponentDividerProperties,
204
- ComponentInputProperties,
205
- ComponentOptionProperties,
206
- ComponentSliderProperties,
207
- ComponentSwitchProperties,
208
- ComponentRatingProperties,
209
- ComponentFileUploadProperties,
210
- ComponentGroupProperties,
211
219
  FlowData,
212
220
  ScreenData,
213
221
  ComponentData,
214
222
  FileUploadData,
215
- FileUploadBase64Data,
216
- FileUploadS3Data,
217
223
  FlowConditionConfig,
218
224
  ConditionOperator,
219
- ConditionRule,
220
- ConditionGroup,
221
- ConditionDependency,
225
+ // …plus per-component property types and condition/rule types
222
226
  } from "@sonata-innovations/fiber-fbre";
223
227
  ```
224
228
 
225
- ## Confirmation Screen
226
-
227
- A terminal "thank you" screen shown after submission, configured on the flow (`config.confirmation`) — not a `Screen`. Renders only when `show` is not `false` and `title`/`body` has content, replacing the final screen and hiding the controls/stepper. `title`/`body` support `${...}` reference markup (collected fields, calculations, and `context` values by name).
228
-
229
- ```tsx
230
- const flow = {
231
- ...myFlow,
232
- config: { ...myFlow.config, confirmation: { show: true, title: "Thank you!", body: "Submitted, ${email}." } },
233
- };
234
- <FBRE flow={flow} onFlowComplete={handleComplete} />
235
- ```
236
-
237
- `onFlowComplete` decides *when* it shows: return `void` → shows immediately; return a `Promise` → the submit button stays in-flight until it settles (resolve shows it, **reject** shows a completion error instead); return/resolve a `ConfirmationResult` (`{ title?, body? }`) → overrides the configured message, e.g. with a server reference number.
238
-
239
- ```tsx
240
- const handleComplete = async (data: FlowData): Promise<ConfirmationResult> => {
241
- const { referenceId } = await saveToBackend(data);
242
- return { title: "All done!", body: `Reference: ${referenceId}.` };
243
- };
244
- ```
245
-
246
- ## Flow JSON Schema
247
-
248
- ```
249
- Flow
250
- ├── uuid: string
251
- ├── metadata: { name?, description?, ...}
252
- ├── config?: { mode?, theme?: { color?, colorScheme?, style?, background?, surface?, text?, border?, radius?, fontFamily?, error?, success?, warning? }, navigation?: { transition?, allowInvalidTransition? }, controls?: { show?, layout?, showStepper? }, summary?, confirmation?: { show?, title?, body? } }
253
- └── screens: FlowScreen[]
254
- ├── uuid: string
255
- ├── label?: string
256
- ├── conditions?: FlowConditionConfig
257
- └── components: Component[]
258
- ├── uuid: string
259
- ├── type: string
260
- ├── properties: { ... }
261
- ├── conditions?: FlowConditionConfig
262
- └── components?: Component[] (groups and repeaters)
263
- ```
264
-
265
- ### Component Types
266
-
267
- | Type | Category | Description |
268
- |------|----------|-------------|
269
- | `header` | Display | Heading text |
270
- | `text` | Display | Rich text with markup support |
271
- | `divider` | Display | Horizontal rule with optional label |
272
- | `callout` | Display | Styled alert/info box with variant coloring (info, success, warning, neutral) |
273
- | `table` | Display | Static comparison/data table with optional column highlighting |
274
- | `inputText` | Input | Single-line text field |
275
- | `inputTextArea` | Input | Multi-line text field |
276
- | `inputNumber` | Input | Numeric input (supports decimal restriction and start adornment) |
277
- | `dropDown` | Selection | Native select dropdown |
278
- | `dropDownMulti` | Selection | Multi-select with tag chips |
279
- | `checkbox` | Selection | Checkbox group |
280
- | `radio` | Selection | Radio button group |
281
- | `toggleSwitch` | Selection | On/off toggle |
282
- | `yesNo` | Selection | Two large tappable buttons for binary yes/no |
283
- | `cardSelect` | Selection | Card-based visual selection with image/icon support |
284
- | `date` | Date & Time | Calendar popup date picker |
285
- | `time` | Date & Time | Hour/minute time selector |
286
- | `dateTime` | Date & Time | Combined date + time picker |
287
- | `dateRange` | Date & Time | Two date pickers for start/end |
288
- | `timeRange` | Date & Time | Two time pickers for start/end |
289
- | `dateTimeRange` | Date & Time | Two datetime pickers for start/end |
290
- | `fileUpload` | Interactive | File picker with size validation |
291
- | `rating` | Interactive | Star rating with half-star precision |
292
- | `slider` | Interactive | Range slider with value label |
293
- | `colorPicker` | Interactive | Saturation/hue picker with hex input |
294
- | `computed` | Display | Calculated field with formula-based computed values |
295
- | `signature` | Interactive | Signature pad — draw on canvas, type name, or both |
296
- | `group` | Container | Component container with layout grid, collapsible |
297
- | `repeater` | Container | Iterable component container (add/remove iterations, pre-populated rows) |
298
-
299
- ### Conditions
300
-
301
- Components and screens can be conditionally shown/hidden using `FlowConditionConfig`:
302
-
303
- ```json
304
- {
305
- "action": "show",
306
- "when": {
307
- "logic": "and",
308
- "rules": [
309
- {
310
- "source": "other-component-uuid",
311
- "operator": "equals",
312
- "value": "yes"
313
- }
314
- ]
315
- }
316
- }
317
- ```
229
+ ## Flow schema, conditions, validation, calculations
318
230
 
319
- **Logic:** `"and"` (all rules must match) or `"or"` (any rule matches).
231
+ The Flow JSON hierarchy (Flow Screen → Component), all **30 component types**, the **19 condition operators**, the **17 validators**, calculation formulas, and inline markup are documented in the schema references — `docs/schema/` in `@sonata-innovations/fiber-types`, mirrored at [schema/flow-schema.md](https://github.com/sonata-innovations/fiber-docs/blob/main/schema/flow-schema.md) (with a [quick reference](https://github.com/sonata-innovations/fiber-docs/blob/main/schema/flow-quick-reference.md)).
320
232
 
321
- **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).
233
+ A few runtime facts worth knowing up front:
322
234
 
323
- **Operators:**
235
+ - **No auto-migration.** Legacy flat `required` / `regex` properties are **not** converted at load. Pre-migrate flows to `validation.rules` (and `conditions`) before passing them to FBRE.
236
+ - **Calculation formulas use single braces** to reference component values: `{uuid}`. They support arithmetic, comparisons, `IF()`, aggregation (`SUM`/`COUNT`/`AVG`/`MIN`/`MAX` over repeaters), and update reactively.
237
+ - **Condition and validation results live on the store** (`conditionResults`, `validationErrors`), not in the Flow JSON. Components hidden by a condition are excluded from FlowData and don't block screen validity.
324
238
 
325
- | Category | Operators |
326
- |----------|-----------|
327
- | Equality | `equals`, `notEquals` |
328
- | String | `contains`, `notContains`, `startsWith`, `endsWith` |
329
- | Presence | `isEmpty`, `isNotEmpty` |
330
- | Numeric | `greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual` |
331
- | Set (single) | `isOneOf`, `isNotOneOf` |
332
- | Set (multi) | `includesAny`, `includesAll`, `includesNone` |
333
- | Boolean | `isTrue`, `isFalse` |
334
-
335
- Condition results are stored separately from the Flow JSON in a `conditionResults` map (keyed by target UUID). Hidden components are excluded from FlowData output.
336
-
337
- ### Validation
338
-
339
- Components support a rules-based validation system via `properties.validation`:
340
-
341
- ```json
342
- {
343
- "validation": {
344
- "rules": [
345
- { "type": "required" },
346
- { "type": "email" },
347
- { "type": "minLength", "params": { "min": 5 } }
348
- ]
349
- }
350
- }
351
- ```
352
-
353
- **17 built-in validators:** `required`, `email`, `phone`, `url`, `minLength`, `maxLength`, `exactLength`, `minValue`, `maxValue`, `pattern`, `minSelected`, `maxSelected`, `fileType`, `fileSize`, `contains`, `excludes`, `matchesField`
354
-
355
- Each validator has a default error message. Custom messages can be set per rule via the `message` field.
356
-
357
- **Error display:** The first failing message is shown below the field with an "(and N more)" count when multiple rules fail.
358
-
359
- **Cross-field validation:** `matchesField` enables bidirectional re-evaluation — changing either field re-validates the other.
360
-
361
- **Migration:** Legacy `required` and `regex` flat properties are automatically converted to validation rules on load.
362
-
363
- 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.
364
-
365
- ### Calculations
366
-
367
- Flows can include formula-based computed values via `FlowCalculation`:
368
-
369
- ```json
370
- {
371
- "calculations": [
372
- {
373
- "uuid": "calc-uuid",
374
- "formula": "{{comp-a-uuid}} + {{comp-b-uuid}} * 0.1",
375
- "label": "Total"
376
- }
377
- ]
378
- }
379
- ```
380
-
381
- - Formulas reference component values via `{uuid}` tokens and support standard arithmetic (`+`, `-`, `*`, `/`)
382
- - Aggregation functions: `SUM()`, `COUNT()`, `AVG()`, `MIN()`, `MAX()` over repeater iterations
383
- - Scalar `MIN(expr, ...)` / `MAX(expr, ...)` for capping values (e.g. `MIN({discount}, 25)`)
384
- - `IF(condition, then, else)` for conditional formulas (e.g. tiered pricing)
385
- - Comparison operators: `>`, `<`, `>=`, `<=`, `==`, `!=` (return 1/0)
386
- - Option metadata is accessible for option-based components
387
- - Results update reactively when source values change
388
- - Access results via `getCalculationResult(uuid)` on the store
389
-
390
- ### Markup
391
-
392
- Text components and `detail` fields support inline markup:
239
+ ## Confirmation Screen
393
240
 
394
- - `[b]bold[/b]` → **bold**
395
- - `[i]italic[/i]` → *italic*
396
- - `[l href="url"]link[/l]` → [link](url)
241
+ A terminal "thank you" screen shown after submission, configured on the flow (`config.confirmation`) rather than as a `Screen`. It replaces the final screen and hides the controls/stepper; `title`/`body` support reference markup (collected fields, calculations, and `context` values by name). `onFlowComplete` decides *when* it shows — return `void` for immediate, a `Promise` to keep the submit button in-flight until it settles, or a `ConfirmationResult` (`{ title?, body? }`) to override the configured message (e.g. with a server reference number). Full guide: [features/confirmation-screen.md](https://github.com/sonata-innovations/fiber-docs/blob/main/features/confirmation-screen.md).
397
242
 
398
243
  ## Conversational Mode
399
244
 
400
- Set `mode: "conversational"` to transform FBRE into a one-question-per-screen experience:
401
-
402
- ```tsx
403
- // Via JSX prop
404
- <FBRE flow={myFlow} mode="conversational" onFlowComplete={handleComplete} />
405
-
406
- // Via flow config
407
- const flow = {
408
- ...myFlow,
409
- config: { ...myFlow.config, mode: "conversational" }
410
- };
411
- <FBRE flow={flow} onFlowComplete={handleComplete} />
412
- ```
413
-
414
- **What changes:**
415
-
416
- - Content is vertically and horizontally centered
417
- - Single-select components (`radio`, `yesNo`, `cardSelect`, `dropDown`) auto-advance ~500ms after selection
418
- - Pressing Enter on `inputText` / `inputNumber` advances to the next screen
419
- - Auto-advance (both Enter and on-change) only fires when the screen renders **exactly one interactive component**. Multi-input screens — including those produced by FBTL's per-card page-break toggle — behave like a normal form: the user fills every field and clicks Continue. Display-only components (text, header, callout, divider, table, computed) and components hidden by conditions don't count toward the input total.
420
- - Components animate in with fade + scale + stagger on screen transitions (respects `prefers-reduced-motion`)
421
- - Option items, Yes/No buttons, card-select cards, and input fields are enlarged for easier tapping
422
- - Each of the 6 existing styles has a tailored conversational presentation (e.g. clean = bordered cards, soft-outlined = pill-shaped options, defined-outlined = inverted selection)
423
-
424
- **What doesn't change:**
245
+ Set `mode: "conversational"` (via the JSX prop or `flow.config.mode`) to turn FBRE into a one-question-per-screen experience: content is centered, single-select components auto-advance ~500ms after selection, and Enter advances text/number fields. Auto-advance only fires when a screen renders **exactly one** interactive component (display-only and condition-hidden components don't count); multi-input screens behave like a normal form. Components fade/scale/stagger in on transitions (respecting `prefers-reduced-motion`), and controls are enlarged for tapping.
425
246
 
426
- - FlowData output is identical
427
- - Conditions, validation, calculations all work the same
428
- - Navigation buttons still work alongside auto-advance
429
- - Works with all 6 transition types and dark mode
247
+ FlowData output, conditions, validation, calculations, transitions, and dark mode all behave identically to standard mode. Styles partition by mode: **6 standard** (`clean`, `outlined`, `refined-clean`, `airy-clean`, `soft-outlined`, `defined-outlined`) and **4 conversational** (`centered-minimal`, `stacked-cards`, `soft-float`, `bold-statement`).
430
248
 
431
249
  ## Theming
432
250
 
433
- FBRE's appearance is driven entirely by CSS custom properties (the `--fbre-*` tokens). There are two independent axes **`style`** controls the *shape* (borders vs. underlines, fills, spacing; set via `theme.style`), and the **palette** controls the *colors*. This section is a complete reference for the palette.
434
-
435
- Palette values resolve through four layers, each overriding the one above, so you only specify what you want to change:
436
-
437
- ```
438
- 1. Light defaults base.css (lowest)
439
- 2. Preset: colorScheme: "dark" base.css [data-mode="dark"]
440
- 3. Knobs: theme.{surface,text…} inline custom properties
441
- 4. Raw CSS: your own --fbre-* rules your stylesheet (highest)
442
- ```
443
-
444
- ### Color scheme (presets)
445
-
446
- `theme.colorScheme` (`"light"` default | `"dark"`) seeds the **entire** token set with a coherent light or dark palette and sets `data-mode` on the container. Set it on the `theme` prop or in the flow config (the prop takes precedence):
447
-
448
- ```tsx
449
- <FBRE flow={myFlow} theme={{ colorScheme: "dark" }} onFlowComplete={handleComplete} />
450
- ```
451
-
452
- > Replaces the former `darkMode: boolean`: `darkMode: true` → `colorScheme: "dark"`, `darkMode: false`/absent → `colorScheme: "light"`. There is no runtime fallback — migrate stored flows before upgrading.
453
-
454
- ### Theme knobs
251
+ FBRE's appearance is driven entirely by `--fbre-*` CSS custom properties. Two independent axes: **`theme.style`** controls shape (borders vs. underlines, fills, spacing), and the **palette** controls colors. There are three ways to set the palette, and they **layer** each overrides the one before, so you specify only what you want to change:
455
252
 
456
- Layer brand colors on top of the preset with the `theme` palette knobs. Each knob sets its primary `--fbre-*` token **plus the tokens derived from it** (e.g. `surface` also drives the input fills and hover/alt surfaces, via mode-aware `color-mix`). Any subset may be set; unset knobs fall through to the preset.
253
+ 1. **`colorScheme` preset** `theme.colorScheme` (`"light"` default | `"dark"`) seeds the entire token set with a coherent palette and sets `data-mode` on the container. (Replaces the former `darkMode` boolean; there is no runtime fallback migrate stored flows.)
254
+ 2. **Palette knobs** — `theme.{color, background, surface, text, border, radius, fontFamily, error, success, warning}` set their primary token plus the tokens derived from it (e.g. `surface` also drives input fills and hover/alt surfaces). Any subset; unset knobs fall through to the preset.
255
+ 3. **Raw `--fbre-*` CSS** — override any token directly (including ones with no knob, like `--fbre-star-filled`) by scoping rules to your wrapper.
457
256
 
458
257
  ```tsx
459
258
  <FBRE
460
259
  flow={myFlow}
461
- theme={{
462
- colorScheme: "dark",
463
- color: "#bcd3cd", // accent
464
- background: "#1a2a3a", // form ground
465
- surface: "#243244", // input fills / cards
466
- text: "#e8ede9",
467
- border: "#2e4158",
468
- radius: "3px",
469
- fontFamily: '"Inter", system-ui, sans-serif',
470
- }}
260
+ theme={{ colorScheme: "dark", color: "#bcd3cd", surface: "#243244", radius: "3px" }}
471
261
  onFlowComplete={handleComplete}
472
262
  />
473
263
  ```
474
264
 
475
- | Knob | Primary token | Also derives |
476
- | --- | --- | --- |
477
- | `color` | `--fbre-theme-color` | light/dark accent tints |
478
- | `background` | `--fbre-bg` | — |
479
- | `surface` | `--fbre-surface` | input fills, hover/alt/subtle surfaces, control surface |
480
- | `text` | `--fbre-text` | secondary/placeholder/disabled, label |
481
- | `border` | `--fbre-border` | hover/light/subtle |
482
- | `radius` | `--fbre-radius` | — |
483
- | `fontFamily` | `--fbre-font` | — |
484
- | `error` / `success` / `warning` | `--fbre-{error,success,warning}` | their `-bg` / `-border` (and `error-light`) |
485
-
486
- ### Raw CSS (escape hatch)
487
-
488
- For tokens not exposed as a knob (e.g. `--fbre-star-filled`, `--fbre-shadow-color`) — or for surgical control — override the `--fbre-*` variables directly. Scope to your wrapper so you out-specify the engine defaults regardless of load order:
489
-
490
- ```css
491
- .my-form-wrapper .fbre-container {
492
- --fbre-surface: #243244;
493
- --fbre-star-filled: #bcd3cd;
494
- }
495
- ```
496
-
497
- Knobs are inline (highest specificity among engine-supplied values), so a knob and a raw rule on the *same* token: the knob wins unless your selector is also high-specificity. Use knobs for the common palette, raw CSS for the long tail.
498
-
499
- ### Token catalog
500
-
501
- Every themeable value, with light/dark preset defaults. Tokens marked **(derived)** are produced from a knob when it's set; you can also set any of them raw.
502
-
503
- **Accent** — `--fbre-theme-color` (`#1976d2` / `#42a5f5`, knob `color`), `--fbre-theme-light` (`#e3f2fd` / `#1a2332`, derived), `--fbre-theme-dark` (`#1565c0` / `#1e88e5`, derived), `--fbre-on-primary` (`#fff` / `#fff`).
504
-
505
- **Ground & surfaces**
506
-
507
- | Token | Light | Dark | Knob |
508
- | --- | --- | --- | --- |
509
- | `--fbre-bg` | `#fff` | `#121212` | `background` |
510
- | `--fbre-surface` | `#fff` | `#1e1e1e` | `surface` |
511
- | `--fbre-surface-hover` | `#f5f5f5` | `#2a2a2a` | (derived) |
512
- | `--fbre-surface-alt` | `#e0e0e0` | `#333` | (derived) |
513
- | `--fbre-surface-subtle` | `#e8e8e8` | `#2a2a2a` | (derived) |
514
- | `--fbre-control-surface` | `#fff` | `#e0e0e0` | (derived) |
515
- | `--fbre-input-bg` | `var(--fbre-surface)` | `var(--fbre-surface)` | (derived) |
516
- | `--fbre-input-bg-focus` | `var(--fbre-bg)` | `var(--fbre-bg)` | (derived) |
517
-
518
- **Text** — `--fbre-text` (`#212121` / `#e0e0e0`, knob `text`), `--fbre-text-secondary` (`#666` / `#9e9e9e`, derived), `--fbre-text-placeholder` (`#aaa` / `#666`, derived), `--fbre-text-disabled` (`#bbb` / `#555`, derived), `--fbre-label` (`var(--fbre-text-secondary)`, derived).
519
-
520
- **Borders** — `--fbre-border` (`#ccc` / `#444`, knob `border`), `--fbre-border-hover` (`#999` / `#666`, derived), `--fbre-border-light` (`#ddd` / `#444`, derived), `--fbre-border-subtle` (`#eee` / `#333`, derived).
521
-
522
- **State colors**
265
+ Defaults worth noting: `--fbre-radius` is `4px` and `--fbre-font` is `"Segoe UI", system-ui, -apple-system, sans-serif`. The complete token catalog with light/dark defaults is in [features/fbre-theming.md](https://github.com/sonata-innovations/fiber-docs/blob/main/features/fbre-theming.md).
523
266
 
524
- | Token | Light | Dark | Knob |
525
- | --- | --- | --- | --- |
526
- | `--fbre-error` | `#d32f2f` | `#ef5350` | `error` |
527
- | `--fbre-error-light` | `#fdecea` | `#2c1b1b` | (derived) |
528
- | `--fbre-success` | `#2d6a28` | `#a8e6a0` | `success` |
529
- | `--fbre-success-bg` | `#ddffda` | `#1a3318` | (derived) |
530
- | `--fbre-success-border` | `#b8e6b4` | `#2d5a28` | (derived) |
531
- | `--fbre-warning` | `#7a5900` | `#ffe082` | `warning` |
532
- | `--fbre-warning-bg` | `#fff8e1` | `#332b00` | (derived) |
533
- | `--fbre-warning-border` | `#ffe082` | `#665500` | (derived) |
534
-
535
- **Controls (no knob — set raw)** — `--fbre-toggle-track` (`#ccc` / `#555`), `--fbre-star-empty` (`#ddd` / `#444`), `--fbre-star-filled` (`#ffc107` / `#ffca28`), `--fbre-slider-track` (`#ddd` / `#444`), `--fbre-shadow-color` (`rgba(0,0,0,.12)` / `rgba(0,0,0,.4)`).
536
-
537
- **Shape & motion** — `--fbre-radius` (`4px`, knob `radius`), `--fbre-font` (`"Segoe UI", system-ui, sans-serif`, knob `fontFamily`), `--fbre-layout-gap` (`4px`, varies by style), `--fbre-transition-duration` (`250ms`), `--fbre-transition-easing` (`cubic-bezier(0.4, 0, 0.2, 1)`).
538
-
539
- ### Screen Transitions
267
+ ## Screen Transitions
540
268
 
541
269
  Animate screen changes by setting `navigation.transition` in the flow config:
542
270
 
543
271
  ```json
544
- {
545
- "config": {
546
- "navigation": { "transition": "slideFade" }
547
- }
548
- }
272
+ { "config": { "navigation": { "transition": "slideFade" } } }
549
273
  ```
550
274
 
551
275
  | Type | Effect | Direction-aware? |
@@ -557,76 +281,25 @@ Animate screen changes by setting `navigation.transition` in the flow config:
557
281
  | `"rise"` | Vertical rise/sink | Yes |
558
282
  | `"scaleFade"` | Scale + opacity | No |
559
283
 
560
- Direction-aware transitions reverse when navigating backward. Buttons are disabled during animation to prevent overlapping transitions.
561
-
562
- Customize timing via CSS custom properties:
563
-
564
- ```css
565
- .fbre-container {
566
- --fbre-transition-duration: 250ms;
567
- --fbre-transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
568
- }
569
- ```
570
-
571
- `@media (prefers-reduced-motion: reduce)` sets duration to `0ms` automatically.
572
-
573
- ## Architecture
574
-
575
- | Concern | Implementation |
576
- |---------|---------------|
577
- | UI framework | Zero-dependency custom CSS |
578
- | Styling | CSS custom properties + BEM (`fbre-` prefix) |
579
- | Display detection | `isDisplayComponent()` checks type against constant set (`header`, `text`, `divider`, `callout`, `table`) |
580
- | State | Zustand store factory, one store per `<FBRE />` instance via React Context |
581
- | Re-renders | Zustand selectors — granular, automatic |
582
- | Build | Vite library mode |
583
- | Components | Registry pattern (`Map<string, FC>`), one file per type |
584
- | Dependencies | react, zustand, @sonata-innovations/fiber-types, @sonata-innovations/fiber-shared |
284
+ Direction-aware transitions reverse when navigating backward; buttons are disabled mid-animation to prevent overlap. Customize timing via `--fbre-transition-duration` / `--fbre-transition-easing`. `@media (prefers-reduced-motion: reduce)` sets the duration to `0ms` automatically.
585
285
 
586
286
  ## FlowData Output
587
287
 
588
- `getFlowData()` returns collected form data. Display-only components (`header`, `text`, `divider`, `callout`, `table`) are excluded automatically based on their type.
589
-
590
- ```
591
- FlowData
592
- ├── uuid: string
593
- ├── metadata: Record<string, string>
594
- └── screens: ScreenData[]
595
- ├── uuid: string
596
- ├── label?: string
597
- └── components: ComponentData[]
598
- ├── uuid: string
599
- ├── type: string
600
- ├── label?: string (omitted when empty)
601
- ├── value: string | string[] | number | number[] | boolean | null | FileUploadData
602
- └── components?: ComponentData[][] (groups and repeaters)
603
- ```
604
-
605
- ## Build
606
-
607
- ```bash
608
- npm run build
609
- ```
610
-
611
- Output:
612
-
613
- ```
614
- dist/
615
- ├── index.d.ts # Bundled type declarations
616
- ├── fiber-fbre.js # ES module
617
- ├── fiber-fbre.cjs # CommonJS
618
- └── fiber-fbre.css # All styles
619
- ```
288
+ `getFlowData()` returns the collected form data, mirroring the Flow structure (Flow → ScreenData → ComponentData, with `components?: ComponentData[][]` for groups and repeaters). Display-only components `header`, `text`, `divider`, `callout`, `table` are excluded automatically; `computed` fields **are** included. Full shape and field semantics: [schema/flow-data-schema.md](https://github.com/sonata-innovations/fiber-docs/blob/main/schema/flow-data-schema.md).
620
289
 
621
290
  ## Responsive Sizing
622
291
 
623
- 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.
292
+ 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.
624
293
 
625
294
  ## Multiple Instances
626
295
 
627
- Each `<FBRE />` creates its own isolated Zustand store. Multiple instances can run simultaneously on the same page without state conflicts.
296
+ Each `<FBRE />` creates its own isolated Zustand store, so multiple instances run simultaneously on the same page without state conflicts. (The one exception is the module-global `file-upload` event bus noted under Events.)
628
297
 
629
298
  ```tsx
630
299
  <FBRE flow={flowA} onFlowComplete={handleA} />
631
300
  <FBRE flow={flowB} onFlowComplete={handleB} />
632
- ```
301
+ ```
302
+
303
+ ## License
304
+
305
+ MIT