@kubex/zinc 1.1.23 → 1.1.24

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.
Files changed (44) hide show
  1. package/custom-elements-manifest.config.js +2 -4
  2. package/dist/custom-elements.json +1158 -74
  3. package/dist/vscode.html-custom-data.json +33 -11
  4. package/dist/web-types.json +132 -23
  5. package/dist/zn.d.ts +864 -0
  6. package/dist/zn.min.js +874 -463
  7. package/docs/_includes/default.njk +1 -0
  8. package/docs/_includes/full-page.njk +38 -0
  9. package/docs/pages/components/flow-builder-demo.njk +194 -0
  10. package/docs/pages/components/flow-builder-troubleshooter-demo.njk +282 -0
  11. package/docs/pages/components/flow-builder.md +377 -0
  12. package/package.json +1 -1
  13. package/src/components/flow-builder/flow-builder.component.ts +1171 -0
  14. package/src/components/flow-builder/flow-builder.scss +489 -0
  15. package/src/components/flow-builder/flow-builder.test.ts +59 -0
  16. package/src/components/flow-builder/flow-layout.ts +139 -0
  17. package/src/components/flow-builder/flow-registry.ts +52 -0
  18. package/src/components/flow-builder/flow.types.ts +407 -0
  19. package/src/components/flow-builder/index.ts +14 -0
  20. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.component.ts +1086 -0
  21. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.scss +371 -0
  22. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.test.ts +39 -0
  23. package/src/components/flow-builder/modules/flow-canvas/index.ts +12 -0
  24. package/src/components/flow-builder/modules/flow-node/flow-node.component.ts +174 -0
  25. package/src/components/flow-builder/modules/flow-node/flow-node.scss +180 -0
  26. package/src/components/flow-builder/modules/flow-node/flow-node.test.ts +50 -0
  27. package/src/components/flow-builder/modules/flow-node/index.ts +12 -0
  28. package/src/components/flow-builder/modules/flow-step/flow-step.component.ts +88 -0
  29. package/src/components/flow-builder/modules/flow-step/flow-step.scss +52 -0
  30. package/src/components/flow-builder/modules/flow-step/flow-step.test.ts +21 -0
  31. package/src/components/flow-builder/modules/flow-step/index.ts +12 -0
  32. package/src/components/flow-builder/modules/flow-step-group/flow-step-group.component.ts +35 -0
  33. package/src/components/flow-builder/modules/flow-step-group/flow-step-group.scss +28 -0
  34. package/src/components/flow-builder/modules/flow-step-group/flow-step-group.test.ts +20 -0
  35. package/src/components/flow-builder/modules/flow-step-group/index.ts +12 -0
  36. package/src/components/flow-builder/modules/flow-steps/flow-steps.component.ts +88 -0
  37. package/src/components/flow-builder/modules/flow-steps/flow-steps.scss +24 -0
  38. package/src/components/flow-builder/modules/flow-steps/flow-steps.test.ts +17 -0
  39. package/src/components/flow-builder/modules/flow-steps/index.ts +12 -0
  40. package/src/events/events.ts +3 -0
  41. package/src/events/zn-flow-change.ts +9 -0
  42. package/src/events/zn-flow-connect.ts +9 -0
  43. package/src/events/zn-flow-selection-change.ts +7 -0
  44. package/src/zinc.ts +4 -0
