@sonata-innovations/fiber-types 1.0.5 → 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.
@@ -0,0 +1,12 @@
1
+ import type { Component } from "./flow";
2
+ import type { FlowValidationConfig } from "./validation";
3
+ /** Returns the component's validation config if its property shape carries
4
+ * one. Returns `undefined` for display-only types (header/text/divider/
5
+ * callout/table/computed) and for `toggleSwitch`/`group`/`repeater`. */
6
+ export declare const getValidationConfig: (component: Component) => FlowValidationConfig | undefined;
7
+ /** Best human-readable identifier for a component, falling back from `label`
8
+ * to `title` to `value`. Returns `undefined` when no display string is set.
9
+ * Used by FBT/FBTL surfaces that need to name a component (stage cards, drag
10
+ * overlays, condition pickers, validation messages). */
11
+ export declare const getComponentDisplayLabel: (component: Component) => string | undefined;
12
+ //# sourceMappingURL=component-accessors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-accessors.d.ts","sourceRoot":"","sources":["../src/component-accessors.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEzD;;yEAEyE;AACzE,eAAO,MAAM,mBAAmB,GAC9B,WAAW,SAAS,KACnB,oBAAoB,GAAG,SAIzB,CAAC;AAEF;;;yDAGyD;AACzD,eAAO,MAAM,wBAAwB,GACnC,WAAW,SAAS,KACnB,MAAM,GAAG,SAYX,CAAC"}
@@ -0,0 +1,39 @@
1
+ /* ==========================================================================
2
+ Component accessors — narrow-access helpers across the discriminated union.
3
+
4
+ With `Component.properties` keyed on `type`, accessing a field that only
5
+ some variants carry (e.g. `validation`, `options`) requires narrowing. The
6
+ helpers here are the canonical narrowing patterns expressed once, so call
7
+ sites don't repeat the `"x" in props` boilerplate.
8
+
9
+ Add a new helper here only if the access pattern appears in multiple
10
+ packages and the narrowing rule is the same for every component type that
11
+ exposes the field. One-off accesses inside a single file should narrow
12
+ inline (`"options" in c.properties ? c.properties.options : undefined`)
13
+ rather than spawning a helper.
14
+ ========================================================================== */
15
+ /** Returns the component's validation config if its property shape carries
16
+ * one. Returns `undefined` for display-only types (header/text/divider/
17
+ * callout/table/computed) and for `toggleSwitch`/`group`/`repeater`. */
18
+ export const getValidationConfig = (component) => {
19
+ return "validation" in component.properties
20
+ ? component.properties.validation
21
+ : undefined;
22
+ };
23
+ /** Best human-readable identifier for a component, falling back from `label`
24
+ * to `title` to `value`. Returns `undefined` when no display string is set.
25
+ * Used by FBT/FBTL surfaces that need to name a component (stage cards, drag
26
+ * overlays, condition pickers, validation messages). */
27
+ export const getComponentDisplayLabel = (component) => {
28
+ const props = component.properties;
29
+ if ("label" in props && typeof props.label === "string" && props.label) {
30
+ return props.label;
31
+ }
32
+ if ("title" in props && typeof props.title === "string" && props.title) {
33
+ return props.title;
34
+ }
35
+ if ("value" in props && typeof props.value === "string" && props.value) {
36
+ return props.value;
37
+ }
38
+ return undefined;
39
+ };
@@ -0,0 +1,39 @@
1
+ import type { ValidationRuleType } from "./validation";
2
+ export type ComponentCategory = "display" | "input" | "selection" | "datetime" | "interactive" | "container";
3
+ export type ComponentManifestEntry = {
4
+ /** Stable type id used everywhere — Flow JSON, registry keys, props.type. */
5
+ type: string;
6
+ /** Human-readable label shown in the palette and badges. */
7
+ label: string;
8
+ /** Icon key used by FBT (resolved via the FBT icon registry). */
9
+ iconKey: string;
10
+ /** UI grouping — drives the pool section and the category badge. */
11
+ category: ComponentCategory;
12
+ /** True for components that render content but never contribute to FlowData
13
+ * and cannot be condition sources or validation targets. */
14
+ displayOnly: boolean;
15
+ /** Validation rules the FBT editor offers for this type. An empty array
16
+ * means the editor surfaces no validation UI. */
17
+ validationRules: readonly ValidationRuleType[];
18
+ /** Doc-only pointer to the TS property type in `properties.ts`.
19
+ * Not used at runtime — exists so a reader can jump from manifest entry to
20
+ * shape definition without grep. */
21
+ propertiesTypeName: string;
22
+ };
23
+ /** Frozen, ordered list of every registered component type. The order is the
24
+ * canonical pool-section order (display → input → selection → datetime →
25
+ * interactive → container) and matches the FBT pool. */
26
+ export declare const COMPONENT_MANIFEST: readonly ComponentManifestEntry[];
27
+ /** Stable, immutable list of all registered component type ids. */
28
+ export declare const COMPONENT_TYPES: readonly string[];
29
+ /** Component types that render content but never contribute to FlowData. */
30
+ export declare const DISPLAY_ONLY_TYPES: ReadonlySet<string>;
31
+ /** Human-readable label for the category badge / pool section header. */
32
+ export declare const CATEGORY_LABELS: Record<ComponentCategory, string>;
33
+ /** Look up a manifest entry by component type id. Returns undefined for
34
+ * unrecognized types — callers decide whether that is an error. */
35
+ export declare function getComponentManifestEntry(type: string): ComponentManifestEntry | undefined;
36
+ /** Manifest entries grouped by category, preserving manifest order within
37
+ * each group. Drives the FBT pool layout. */
38
+ export declare function getManifestByCategory(): Record<ComponentCategory, readonly ComponentManifestEntry[]>;
39
+ //# sourceMappingURL=component-manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-manifest.d.ts","sourceRoot":"","sources":["../src/component-manifest.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,MAAM,iBAAiB,GACzB,SAAS,GACT,OAAO,GACP,WAAW,GACX,UAAU,GACV,aAAa,GACb,WAAW,CAAC;AAEhB,MAAM,MAAM,sBAAsB,GAAG;IACnC,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,QAAQ,EAAE,iBAAiB,CAAC;IAC5B;iEAC6D;IAC7D,WAAW,EAAE,OAAO,CAAC;IACrB;sDACkD;IAClD,eAAe,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC/C;;yCAEqC;IACrC,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAuTF;;yDAEyD;AACzD,eAAO,MAAM,kBAAkB,EAAE,SAAS,sBAAsB,EAAY,CAAC;AAE7E,mEAAmE;AACnE,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAA+B,CAAC;AAE7E,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAElD,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAO7D,CAAC;AAEF;oEACoE;AACpE,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,GACX,sBAAsB,GAAG,SAAS,CAEpC;AAED;8CAC8C;AAC9C,wBAAgB,qBAAqB,IAAI,MAAM,CAC7C,iBAAiB,EACjB,SAAS,sBAAsB,EAAE,CAClC,CAaA"}
@@ -0,0 +1,355 @@
1
+ /* ==========================================================================
2
+ Component Manifest — single source of truth for the registered component
3
+ types. Every package that needs per-type metadata (labels, icons,
4
+ categories, display-only behavior, allowed validation rules) derives that
5
+ knowledge from this file rather than restating it.
6
+
7
+ Adding a new component type means:
8
+ (1) add an entry here,
9
+ (2) add the property type in `properties.ts`,
10
+ (3) register a renderer in FBRE (`registerComponent`),
11
+ (4) add a field-map entry in FBT (`fbt/src/ui/editor/field-map.tsx`).
12
+
13
+ The parity guard in `fbre/src/__tests__/manifest-parity.test.ts` fails if
14
+ the renderer set drifts from the manifest, and the FBT-side guard fails if
15
+ the field-map drifts. Silent registry drift was the largest growth-friction
16
+ item in the 2026-05-19 core libs audit (HIGH-1); this file closes that gap.
17
+ ========================================================================== */
18
+ const ENTRIES = [
19
+ // ---------- Display ----------
20
+ {
21
+ type: "header",
22
+ label: "Header",
23
+ iconKey: "header",
24
+ category: "display",
25
+ displayOnly: true,
26
+ validationRules: [],
27
+ propertiesTypeName: "ComponentDisplayProperties",
28
+ },
29
+ {
30
+ type: "text",
31
+ label: "Description Text",
32
+ iconKey: "descriptionText",
33
+ category: "display",
34
+ displayOnly: true,
35
+ validationRules: [],
36
+ propertiesTypeName: "ComponentDisplayProperties",
37
+ },
38
+ {
39
+ type: "divider",
40
+ label: "Divider",
41
+ iconKey: "divider",
42
+ category: "display",
43
+ displayOnly: true,
44
+ validationRules: [],
45
+ propertiesTypeName: "ComponentDividerProperties",
46
+ },
47
+ {
48
+ type: "callout",
49
+ label: "Callout",
50
+ iconKey: "callout",
51
+ category: "display",
52
+ displayOnly: true,
53
+ validationRules: [],
54
+ propertiesTypeName: "ComponentCalloutProperties",
55
+ },
56
+ {
57
+ type: "table",
58
+ label: "Table",
59
+ iconKey: "table",
60
+ category: "display",
61
+ displayOnly: true,
62
+ validationRules: [],
63
+ propertiesTypeName: "ComponentTableProperties",
64
+ },
65
+ {
66
+ type: "computed",
67
+ label: "Calculated Field",
68
+ iconKey: "calculated",
69
+ category: "display",
70
+ displayOnly: false,
71
+ validationRules: [],
72
+ propertiesTypeName: "ComponentComputedProperties",
73
+ },
74
+ // ---------- Text & Number ----------
75
+ {
76
+ type: "inputText",
77
+ label: "Text Input",
78
+ iconKey: "textInput",
79
+ category: "input",
80
+ displayOnly: false,
81
+ validationRules: [
82
+ "required",
83
+ "email",
84
+ "phone",
85
+ "url",
86
+ "minLength",
87
+ "maxLength",
88
+ "exactLength",
89
+ "pattern",
90
+ "contains",
91
+ "excludes",
92
+ "matchesField",
93
+ ],
94
+ propertiesTypeName: "ComponentInputProperties",
95
+ },
96
+ {
97
+ type: "inputTextArea",
98
+ label: "Text Area",
99
+ iconKey: "textArea",
100
+ category: "input",
101
+ displayOnly: false,
102
+ validationRules: [
103
+ "required",
104
+ "minLength",
105
+ "maxLength",
106
+ "exactLength",
107
+ "pattern",
108
+ "contains",
109
+ "excludes",
110
+ "matchesField",
111
+ ],
112
+ propertiesTypeName: "ComponentInputProperties",
113
+ },
114
+ {
115
+ type: "inputNumber",
116
+ label: "Number Input",
117
+ iconKey: "numberInput",
118
+ category: "input",
119
+ displayOnly: false,
120
+ validationRules: ["required", "minValue", "maxValue", "matchesField"],
121
+ propertiesTypeName: "ComponentInputProperties",
122
+ },
123
+ // ---------- Selection ----------
124
+ {
125
+ type: "dropDown",
126
+ label: "Select",
127
+ iconKey: "select",
128
+ category: "selection",
129
+ displayOnly: false,
130
+ validationRules: ["required"],
131
+ propertiesTypeName: "ComponentOptionProperties",
132
+ },
133
+ {
134
+ type: "dropDownMulti",
135
+ label: "Multi-select",
136
+ iconKey: "multiSelect",
137
+ category: "selection",
138
+ displayOnly: false,
139
+ validationRules: ["required", "minSelected", "maxSelected"],
140
+ propertiesTypeName: "ComponentOptionProperties",
141
+ },
142
+ {
143
+ type: "checkbox",
144
+ label: "Checkbox",
145
+ iconKey: "checkbox",
146
+ category: "selection",
147
+ displayOnly: false,
148
+ validationRules: ["required", "minSelected", "maxSelected"],
149
+ propertiesTypeName: "ComponentOptionProperties",
150
+ },
151
+ {
152
+ type: "radio",
153
+ label: "Radio",
154
+ iconKey: "radio",
155
+ category: "selection",
156
+ displayOnly: false,
157
+ validationRules: ["required"],
158
+ propertiesTypeName: "ComponentOptionProperties",
159
+ },
160
+ {
161
+ type: "toggleSwitch",
162
+ label: "Toggle Switch",
163
+ iconKey: "toggleSwitch",
164
+ category: "selection",
165
+ displayOnly: false,
166
+ validationRules: [],
167
+ propertiesTypeName: "ComponentSwitchProperties",
168
+ },
169
+ {
170
+ type: "yesNo",
171
+ label: "Yes / No",
172
+ iconKey: "yesNo",
173
+ category: "selection",
174
+ displayOnly: false,
175
+ validationRules: ["required"],
176
+ propertiesTypeName: "ComponentYesNoProperties",
177
+ },
178
+ {
179
+ type: "confirm",
180
+ label: "Confirmation",
181
+ iconKey: "confirm",
182
+ category: "selection",
183
+ displayOnly: false,
184
+ validationRules: ["required"],
185
+ propertiesTypeName: "ComponentConfirmProperties",
186
+ },
187
+ {
188
+ type: "cardSelect",
189
+ label: "Card Select",
190
+ iconKey: "cardSelect",
191
+ category: "selection",
192
+ displayOnly: false,
193
+ validationRules: ["required"],
194
+ propertiesTypeName: "ComponentCardSelectProperties",
195
+ },
196
+ // ---------- Date & Time ----------
197
+ {
198
+ type: "date",
199
+ label: "Date",
200
+ iconKey: "date",
201
+ category: "datetime",
202
+ displayOnly: false,
203
+ validationRules: ["required"],
204
+ propertiesTypeName: "ComponentDateProperties",
205
+ },
206
+ {
207
+ type: "time",
208
+ label: "Time",
209
+ iconKey: "time",
210
+ category: "datetime",
211
+ displayOnly: false,
212
+ validationRules: ["required"],
213
+ propertiesTypeName: "ComponentTimeProperties",
214
+ },
215
+ {
216
+ type: "dateTime",
217
+ label: "Date & Time",
218
+ iconKey: "dateTime",
219
+ category: "datetime",
220
+ displayOnly: false,
221
+ validationRules: ["required"],
222
+ propertiesTypeName: "ComponentDateTimeProperties",
223
+ },
224
+ {
225
+ type: "dateRange",
226
+ label: "Date Range",
227
+ iconKey: "dateRange",
228
+ category: "datetime",
229
+ displayOnly: false,
230
+ validationRules: ["required"],
231
+ propertiesTypeName: "ComponentDateRangeProperties",
232
+ },
233
+ {
234
+ type: "timeRange",
235
+ label: "Time Range",
236
+ iconKey: "timeRange",
237
+ category: "datetime",
238
+ displayOnly: false,
239
+ validationRules: ["required"],
240
+ propertiesTypeName: "ComponentTimeRangeProperties",
241
+ },
242
+ {
243
+ type: "dateTimeRange",
244
+ label: "Date & Time Range",
245
+ iconKey: "dateTimeRange",
246
+ category: "datetime",
247
+ displayOnly: false,
248
+ validationRules: ["required"],
249
+ propertiesTypeName: "ComponentDateTimeRangeProperties",
250
+ },
251
+ // ---------- Interactive ----------
252
+ {
253
+ type: "fileUpload",
254
+ label: "File Upload",
255
+ iconKey: "fileUpload",
256
+ category: "interactive",
257
+ displayOnly: false,
258
+ validationRules: ["required", "fileType", "fileSize"],
259
+ propertiesTypeName: "ComponentFileUploadProperties",
260
+ },
261
+ {
262
+ type: "rating",
263
+ label: "Rating",
264
+ iconKey: "rating",
265
+ category: "interactive",
266
+ displayOnly: false,
267
+ validationRules: ["required"],
268
+ propertiesTypeName: "ComponentRatingProperties",
269
+ },
270
+ {
271
+ type: "slider",
272
+ label: "Slider",
273
+ iconKey: "slider",
274
+ category: "interactive",
275
+ displayOnly: false,
276
+ validationRules: [],
277
+ propertiesTypeName: "ComponentSliderProperties",
278
+ },
279
+ {
280
+ type: "colorPicker",
281
+ label: "Color Picker",
282
+ iconKey: "colorPicker",
283
+ category: "interactive",
284
+ displayOnly: false,
285
+ validationRules: ["required"],
286
+ propertiesTypeName: "ComponentColorPickerProperties",
287
+ },
288
+ {
289
+ type: "signature",
290
+ label: "Signature",
291
+ iconKey: "signature",
292
+ category: "interactive",
293
+ displayOnly: false,
294
+ validationRules: ["required"],
295
+ propertiesTypeName: "ComponentSignatureProperties",
296
+ },
297
+ // ---------- Containers ----------
298
+ {
299
+ type: "group",
300
+ label: "Group",
301
+ iconKey: "contactInfo",
302
+ category: "container",
303
+ displayOnly: false,
304
+ validationRules: [],
305
+ propertiesTypeName: "ComponentGroupProperties",
306
+ },
307
+ {
308
+ type: "repeater",
309
+ label: "Repeater",
310
+ iconKey: "repeater",
311
+ category: "container",
312
+ displayOnly: false,
313
+ validationRules: [],
314
+ propertiesTypeName: "ComponentRepeaterProperties",
315
+ },
316
+ ];
317
+ const ENTRY_BY_TYPE = new Map(ENTRIES.map((e) => [e.type, e]));
318
+ /** Frozen, ordered list of every registered component type. The order is the
319
+ * canonical pool-section order (display → input → selection → datetime →
320
+ * interactive → container) and matches the FBT pool. */
321
+ export const COMPONENT_MANIFEST = ENTRIES;
322
+ /** Stable, immutable list of all registered component type ids. */
323
+ export const COMPONENT_TYPES = ENTRIES.map((e) => e.type);
324
+ /** Component types that render content but never contribute to FlowData. */
325
+ export const DISPLAY_ONLY_TYPES = new Set(ENTRIES.filter((e) => e.displayOnly).map((e) => e.type));
326
+ /** Human-readable label for the category badge / pool section header. */
327
+ export const CATEGORY_LABELS = {
328
+ display: "Display",
329
+ input: "Text & Number",
330
+ selection: "Selection",
331
+ datetime: "Date & Time",
332
+ interactive: "Interactive",
333
+ container: "Containers",
334
+ };
335
+ /** Look up a manifest entry by component type id. Returns undefined for
336
+ * unrecognized types — callers decide whether that is an error. */
337
+ export function getComponentManifestEntry(type) {
338
+ return ENTRY_BY_TYPE.get(type);
339
+ }
340
+ /** Manifest entries grouped by category, preserving manifest order within
341
+ * each group. Drives the FBT pool layout. */
342
+ export function getManifestByCategory() {
343
+ const out = {
344
+ display: [],
345
+ input: [],
346
+ selection: [],
347
+ datetime: [],
348
+ interactive: [],
349
+ container: [],
350
+ };
351
+ for (const entry of ENTRIES) {
352
+ out[entry.category].push(entry);
353
+ }
354
+ return out;
355
+ }
@@ -0,0 +1,51 @@
1
+ import type { ComponentCalloutProperties, ComponentCardSelectProperties, ComponentColorPickerProperties, ComponentComputedProperties, ComponentConfirmProperties, ComponentDateProperties, ComponentDateRangeProperties, ComponentDateTimeProperties, ComponentDateTimeRangeProperties, ComponentDisplayProperties, ComponentDividerProperties, ComponentFileUploadProperties, ComponentGroupProperties, ComponentInputProperties, ComponentOptionProperties, ComponentRatingProperties, ComponentRepeaterProperties, ComponentSignatureProperties, ComponentSliderProperties, ComponentSwitchProperties, ComponentTableProperties, ComponentTimeProperties, ComponentTimeRangeProperties, ComponentYesNoProperties } from "./properties";
2
+ /** Per-type property map. Keys here are the canonical `Component.type` ids;
3
+ * values are the corresponding property shapes from `properties.ts`. */
4
+ export interface ComponentPropertiesByType {
5
+ header: ComponentDisplayProperties;
6
+ text: ComponentDisplayProperties;
7
+ divider: ComponentDividerProperties;
8
+ callout: ComponentCalloutProperties;
9
+ table: ComponentTableProperties;
10
+ computed: ComponentComputedProperties;
11
+ inputText: ComponentInputProperties;
12
+ inputTextArea: ComponentInputProperties;
13
+ inputNumber: ComponentInputProperties;
14
+ dropDown: ComponentOptionProperties;
15
+ dropDownMulti: ComponentOptionProperties;
16
+ checkbox: ComponentOptionProperties;
17
+ radio: ComponentOptionProperties;
18
+ toggleSwitch: ComponentSwitchProperties;
19
+ yesNo: ComponentYesNoProperties;
20
+ confirm: ComponentConfirmProperties;
21
+ cardSelect: ComponentCardSelectProperties;
22
+ date: ComponentDateProperties;
23
+ time: ComponentTimeProperties;
24
+ dateTime: ComponentDateTimeProperties;
25
+ dateRange: ComponentDateRangeProperties;
26
+ timeRange: ComponentTimeRangeProperties;
27
+ dateTimeRange: ComponentDateTimeRangeProperties;
28
+ fileUpload: ComponentFileUploadProperties;
29
+ rating: ComponentRatingProperties;
30
+ slider: ComponentSliderProperties;
31
+ colorPicker: ComponentColorPickerProperties;
32
+ signature: ComponentSignatureProperties;
33
+ group: ComponentGroupProperties;
34
+ repeater: ComponentRepeaterProperties;
35
+ }
36
+ /** Union of every registered component `type` string. */
37
+ export type ComponentType = keyof ComponentPropertiesByType;
38
+ /** Runtime list of every registered component `type` — sourced from the type
39
+ * map so the parity check below can verify it matches the manifest. */
40
+ export declare const COMPONENT_VARIANT_TYPES: readonly ComponentType[];
41
+ /** Asserts at runtime that `ComponentPropertiesByType` keys match the
42
+ * COMPONENT_MANIFEST entry set. Pure-function check — call it from a unit
43
+ * test to fail the build when the two drift. */
44
+ export declare function assertManifestTypeMapParity(manifestTypes: readonly string[]): {
45
+ ok: true;
46
+ } | {
47
+ ok: false;
48
+ missingFromMap: string[];
49
+ missingFromManifest: string[];
50
+ };
51
+ //# sourceMappingURL=component-variants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-variants.d.ts","sourceRoot":"","sources":["../src/component-variants.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EACV,0BAA0B,EAC1B,6BAA6B,EAC7B,8BAA8B,EAC9B,2BAA2B,EAC3B,0BAA0B,EAC1B,uBAAuB,EACvB,4BAA4B,EAC5B,2BAA2B,EAC3B,gCAAgC,EAChC,0BAA0B,EAC1B,0BAA0B,EAC1B,6BAA6B,EAC7B,wBAAwB,EACxB,wBAAwB,EACxB,yBAAyB,EACzB,yBAAyB,EACzB,2BAA2B,EAC3B,4BAA4B,EAC5B,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,uBAAuB,EACvB,4BAA4B,EAC5B,wBAAwB,EACzB,MAAM,cAAc,CAAC;AAEtB;yEACyE;AACzE,MAAM,WAAW,yBAAyB;IAExC,MAAM,EAAE,0BAA0B,CAAC;IACnC,IAAI,EAAE,0BAA0B,CAAC;IACjC,OAAO,EAAE,0BAA0B,CAAC;IACpC,OAAO,EAAE,0BAA0B,CAAC;IACpC,KAAK,EAAE,wBAAwB,CAAC;IAChC,QAAQ,EAAE,2BAA2B,CAAC;IAGtC,SAAS,EAAE,wBAAwB,CAAC;IACpC,aAAa,EAAE,wBAAwB,CAAC;IACxC,WAAW,EAAE,wBAAwB,CAAC;IAGtC,QAAQ,EAAE,yBAAyB,CAAC;IACpC,aAAa,EAAE,yBAAyB,CAAC;IACzC,QAAQ,EAAE,yBAAyB,CAAC;IACpC,KAAK,EAAE,yBAAyB,CAAC;IACjC,YAAY,EAAE,yBAAyB,CAAC;IACxC,KAAK,EAAE,wBAAwB,CAAC;IAChC,OAAO,EAAE,0BAA0B,CAAC;IACpC,UAAU,EAAE,6BAA6B,CAAC;IAG1C,IAAI,EAAE,uBAAuB,CAAC;IAC9B,IAAI,EAAE,uBAAuB,CAAC;IAC9B,QAAQ,EAAE,2BAA2B,CAAC;IACtC,SAAS,EAAE,4BAA4B,CAAC;IACxC,SAAS,EAAE,4BAA4B,CAAC;IACxC,aAAa,EAAE,gCAAgC,CAAC;IAGhD,UAAU,EAAE,6BAA6B,CAAC;IAC1C,MAAM,EAAE,yBAAyB,CAAC;IAClC,MAAM,EAAE,yBAAyB,CAAC;IAClC,WAAW,EAAE,8BAA8B,CAAC;IAC5C,SAAS,EAAE,4BAA4B,CAAC;IAGxC,KAAK,EAAE,wBAAwB,CAAC;IAChC,QAAQ,EAAE,2BAA2B,CAAC;CACvC;AAED,yDAAyD;AACzD,MAAM,MAAM,aAAa,GAAG,MAAM,yBAAyB,CAAC;AAE5D;wEACwE;AACxE,eAAO,MAAM,uBAAuB,EAAE,SAAS,aAAa,EA+BlD,CAAC;AAEX;;iDAEiD;AACjD,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAC;IAAC,mBAAmB,EAAE,MAAM,EAAE,CAAA;CAAE,CAWvF"}
@@ -0,0 +1,64 @@
1
+ /* ==========================================================================
2
+ Component variants — type-level mapping from each component `type` to the
3
+ shape of its `properties`. This is the type-level companion to
4
+ COMPONENT_MANIFEST: the manifest is runtime data (labels, icons, validation
5
+ rules), this file is types (per-type property shapes).
6
+
7
+ Adding a new component type means:
8
+ (1) add its property shape in `properties.ts`,
9
+ (2) add a key here mapping `type` → that shape,
10
+ (3) add a manifest entry in `component-manifest.ts`,
11
+ (4) add the renderer in FBRE and the field-map entry in FBT.
12
+
13
+ The runtime parity guard inside `assertManifestTypeMapParity` (called from
14
+ tests in fbre/fbt) fails if step (2) is missed against step (3). The casts
15
+ in fbre/fbt fall back to the loose union and start firing TS errors at the
16
+ read sites if step (1) is missed against step (2).
17
+ ========================================================================== */
18
+ /** Runtime list of every registered component `type` — sourced from the type
19
+ * map so the parity check below can verify it matches the manifest. */
20
+ export const COMPONENT_VARIANT_TYPES = [
21
+ "header",
22
+ "text",
23
+ "divider",
24
+ "callout",
25
+ "table",
26
+ "computed",
27
+ "inputText",
28
+ "inputTextArea",
29
+ "inputNumber",
30
+ "dropDown",
31
+ "dropDownMulti",
32
+ "checkbox",
33
+ "radio",
34
+ "toggleSwitch",
35
+ "yesNo",
36
+ "confirm",
37
+ "cardSelect",
38
+ "date",
39
+ "time",
40
+ "dateTime",
41
+ "dateRange",
42
+ "timeRange",
43
+ "dateTimeRange",
44
+ "fileUpload",
45
+ "rating",
46
+ "slider",
47
+ "colorPicker",
48
+ "signature",
49
+ "group",
50
+ "repeater",
51
+ ];
52
+ /** Asserts at runtime that `ComponentPropertiesByType` keys match the
53
+ * COMPONENT_MANIFEST entry set. Pure-function check — call it from a unit
54
+ * test to fail the build when the two drift. */
55
+ export function assertManifestTypeMapParity(manifestTypes) {
56
+ const mapSet = new Set(COMPONENT_VARIANT_TYPES);
57
+ const manifestSet = new Set(manifestTypes);
58
+ const missingFromMap = manifestTypes.filter((t) => !mapSet.has(t));
59
+ const missingFromManifest = COMPONENT_VARIANT_TYPES.filter((t) => !manifestSet.has(t));
60
+ if (missingFromMap.length === 0 && missingFromManifest.length === 0) {
61
+ return { ok: true };
62
+ }
63
+ return { ok: false, missingFromMap, missingFromManifest };
64
+ }
package/dist/flow.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { FlowConditionConfig } from "./conditions";
2
2
  import type { FlowCalculation } from "./calculation";
