@unofficialbox/box-open-elements 0.5.0 → 0.6.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.
Files changed (219) hide show
  1. package/README.md +3 -3
  2. package/dist/components/collections/table.d.ts +57 -3
  3. package/dist/components/collections/table.js +339 -20
  4. package/dist/components/feedback/due-badge.d.ts +38 -0
  5. package/dist/components/feedback/due-badge.js +168 -0
  6. package/dist/components/feedback/due-types.d.ts +32 -0
  7. package/dist/components/feedback/due-types.js +91 -0
  8. package/dist/components/feedback/progress-steps.d.ts +51 -5
  9. package/dist/components/feedback/progress-steps.js +200 -22
  10. package/dist/components/feedback/stage-path.d.ts +44 -0
  11. package/dist/components/feedback/stage-path.js +235 -0
  12. package/dist/components/forms/select.d.ts +21 -0
  13. package/dist/components/forms/select.js +100 -3
  14. package/dist/components/forms/text-field.d.ts +21 -0
  15. package/dist/components/forms/text-field.js +100 -1
  16. package/dist/components/layout/nav-sidebar.d.ts +14 -0
  17. package/dist/components/layout/nav-sidebar.js +49 -0
  18. package/dist/components/layout/split-view.d.ts +14 -0
  19. package/dist/components/layout/split-view.js +80 -1
  20. package/dist/components/overlays/command-palette.d.ts +62 -0
  21. package/dist/components/overlays/command-palette.js +537 -0
  22. package/dist/components/overlays/command-types.d.ts +90 -0
  23. package/dist/components/overlays/command-types.js +259 -0
  24. package/dist/components/overlays/drawer.d.ts +18 -0
  25. package/dist/components/overlays/drawer.js +135 -9
  26. package/dist/components/overlays/shortcuts-overlay.d.ts +48 -0
  27. package/dist/components/overlays/shortcuts-overlay.js +385 -0
  28. package/dist/components/overlays/tooltip.js +1 -1
  29. package/dist/element-maps.d.ts +292 -0
  30. package/dist/element-maps.js +1 -0
  31. package/dist/entries/activity-density.d.ts +1 -0
  32. package/dist/entries/activity-density.js +1 -0
  33. package/dist/entries/agent-chat.d.ts +1 -0
  34. package/dist/entries/agent-chat.js +1 -0
  35. package/dist/entries/audit-log.d.ts +1 -0
  36. package/dist/entries/audit-log.js +1 -0
  37. package/dist/entries/command-palette.d.ts +1 -0
  38. package/dist/entries/command-palette.js +1 -0
  39. package/dist/entries/compare-view.d.ts +1 -0
  40. package/dist/entries/compare-view.js +1 -0
  41. package/dist/entries/diff-viewer.d.ts +1 -0
  42. package/dist/entries/diff-viewer.js +1 -0
  43. package/dist/entries/due-badge.d.ts +1 -0
  44. package/dist/entries/due-badge.js +1 -0
  45. package/dist/entries/form-wizard.d.ts +1 -0
  46. package/dist/entries/form-wizard.js +1 -0
  47. package/dist/entries/lineage-graph.d.ts +1 -0
  48. package/dist/entries/lineage-graph.js +1 -0
  49. package/dist/entries/notification-bell.d.ts +1 -0
  50. package/dist/entries/notification-bell.js +1 -0
  51. package/dist/entries/notification-inbox.d.ts +1 -0
  52. package/dist/entries/notification-inbox.js +1 -0
  53. package/dist/entries/provenance-strip.d.ts +1 -0
  54. package/dist/entries/provenance-strip.js +1 -0
  55. package/dist/entries/run-trace.d.ts +1 -0
  56. package/dist/entries/run-trace.js +1 -0
  57. package/dist/entries/shortcuts-overlay.d.ts +1 -0
  58. package/dist/entries/shortcuts-overlay.js +1 -0
  59. package/dist/entries/signature-ceremony.d.ts +1 -0
  60. package/dist/entries/signature-ceremony.js +1 -0
  61. package/dist/entries/stage-path.d.ts +1 -0
  62. package/dist/entries/stage-path.js +1 -0
  63. package/dist/entries/timeline.d.ts +1 -0
  64. package/dist/entries/timeline.js +1 -0
  65. package/dist/entries/version-graph.d.ts +1 -0
  66. package/dist/entries/version-graph.js +1 -0
  67. package/dist/entries/version-list.d.ts +1 -0
  68. package/dist/entries/version-list.js +1 -0
  69. package/dist/entries/wizard-summary.d.ts +1 -0
  70. package/dist/entries/wizard-summary.js +1 -0
  71. package/dist/entries/work-queue.d.ts +1 -0
  72. package/dist/entries/work-queue.js +1 -0
  73. package/dist/entries/workload-board.d.ts +1 -0
  74. package/dist/entries/workload-board.js +1 -0
  75. package/dist/index.d.ts +21 -0
  76. package/dist/index.js +21 -0
  77. package/dist/patterns/agent-chat/agent-chat.d.ts +75 -0
  78. package/dist/patterns/agent-chat/agent-chat.js +820 -0
  79. package/dist/patterns/agent-chat/controller.d.ts +27 -0
  80. package/dist/patterns/agent-chat/controller.js +182 -0
  81. package/dist/patterns/agent-chat/index.d.ts +3 -0
  82. package/dist/patterns/agent-chat/index.js +6 -0
  83. package/dist/patterns/agent-chat/types.d.ts +109 -0
  84. package/dist/patterns/agent-chat/types.js +1 -0
  85. package/dist/patterns/audit/activity-density.d.ts +68 -0
  86. package/dist/patterns/audit/activity-density.js +428 -0
  87. package/dist/patterns/audit/audit-log.d.ts +81 -0
  88. package/dist/patterns/audit/audit-log.js +794 -0
  89. package/dist/patterns/audit/index.d.ts +3 -0
  90. package/dist/patterns/audit/index.js +7 -0
  91. package/dist/patterns/audit/shared.d.ts +10 -0
  92. package/dist/patterns/audit/shared.js +50 -0
  93. package/dist/patterns/audit/types.d.ts +143 -0
  94. package/dist/patterns/audit/types.js +327 -0
  95. package/dist/patterns/content-explorer/actions/controller.js +12 -2
  96. package/dist/patterns/content-explorer/box-transport.js +117 -25
  97. package/dist/patterns/content-explorer/content-explorer.d.ts +13 -1
  98. package/dist/patterns/content-explorer/content-explorer.js +36 -3
  99. package/dist/patterns/content-explorer/contracts.d.ts +24 -1
  100. package/dist/patterns/content-explorer/contracts.js +68 -11
  101. package/dist/patterns/content-explorer/controller.d.ts +16 -1
  102. package/dist/patterns/content-explorer/controller.js +130 -4
  103. package/dist/patterns/content-explorer/navigation/controller.js +11 -9
  104. package/dist/patterns/content-explorer/types.d.ts +58 -1
  105. package/dist/patterns/content-picker/content-picker.d.ts +71 -0
  106. package/dist/patterns/content-picker/content-picker.js +747 -0
  107. package/dist/patterns/content-picker/controller.d.ts +38 -0
  108. package/dist/patterns/content-picker/controller.js +141 -0
  109. package/dist/patterns/content-picker/index.d.ts +3 -0
  110. package/dist/patterns/content-picker/index.js +6 -0
  111. package/dist/patterns/content-picker/types.d.ts +52 -0
  112. package/dist/patterns/content-picker/types.js +26 -0
  113. package/dist/patterns/content-sidebar/content-sidebar.d.ts +43 -0
  114. package/dist/patterns/content-sidebar/content-sidebar.js +293 -0
  115. package/dist/patterns/content-sidebar/index.d.ts +2 -0
  116. package/dist/patterns/content-sidebar/index.js +4 -0
  117. package/dist/patterns/content-sidebar/types.d.ts +18 -0
  118. package/dist/patterns/content-sidebar/types.js +20 -0
  119. package/dist/patterns/content-uploader/box-transport.d.ts +14 -0
  120. package/dist/patterns/content-uploader/box-transport.js +54 -0
  121. package/dist/patterns/content-uploader/content-uploader.d.ts +62 -0
  122. package/dist/patterns/content-uploader/content-uploader.js +559 -0
  123. package/dist/patterns/content-uploader/controller.d.ts +39 -0
  124. package/dist/patterns/content-uploader/controller.js +227 -0
  125. package/dist/patterns/content-uploader/index.d.ts +4 -0
  126. package/dist/patterns/content-uploader/index.js +8 -0
  127. package/dist/patterns/content-uploader/types.d.ts +120 -0
  128. package/dist/patterns/content-uploader/types.js +38 -0
  129. package/dist/patterns/diff/compare-view.d.ts +47 -0
  130. package/dist/patterns/diff/compare-view.js +276 -0
  131. package/dist/patterns/diff/diff-viewer.d.ts +42 -0
  132. package/dist/patterns/diff/diff-viewer.js +434 -0
  133. package/dist/patterns/diff/engine.d.ts +22 -0
  134. package/dist/patterns/diff/engine.js +276 -0
  135. package/dist/patterns/diff/index.d.ts +4 -0
  136. package/dist/patterns/diff/index.js +8 -0
  137. package/dist/patterns/diff/types.d.ts +69 -0
  138. package/dist/patterns/diff/types.js +24 -0
  139. package/dist/patterns/form-wizard/controller.d.ts +39 -0
  140. package/dist/patterns/form-wizard/controller.js +157 -0
  141. package/dist/patterns/form-wizard/form-wizard.d.ts +63 -0
  142. package/dist/patterns/form-wizard/form-wizard.js +468 -0
  143. package/dist/patterns/form-wizard/index.d.ts +4 -0
  144. package/dist/patterns/form-wizard/index.js +8 -0
  145. package/dist/patterns/form-wizard/types.d.ts +109 -0
  146. package/dist/patterns/form-wizard/types.js +83 -0
  147. package/dist/patterns/form-wizard/wizard-summary.d.ts +62 -0
  148. package/dist/patterns/form-wizard/wizard-summary.js +324 -0
  149. package/dist/patterns/internal/safe-href.d.ts +17 -0
  150. package/dist/patterns/internal/safe-href.js +17 -0
  151. package/dist/patterns/lineage/index.d.ts +3 -0
  152. package/dist/patterns/lineage/index.js +6 -0
  153. package/dist/patterns/lineage/lineage-graph.d.ts +30 -0
  154. package/dist/patterns/lineage/lineage-graph.js +434 -0
  155. package/dist/patterns/lineage/provenance-strip.d.ts +22 -0
  156. package/dist/patterns/lineage/provenance-strip.js +194 -0
  157. package/dist/patterns/lineage/types.d.ts +53 -0
  158. package/dist/patterns/lineage/types.js +49 -0
  159. package/dist/patterns/notifications/index.d.ts +3 -0
  160. package/dist/patterns/notifications/index.js +6 -0
  161. package/dist/patterns/notifications/notification-bell.d.ts +38 -0
  162. package/dist/patterns/notifications/notification-bell.js +198 -0
  163. package/dist/patterns/notifications/notification-inbox.d.ts +46 -0
  164. package/dist/patterns/notifications/notification-inbox.js +537 -0
  165. package/dist/patterns/notifications/types.d.ts +61 -0
  166. package/dist/patterns/notifications/types.js +108 -0
  167. package/dist/patterns/preview/annotation-inspector.d.ts +25 -6
  168. package/dist/patterns/preview/annotation-inspector.js +239 -94
  169. package/dist/patterns/preview/annotation-thread.d.ts +25 -5
  170. package/dist/patterns/preview/annotation-thread.js +233 -82
  171. package/dist/patterns/preview/annotation-toolbar.d.ts +20 -6
  172. package/dist/patterns/preview/annotation-toolbar.js +162 -96
  173. package/dist/patterns/preview/content-preview-adapter.d.ts +10 -1
  174. package/dist/patterns/preview/content-preview-adapter.js +26 -1
  175. package/dist/patterns/preview/preview-element.d.ts +15 -2
  176. package/dist/patterns/preview/preview-element.js +223 -52
  177. package/dist/patterns/preview/provider-adapter.d.ts +47 -0
  178. package/dist/patterns/preview/provider-adapter.js +17 -0
  179. package/dist/patterns/run/index.d.ts +2 -0
  180. package/dist/patterns/run/index.js +4 -0
  181. package/dist/patterns/run/run-trace.d.ts +40 -0
  182. package/dist/patterns/run/run-trace.js +441 -0
  183. package/dist/patterns/run/types.d.ts +66 -0
  184. package/dist/patterns/run/types.js +152 -0
  185. package/dist/patterns/signature/index.d.ts +2 -0
  186. package/dist/patterns/signature/index.js +4 -0
  187. package/dist/patterns/signature/signature-ceremony.d.ts +33 -0
  188. package/dist/patterns/signature/signature-ceremony.js +317 -0
  189. package/dist/patterns/signature/types.d.ts +68 -0
  190. package/dist/patterns/signature/types.js +89 -0
  191. package/dist/patterns/timeline/index.d.ts +2 -0
  192. package/dist/patterns/timeline/index.js +4 -0
  193. package/dist/patterns/timeline/timeline.d.ts +27 -0
  194. package/dist/patterns/timeline/timeline.js +430 -0
  195. package/dist/patterns/timeline/types.d.ts +36 -0
  196. package/dist/patterns/timeline/types.js +27 -0
  197. package/dist/patterns/versions/graph-layout.d.ts +43 -0
  198. package/dist/patterns/versions/graph-layout.js +159 -0
  199. package/dist/patterns/versions/index.d.ts +4 -0
  200. package/dist/patterns/versions/index.js +8 -0
  201. package/dist/patterns/versions/shared.d.ts +4 -0
  202. package/dist/patterns/versions/shared.js +13 -0
  203. package/dist/patterns/versions/types.d.ts +37 -0
  204. package/dist/patterns/versions/types.js +39 -0
  205. package/dist/patterns/versions/version-graph.d.ts +36 -0
  206. package/dist/patterns/versions/version-graph.js +438 -0
  207. package/dist/patterns/versions/version-list.d.ts +39 -0
  208. package/dist/patterns/versions/version-list.js +407 -0
  209. package/dist/patterns/work-queue/controller.d.ts +26 -0
  210. package/dist/patterns/work-queue/controller.js +150 -0
  211. package/dist/patterns/work-queue/index.d.ts +4 -0
  212. package/dist/patterns/work-queue/index.js +7 -0
  213. package/dist/patterns/work-queue/types.d.ts +128 -0
  214. package/dist/patterns/work-queue/types.js +45 -0
  215. package/dist/patterns/work-queue/work-queue.d.ts +48 -0
  216. package/dist/patterns/work-queue/work-queue.js +534 -0
  217. package/dist/patterns/work-queue/workload-board.d.ts +53 -0
  218. package/dist/patterns/work-queue/workload-board.js +608 -0
  219. package/package.json +89 -5