@@ -33,6 +33,7 @@
33
33
  {# Zinc #}
34
34
  <script type="module" src="/dist/zn.min.js"></script>
35
35
  <link rel="stylesheet" href="/dist/zn.min.css"/>
36
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:400,500,600,700&display=swap">
36
37
 
37
38
  {# Scripts #}
38
39
  <script src="{{ assetUrl('scripts/turbo.js') }}" type="module"></script>
@@ -0,0 +1,38 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-layout="full-page" data-zinc-version="{{ meta.version }}">
3
+ <head>
4
+ <meta charset="utf-8"/>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <meta name="description" content="{{ meta.description }}"/>
7
+ <title>{{ meta.title }}</title>
8
+
9
+ {# Zinc #}
10
+ <script type="module" src="/dist/zn.min.js"></script>
11
+ <link rel="stylesheet" href="/dist/zn.min.css"/>
12
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:400,500,600,700&display=swap">
13
+
14
+ <style>
15
+ html, body {
16
+ height: 100%;
17
+ margin: 0;
18
+ }
19
+
20
+ body {
21
+ font-family: var(--zn-font-sans, 'Inter', sans-serif);
22
+ background: rgb(var(--zn-body));
23
+ color: rgb(var(--zn-text));
24
+ }
25
+
26
+ /* The page content fills the viewport — no docs chrome, no code preview. */
27
+ .full-page {
28
+ height: 100vh;
29
+ width: 100vw;
30
+ }
31
+ </style>
32
+ </head>
33
+ <body>
34
+ <div class="full-page">
35
+ {{ content | safe }}
36
+ </div>
37
+ </body>
38
+ </html>
@@ -0,0 +1,194 @@
1
+ ---
2
+ meta:
3
+ title: Flow Builder — Full Page Demo
4
+ description: A full-page, chrome-free demo of the zn-flow-builder component.
5
+ layout: full-page
6
+ toc: false
7
+ eleventyExcludeFromCollections: true
8
+ permalink: /components/flow-builder-demo/index.html
9
+ ---
10
+ {% raw %}
11
+ <zn-flow-builder
12
+ id="flow-demo"
13
+ style="height: 100%;"
14
+ heading="Retention – Annual Cancellation_v1"
15
+ subheading="Last updated 16th June 2026"
16
+ triggers-hint="Automations require a trigger to start. Choose a trigger below and drag it to the canvas to begin."
17
+ actions-hint="Actions run as the automation progresses. Drag one onto the canvas or a + slot."
18
+ rules-hint="Rules branch the flow based on conditions you configure on each step.">
19
+
20
+ <!-- Node types are declared as slotted steps; the builder routes each into a tab
21
+ by its `group` (empty groups get no tab), and groups it under its `category`. -->
22
+ <zn-flow-step type="webhook" group="trigger" category="Events"
23
+ icon="webhook@lu" color="rgb(236, 68, 91)" inputs=""
24
+ description="User hits webhook XYZ">Cancellation Requested</zn-flow-step>
25
+ <zn-flow-step type="manual" group="trigger" category="Contacts"
26
+ icon="user-plus@lu" color="rgb(236, 68, 91)" inputs="">Contact added manually</zn-flow-step>
27
+ <zn-flow-step type="list-add" group="trigger" category="Contacts"
28
+ icon="list-plus@lu" color="rgb(236, 68, 91)" inputs="">Contact added to a list</zn-flow-step>
29
+
30
+ <zn-flow-step type="email-parser" group="action" category="Email"
31
+ icon="mail@lu" color="rgb(43, 192, 145)">IMAP Email Parser</zn-flow-step>
32
+ <zn-flow-step type="send-reply" group="action" category="Email"
33
+ icon="send@lu" color="rgb(43, 192, 145)">Send Reply</zn-flow-step>
34
+ <zn-flow-step type="claude" group="action" category="AI"
35
+ icon="bot@lu" color="rgb(43, 192, 145)">Claude</zn-flow-step>
36
+ <zn-flow-step type="notify" group="action" category="Support"
37
+ icon="user@lu" color="rgb(43, 192, 145)">Notify User</zn-flow-step>
38
+
39
+ <zn-flow-step type="split" group="rule" category="Logic"
40
+ icon="split@lu" color="rgb(105, 54, 245)"
41
+ outputs='[{"id":"true","label":"TRUE"},{"id":"false","label":"FALSE"}]'>Conditional Split</zn-flow-step>
42
+
43
+ <div slot="sidebar" class="flow-sidebar-demo">
44
+ <label>Status
45
+ <zn-select>
46
+ <zn-option selected>Active</zn-option>
47
+ <zn-option>Paused</zn-option>
48
+ <zn-option>Draft</zn-option>
49
+ </zn-select>
50
+ </label>
51
+ <h4>Version History</h4>
52
+ <ul>
53
+ <li><strong>John Smith</strong><span>16th June 2026 · 14:03</span></li>
54
+ <li><strong>John Smith</strong><span>16th June 2026 · 11:20</span></li>
55
+ <li><strong>Jane Doe</strong><span>15th June 2026 · 09:48</span></li>
56
+ </ul>
57
+ </div>
58
+ </zn-flow-builder>
59
+
60
+ <style>
61
+ .flow-sidebar-demo {
62
+ margin-top: 16px;
63
+ font-size: 0.8125rem;
64
+ }
65
+
66
+ .flow-sidebar-demo label {
67
+ display: flex;
68
+ flex-direction: column;
69
+ gap: 4px;
70
+ font-weight: 600;
71
+ }
72
+
73
+ .flow-sidebar-demo select {
74
+ padding: 6px 8px;
75
+ border: 1px solid rgb(var(--zn-border-color));
76
+ border-radius: 6px;
77
+ }
78
+
79
+ .flow-sidebar-demo h4 {
80
+ margin: 16px 0 8px;
81
+ }
82
+
83
+ .flow-sidebar-demo ul {
84
+ list-style: none;
85
+ margin: 0;
86
+ padding: 0;
87
+ }
88
+
89
+ .flow-sidebar-demo li {
90
+ display: flex;
91
+ flex-direction: column;
92
+ padding: 8px 0;
93
+ border-top: 1px solid rgb(var(--zn-border-color));
94
+ }
95
+
96
+ .flow-sidebar-demo li span {
97
+ color: rgb(var(--zn-color-muted-text));
98
+ font-size: 0.75rem;
99
+ }
100
+ </style>
101
+
102
+ <script type="module">
103
+ const initial = {
104
+ nodes: [
105
+ {id: 'n1', type: 'webhook', x: 280, y: 60, data: {subtitle: 'User hits webhook XYZ'}},
106
+ {id: 'n2', type: 'split', x: 280, y: 210, data: {subtitle: 'ARR Threshold'}},
107
+ {id: 'n3', type: 'email-parser', x: 80, y: 470, data: {subtitle: 'Import Email Attachment'}},
108
+ {id: 'n4', type: 'notify', x: 80, y: 640, data: {subtitle: 'Notify Frank Furt'}},
109
+ {id: 'n5', type: 'claude', x: 480, y: 470, data: {subtitle: 'Generate Reply'}},
110
+ {id: 'n6', type: 'send-reply', x: 480, y: 640, data: {subtitle: 'Generate Reply'}}
111
+ ],
112
+ connections: [
113
+ {id: 'c1', source: {node: 'n1', port: 'out'}, target: {node: 'n2', port: 'in'}},
114
+ {id: 'c2', source: {node: 'n2', port: 'true'}, target: {node: 'n3', port: 'in'}},
115
+ {id: 'c3', source: {node: 'n2', port: 'false'}, target: {node: 'n5', port: 'in'}},
116
+ {id: 'c4', source: {node: 'n3', port: 'out'}, target: {node: 'n4', port: 'in'}},
117
+ {id: 'c5', source: {node: 'n5', port: 'out'}, target: {node: 'n6', port: 'in'}}
118
+ ],
119
+ notes: [
120
+ {id: 'note1', x: 760, y: 430, text: 'This is a sticky note.'}
121
+ ]
122
+ };
123
+
124
+ // The Conditional Split's outputs are user-configurable per node: its renderConfig
125
+ // edits node.outputs (add / rename / remove branches). The canvas re-renders the
126
+ // outputs and the builder prunes connections to any removed branch.
127
+ const splitType = {
128
+ type: 'split',
129
+ label: 'Conditional Split',
130
+ group: 'rule',
131
+ category: 'Logic',
132
+ icon: 'split@lu',
133
+ color: 'rgb(105, 54, 245)',
134
+ outputs: [{id: 'true', label: 'TRUE'}, {id: 'false', label: 'FALSE'}],
135
+ renderConfig: (node, update) =>
136
+ {
137
+ const branches = node.outputs ?? [{id: 'true', label: 'TRUE'}, {id: 'false', label: 'FALSE'}];
138
+ const setBranches = (next) =>
139
+ {
140
+ node.outputs = next;
141
+ update({});
142
+ };
143
+
144
+ const wrap = document.createElement('div');
145
+ wrap.style.cssText = 'display:flex;flex-direction:column;gap:8px';
146
+
147
+ const heading = document.createElement('p');
148
+ heading.textContent = 'Branches';
149
+ heading.style.cssText = 'margin:0;font-weight:600;font-size:0.8125rem';
150
+ wrap.appendChild(heading);
151
+
152
+ branches.forEach((branch, i) =>
153
+ {
154
+ const row = document.createElement('div');
155
+ row.style.cssText = 'display:flex;gap:6px;align-items:center';
156
+
157
+ const input = document.createElement('zn-input');
158
+ input.setAttribute('size', 'small');
159
+ input.value = branch.label ?? branch.id;
160
+ input.style.flex = '1';
161
+ input.addEventListener('change', () =>
162
+ setBranches(branches.map((b, j) => (j === i ? {...b, label: input.value} : b))));
163
+
164
+ const remove = document.createElement('zn-button');
165
+ remove.setAttribute('icon', 'x@lu');
166
+ remove.setAttribute('icon-button', 'small');
167
+ remove.setAttribute('plain', '');
168
+ remove.addEventListener('click', () => setBranches(branches.filter((_, j) => j !== i)));
169
+
170
+ row.append(input, remove);
171
+ wrap.appendChild(row);
172
+ });
173
+
174
+ const add = document.createElement('zn-button');
175
+ add.setAttribute('size', 'small');
176
+ add.setAttribute('icon', 'plus@lu');
177
+ add.textContent = 'Add branch';
178
+ add.addEventListener('click', () =>
179
+ setBranches([...branches, {id: 'branch-' + (branches.length + 1), label: 'New branch'}]));
180
+ wrap.appendChild(add);
181
+
182
+ return wrap;
183
+ }
184
+ };
185
+
186
+ customElements.whenDefined('zn-flow-builder').then(() =>
187
+ {
188
+ const el = document.getElementById('flow-demo');
189
+ el.registerNodeType(splitType);
190
+ el.setState(initial);
191
+ el.errorNodes = ['n3'];
192
+ });
193
+ </script>
194
+ {% endraw %}
@@ -0,0 +1,282 @@
1
+ ---
2
+ meta:
3
+ title: Flow Builder — Troubleshoot Q&A Demo
4
+ description: A full-page demo of zn-flow-builder driving a tiered troubleshooting Q&A flow.
5
+ layout: full-page
6
+ toc: false
7
+ eleventyExcludeFromCollections: true
8
+ permalink: /components/flow-builder-troubleshooter-demo/index.html
9
+ ---
10
+ {% raw %}
11
+ <zn-flow-builder
12
+ id="ts-demo"
13
+ style="height: 100%;"
14
+ heading="Total AV - Installation Troubleshooter"
15
+ subheading="Last updated 16th June 2026"
16
+ triggers-hint="Automations require a trigger to start. Choose a trigger below and drag it to the canvas to begin."
17
+ actions-hint="Actions run as the troubleshooter progresses. Drag one onto the canvas or a + slot.">
18
+
19
+ <!-- Header actions slot left/right of the full-width action bar. -->
20
+ <zn-button slot="header-left" icon="x@lu" id="ts-close" panel-bg>Close</zn-button>
21
+ <zn-button slot="header-left" icon="refresh-cw@lu" id="ts-reset" panel-bg>Undo All Changes</zn-button>
22
+ <zn-button slot="header-right" icon="check@lu" color="primary" id="ts-apply">Apply Changes</zn-button>
23
+
24
+ <!-- Node types are declared as slotted steps; the builder routes each into a tab
25
+ by its `group` (empty groups get no tab), and groups it under its `category`. -->
26
+ <zn-flow-step type="manual" group="trigger" category="Contacts"
27
+ icon="user-plus@lu" color="rgb(236, 68, 91)" inputs="">Contact added manually</zn-flow-step>
28
+ <zn-flow-step type="form-submit" group="trigger" category="Forms"
29
+ icon="clipboard-list@lu" color="rgb(236, 68, 91)" inputs="">Form submitted</zn-flow-step>
30
+
31
+ <zn-flow-step type="send-email" group="action" category="Email"
32
+ icon="mail@lu" color="rgb(43, 192, 145)">Send Email</zn-flow-step>
33
+ <zn-flow-step type="notify" group="action" category="Support"
34
+ icon="user@lu" color="rgb(43, 192, 145)">Notify User</zn-flow-step>
35
+
36
+ <div slot="sidebar" class="ts-sidebar">
37
+ <label>Status
38
+ <zn-select>
39
+ <zn-option>Active</zn-option>
40
+ <zn-option>Paused</zn-option>
41
+ <zn-option selected>Draft</zn-option>
42
+ </zn-select>
43
+ </label>
44
+ <h4>Version History</h4>
45
+ <ul>
46
+ <li><strong>John Smith</strong><span>16th June 2026 · 14:03</span></li>
47
+ <li><strong>John Smith</strong><span>16th June 2026 · 14:03</span></li>
48
+ <li><strong>John Smith</strong><span>16th June 2026 · 14:03</span></li>
49
+ <li><strong>John Smith</strong><span>16th June 2026 · 14:03</span></li>
50
+ <li><strong>John Smith</strong><span>16th June 2026 · 14:03</span></li>
51
+ </ul>
52
+ </div>
53
+ </zn-flow-builder>
54
+
55
+ <style>
56
+ .ts-sidebar {
57
+ margin-top: 16px;
58
+ font-size: 0.8125rem;
59
+ }
60
+
61
+ .ts-sidebar label {
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: 4px;
65
+ font-weight: 600;
66
+ }
67
+
68
+ .ts-sidebar h4 {
69
+ margin: 16px 0 8px;
70
+ }
71
+
72
+ .ts-sidebar ul {
73
+ list-style: none;
74
+ margin: 0;
75
+ padding: 0;
76
+ }
77
+
78
+ .ts-sidebar li {
79
+ display: flex;
80
+ flex-direction: column;
81
+ padding: 8px 0;
82
+ border-top: 1px solid rgb(var(--zn-border-color));
83
+ }
84
+
85
+ .ts-sidebar li span {
86
+ color: rgb(var(--zn-color-muted-text));
87
+ font-size: 0.75rem;
88
+ }
89
+ </style>
90
+
91
+ <script type="module">
92
+ const QUESTION_COLOR = 'rgb(43, 192, 145)';
93
+ const ANSWER_COLOR = 'rgb(59, 130, 246)';
94
+
95
+ // Shared branch editor body: the conditions/filters required to take this path.
96
+ // The builder supplies the "Branch name" input; this adds the condition fields,
97
+ // persisted on the output port's `data`.
98
+ const renderBranchConfig = (node, port, update) =>
99
+ {
100
+ const wrap = document.createElement('div');
101
+ wrap.style.cssText = 'display:flex;flex-direction:column;gap:10px';
102
+
103
+ const filter = document.createElement('zn-input');
104
+ filter.setAttribute('label', 'Filter');
105
+ filter.setAttribute('placeholder', 'e.g. platform = macOS');
106
+ filter.value = port.data?.filter ?? '';
107
+ filter.addEventListener('change', () =>
108
+ update({data: {...port.data, filter: filter.value}}));
109
+
110
+ const condition = document.createElement('zn-input');
111
+ condition.setAttribute('label', 'Condition');
112
+ condition.setAttribute('placeholder', 'e.g. answer contains "reinstalled"');
113
+ condition.value = port.data?.condition ?? '';
114
+ condition.addEventListener('change', () =>
115
+ update({data: {...port.data, condition: condition.value}}));
116
+
117
+ wrap.append(filter, condition);
118
+ return wrap;
119
+ };
120
+
121
+ // Node config: label + body text, plus a branch manager — every question or
122
+ // answer can have as many outputs as it likes. Inputs commit on every keystroke
123
+ // so the canvas (node card, branch pills) updates live; the builder keeps this
124
+ // DOM in place across those re-renders, so read node.outputs at event time.
125
+ const live = (input, fn) =>
126
+ ['input', 'zn-input'].forEach(ev => input.addEventListener(ev, fn));
127
+
128
+ const renderConfig = (bodyLabel) => (node, update) =>
129
+ {
130
+ const wrap = document.createElement('div');
131
+ wrap.style.cssText = 'display:flex;flex-direction:column;gap:10px';
132
+
133
+ const label = document.createElement('zn-input');
134
+ label.setAttribute('label', 'Label');
135
+ label.value = node.label ?? '';
136
+ live(label, () =>
137
+ {
138
+ node.label = label.value;
139
+ update({});
140
+ });
141
+
142
+ const body = document.createElement('zn-input');
143
+ body.setAttribute('label', bodyLabel);
144
+ body.value = node.data.subtitle ?? '';
145
+ live(body, () => update({subtitle: body.value}));
146
+
147
+ const heading = document.createElement('p');
148
+ heading.textContent = 'Branches';
149
+ heading.style.cssText = 'margin:4px 0 0;font-weight:600;font-size:0.8125rem';
150
+
151
+ wrap.append(label, body, heading);
152
+
153
+ const branches = node.outputs ?? [{id: 'out'}];
154
+ const setBranches = (next) =>
155
+ {
156
+ node.outputs = next;
157
+ update({});
158
+ };
159
+ const currentBranches = () => node.outputs ?? branches;
160
+
161
+ branches.forEach((branch, i) =>
162
+ {
163
+ const row = document.createElement('div');
164
+ row.style.cssText = 'display:flex;gap:6px;align-items:center';
165
+
166
+ const input = document.createElement('zn-input');
167
+ input.setAttribute('size', 'small');
168
+ input.value = branch.label ?? branch.id;
169
+ input.style.flex = '1';
170
+ live(input, () =>
171
+ setBranches(currentBranches().map((b, j) => (j === i ? {...b, label: input.value} : b))));
172
+
173
+ const remove = document.createElement('zn-button');
174
+ remove.setAttribute('icon', 'x@lu');
175
+ remove.setAttribute('icon-button', 'small');
176
+ remove.setAttribute('plain', '');
177
+ remove.addEventListener('click', () => setBranches(currentBranches().filter((_, j) => j !== i)));
178
+
179
+ row.append(input, remove);
180
+ wrap.appendChild(row);
181
+ });
182
+
183
+ const add = document.createElement('zn-button');
184
+ add.setAttribute('size', 'small');
185
+ add.setAttribute('icon', 'plus@lu');
186
+ add.textContent = 'Add branch';
187
+ add.addEventListener('click', () =>
188
+ setBranches([...currentBranches(), {id: 'branch-' + Date.now().toString(36), label: 'New branch'}]));
189
+ wrap.appendChild(add);
190
+
191
+ return wrap;
192
+ };
193
+
194
+ const questionType = {
195
+ type: 'ts-question',
196
+ label: 'Question',
197
+ group: 'action',
198
+ category: 'Troubleshooter',
199
+ icon: 'message-circle-more@lu',
200
+ color: QUESTION_COLOR,
201
+ description: 'Ask the user a question',
202
+ renderConfig: renderConfig('Question'),
203
+ renderBranchConfig,
204
+ };
205
+
206
+ const answerType = {
207
+ type: 'ts-answer',
208
+ label: 'Answer',
209
+ group: 'action',
210
+ category: 'Troubleshooter',
211
+ icon: 'message-circle-reply@lu',
212
+ color: ANSWER_COLOR,
213
+ description: 'A user answer to branch on',
214
+ renderConfig: renderConfig('Answer'),
215
+ renderBranchConfig,
216
+ };
217
+
218
+ // The tiered Q&A flow from the design: one question fanning out to three
219
+ // answers, second-tier questions below, and a fan-in — Answer 2 and Answer 3
220
+ // both route to Tier 2 - Question 2.
221
+ const initial = {
222
+ nodes: [
223
+ // The root question is the flow's entrypoint: `inputs: []` removes its
224
+ // input ports so nothing can be wired into it.
225
+ {
226
+ id: 't1q1', type: 'ts-question', x: 620, y: 40, label: 'Tier 1 - Question 1', inputs: [],
227
+ outputs: [{id: 'a', label: 'Branch A'}, {id: 'b', label: 'Branch B'}, {id: 'c', label: 'Branch C'}],
228
+ data: {subtitle: 'How do you....'}
229
+ },
230
+ {
231
+ id: 't1a1', type: 'ts-answer', x: 220, y: 340, label: 'Tier 1 - Answer 1',
232
+ outputs: [{id: 'out', label: 'Branch C'}], data: {subtitle: 'I did XYZ...'}
233
+ },
234
+ {
235
+ id: 't1a2', type: 'ts-answer', x: 620, y: 340, label: 'Tier 1 - Answer 2',
236
+ outputs: [{id: 'out', label: 'Branch D'}], data: {subtitle: 'I did XYZ...'}
237
+ },
238
+ {
239
+ id: 't1a3', type: 'ts-answer', x: 1020, y: 340, label: 'Tier 1 - Answer 3',
240
+ outputs: [{id: 'out', label: 'Branch E'}], data: {subtitle: 'I did XYZ...'}
241
+ },
242
+ {
243
+ id: 't2q1', type: 'ts-question', x: 420, y: 640, label: 'Tier 2 - Question 1',
244
+ outputs: [{id: 'out', label: 'Branch F'}], data: {subtitle: 'How do you....'}
245
+ },
246
+ {
247
+ id: 't2q2', type: 'ts-question', x: 820, y: 640, label: 'Tier 2 - Question 2',
248
+ outputs: [{id: 'out'}], data: {subtitle: 'How do you....'}
249
+ },
250
+ {
251
+ id: 't2a2', type: 'ts-answer', x: 420, y: 940, label: 'Tier 2 - Answer 2',
252
+ outputs: [{id: 'out'}], data: {subtitle: 'I did XYZ...'}
253
+ }
254
+ ],
255
+ connections: [
256
+ {id: 'c1', source: {node: 't1q1', port: 'a'}, target: {node: 't1a1', port: 'in'}},
257
+ {id: 'c2', source: {node: 't1q1', port: 'b'}, target: {node: 't1a2', port: 'in'}},
258
+ {id: 'c3', source: {node: 't1q1', port: 'c'}, target: {node: 't1a3', port: 'in'}},
259
+ {id: 'c4', source: {node: 't1a1', port: 'out'}, target: {node: 't2q1', port: 'in'}},
260
+ {id: 'c5', source: {node: 't1a2', port: 'out'}, target: {node: 't2q2', port: 'in'}},
261
+ // Fan-in: Answer 3 routes to the same question as Answer 2.
262
+ {id: 'c6', source: {node: 't1a3', port: 'out'}, target: {node: 't2q2', port: 'in'}},
263
+ {id: 'c7', source: {node: 't2q1', port: 'out'}, target: {node: 't2a2', port: 'in'}}
264
+ ],
265
+ notes: []
266
+ };
267
+
268
+ customElements.whenDefined('zn-flow-builder').then(() =>
269
+ {
270
+ const el = document.getElementById('ts-demo');
271
+ el.registerNodeTypes([questionType, answerType]);
272
+ el.setState(initial);
273
+ el.errorNodes = ['t2q2']; // "Missing Answers" in the design
274
+
275
+ const pristine = el.value;
276
+ document.getElementById('ts-reset').addEventListener('click', () => (el.value = pristine));
277
+ document.getElementById('ts-apply').addEventListener('click', () =>
278
+ console.log('Apply changes', el.getState()));
279
+ document.getElementById('ts-close').addEventListener('click', () => history.back());
280
+ });
281
+ </script>
282
+ {% endraw %}