@sonata-innovations/fiber-types 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export type { ValidationRuleType, ValidationRule, FlowValidationConfig, } from "
4
4
  export { normalizeOptions } from "./normalize-options";
5
5
  export type { FlowData, ScreenData, ComponentData, CalculationData, FileUploadData, FileUploadBase64Data, FileUploadS3Data, RangeValue, } from "./flow-data";
6
6
  export { regenerateComponentUUIDs, regenerateFlowUUIDs, } from "./uuid-regeneration";
7
+ export { normalizeConditionalOrder } from "./normalize-conditional-order";
7
8
  export type { PresetData, TemplateData } from "./preset-template-data";
8
9
  export type { CalculationFormat, FlowCalculation, } from "./calculation";
9
10
  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";
@@ -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,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,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAE1E,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"}
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { normalizeOptions } from "./normalize-options";
2
2
  export { regenerateComponentUUIDs, regenerateFlowUUIDs, } from "./uuid-regeneration";
3
+ export { normalizeConditionalOrder } from "./normalize-conditional-order";
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonata-innovations/fiber-types",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
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",