3
+ import type { ComponentPropertiesByType, ComponentType } from "./component-variants";
3
4
  export type ScreenTransitionType = "none" | "slide" | "fade" | "slideFade" | "rise" | "scaleFade";
4
5
  export type FlowStyleType = "clean" | "outlined" | "refined-clean" | "airy-clean" | "soft-outlined" | "defined-outlined" | "centered-minimal" | "stacked-cards" | "soft-float" | "bold-statement";
5
6
  export type ControlsLayout = "default" | "centered" | "inline-full" | "stacked";
@@ -31,10 +32,9 @@ export type FlowConfiguration = {
31
32
  controls?: ControlsConfig;
32
33
  summary?: boolean;
33
34
  };
34
- export type Component = {
35
+ /** Fields every component variant carries regardless of `type`. */
36
+ type ComponentCommon = {
35
37
  uuid: string;
36
- type: string;
37
- properties: ComponentProperties;
38
38
  conditions?: FlowConditionConfig;
39
39
  components?: Component[];
40
40
  value?: any;
@@ -42,6 +42,23 @@ export type Component = {
42
42
  valid?: boolean;
43
43
  display?: Record<string, any>;
44
44
  };
45
+ /** A component with a specific known `type`. Useful for helpers that want to
46
+ * accept only the matching variant (e.g. `ComponentOfType<"inputText">`). */
47
+ export type ComponentOfType<K extends ComponentType> = ComponentCommon & {
48
+ type: K;
49
+ properties: ComponentPropertiesByType[K];
50
+ };
51
+ /** Component is a discriminated union keyed on `type`. Switching or guarding
52
+ * on `component.type === "..."` narrows `component.properties` to the
53
+ * matching `Component*Properties` shape from `properties.ts`. */
54
+ export type Component = {
55
+ [K in ComponentType]: ComponentOfType<K>;
56
+ }[ComponentType];
57
+ /** Union of every registered properties shape. Replaces the prior
58
+ * `Record<string, any>` alias — consumers that previously typed a value as
59
+ * `ComponentProperties` now get the union and must narrow (most often via
60
+ * `component.type` on the enclosing `Component`). */
61
+ export type ComponentProperties = ComponentPropertiesByType[ComponentType];
45
62
  export type FlowScreen = {
46
63
  uuid: string;
47
64
  label?: string;
@@ -58,5 +75,5 @@ export type Flow = {
58
75
  screens: FlowScreen[];
59
76
  calculations?: FlowCalculation[];
60
77
  };
61
- export type ComponentProperties = Record<string, any>;
78
+ export {};
62
79
  //# sourceMappingURL=flow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../src/flow.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../src/flow.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,10 +1,17 @@
1
- export type { Flow, FlowScreen, FlowConfiguration, ThemeConfig, NavigationConfig, ControlsConfig, FlowMetadata, Component, ComponentProperties, ScreenTransitionType, FlowStyleType, ControlsLayout, StepperStyle, FlowModeType, } from "./flow";
1
+ export type { Flow, FlowScreen, FlowConfiguration, ThemeConfig, NavigationConfig, ControlsConfig, FlowMetadata, Component, ComponentOfType, ComponentProperties, ScreenTransitionType, FlowStyleType, ControlsLayout, StepperStyle, FlowModeType, } from "./flow";
2
+ export type { ComponentType, ComponentPropertiesByType, } from "./component-variants";
3
+ export { COMPONENT_VARIANT_TYPES, assertManifestTypeMapParity, } from "./component-variants";
2
4
  export type { ConditionAction, ConditionLogic, ConditionOperator, ConditionRule, ConditionRuleSource, ConditionGroup, FlowConditionConfig, } from "./conditions";
3
5
  export type { ValidationRuleType, ValidationRule, FlowValidationConfig, } from "./validation";
6
+ export { generateUUID } from "./uuid";
4
7
  export { normalizeOptions } from "./normalize-options";
5
8
  export type { FlowData, ScreenData, ComponentData, CalculationData, FileUploadData, FileUploadBase64Data, FileUploadS3Data, RangeValue, } from "./flow-data";
6
9
  export { regenerateComponentUUIDs, regenerateFlowUUIDs, } from "./uuid-regeneration";
10
+ export { normalizeConditionalOrder } from "./normalize-conditional-order";
11
+ export { getValidationConfig, getComponentDisplayLabel, } from "./component-accessors";
7
12
  export type { PresetData, TemplateData } from "./preset-template-data";
8
13
  export type { CalculationFormat, FlowCalculation, } from "./calculation";
9
- export type { ComponentWidth, DateFormat, ComponentInputType, ComponentDisplayProperties, ComponentDividerProperties, ComponentInputProperties, OptionItem, ComponentOptionProperties, ComponentSliderProperties, ComponentSwitchProperties, ComponentRatingProperties, ComponentFileUploadProperties, ComponentGroupProperties, ComponentRepeaterProperties, ComponentDateProperties, ComponentTimeProperties, ComponentDateTimeProperties, ComponentDateRangeProperties, ComponentTimeRangeProperties, ComponentDateTimeRangeProperties, ComponentYesNoProperties, ComponentConfirmProperties, ComponentColorPickerProperties, CardSelectOption, ComponentCardSelectProperties, ComponentCalloutVariant, ComponentCalloutProperties, TableColumn, TableRow, ComponentTableProperties, SignatureMode, ComponentSignatureProperties, ComponentComputedProperties, } from "./properties";
14
+ export type { ComponentCategory, ComponentManifestEntry, } from "./component-manifest";
15
+ export { COMPONENT_MANIFEST, COMPONENT_TYPES, DISPLAY_ONLY_TYPES, CATEGORY_LABELS, getComponentManifestEntry, getManifestByCategory, } from "./component-manifest";
16
+ export type { ComponentWidth, DateFormat, ComponentDisplayProperties, ComponentDividerProperties, ComponentInputProperties, OptionItem, ComponentOptionProperties, ComponentSliderProperties, ComponentSwitchProperties, ComponentRatingProperties, ComponentFileUploadProperties, ComponentGroupProperties, ComponentRepeaterProperties, ComponentDateProperties, ComponentTimeProperties, ComponentDateTimeProperties, ComponentDateRangeProperties, ComponentTimeRangeProperties, ComponentDateTimeRangeProperties, ComponentYesNoProperties, ComponentConfirmProperties, ComponentColorPickerProperties, CardSelectOption, ComponentCardSelectProperties, ComponentCalloutVariant, ComponentCalloutProperties, TableColumn, TableRow, ComponentTableProperties, SignatureMode, ComponentSignatureProperties, ComponentComputedProperties, } from "./properties";
10
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,IAAI,EACJ,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,SAAS,EACT,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,YAAY,GACb,MAAM,QAAQ,CAAC;AAEhB,YAAY,EACV,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,cAAc,EACd,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAEtB,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,oBAAoB,GACrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,YAAY,EACV,QAAQ,EACR,UAAU,EACV,aAAa,EACb,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEvE,YAAY,EACV,iBAAiB,EACjB,eAAe,GAChB,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,0BAA0B,EAC1B,0BAA0B,EAC1B,wBAAwB,EACxB,UAAU,EACV,yBAAyB,EACzB,yBAAyB,EACzB,yBAAyB,EACzB,yBAAyB,EACzB,6BAA6B,EAC7B,wBAAwB,EACxB,2BAA2B,EAC3B,uBAAuB,EACvB,uBAAuB,EACvB,2BAA2B,EAC3B,4BAA4B,EAC5B,4BAA4B,EAC5B,gCAAgC,EAChC,wBAAwB,EACxB,0BAA0B,EAC1B,8BAA8B,EAC9B,gBAAgB,EAChB,6BAA6B,EAC7B,uBAAuB,EACvB,0BAA0B,EAC1B,WAAW,EACX,QAAQ,EACR,wBAAwB,EACxB,aAAa,EACb,4BAA4B,EAC5B,2BAA2B,GAC5B,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,IAAI,EACJ,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,YAAY,GACb,MAAM,QAAQ,CAAC;AAEhB,YAAY,EACV,aAAa,EACb,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,cAAc,EACd,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAEtB,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,oBAAoB,GACrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,YAAY,EACV,QAAQ,EACR,UAAU,EACV,aAAa,EACb,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAE1E,OAAO,EACL,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEvE,YAAY,EACV,iBAAiB,EACjB,eAAe,GAChB,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,cAAc,EACd,UAAU,EACV,0BAA0B,EAC1B,0BAA0B,EAC1B,wBAAwB,EACxB,UAAU,EACV,yBAAyB,EACzB,yBAAyB,EACzB,yBAAyB,EACzB,yBAAyB,EACzB,6BAA6B,EAC7B,wBAAwB,EACxB,2BAA2B,EAC3B,uBAAuB,EACvB,uBAAuB,EACvB,2BAA2B,EAC3B,4BAA4B,EAC5B,4BAA4B,EAC5B,gCAAgC,EAChC,wBAAwB,EACxB,0BAA0B,EAC1B,8BAA8B,EAC9B,gBAAgB,EAChB,6BAA6B,EAC7B,uBAAuB,EACvB,0BAA0B,EAC1B,WAAW,EACX,QAAQ,EACR,wBAAwB,EACxB,aAAa,EACb,4BAA4B,EAC5B,2BAA2B,GAC5B,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,7 @@
1
+ export { COMPONENT_VARIANT_TYPES, assertManifestTypeMapParity, } from "./component-variants";
2
+ export { generateUUID } from "./uuid";
1
3
  export { normalizeOptions } from "./normalize-options";
2
4
  export { regenerateComponentUUIDs, regenerateFlowUUIDs, } from "./uuid-regeneration";
5
+ export { normalizeConditionalOrder } from "./normalize-conditional-order";
6
+ export { getValidationConfig, getComponentDisplayLabel, } from "./component-accessors";
7
+ export { COMPONENT_MANIFEST, COMPONENT_TYPES, DISPLAY_ONLY_TYPES, CATEGORY_LABELS, getComponentManifestEntry, getManifestByCategory, } from "./component-manifest";
@@ -0,0 +1,8 @@
1
+ import type { Flow } from "./flow";
2
+ /**
3
+ * Returns a new Flow in which every top-level conditional component is moved
4
+ * to immediately follow the component it depends on. Pure: the input Flow is
5
+ * not mutated and component objects are reused by reference.
6
+ */
7
+ export declare function normalizeConditionalOrder(flow: Flow): Flow;
8
+ //# sourceMappingURL=normalize-conditional-order.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-conditional-order.d.ts","sourceRoot":"","sources":["../src/normalize-conditional-order.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,IAAI,EAAyB,MAAM,QAAQ,CAAC;AAgB1D;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAqL1D"}
@@ -0,0 +1,230 @@
1
+ /* ==========================================================================
2
+ Conditional-order normalization — relocate each conditional component so it
3
+ sits immediately after the component it depends on.
4
+
5
+ Why: FBTL groups a conditional with its trigger ONLY when the conditional
6
+ immediately follows that trigger in document order. Generators (e.g. an AI
7
+ authoring step) often emit a conditional after an unrelated non-conditional
8
+ sibling, so FBTL can't group them and the conditional becomes a detached
9
+ downstream screen. Run a Flow through this BEFORE loading it into FBTL and
10
+ the adjacency FBTL expects is guaranteed.
11
+
12
+ Scope and guarantees:
13
+ - Operates on TOP-LEVEL screen components only. Nested children (inside
14
+ groups/repeaters) already share their parent's screen and are untouched.
15
+ - Only REORDERS. No condition, property, uuid, label, or screen-level
16
+ condition is ever rewritten. Component object references are preserved.
17
+ - A condition source pointing at a question nested inside a group/repeater
18
+ resolves to that nested question's top-level ancestor card, so the
19
+ conditional anchors on the card that actually carries the trigger.
20
+ - A conditional with multiple component sources is anchored after the
21
+ LATEST of its (resolved) sources, so every dependency precedes it.
22
+ - Dependency chains (A → C → D) are resolved trigger-before-dependent.
23
+ - Cycles and dangling sources are left in place (never relocated, never
24
+ loops).
25
+ - Every empty screen is dropped — both those emptied by relocation and any
26
+ the generator emitted empty. Original screen order, uuids, labels, and
27
+ screen-level conditions of surviving screens are preserved.
28
+ - First-screen identity is preserved: if relocation drops the original
29
+ first screen, its uuid is transplanted onto the new first screen so
30
+ FBTL's stable round-trip screenUUID does not shift.
31
+ ========================================================================== */
32
+ /** All component-type source UUIDs referenced by a condition config.
33
+ * `sourceType` undefined is treated as "component" (matches FBTL). */
34
+ function conditionSourceUUIDs(config) {
35
+ const rules = config?.when?.rules;
36
+ if (!rules || rules.length === 0)
37
+ return [];
38
+ const out = [];
39
+ for (const rule of rules) {
40
+ if (rule.sourceType && rule.sourceType !== "component")
41
+ continue;
42
+ if (rule.source)
43
+ out.push(rule.source);
44
+ }
45
+ return out;
46
+ }
47
+ /**
48
+ * Returns a new Flow in which every top-level conditional component is moved
49
+ * to immediately follow the component it depends on. Pure: the input Flow is
50
+ * not mutated and component objects are reused by reference.
51
+ */
52
+ export function normalizeConditionalOrder(flow) {
53
+ const screens = flow.screens ?? [];
54
+ const flat = [];
55
+ screens.forEach((screen, screenIndex) => {
56
+ for (const comp of screen.components ?? []) {
57
+ flat.push({ comp, screenIndex });
58
+ }
59
+ });
60
+ const byUUID = new Map();
61
+ for (const e of flat)
62
+ byUUID.set(e.comp.uuid, e);
63
+ const originalIndex = new Map();
64
+ flat.forEach((e, i) => originalIndex.set(e.comp.uuid, i));
65
+ // Map every component UUID — including ones nested inside group/repeater
66
+ // children — to its TOP-LEVEL ancestor. Lets a condition that points at a
67
+ // nested question anchor on the top-level card that contains it instead of
68
+ // being treated as dangling.
69
+ const topAncestor = new Map();
70
+ function indexSubtree(comp, rootUUID) {
71
+ topAncestor.set(comp.uuid, rootUUID);
72
+ for (const child of comp.components ?? []) {
73
+ indexSubtree(child, rootUUID);
74
+ }
75
+ }
76
+ for (const e of flat)
77
+ indexSubtree(e.comp, e.comp.uuid);
78
+ // Resolve each component's anchor: the source it should follow. Each source
79
+ // UUID is mapped to its top-level ancestor; the anchor is the resolved
80
+ // ancestor that appears LAST in original document order, so every dependency
81
+ // of a multi-source condition ends up before it. Returns null when there is
82
+ // no condition, no component source, or the source resolves to nothing /
83
+ // back to this component itself.
84
+ function anchorUUID(comp) {
85
+ const resolved = conditionSourceUUIDs(comp.conditions)
86
+ .map((uuid) => topAncestor.get(uuid))
87
+ .filter((uuid) => uuid !== undefined && uuid !== comp.uuid && byUUID.has(uuid));
88
+ if (resolved.length === 0)
89
+ return null;
90
+ let best = null;
91
+ let bestIdx = -1;
92
+ for (const uuid of resolved) {
93
+ const idx = originalIndex.get(uuid) ?? -1;
94
+ if (idx > bestIdx) {
95
+ bestIdx = idx;
96
+ best = uuid;
97
+ }
98
+ }
99
+ return best;
100
+ }
101
+ // Dependency depth, with cycle detection. Cyclic components report depth 0
102
+ // so they are treated as un-anchored (left in place, never looped on).
103
+ const depthCache = new Map();
104
+ function depthOf(uuid, seen) {
105
+ const cached = depthCache.get(uuid);
106
+ if (cached !== undefined)
107
+ return cached;
108
+ if (seen.has(uuid))
109
+ return 0; // cycle — break
110
+ const entry = byUUID.get(uuid);
111
+ if (!entry)
112
+ return 0;
113
+ const anchor = anchorUUID(entry.comp);
114
+ if (!anchor) {
115
+ depthCache.set(uuid, 0);
116
+ return 0;
117
+ }
118
+ seen.add(uuid);
119
+ const d = depthOf(anchor, seen) + 1;
120
+ seen.delete(uuid);
121
+ depthCache.set(uuid, d);
122
+ return d;
123
+ }
124
+ function isCyclic(uuid) {
125
+ // Walk anchors; if we revisit a node before hitting a root, it's a cycle.
126
+ const path = new Set();
127
+ let cur = uuid;
128
+ while (cur) {
129
+ if (path.has(cur))
130
+ return true;
131
+ path.add(cur);
132
+ const entry = byUUID.get(cur);
133
+ if (!entry)
134
+ return false;
135
+ cur = anchorUUID(entry.comp);
136
+ }
137
+ return false;
138
+ }
139
+ // Working order starts as original document order; relocations splice in.
140
+ const order = flat.map((e) => e.comp);
141
+ // Process relocatable conditionals trigger-before-dependent (depth asc),
142
+ // tie-broken by original index for stable sibling ordering.
143
+ const movable = flat
144
+ .map((e) => e.comp)
145
+ .filter((c) => anchorUUID(c) !== null && !isCyclic(c.uuid))
146
+ .sort((a, b) => {
147
+ const da = depthOf(a.uuid, new Set());
148
+ const db = depthOf(b.uuid, new Set());
149
+ if (da !== db)
150
+ return da - db;
151
+ return (originalIndex.get(a.uuid) ?? 0) - (originalIndex.get(b.uuid) ?? 0);
152
+ });
153
+ for (const comp of movable) {
154
+ const anchor = anchorUUID(comp);
155
+ if (!anchor)
156
+ continue;
157
+ const from = order.indexOf(comp);
158
+ if (from === -1)
159
+ continue;
160
+ order.splice(from, 1);
161
+ let at = order.findIndex((c) => c.uuid === anchor);
162
+ if (at === -1) {
163
+ // Anchor vanished (shouldn't happen) — restore original slot.
164
+ order.splice(Math.min(from, order.length), 0, comp);
165
+ continue;
166
+ }
167
+ // Skip past any conditionals already attached to this anchor so multiple
168
+ // dependents of one trigger keep their original relative order.
169
+ let insertAt = at + 1;
170
+ while (insertAt < order.length && anchorUUID(order[insertAt]) === anchor) {
171
+ insertAt++;
172
+ }
173
+ order.splice(insertAt, 0, comp);
174
+ }
175
+ // Resolve the screen a component lands on: follow anchors to the root
176
+ // (non-conditional / cyclic) component and use that screen.
177
+ const screenCache = new Map();
178
+ function targetScreen(uuid, seen) {
179
+ const cached = screenCache.get(uuid);
180
+ if (cached !== undefined)
181
+ return cached;
182
+ const entry = byUUID.get(uuid);
183
+ if (!entry)
184
+ return 0;
185
+ if (seen.has(uuid))
186
+ return entry.screenIndex; // cycle — own screen
187
+ if (isCyclic(uuid))
188
+ return entry.screenIndex;
189
+ const anchor = anchorUUID(entry.comp);
190
+ if (!anchor) {
191
+ screenCache.set(uuid, entry.screenIndex);
192
+ return entry.screenIndex;
193
+ }
194
+ seen.add(uuid);
195
+ const s = targetScreen(anchor, seen);
196
+ seen.delete(uuid);
197
+ screenCache.set(uuid, s);
198
+ return s;
199
+ }
200
+ // Re-emit screens in original order; components in final relocated order
201
+ // filtered to their target screen. Drop every empty screen — both those
202
+ // emptied by relocation and any that arrived empty from the generator.
203
+ const buckets = screens.map(() => []);
204
+ for (const comp of order) {
205
+ buckets[targetScreen(comp.uuid, new Set())].push(comp);
206
+ }
207
+ const newScreens = [];
208
+ screens.forEach((screen, i) => {
209
+ const comps = buckets[i];
210
+ if (comps.length === 0)
211
+ return; // empty — drop
212
+ newScreens.push({ ...screen, components: comps });
213
+ });
214
+ // Preserve first-screen identity: FBTL pins screens[0]'s uuid as its stable
215
+ // round-trip screenUUID. If relocation dropped the original first screen,
216
+ // transplant its uuid onto the new first screen so that anchor doesn't
217
+ // shift. The surviving screen keeps its own label — it carries meaning the
218
+ // dropped screen's would not.
219
+ const originalFirst = screens[0];
220
+ if (originalFirst && newScreens.length > 0) {
221
+ if (newScreens[0].uuid !== originalFirst.uuid) {
222
+ newScreens[0] = { ...newScreens[0], uuid: originalFirst.uuid };
223
+ }
224
+ }
225
+ else if (originalFirst && newScreens.length === 0) {
226
+ // Whole flow had no components — keep one well-formed empty screen.
227
+ newScreens.push({ ...originalFirst, components: [] });
228
+ }
229
+ return { ...flow, screens: newScreens };
230
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"uuid-regeneration.d.ts","sourceRoot":"","sources":["../src/uuid-regeneration.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE9C;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAe7E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAYpD"}
1
+ {"version":3,"file":"uuid-regeneration.d.ts","sourceRoot":"","sources":["../src/uuid-regeneration.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAG9C;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAkB7E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAYpD"}
@@ -2,15 +2,19 @@
2
2
  UUID regeneration — deep-clone components/flows and replace all UUIDs.
3
3
  Used when dropping stored presets/templates onto the stage.
4
4
  ========================================================================== */
5
+ import { generateUUID } from "./uuid";
5
6
  /**
6
7
  * Deep-clones an array of components and replaces every `uuid`
7
8
  * (including recursively within group `components`).
8
9
  */
9
10
  export function regenerateComponentUUIDs(components) {
10
11
  return components.map((comp) => {
12
+ // Cast through the union because the spread breaks TS's correlation
13
+ // between `type` and `properties`. At runtime the spread preserves both
14
+ // verbatim and structuredClone preserves the matching shape.
11
15
  const cloned = {
12
16
  ...comp,
13
- uuid: crypto.randomUUID(),
17
+ uuid: generateUUID(),
14
18
  properties: structuredClone(comp.properties),
15
19
  };
16
20
  // Recurse into group children
@@ -27,12 +31,12 @@ export function regenerateComponentUUIDs(components) {
27
31
  export function regenerateFlowUUIDs(flow) {
28
32
  return {
29
33
  ...flow,
30
- uuid: crypto.randomUUID(),
34
+ uuid: generateUUID(),
31
35
  metadata: { ...flow.metadata },
32
36
  config: flow.config ? { ...flow.config } : undefined,
33
37
  screens: flow.screens.map((screen) => ({
34
38
  ...screen,
35
- uuid: crypto.randomUUID(),
39
+ uuid: generateUUID(),
36
40
  components: regenerateComponentUUIDs(screen.components),
37
41
  })),
38
42
  };
package/dist/uuid.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare const generateUUID: () => string;
2
+ //# sourceMappingURL=uuid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,YAAY,QAAO,MAA6B,CAAC"}
package/dist/uuid.js ADDED
@@ -0,0 +1,5 @@
1
+ /* ==========================================================================
2
+ UUID generator — thin wrapper around crypto.randomUUID.
3
+ Single source for fbt/fbtl (which re-export this) and uuid-regeneration.
4
+ ========================================================================== */
5
+ export const generateUUID = () => crypto.randomUUID();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonata-innovations/fiber-types",
3
- "version": "1.0.5",
3
+ "version": "2.0.0",
4
4
  "description": "TypeScript type definitions for the Fiber form builder system",
5
5
  "keywords": ["fiber", "form-builder", "types", "typescript", "flow-json"],
6
6
  "license": "MIT",
@@ -25,7 +25,9 @@
25
25
  ],
26
26
  "sideEffects": false,
27
27
  "scripts": {
28
- "build": "tsc"
28
+ "clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\"",
29
+ "prebuild": "node scripts/check-variant-parity.mjs",
30
+ "build": "npm run clean && tsc"
29
31
  },
30
32
  "publishConfig": {
31
33
  "access": "public"