@@ -0,0 +1,468 @@
1
+ import { FormWizardController } from "./controller.js";
2
+ import { ProgressSteps } from "../../components/feedback/progress-steps.js";
3
+ import { BaseElement } from "../../core/index.js";
4
+ import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
5
+ import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
6
+ const DEFAULT_TAG_NAME = "box-form-wizard";
7
+ const escapeHtml = (value) => value
8
+ .replaceAll("&", "&")
9
+ .replaceAll("<", "&lt;")
10
+ .replaceAll(">", "&gt;")
11
+ .replaceAll('"', "&quot;")
12
+ .replaceAll("'", "&#39;");
13
+ /** Attribute payloads are author input — validate every record. */
14
+ const isWizardStepRecord = (value) => {
15
+ if (typeof value !== "object" || value === null) {
16
+ return false;
17
+ }
18
+ const step = value;
19
+ return typeof step.id === "string" && step.id.length > 0 && typeof step.label === "string";
20
+ };
21
+ const elementStyles = `
22
+ :host {
23
+ display: block;
24
+ color: inherit;
25
+ font: inherit;
26
+ }
27
+
28
+ [part="wizard"] {
29
+ display: grid;
30
+ gap: ${boePanel.gap};
31
+ padding: ${boePanel.padding};
32
+ border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 82%, transparent);
33
+ border-radius: ${boePanel.radius};
34
+ background: var(--boe-token-surface-surface, #ffffff);
35
+ }
36
+
37
+ [part="title"] {
38
+ margin: 0;
39
+ font: inherit;
40
+ font-size: 1.1rem;
41
+ font-weight: 700;
42
+ color: var(--boe-token-text-text, #1f1e1b);
43
+ }
44
+
45
+ [part="layout"] {
46
+ display: grid;
47
+ grid-template-columns: minmax(180px, 260px) 1fr;
48
+ gap: ${boePanel.gap};
49
+ align-items: start;
50
+ }
51
+
52
+ @media (max-width: 720px) {
53
+ [part="layout"] {
54
+ grid-template-columns: 1fr;
55
+ }
56
+ }
57
+
58
+ [part="panels"] {
59
+ display: grid;
60
+ gap: ${boePanel.gap};
61
+ }
62
+
63
+ [part="panel"][hidden] {
64
+ display: none;
65
+ }
66
+
67
+ [part="error"] {
68
+ margin: 0;
69
+ padding: 0.55rem 0.7rem;
70
+ border-radius: ${boeRadius.large};
71
+ font-size: 0.9rem;
72
+ background: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 10%, var(--boe-token-surface-surface, #ffffff));
73
+ border: 1px solid color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 34%, transparent);
74
+ color: color-mix(in srgb, var(--boe-token-surface-status-surface-error, #ed3757) 72%, black 28%);
75
+ }
76
+
77
+ [part="error"][hidden] {
78
+ display: none;
79
+ }
80
+
81
+ [part="footer"] {
82
+ display: flex;
83
+ align-items: center;
84
+ gap: ${boePanel.gap};
85
+ padding-top: 0.2rem;
86
+ border-top: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 62%, transparent);
87
+ }
88
+
89
+ [part="footer-spacer"] {
90
+ flex: 1;
91
+ }
92
+
93
+ [part="back"],
94
+ [part="draft"],
95
+ [part="next"],
96
+ [part="submit"] {
97
+ appearance: none;
98
+ font: inherit;
99
+ font-size: 0.875rem;
100
+ font-weight: 600;
101
+ min-height: 2.1rem;
102
+ padding: 0.4rem 0.9rem;
103
+ border-radius: ${boeRadius.control};
104
+ cursor: pointer;
105
+ transition: background ${boeMotionDuration.interactive} ${boeMotionEasing.standard}, border-color ${boeMotionDuration.interactive} ${boeMotionEasing.standard}, box-shadow ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
106
+ }
107
+
108
+ [part="back"],
109
+ [part="draft"] {
110
+ border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8);
111
+ background: var(--boe-token-surface-surface, #ffffff);
112
+ color: var(--boe-token-text-text, #222222);
113
+ }
114
+
115
+ [part="back"]:hover:not(:disabled),
116
+ [part="draft"]:hover {
117
+ background: var(--boe-token-surface-surface-hover, #f4f4f4);
118
+ border-color: var(--boe-token-stroke-stroke-hover, #bcbcbc);
119
+ }
120
+
121
+ [part="back"]:disabled {
122
+ cursor: not-allowed;
123
+ opacity: 0.55;
124
+ }
125
+
126
+ [part="next"],
127
+ [part="submit"] {
128
+ border: 1px solid transparent;
129
+ background: var(--boe-token-surface-surface-brand, #0061d5);
130
+ color: var(--boe-token-text-text-on-brand, #ffffff);
131
+ }
132
+
133
+ [part="next"]:hover,
134
+ [part="submit"]:hover {
135
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 88%, black 12%);
136
+ }
137
+
138
+ [part="next"][hidden],
139
+ [part="submit"][hidden],
140
+ [part="draft"][hidden] {
141
+ display: none;
142
+ }
143
+
144
+ [part="back"]:focus-visible,
145
+ [part="draft"]:focus-visible,
146
+ [part="next"]:focus-visible,
147
+ [part="submit"]:focus-visible {
148
+ outline: none;
149
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 18%, transparent);
150
+ }
151
+ `;
152
+ export class FormWizard extends BaseElement {
153
+ static tagName = DEFAULT_TAG_NAME;
154
+ static get observedAttributes() {
155
+ return ["draft-label", "heading", "steps", "submit-label"];
156
+ }
157
+ controller = null;
158
+ pendingStart = false;
159
+ unsubscribeFns = [];
160
+ validatorsValue = {};
161
+ initialValuesValue = {};
162
+ titleEl;
163
+ railEl;
164
+ panelsEl;
165
+ errorEl;
166
+ backEl;
167
+ draftEl;
168
+ nextEl;
169
+ submitEl;
170
+ panelsSignature = "";
171
+ suppressRailEvent = false;
172
+ get heading() {
173
+ return this.getAttribute("heading") ?? "";
174
+ }
175
+ set heading(value) {
176
+ if (!value) {
177
+ this.removeAttribute("heading");
178
+ return;
179
+ }
180
+ this.setAttribute("heading", value);
181
+ }
182
+ /**
183
+ * Step configuration; JSON `[{"id","label","description?","optional?"}]`.
184
+ * A step's id doubles as the slot name feeding its panel.
185
+ */
186
+ get steps() {
187
+ const raw = this.getAttribute("steps");
188
+ if (!raw) {
189
+ return [];
190
+ }
191
+ try {
192
+ const parsed = JSON.parse(raw);
193
+ return Array.isArray(parsed) && parsed.every(isWizardStepRecord) ? parsed : [];
194
+ }
195
+ catch {
196
+ return [];
197
+ }
198
+ }
199
+ set steps(value) {
200
+ if (value.length) {
201
+ this.setAttribute("steps", JSON.stringify(value));
202
+ return;
203
+ }
204
+ this.removeAttribute("steps");
205
+ }
206
+ get submitLabel() {
207
+ return this.getAttribute("submit-label") ?? "Submit";
208
+ }
209
+ set submitLabel(value) {
210
+ this.setAttribute("submit-label", value);
211
+ }
212
+ /** When set, a Save draft button renders and emits `draft-saved`. */
213
+ get draftLabel() {
214
+ return this.getAttribute("draft-label");
215
+ }
216
+ set draftLabel(value) {
217
+ if (!value) {
218
+ this.removeAttribute("draft-label");
219
+ return;
220
+ }
221
+ this.setAttribute("draft-label", value);
222
+ }
223
+ get validators() {
224
+ return this.validatorsValue;
225
+ }
226
+ set validators(value) {
227
+ this.validatorsValue = value;
228
+ this.scheduleStart();
229
+ }
230
+ get initialValues() {
231
+ return this.initialValuesValue;
232
+ }
233
+ set initialValues(value) {
234
+ this.initialValuesValue = value;
235
+ this.scheduleStart();
236
+ }
237
+ get values() {
238
+ return this.controller?.getState().values ?? {};
239
+ }
240
+ get activeStep() {
241
+ return this.controller?.getState().currentStepId ?? "";
242
+ }
243
+ /** The live session controller. Null until steps are configured. */
244
+ get wizardController() {
245
+ return this.controller;
246
+ }
247
+ attributeChangedCallback(name, oldValue, newValue) {
248
+ // Labels are presentation-only; steps re-create the session.
249
+ if (name === "steps") {
250
+ this.scheduleStart();
251
+ }
252
+ super.attributeChangedCallback(name, oldValue, newValue);
253
+ }
254
+ connectedCallback() {
255
+ super.connectedCallback();
256
+ this.scheduleStart();
257
+ }
258
+ disconnectedCallback() {
259
+ this.teardownController();
260
+ }
261
+ setValue(field, value) {
262
+ this.controller?.setValue(field, value);
263
+ }
264
+ setValues(patch) {
265
+ this.controller?.setValues(patch);
266
+ }
267
+ next() {
268
+ return this.controller?.next() ?? false;
269
+ }
270
+ previous() {
271
+ return this.controller?.previous() ?? false;
272
+ }
273
+ goTo(stepId) {
274
+ return this.controller?.goTo(stepId) ?? false;
275
+ }
276
+ saveDraft() {
277
+ this.controller?.saveDraft();
278
+ }
279
+ submit() {
280
+ return this.controller?.submit() ?? false;
281
+ }
282
+ reset() {
283
+ this.controller?.reset();
284
+ }
285
+ scheduleStart() {
286
+ if (this.pendingStart) {
287
+ return;
288
+ }
289
+ this.pendingStart = true;
290
+ queueMicrotask(() => {
291
+ this.pendingStart = false;
292
+ this.startController();
293
+ });
294
+ }
295
+ startController() {
296
+ if (!this.isConnected) {
297
+ return;
298
+ }
299
+ const steps = this.steps;
300
+ if (!steps.length) {
301
+ this.teardownController();
302
+ if (this.isRendered) {
303
+ this.update();
304
+ }
305
+ return;
306
+ }
307
+ this.teardownController();
308
+ const controller = new FormWizardController({
309
+ steps,
310
+ validators: this.validatorsValue,
311
+ initialValues: this.initialValuesValue,
312
+ });
313
+ this.controller = controller;
314
+ this.subscribeToController(controller);
315
+ if (this.isRendered) {
316
+ this.update();
317
+ }
318
+ }
319
+ subscribeToController(controller) {
320
+ const events = [
321
+ ["stepChanged", "step-changed"],
322
+ ["valuesChanged", "values-changed"],
323
+ ["stepInvalid", "step-invalid"],
324
+ ["draftSaved", "draft-saved"],
325
+ ["submitted", "submitted"],
326
+ ];
327
+ this.unsubscribeFns = events.map(([eventName, domEventName]) => controller.subscribe(eventName, payload => {
328
+ this.dispatchEvent(new CustomEvent(domEventName, {
329
+ bubbles: true,
330
+ composed: true,
331
+ detail: payload,
332
+ }));
333
+ if (this.isRendered) {
334
+ this.update();
335
+ }
336
+ }));
337
+ }
338
+ teardownController() {
339
+ for (const unsubscribe of this.unsubscribeFns) {
340
+ unsubscribe();
341
+ }
342
+ this.unsubscribeFns = [];
343
+ this.controller?.destroy();
344
+ this.controller = null;
345
+ this.panelsSignature = "";
346
+ }
347
+ renderTemplate() {
348
+ if (!this.shadowRoot) {
349
+ return;
350
+ }
351
+ this.shadowRoot.innerHTML = `
352
+ <style>${elementStyles}</style>
353
+ <section part="wizard" aria-label="Form wizard">
354
+ <h2 part="title" hidden></h2>
355
+ <div part="layout">
356
+ <box-progress-steps part="rail"></box-progress-steps>
357
+ <div part="body">
358
+ <p part="error" role="alert" hidden></p>
359
+ <div part="panels"></div>
360
+ </div>
361
+ </div>
362
+ <footer part="footer">
363
+ <button type="button" part="back">Back</button>
364
+ <span part="footer-spacer"></span>
365
+ <button type="button" part="draft" hidden></button>
366
+ <button type="button" part="next">Next</button>
367
+ <button type="button" part="submit" hidden></button>
368
+ </footer>
369
+ </section>
370
+ `;
371
+ this.titleEl = this.shadowRoot.querySelector('[part="title"]');
372
+ this.railEl = this.shadowRoot.querySelector('[part="rail"]');
373
+ this.panelsEl = this.shadowRoot.querySelector('[part="panels"]');
374
+ this.errorEl = this.shadowRoot.querySelector('[part="error"]');
375
+ this.backEl = this.shadowRoot.querySelector('[part="back"]');
376
+ this.draftEl = this.shadowRoot.querySelector('[part="draft"]');
377
+ this.nextEl = this.shadowRoot.querySelector('[part="next"]');
378
+ this.submitEl = this.shadowRoot.querySelector('[part="submit"]');
379
+ }
380
+ setupListeners() {
381
+ this.backEl.addEventListener("click", () => {
382
+ this.previous();
383
+ });
384
+ this.nextEl.addEventListener("click", () => {
385
+ this.next();
386
+ });
387
+ this.submitEl.addEventListener("click", () => {
388
+ this.submit();
389
+ });
390
+ this.draftEl.addEventListener("click", () => {
391
+ this.saveDraft();
392
+ });
393
+ // Rail clicks route through the controller's gating: visited steps are
394
+ // reachable, jumping ahead validates the steps in between.
395
+ this.railEl.addEventListener("value-changed", event => {
396
+ event.stopPropagation();
397
+ if (this.suppressRailEvent) {
398
+ return;
399
+ }
400
+ const stepId = event.detail?.value ?? "";
401
+ const current = this.controller?.getState().currentStepId ?? "";
402
+ if (stepId && stepId !== current) {
403
+ this.goTo(stepId);
404
+ // goTo may refuse (or stop partway); re-sync the rail to the truth.
405
+ if (this.isRendered) {
406
+ this.update();
407
+ }
408
+ }
409
+ });
410
+ }
411
+ /** One panel per step, fed by a slot named after the step id. */
412
+ rebuildPanels(steps) {
413
+ this.panelsEl.innerHTML = steps
414
+ .map(step => `
415
+ <div part="panel" data-step-id="${escapeHtml(step.id)}" role="group" aria-label="${escapeHtml(step.label)}" hidden>
416
+ <slot name="${escapeHtml(step.id)}"></slot>
417
+ </div>
418
+ `)
419
+ .join("");
420
+ }
421
+ update() {
422
+ if (!this.railEl) {
423
+ return;
424
+ }
425
+ const state = this.controller?.getState() ?? null;
426
+ const steps = state?.steps ?? [];
427
+ this.titleEl.hidden = !this.heading;
428
+ this.titleEl.textContent = this.heading;
429
+ const signature = JSON.stringify(steps.map(step => step.id));
430
+ if (signature !== this.panelsSignature) {
431
+ this.panelsSignature = signature;
432
+ this.rebuildPanels(steps);
433
+ }
434
+ this.suppressRailEvent = true;
435
+ try {
436
+ this.railEl.label = this.heading ? `${this.heading} steps` : "Wizard steps";
437
+ this.railEl.items = steps.map(step => ({
438
+ label: step.label,
439
+ value: step.id,
440
+ ...(step.description ? { description: step.description } : {}),
441
+ }));
442
+ if (state && this.railEl.value !== state.currentStepId) {
443
+ this.railEl.value = state.currentStepId;
444
+ }
445
+ }
446
+ finally {
447
+ this.suppressRailEvent = false;
448
+ }
449
+ this.panelsEl.querySelectorAll('[part="panel"]').forEach(panel => {
450
+ panel.hidden = panel.dataset.stepId !== state?.currentStepId;
451
+ });
452
+ const error = state?.stepError ?? null;
453
+ this.errorEl.hidden = !error;
454
+ this.errorEl.textContent = error?.message ?? (error ? "Complete the required fields to continue." : "");
455
+ const isLast = this.controller?.isLastStep ?? false;
456
+ this.backEl.disabled = this.controller?.isFirstStep ?? true;
457
+ this.nextEl.hidden = isLast;
458
+ this.submitEl.hidden = !isLast;
459
+ this.submitEl.textContent = this.submitLabel;
460
+ const draftLabel = this.draftLabel;
461
+ this.draftEl.hidden = !draftLabel;
462
+ if (draftLabel) {
463
+ this.draftEl.textContent = draftLabel;
464
+ }
465
+ }
466
+ }
467
+ ProgressSteps.register();
468
+ FormWizard.register();
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export * from "./controller.js";
3
+ export * from "./form-wizard.js";
4
+ export * from "./wizard-summary.js";
@@ -0,0 +1,8 @@
1
+ // Wizard step model, validation contract, and state types
2
+ export * from "./types.js";
3
+ // Headless step/session controller
4
+ export * from "./controller.js";
5
+ // Composed workflow surface
6
+ export * from "./form-wizard.js";
7
+ // Review step: collected values grouped by step, with per-step Edit intents
8
+ export * from "./wizard-summary.js";
@@ -0,0 +1,109 @@
1
+ export interface WizardStepConfig {
2
+ /** Step id — doubles as the slot name feeding the step's panel. */
3
+ id: string;
4
+ label: string;
5
+ description?: string;
6
+ /** Optional steps can be skipped by Next without passing validation. */
7
+ optional?: boolean;
8
+ }
9
+ /** Result of validating one step against the current values. */
10
+ export interface WizardStepValidation {
11
+ valid: boolean;
12
+ /** Human-readable summary shown by the shell when the step blocks. */
13
+ message?: string;
14
+ /** Field-level errors for hosts that render per-field messaging. */
15
+ fieldErrors?: Record<string, string>;
16
+ }
17
+ export type WizardStepValidator = (values: Readonly<Record<string, unknown>>) => WizardStepValidation;
18
+ export interface WizardSessionConfig {
19
+ steps: WizardStepConfig[];
20
+ /** Per-step gate, keyed by step id. Steps without a validator always pass. */
21
+ validators?: Record<string, WizardStepValidator>;
22
+ /** Seed values (e.g. a restored draft). */
23
+ initialValues?: Record<string, unknown>;
24
+ }
25
+ export interface WizardState {
26
+ steps: WizardStepConfig[];
27
+ currentStepId: string;
28
+ currentStepIndex: number;
29
+ values: Record<string, unknown>;
30
+ /** Ids the user has reached — the rail lets them jump back to any of these. */
31
+ visitedStepIds: string[];
32
+ /** Last validation failure for the current step; null once it passes or changes. */
33
+ stepError: WizardStepValidation | null;
34
+ /** True after a successful submit(). */
35
+ submitted: boolean;
36
+ }
37
+ export interface WizardEvents {
38
+ stepChanged: {
39
+ stepId: string;
40
+ stepIndex: number;
41
+ };
42
+ valuesChanged: {
43
+ values: Record<string, unknown>;
44
+ };
45
+ /** The current step blocked navigation or submission. */
46
+ stepInvalid: {
47
+ stepId: string;
48
+ validation: WizardStepValidation;
49
+ };
50
+ draftSaved: {
51
+ values: Record<string, unknown>;
52
+ };
53
+ submitted: {
54
+ values: Record<string, unknown>;
55
+ };
56
+ }
57
+ /**
58
+ * One collected value to show on the review step. `stepId` is what makes the
59
+ * Edit link possible: the summary knows which step to send the user back to.
60
+ */
61
+ export interface WizardSummaryField {
62
+ /** Key into the wizard's `values`. */
63
+ key: string;
64
+ label: string;
65
+ /** Step this field was collected on. */
66
+ stepId: string;
67
+ /** Display override. Return an empty string to render the not-provided state. */
68
+ format?: (value: unknown) => string;
69
+ }
70
+ export interface WizardSummaryRow {
71
+ key: string;
72
+ label: string;
73
+ /** Already formatted for display; empty string when nothing was collected. */
74
+ value: string;
75
+ /** Nothing was collected — rendered as a placeholder rather than a blank line. */
76
+ empty: boolean;
77
+ }
78
+ export interface WizardSummarySection {
79
+ stepId: string;
80
+ label: string;
81
+ rows: WizardSummaryRow[];
82
+ /**
83
+ * The field named a step that is not in `steps`. Surfaced rather than
84
+ * dropped — see `summarizeWizardValues`.
85
+ */
86
+ unknownStep: boolean;
87
+ }
88
+ /**
89
+ * Default rendering for a collected value. Deliberately conservative: a
90
+ * boolean reads as Yes/No because `false` is a real answer that must not look
91
+ * like an unanswered question, and an empty array counts as nothing collected.
92
+ */
93
+ export declare const formatWizardValue: (value: unknown) => string;
94
+ /**
95
+ * Group collected values into per-step sections for a review card.
96
+ *
97
+ * Sections follow **step order**, not field order, so the summary reads back
98
+ * in the sequence the user filled it in.
99
+ *
100
+ * A field naming a step that does not exist is collected into a trailing
101
+ * section rather than dropped. Dropping would be the dangerous behaviour: the
102
+ * review step exists so someone can confirm what they are about to submit, and
103
+ * a mistyped `stepId` silently hiding an answer would let them confirm a value
104
+ * they never saw. A visibly odd extra section is a bug someone reports; a
105
+ * missing row is a bug nobody notices.
106
+ */
107
+ export declare const summarizeWizardValues: (fields: readonly WizardSummaryField[], values: Readonly<Record<string, unknown>>, steps: readonly WizardStepConfig[]) => WizardSummarySection[];
108
+ /** Attribute payloads are author input — validate every record. */
109
+ export declare const isWizardSummaryFieldRecord: (value: unknown) => value is WizardSummaryField;
@@ -0,0 +1,83 @@
1
+ const WIZARD_SUMMARY_UNKNOWN_LABEL = "Other answers";
2
+ /**
3
+ * Default rendering for a collected value. Deliberately conservative: a
4
+ * boolean reads as Yes/No because `false` is a real answer that must not look
5
+ * like an unanswered question, and an empty array counts as nothing collected.
6
+ */
7
+ export const formatWizardValue = (value) => {
8
+ if (value === null || value === undefined) {
9
+ return "";
10
+ }
11
+ if (typeof value === "boolean") {
12
+ return value ? "Yes" : "No";
13
+ }
14
+ if (typeof value === "number") {
15
+ return Number.isFinite(value) ? String(value) : "";
16
+ }
17
+ if (Array.isArray(value)) {
18
+ return value.map(entry => formatWizardValue(entry)).filter(Boolean).join(", ");
19
+ }
20
+ if (value instanceof Date) {
21
+ return Number.isNaN(value.getTime()) ? "" : value.toISOString().slice(0, 10);
22
+ }
23
+ if (typeof value === "object") {
24
+ return "";
25
+ }
26
+ return String(value);
27
+ };
28
+ /**
29
+ * Group collected values into per-step sections for a review card.
30
+ *
31
+ * Sections follow **step order**, not field order, so the summary reads back
32
+ * in the sequence the user filled it in.
33
+ *
34
+ * A field naming a step that does not exist is collected into a trailing
35
+ * section rather than dropped. Dropping would be the dangerous behaviour: the
36
+ * review step exists so someone can confirm what they are about to submit, and
37
+ * a mistyped `stepId` silently hiding an answer would let them confirm a value
38
+ * they never saw. A visibly odd extra section is a bug someone reports; a
39
+ * missing row is a bug nobody notices.
40
+ */
41
+ export const summarizeWizardValues = (fields, values, steps) => {
42
+ const order = new Map(steps.map((step, index) => [step.id, index]));
43
+ const sections = new Map();
44
+ for (const field of fields) {
45
+ const known = order.has(field.stepId);
46
+ let section = sections.get(field.stepId);
47
+ if (!section) {
48
+ section = {
49
+ stepId: field.stepId,
50
+ label: known
51
+ ? (steps[order.get(field.stepId)]?.label ?? field.stepId)
52
+ : WIZARD_SUMMARY_UNKNOWN_LABEL,
53
+ rows: [],
54
+ unknownStep: !known,
55
+ };
56
+ sections.set(field.stepId, section);
57
+ }
58
+ const raw = values[field.key];
59
+ const formatted = field.format ? field.format(raw) : formatWizardValue(raw);
60
+ section.rows.push({
61
+ key: field.key,
62
+ label: field.label,
63
+ value: formatted,
64
+ empty: formatted.length === 0,
65
+ });
66
+ }
67
+ const known = [...sections.values()].filter(section => !section.unknownStep);
68
+ known.sort((left, right) => (order.get(left.stepId) ?? 0) - (order.get(right.stepId) ?? 0));
69
+ return [...known, ...[...sections.values()].filter(section => section.unknownStep)];
70
+ };
71
+ /** Attribute payloads are author input — validate every record. */
72
+ export const isWizardSummaryFieldRecord = (value) => {
73
+ if (typeof value !== "object" || value === null) {
74
+ return false;
75
+ }
76
+ const field = value;
77
+ return (typeof field.key === "string" &&
78
+ field.key.length > 0 &&
79
+ typeof field.label === "string" &&
80
+ field.label.length > 0 &&
81
+ typeof field.stepId === "string" &&
82
+ field.stepId.length > 0);
83
+ };