@zeyos/zx 2.1.0 → 2.2.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 (311) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +15 -0
  3. package/dist/types/compat/base.d.ts +156 -0
  4. package/dist/types/compat/globals.d.ts +20 -0
  5. package/dist/types/compat/index.d.ts +128 -0
  6. package/dist/types/compat/map/basic.d.ts +175 -0
  7. package/dist/types/compat/map/data.d.ts +102 -0
  8. package/dist/types/compat/map/date-time.d.ts +80 -0
  9. package/dist/types/compat/map/factory.d.ts +9 -0
  10. package/dist/types/compat/map/form.d.ts +98 -0
  11. package/dist/types/compat/map/helpers.d.ts +12 -0
  12. package/dist/types/compat/map/http.d.ts +36 -0
  13. package/dist/types/compat/map/layout.d.ts +106 -0
  14. package/dist/types/compat/map/options.d.ts +80 -0
  15. package/dist/types/compat/map/select.d.ts +95 -0
  16. package/dist/types/compat/map/stubs.d.ts +32 -0
  17. package/dist/types/compat/map/utilities.d.ts +37 -0
  18. package/dist/types/compat-entry.d.ts +5 -0
  19. package/dist/types/components/badge/badge.d.ts +46 -0
  20. package/dist/types/components/breadcrumb/breadcrumb.d.ts +200 -0
  21. package/dist/types/components/button/button.d.ts +42 -0
  22. package/dist/types/components/check-button/check-button.d.ts +98 -0
  23. package/dist/types/components/checklist/checklist.d.ts +143 -0
  24. package/dist/types/components/checklist/field-adapter.d.ts +5 -0
  25. package/dist/types/components/context-menu/context-menu.d.ts +130 -0
  26. package/dist/types/components/copy/copy-button.d.ts +48 -0
  27. package/dist/types/components/copy/copy-input.d.ts +91 -0
  28. package/dist/types/components/data-filter/data-filter.d.ts +250 -0
  29. package/dist/types/components/data-filter/filter-core.d.ts +52 -0
  30. package/dist/types/components/date-picker/date-picker.d.ts +137 -0
  31. package/dist/types/components/date-picker/date-range-picker.d.ts +350 -0
  32. package/dist/types/components/date-picker/field-adapter.d.ts +6 -0
  33. package/dist/types/components/date-picker/month-picker.d.ts +75 -0
  34. package/dist/types/components/date-picker/range-field-adapter.d.ts +7 -0
  35. package/dist/types/components/date-picker/time-picker.d.ts +127 -0
  36. package/dist/types/components/datebox/date-range-box.d.ts +218 -0
  37. package/dist/types/components/datebox/datebox.d.ts +160 -0
  38. package/dist/types/components/datebox/field-adapter.d.ts +5 -0
  39. package/dist/types/components/dialog/dialog.d.ts +284 -0
  40. package/dist/types/components/dropdown/dropdown.d.ts +118 -0
  41. package/dist/types/components/empty-state/empty-state.d.ts +38 -0
  42. package/dist/types/components/field/field.d.ts +231 -0
  43. package/dist/types/components/field-upload/field-adapter.d.ts +5 -0
  44. package/dist/types/components/field-upload/field-upload.d.ts +213 -0
  45. package/dist/types/components/fieldset/fieldset.d.ts +125 -0
  46. package/dist/types/components/finder/finder.d.ts +252 -0
  47. package/dist/types/components/form/form.d.ts +194 -0
  48. package/dist/types/components/groupbox/groupbox.d.ts +89 -0
  49. package/dist/types/components/layout/layout.d.ts +132 -0
  50. package/dist/types/components/loading/inline-loading.d.ts +86 -0
  51. package/dist/types/components/loading/progress-bar.d.ts +168 -0
  52. package/dist/types/components/loading/spinner.d.ts +34 -0
  53. package/dist/types/components/master-panel/master-panel.d.ts +112 -0
  54. package/dist/types/components/menu-button/menu-button.d.ts +142 -0
  55. package/dist/types/components/message/message.d.ts +212 -0
  56. package/dist/types/components/modal/modal.d.ts +105 -0
  57. package/dist/types/components/multi-value-editor/field-adapter.d.ts +5 -0
  58. package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +66 -0
  59. package/dist/types/components/navigation-bar/navigation-bar.d.ts +229 -0
  60. package/dist/types/components/number-field/field-adapter.d.ts +5 -0
  61. package/dist/types/components/number-field/number-field.d.ts +292 -0
  62. package/dist/types/components/pagination/pagination.d.ts +267 -0
  63. package/dist/types/components/panel/panel.d.ts +194 -0
  64. package/dist/types/components/permission/permission.d.ts +93 -0
  65. package/dist/types/components/rating/field-adapter.d.ts +5 -0
  66. package/dist/types/components/rating/rating.d.ts +213 -0
  67. package/dist/types/components/search/search.d.ts +98 -0
  68. package/dist/types/components/select/field-adapter.d.ts +5 -0
  69. package/dist/types/components/select/filter.d.ts +11 -0
  70. package/dist/types/components/select/select.d.ts +235 -0
  71. package/dist/types/components/skeleton/skeleton.d.ts +97 -0
  72. package/dist/types/components/slider/field-adapter.d.ts +5 -0
  73. package/dist/types/components/slider/slider.d.ts +216 -0
  74. package/dist/types/components/split-view/split-view.d.ts +335 -0
  75. package/dist/types/components/stepper/stepper.d.ts +314 -0
  76. package/dist/types/components/stepper/steps.d.ts +131 -0
  77. package/dist/types/components/tabbox/tabbox.d.ts +230 -0
  78. package/dist/types/components/table/sort.d.ts +49 -0
  79. package/dist/types/components/table/table.d.ts +872 -0
  80. package/dist/types/components/tag-picker/field-adapter.d.ts +5 -0
  81. package/dist/types/components/tag-picker/tag-picker.d.ts +375 -0
  82. package/dist/types/components/timebox/field-adapter.d.ts +5 -0
  83. package/dist/types/components/timebox/timebox.d.ts +121 -0
  84. package/dist/types/components/toggle/field-adapter.d.ts +5 -0
  85. package/dist/types/components/toggle/toggle.d.ts +94 -0
  86. package/dist/types/components/toolbar/toolbar.d.ts +230 -0
  87. package/dist/types/components/tooltip/tooltip.d.ts +183 -0
  88. package/dist/types/components/tree/hierarchy.d.ts +113 -0
  89. package/dist/types/components/tree/tree.d.ts +383 -0
  90. package/dist/types/components/truncate/truncate.d.ts +61 -0
  91. package/dist/types/components/value-list/field-adapter.d.ts +5 -0
  92. package/dist/types/components/value-list/value-list.d.ts +143 -0
  93. package/dist/types/core/breakpoint.d.ts +68 -0
  94. package/dist/types/core/component.d.ts +115 -0
  95. package/dist/types/core/date.d.ts +50 -0
  96. package/dist/types/core/dom.d.ts +86 -0
  97. package/dist/types/core/export.d.ts +70 -0
  98. package/dist/types/core/fontawesome.d.ts +107 -0
  99. package/dist/types/core/format.d.ts +107 -0
  100. package/dist/types/core/http.d.ts +122 -0
  101. package/dist/types/core/i18n.d.ts +35 -0
  102. package/dist/types/core/icons.d.ts +137 -0
  103. package/dist/types/core/keyboard.d.ts +72 -0
  104. package/dist/types/core/position.d.ts +45 -0
  105. package/dist/types/core/storage.d.ts +46 -0
  106. package/dist/types/core/util.d.ts +117 -0
  107. package/dist/types/elements/define.d.ts +73 -0
  108. package/dist/types/elements/reflect.d.ts +27 -0
  109. package/dist/types/index.d.ts +79 -0
  110. package/dist/types/zeyos/connect.d.ts +65 -0
  111. package/dist/types/zeyos/form.d.ts +140 -0
  112. package/dist/types/zeyos/icons.d.ts +104 -0
  113. package/dist/types/zeyos/index.d.ts +12 -0
  114. package/dist/types/zeyos/modules.d.ts +110 -0
  115. package/dist/types/zeyos/query.d.ts +157 -0
  116. package/dist/types/zeyos/schema.d.ts +79 -0
  117. package/dist/types/zeyos/select.d.ts +167 -0
  118. package/dist/types/zeyos/table.d.ts +205 -0
  119. package/dist/zx-compat.esm.js +362 -33
  120. package/dist/zx-compat.esm.min.js +1 -1
  121. package/dist/zx-compat.global.js +359 -67
  122. package/dist/zx-compat.global.min.js +3 -3
  123. package/dist/zx-zeyos.esm.js +342 -43
  124. package/dist/zx-zeyos.esm.min.js +1 -1
  125. package/dist/zx.css +68 -6
  126. package/dist/zx.esm.js +346 -67
  127. package/dist/zx.esm.min.js +3 -3
  128. package/dist/zx.global.js +346 -67
  129. package/dist/zx.global.min.js +3 -3
  130. package/dist/zx.min.css +1 -1
  131. package/docs/api.json +48 -2
  132. package/docs/llms.md +28 -0
  133. package/package.json +37 -9
  134. package/src/compat/base.js +291 -0
  135. package/src/compat/globals.js +165 -0
  136. package/src/compat/index.js +84 -0
  137. package/src/compat/map/basic.js +464 -0
  138. package/src/compat/map/data.js +232 -0
  139. package/src/compat/map/date-time.js +177 -0
  140. package/src/compat/map/factory.js +45 -0
  141. package/src/compat/map/form.js +330 -0
  142. package/src/compat/map/helpers.js +46 -0
  143. package/src/compat/map/http.js +117 -0
  144. package/src/compat/map/layout.js +256 -0
  145. package/src/compat/map/options.js +267 -0
  146. package/src/compat/map/select.js +198 -0
  147. package/src/compat/map/stubs.js +26 -0
  148. package/src/compat/map/utilities.js +113 -0
  149. package/src/compat-entry.js +11 -0
  150. package/src/components/badge/badge.css +147 -0
  151. package/src/components/badge/badge.js +92 -0
  152. package/src/components/breadcrumb/breadcrumb.css +105 -0
  153. package/src/components/breadcrumb/breadcrumb.js +331 -0
  154. package/src/components/button/button.css +104 -0
  155. package/src/components/button/button.js +76 -0
  156. package/src/components/check-button/check-button.css +66 -0
  157. package/src/components/check-button/check-button.js +167 -0
  158. package/src/components/checklist/checklist.css +65 -0
  159. package/src/components/checklist/checklist.js +282 -0
  160. package/src/components/checklist/field-adapter.js +46 -0
  161. package/src/components/context-menu/context-menu.css +54 -0
  162. package/src/components/context-menu/context-menu.js +334 -0
  163. package/src/components/copy/copy-button.css +27 -0
  164. package/src/components/copy/copy-button.js +78 -0
  165. package/src/components/copy/copy-input.css +59 -0
  166. package/src/components/copy/copy-input.js +123 -0
  167. package/src/components/data-filter/data-filter.css +53 -0
  168. package/src/components/data-filter/data-filter.js +400 -0
  169. package/src/components/data-filter/filter-core.js +128 -0
  170. package/src/components/date-picker/date-picker.css +181 -0
  171. package/src/components/date-picker/date-picker.js +500 -0
  172. package/src/components/date-picker/date-range-picker.css +264 -0
  173. package/src/components/date-picker/date-range-picker.js +990 -0
  174. package/src/components/date-picker/field-adapter.js +36 -0
  175. package/src/components/date-picker/month-picker.js +223 -0
  176. package/src/components/date-picker/range-field-adapter.js +31 -0
  177. package/src/components/date-picker/time-picker.css +191 -0
  178. package/src/components/date-picker/time-picker.js +406 -0
  179. package/src/components/datebox/date-range-box.css +128 -0
  180. package/src/components/datebox/date-range-box.js +492 -0
  181. package/src/components/datebox/datebox.css +108 -0
  182. package/src/components/datebox/datebox.js +358 -0
  183. package/src/components/datebox/field-adapter.js +34 -0
  184. package/src/components/dialog/dialog.css +74 -0
  185. package/src/components/dialog/dialog.js +450 -0
  186. package/src/components/dropdown/dropdown.css +24 -0
  187. package/src/components/dropdown/dropdown.js +288 -0
  188. package/src/components/empty-state/empty-state.css +80 -0
  189. package/src/components/empty-state/empty-state.js +92 -0
  190. package/src/components/field/field.css +127 -0
  191. package/src/components/field/field.js +605 -0
  192. package/src/components/field-upload/field-adapter.js +21 -0
  193. package/src/components/field-upload/field-upload.css +108 -0
  194. package/src/components/field-upload/field-upload.js +475 -0
  195. package/src/components/fieldset/fieldset.css +37 -0
  196. package/src/components/fieldset/fieldset.js +307 -0
  197. package/src/components/finder/finder.css +98 -0
  198. package/src/components/finder/finder.js +509 -0
  199. package/src/components/form/form.css +32 -0
  200. package/src/components/form/form.js +302 -0
  201. package/src/components/groupbox/groupbox.css +51 -0
  202. package/src/components/groupbox/groupbox.js +199 -0
  203. package/src/components/layout/layout.css +117 -0
  204. package/src/components/layout/layout.js +136 -0
  205. package/src/components/loading/inline-loading.css +34 -0
  206. package/src/components/loading/inline-loading.js +140 -0
  207. package/src/components/loading/progress-bar.css +115 -0
  208. package/src/components/loading/progress-bar.js +259 -0
  209. package/src/components/loading/spinner.css +58 -0
  210. package/src/components/loading/spinner.js +57 -0
  211. package/src/components/master-panel/master-panel.css +51 -0
  212. package/src/components/master-panel/master-panel.js +203 -0
  213. package/src/components/menu-button/menu-button.css +49 -0
  214. package/src/components/menu-button/menu-button.js +294 -0
  215. package/src/components/message/message.css +122 -0
  216. package/src/components/message/message.js +499 -0
  217. package/src/components/modal/modal.css +47 -0
  218. package/src/components/modal/modal.js +203 -0
  219. package/src/components/multi-value-editor/field-adapter.js +44 -0
  220. package/src/components/multi-value-editor/multi-value-editor.css +56 -0
  221. package/src/components/multi-value-editor/multi-value-editor.js +170 -0
  222. package/src/components/navigation-bar/navigation-bar.css +130 -0
  223. package/src/components/navigation-bar/navigation-bar.js +450 -0
  224. package/src/components/number-field/field-adapter.js +40 -0
  225. package/src/components/number-field/number-field.css +77 -0
  226. package/src/components/number-field/number-field.js +545 -0
  227. package/src/components/pagination/pagination.css +95 -0
  228. package/src/components/pagination/pagination.js +481 -0
  229. package/src/components/panel/panel.css +123 -0
  230. package/src/components/panel/panel.js +336 -0
  231. package/src/components/permission/permission.css +47 -0
  232. package/src/components/permission/permission.js +198 -0
  233. package/src/components/rating/field-adapter.js +34 -0
  234. package/src/components/rating/rating.css +97 -0
  235. package/src/components/rating/rating.js +395 -0
  236. package/src/components/search/search.css +70 -0
  237. package/src/components/search/search.js +193 -0
  238. package/src/components/select/field-adapter.js +24 -0
  239. package/src/components/select/filter.js +39 -0
  240. package/src/components/select/select.css +180 -0
  241. package/src/components/select/select.js +663 -0
  242. package/src/components/skeleton/skeleton.css +76 -0
  243. package/src/components/skeleton/skeleton.js +139 -0
  244. package/src/components/slider/field-adapter.js +36 -0
  245. package/src/components/slider/slider.css +181 -0
  246. package/src/components/slider/slider.js +399 -0
  247. package/src/components/split-view/split-view.css +139 -0
  248. package/src/components/split-view/split-view.js +806 -0
  249. package/src/components/stepper/stepper.css +234 -0
  250. package/src/components/stepper/stepper.js +459 -0
  251. package/src/components/stepper/steps.js +131 -0
  252. package/src/components/tabbox/tabbox.css +281 -0
  253. package/src/components/tabbox/tabbox.js +484 -0
  254. package/src/components/table/sort.js +85 -0
  255. package/src/components/table/table.css +364 -0
  256. package/src/components/table/table.js +1693 -0
  257. package/src/components/tag-picker/field-adapter.js +35 -0
  258. package/src/components/tag-picker/tag-picker.css +169 -0
  259. package/src/components/tag-picker/tag-picker.js +837 -0
  260. package/src/components/timebox/field-adapter.js +24 -0
  261. package/src/components/timebox/timebox.css +51 -0
  262. package/src/components/timebox/timebox.js +276 -0
  263. package/src/components/toggle/field-adapter.js +24 -0
  264. package/src/components/toggle/toggle.css +66 -0
  265. package/src/components/toggle/toggle.js +150 -0
  266. package/src/components/toolbar/toolbar.css +81 -0
  267. package/src/components/toolbar/toolbar.js +511 -0
  268. package/src/components/tooltip/tooltip.css +36 -0
  269. package/src/components/tooltip/tooltip.js +420 -0
  270. package/src/components/tree/hierarchy.js +151 -0
  271. package/src/components/tree/tree.css +99 -0
  272. package/src/components/tree/tree.js +784 -0
  273. package/src/components/truncate/truncate.css +20 -0
  274. package/src/components/truncate/truncate.js +92 -0
  275. package/src/components/value-list/field-adapter.js +24 -0
  276. package/src/components/value-list/value-list.css +108 -0
  277. package/src/components/value-list/value-list.js +336 -0
  278. package/src/core/breakpoint.js +150 -0
  279. package/src/core/component.js +292 -0
  280. package/src/core/date.js +216 -0
  281. package/src/core/dom.js +260 -0
  282. package/src/core/export.js +204 -0
  283. package/src/core/fontawesome.js +177 -0
  284. package/src/core/format.js +283 -0
  285. package/src/core/http.js +246 -0
  286. package/src/core/i18n.js +83 -0
  287. package/src/core/icons.js +283 -0
  288. package/src/core/keyboard.js +240 -0
  289. package/src/core/position.js +177 -0
  290. package/src/core/storage.js +175 -0
  291. package/src/core/util.js +336 -0
  292. package/src/elements/define.js +715 -0
  293. package/src/elements/reflect.js +79 -0
  294. package/src/index.js +131 -0
  295. package/src/zeyos/connect.js +142 -0
  296. package/src/zeyos/form.js +254 -0
  297. package/src/zeyos/icons.js +92 -0
  298. package/src/zeyos/index.js +21 -0
  299. package/src/zeyos/modules.js +269 -0
  300. package/src/zeyos/query.js +199 -0
  301. package/src/zeyos/schema.js +474 -0
  302. package/src/zeyos/select.js +175 -0
  303. package/src/zeyos/table.js +272 -0
  304. package/styles/base.css +124 -0
  305. package/styles/icons.css +34 -0
  306. package/styles/tokens/dark.css +84 -0
  307. package/styles/tokens/density.css +9 -0
  308. package/styles/tokens/global.css +80 -0
  309. package/styles/tokens/modules.css +109 -0
  310. package/styles/tokens/semantic.css +49 -0
  311. package/styles/zx.css +61 -0
@@ -0,0 +1,234 @@
1
+ /*
2
+ * Stepper — the wizard progress rail promoted out of website/docs.css.
3
+ *
4
+ * One `<li>` per step, each carrying `data-state`; the marker bubble and the connector to the next
5
+ * step are the only things that change colour, so a rail reads at a glance in both themes:
6
+ *
7
+ * upcoming outlined bubble, muted text
8
+ * active accent ring on a subtle accent fill, full-strength title
9
+ * complete solid accent bubble with a check, accent connector behind it
10
+ * error danger ring on a danger fill with a warning glyph
11
+ */
12
+
13
+ .zx-stepper {
14
+ /* Bubble diameter. The vertical connector is positioned from it, so it is a variable. */
15
+ --zx-stepper-marker: 26px;
16
+ display: grid;
17
+ gap: var(--zx-space-3);
18
+ min-inline-size: 0;
19
+ }
20
+
21
+ .zx-stepper__counter {
22
+ margin: 0;
23
+ color: var(--zx-color-text-muted);
24
+ font-size: var(--zx-text-sm);
25
+ font-variant-numeric: tabular-nums;
26
+ }
27
+
28
+ .zx-stepper__list {
29
+ display: flex;
30
+ margin: 0;
31
+ padding: 0;
32
+ list-style: none;
33
+ }
34
+
35
+ .zx-stepper__step {
36
+ position: relative;
37
+ display: flex;
38
+ align-items: center;
39
+ min-inline-size: 0;
40
+ }
41
+
42
+ /* The whole step is one control: a button when the rail is clickable, an inert span otherwise.
43
+ Both share the layout so the two modes are pixel-identical. */
44
+ .zx-stepper__control {
45
+ display: flex;
46
+ flex: 1 1 auto;
47
+ align-items: center;
48
+ gap: var(--zx-space-3);
49
+ min-inline-size: 0;
50
+ min-block-size: 0;
51
+ border: 0;
52
+ border-radius: var(--zx-radius-lg);
53
+ background: none;
54
+ padding: var(--zx-space-2);
55
+ color: inherit;
56
+ font: inherit;
57
+ text-align: start;
58
+ }
59
+
60
+ button.zx-stepper__control:not(:disabled) {
61
+ cursor: pointer;
62
+ }
63
+
64
+ button.zx-stepper__control:not(:disabled):hover {
65
+ background: var(--zx-color-bg-hover);
66
+ }
67
+
68
+ button.zx-stepper__control:disabled {
69
+ cursor: default;
70
+ }
71
+
72
+ .zx-stepper__control:focus-visible {
73
+ outline: 0;
74
+ box-shadow: var(--zx-focus-ring);
75
+ }
76
+
77
+ /* ------------------------------------------------------------------------ marker -- */
78
+
79
+ .zx-stepper__marker {
80
+ display: grid;
81
+ place-items: center;
82
+ flex: none;
83
+ inline-size: var(--zx-stepper-marker);
84
+ block-size: var(--zx-stepper-marker);
85
+ border: 1px solid var(--zx-color-border-strong);
86
+ border-radius: var(--zx-radius-full);
87
+ color: var(--zx-color-text-muted);
88
+ font-size: var(--zx-text-xs);
89
+ font-weight: 600;
90
+ font-variant-numeric: tabular-nums;
91
+ line-height: 1;
92
+ }
93
+
94
+ .zx-stepper__step[data-state="active"] .zx-stepper__marker {
95
+ border-color: var(--zx-color-accent);
96
+ background: var(--zx-color-accent-subtle);
97
+ color: var(--zx-color-accent);
98
+ }
99
+
100
+ .zx-stepper__step[data-state="complete"] .zx-stepper__marker {
101
+ border-color: var(--zx-color-accent);
102
+ background: var(--zx-color-accent);
103
+ color: var(--zx-color-on-accent);
104
+ }
105
+
106
+ .zx-stepper__step[data-state="error"] .zx-stepper__marker {
107
+ border-color: var(--zx-color-danger);
108
+ background: var(--zx-color-danger-bg);
109
+ color: var(--zx-color-danger);
110
+ }
111
+
112
+ /* -------------------------------------------------------------------------- text -- */
113
+
114
+ .zx-stepper__text {
115
+ display: grid;
116
+ gap: 2px;
117
+ min-inline-size: 0;
118
+ }
119
+
120
+ .zx-stepper__title {
121
+ color: var(--zx-color-text-muted);
122
+ font-size: var(--zx-control-font-size);
123
+ font-weight: 500;
124
+ overflow-wrap: anywhere;
125
+ }
126
+
127
+ .zx-stepper__step[data-state="active"] .zx-stepper__title,
128
+ .zx-stepper__step[data-state="complete"] .zx-stepper__title {
129
+ color: var(--zx-color-text);
130
+ }
131
+
132
+ .zx-stepper__step[data-state="error"] .zx-stepper__title {
133
+ color: var(--zx-color-danger);
134
+ }
135
+
136
+ /* The step the user stands on stays the heaviest label even when it is also in error. */
137
+ .zx-stepper__step[aria-current="step"] .zx-stepper__title {
138
+ font-weight: 600;
139
+ }
140
+
141
+ .zx-stepper__description {
142
+ color: var(--zx-color-text-muted);
143
+ font-size: var(--zx-text-xs);
144
+ overflow-wrap: anywhere;
145
+ }
146
+
147
+ .zx-stepper__optional {
148
+ color: var(--zx-color-text-placeholder);
149
+ font-size: var(--zx-text-xs);
150
+ font-style: italic;
151
+ }
152
+
153
+ .zx-stepper__step[data-disabled] {
154
+ opacity: 0.55;
155
+ }
156
+
157
+ /* State text for assistive technology; the marker itself is aria-hidden decoration. */
158
+ .zx-stepper__status {
159
+ position: absolute;
160
+ inline-size: 1px;
161
+ block-size: 1px;
162
+ margin: -1px;
163
+ border: 0;
164
+ padding: 0;
165
+ overflow: hidden;
166
+ clip-path: inset(50%);
167
+ white-space: nowrap;
168
+ }
169
+
170
+ /* -------------------------------------------------------------------- horizontal -- */
171
+
172
+ .zx-stepper[data-orientation="horizontal"] .zx-stepper__list {
173
+ flex-wrap: wrap;
174
+ align-items: center;
175
+ gap: var(--zx-space-2);
176
+ }
177
+
178
+ .zx-stepper[data-orientation="horizontal"] .zx-stepper__step {
179
+ flex: 0 1 auto;
180
+ gap: var(--zx-space-2);
181
+ }
182
+
183
+ /* The connector doubles as the flexible gap, so the steps size to their labels and the rules
184
+ absorb whatever is left over. */
185
+ .zx-stepper[data-orientation="horizontal"] .zx-stepper__step:not(:last-child)::after {
186
+ content: "";
187
+ flex: 1 1 var(--zx-space-6);
188
+ min-inline-size: var(--zx-space-4);
189
+ block-size: 2px;
190
+ border-radius: var(--zx-radius-full);
191
+ background: var(--zx-color-border);
192
+ }
193
+
194
+ /* ---------------------------------------------------------------------- vertical -- */
195
+
196
+ .zx-stepper[data-orientation="vertical"] .zx-stepper__list {
197
+ flex-direction: column;
198
+ align-items: stretch;
199
+ }
200
+
201
+ .zx-stepper[data-orientation="vertical"] .zx-stepper__step {
202
+ padding-block-end: var(--zx-space-5);
203
+ }
204
+
205
+ .zx-stepper[data-orientation="vertical"] .zx-stepper__step:last-child {
206
+ padding-block-end: 0;
207
+ }
208
+
209
+ /* Drawn from the marker's centre line: the control's own padding plus half a bubble. */
210
+ .zx-stepper[data-orientation="vertical"] .zx-stepper__step:not(:last-child)::after {
211
+ content: "";
212
+ position: absolute;
213
+ inset-block: calc(var(--zx-space-2) + var(--zx-stepper-marker) + var(--zx-space-1)) var(--zx-space-1);
214
+ inset-inline-start: calc(var(--zx-space-2) + var(--zx-stepper-marker) / 2 - 1px);
215
+ inline-size: 2px;
216
+ border-radius: var(--zx-radius-full);
217
+ background: var(--zx-color-border);
218
+ }
219
+
220
+ /* A completed step has been walked through, so the rule leading out of it is walked too. */
221
+ .zx-stepper__step[data-state="complete"]::after {
222
+ background: var(--zx-color-accent);
223
+ }
224
+
225
+ @media (prefers-reduced-motion: no-preference) {
226
+ .zx-stepper__control,
227
+ .zx-stepper__marker,
228
+ .zx-stepper__title,
229
+ .zx-stepper__step::after {
230
+ transition: color var(--zx-dur-fast) var(--zx-ease),
231
+ background-color var(--zx-dur-fast) var(--zx-ease),
232
+ border-color var(--zx-dur-fast) var(--zx-ease);
233
+ }
234
+ }
@@ -0,0 +1,459 @@
1
+ import { Component } from '../../core/component.js';
2
+ import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
3
+ import { printf } from '../../core/i18n.js';
4
+ import { icon } from '../../core/icons.js';
5
+ import { completedByAdvance, normalizeSteps, resolveStepIndex, stepIndex, stepState } from './steps.js';
6
+
7
+ /** @typedef {import('./steps.js').StepDefinition} StepDefinition */
8
+ /** @typedef {import('./steps.js').NormalizedStep} NormalizedStep */
9
+
10
+ /** @typedef {'horizontal'|'vertical'} StepperOrientation */
11
+ /** @typedef {'completed'|'all'|false} StepperClickable */
12
+ /** @typedef {'upcoming'|'active'|'complete'|'error'} StepperStepState */
13
+
14
+ /** Layouts the rail supports. */
15
+ const ORIENTATIONS = new Set(['horizontal', 'vertical']);
16
+ /** Values `clickable` accepts. */
17
+ const CLICKABLE = new Set(['completed', 'all', false]);
18
+
19
+ /**
20
+ * @typedef {Object} StepperOptions
21
+ * @property {StepDefinition[]} [steps=[]] Ordered step definitions.
22
+ * @property {string|null} [active=null] Initially active step name; defaults to the first enabled step.
23
+ * @property {StepperOrientation} [orientation='horizontal'] `horizontal` lays the rail out above the
24
+ * stage, `vertical` puts it beside the stage.
25
+ * @property {StepperClickable} [clickable='completed'] Which steps are activated by a click:
26
+ * `completed` (go back only), `all`, or `false` for a display-only rail.
27
+ * @property {boolean} [showNumbers=true] Whether upcoming and active markers show their number.
28
+ * @property {boolean} [counter=false] Whether to render a "Step 2 of 4" line above the rail.
29
+ * @property {Record<string, string>|Record<string, Record<string, string>>} [msg] Localized messages.
30
+ * @property {(event: CustomEvent<StepperChangeDetail>) => void} [onchange] Change listener.
31
+ */
32
+
33
+ /**
34
+ * @typedef {Object} StepperChangeDetail
35
+ * @property {string} name Name of the step being activated.
36
+ * @property {string|null} previous Name of the step being left, or null.
37
+ * @property {number} index Zero-based index of the step being activated.
38
+ */
39
+
40
+ /**
41
+ * @typedef {Object} StepperState
42
+ * @property {string|null} active Active step name.
43
+ * @property {number} index Zero-based active index, or -1 when nothing is active.
44
+ * @property {string[]} completed Completed step names, in step order.
45
+ * @property {string[]} errored Errored step names, in step order.
46
+ */
47
+
48
+ /**
49
+ * @typedef {Object} StepRecord
50
+ * @property {NormalizedStep} step Normalized definition.
51
+ * @property {HTMLLIElement} item List item carrying `data-state` and `aria-current`.
52
+ * @property {HTMLElement} control Button (clickable rails) or span (display-only rails).
53
+ * @property {HTMLElement} marker Number / check / warning bubble.
54
+ * @property {HTMLElement} status Visually hidden state text for assistive technology.
55
+ */
56
+
57
+ /**
58
+ * A wizard progress rail: an ordered list of steps, each in one of four states, with the active
59
+ * step marked `aria-current="step"`. Advancing past a step marks it complete automatically, so a
60
+ * wizard only has to call `next()` / `previous()` and veto `change` when validation fails.
61
+ *
62
+ * The rail owns no stage: it reports where the user is and where they asked to go. Rendering the
63
+ * step's content stays with the application, exactly as in the checkout and record wizards.
64
+ * @fires Stepper#change
65
+ * @extends {Component<StepperOptions>}
66
+ */
67
+ export class Stepper extends Component {
68
+ static cssName = 'stepper';
69
+
70
+ /** @type {Readonly<StepperOptions>} */
71
+ static defaults = {
72
+ steps: [],
73
+ active: null,
74
+ orientation: 'horizontal',
75
+ clickable: 'completed',
76
+ showNumbers: true,
77
+ counter: false
78
+ };
79
+
80
+ /**
81
+ * Creates or enhances a stepper.
82
+ * @param {Element|string|null} target Existing container, selector, or null.
83
+ * @param {StepperOptions} [options={}] Stepper options.
84
+ */
85
+ constructor(target, options = {}) {
86
+ super(target, options);
87
+ }
88
+
89
+ /**
90
+ * Builds the rail. Runs inside the base constructor, so every piece of instance state it needs
91
+ * is initialized here rather than in class fields.
92
+ * @returns {HTMLElement}
93
+ */
94
+ render() {
95
+ this._createdRoot = this.el === null;
96
+ this._snapshot = this._createdRoot ? null : snapshotTarget(this.el);
97
+ this._destroyed = false;
98
+ /** @type {NormalizedStep[]} */
99
+ this._steps = [];
100
+ /** @type {StepRecord[]} */
101
+ this._records = [];
102
+ this._activeIndex = -1;
103
+ /** @type {Set<string>} */
104
+ this._completed = new Set();
105
+ /** @type {Set<string>} */
106
+ this._errored = new Set();
107
+
108
+ if (!ORIENTATIONS.has(this.options.orientation)) {
109
+ throw new RangeError(`Unknown stepper orientation: ${this.options.orientation}`);
110
+ }
111
+ if (!CLICKABLE.has(this.options.clickable)) {
112
+ throw new RangeError(`Unknown stepper clickable mode: ${this.options.clickable}`);
113
+ }
114
+
115
+ const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
116
+ this.el = root;
117
+ root.dataset.orientation = this.options.orientation;
118
+
119
+ const counter = h('p', {
120
+ class: 'zx-stepper__counter',
121
+ ref: 'counter',
122
+ ariaLive: 'polite',
123
+ hidden: true
124
+ });
125
+ const list = h('ol', { class: 'zx-stepper__list', ref: 'list' });
126
+ root.replaceChildren(...(this.options.counter ? [counter, list] : [list]));
127
+
128
+ this.setSteps(this.options.steps);
129
+
130
+ this.listen(list, 'click', (event) => {
131
+ const control = /** @type {Element} */ (event.target).closest?.('.zx-stepper__control');
132
+ if (!control || !list.contains(control) || control.tagName !== 'BUTTON') return;
133
+ const name = /** @type {HTMLElement} */ (control).dataset.step;
134
+ if (typeof name === 'string') this.goTo(name);
135
+ });
136
+ return root;
137
+ }
138
+
139
+ /**
140
+ * Replaces the whole step list. Completion and error flags survive for steps that are still
141
+ * present, and so does the active step; when it is gone the first enabled step takes over. This
142
+ * is a structural change, so it never emits `change`.
143
+ * @param {StepDefinition[]} list Step definitions.
144
+ * @returns {this}
145
+ */
146
+ setSteps(list) {
147
+ const previous = this.getActive();
148
+ const steps = normalizeSteps(list);
149
+ const names = new Set(steps.map((step) => step.name));
150
+ this._steps = steps;
151
+ this._completed = new Set([...this._completed].filter((name) => names.has(name)));
152
+ this._errored = new Set([...this._errored].filter((name) => names.has(name)));
153
+
154
+ const wanted = previous ?? (this.options.active === null ? null : String(this.options.active));
155
+ let index = wanted === null ? -1 : stepIndex(steps, wanted);
156
+ if (index >= 0 && steps[index].disabled) index = -1;
157
+ if (index < 0) index = resolveStepIndex(steps, -1, 1);
158
+ this._activeIndex = index;
159
+
160
+ this._records = steps.map((step) => this._createRecord(step));
161
+ this.refs.list.replaceChildren(...this._records.map((record) => record.item));
162
+ this._sync();
163
+ return this;
164
+ }
165
+
166
+ /**
167
+ * Activates a step unless its `change` event is vetoed. Disabled steps are ignored.
168
+ * @param {string} name Step name.
169
+ * @returns {this}
170
+ * @throws {RangeError} When no step carries that name.
171
+ * @fires Stepper#change
172
+ */
173
+ goTo(name) {
174
+ const index = stepIndex(this._steps, name);
175
+ if (index < 0) throw new RangeError(`Unknown step: ${name}`);
176
+ return this._activate(index);
177
+ }
178
+
179
+ /**
180
+ * Activates the next enabled step, marking the current one complete. Does nothing on the last
181
+ * enabled step.
182
+ * @returns {this}
183
+ * @fires Stepper#change
184
+ */
185
+ next() {
186
+ return this._activate(resolveStepIndex(this._steps, this._activeIndex, 1));
187
+ }
188
+
189
+ /**
190
+ * Activates the previous enabled step. Going back never clears completion flags.
191
+ * @returns {this}
192
+ * @fires Stepper#change
193
+ */
194
+ previous() {
195
+ return this._activate(resolveStepIndex(this._steps, this._activeIndex, -1));
196
+ }
197
+
198
+ /**
199
+ * Returns the active step name.
200
+ * @returns {string|null}
201
+ */
202
+ getActive() {
203
+ return this._activeIndex < 0 ? null : this._steps[this._activeIndex].name;
204
+ }
205
+
206
+ /**
207
+ * Returns the zero-based active index.
208
+ * @returns {number} -1 when no step is active.
209
+ */
210
+ getIndex() {
211
+ return this._activeIndex;
212
+ }
213
+
214
+ /**
215
+ * Marks a step complete.
216
+ * @param {string} name Step name.
217
+ * @returns {this}
218
+ */
219
+ complete(name) {
220
+ this._require(name);
221
+ this._completed.add(name);
222
+ this._sync();
223
+ return this;
224
+ }
225
+
226
+ /**
227
+ * Clears a step's completion flag.
228
+ * @param {string} name Step name.
229
+ * @returns {this}
230
+ */
231
+ uncomplete(name) {
232
+ this._require(name);
233
+ this._completed.delete(name);
234
+ this._sync();
235
+ return this;
236
+ }
237
+
238
+ /**
239
+ * Flags or clears a step's error state. An errored step renders a warning marker and keeps its
240
+ * completion flag, so a wizard can still send the user back to fix it.
241
+ * @param {string} name Step name.
242
+ * @param {boolean} [errored=true] Whether the step is in error.
243
+ * @returns {this}
244
+ */
245
+ setError(name, errored = true) {
246
+ this._require(name);
247
+ if (errored) this._errored.add(name);
248
+ else this._errored.delete(name);
249
+ this._sync();
250
+ return this;
251
+ }
252
+
253
+ /**
254
+ * Enables or disables a step. Disabling the active step leaves it active — the user is still
255
+ * standing on it — but `next()`, `previous()`, and `goTo()` will skip or refuse it afterwards.
256
+ * @param {string} name Step name.
257
+ * @param {boolean} [disabled=true] Whether the step is unavailable.
258
+ * @returns {this}
259
+ */
260
+ setDisabled(name, disabled = true) {
261
+ this._require(name).disabled = Boolean(disabled);
262
+ this._sync();
263
+ return this;
264
+ }
265
+
266
+ /**
267
+ * Returns a snapshot of the rail's state.
268
+ * @returns {StepperState}
269
+ */
270
+ getState() {
271
+ return {
272
+ active: this.getActive(),
273
+ index: this._activeIndex,
274
+ completed: this._steps.filter((step) => this._completed.has(step.name)).map((step) => step.name),
275
+ errored: this._steps.filter((step) => this._errored.has(step.name)).map((step) => step.name)
276
+ };
277
+ }
278
+
279
+ /**
280
+ * Aborts listeners and restores an enhanced target to the markup it had before the takeover.
281
+ * @returns {void}
282
+ */
283
+ destroy() {
284
+ if (this._destroyed) return;
285
+ this._destroyed = true;
286
+ const root = this.el;
287
+ super.destroy();
288
+ if (!this._createdRoot && root) restoreTarget(root, this._snapshot);
289
+ }
290
+
291
+ /* ------------------------------------------------------------------ internals -- */
292
+
293
+ /**
294
+ * Activates an index, marking every step it advanced past complete.
295
+ * @param {number} index Zero-based index, or -1 for "nowhere to go".
296
+ * @returns {this}
297
+ * @fires Stepper#change
298
+ */
299
+ _activate(index) {
300
+ if (index < 0 || index >= this._steps.length || index === this._activeIndex) return this;
301
+ const step = this._steps[index];
302
+ if (step.disabled) return this;
303
+ const from = this._activeIndex;
304
+ const event = this.emit('change', {
305
+ name: step.name,
306
+ previous: from < 0 ? null : this._steps[from].name,
307
+ index
308
+ });
309
+ if (event.defaultPrevented) return this;
310
+ for (const name of completedByAdvance(this._steps, from, index)) this._completed.add(name);
311
+ this._activeIndex = index;
312
+ this._sync();
313
+ return this;
314
+ }
315
+
316
+ /**
317
+ * Builds one step's elements. The control's tag depends only on the `clickable` option, never on
318
+ * the step's state, so state changes never have to swap an element out from under the focus.
319
+ * @param {NormalizedStep} step Normalized step.
320
+ * @returns {StepRecord}
321
+ */
322
+ _createRecord(step) {
323
+ const marker = h('span', { class: 'zx-stepper__marker', ariaHidden: 'true' });
324
+ const status = h('span', { class: 'zx-stepper__status' });
325
+ const text = [h('span', { class: 'zx-stepper__title' }, step.title)];
326
+ if (step.description) {
327
+ text.push(h('span', { class: 'zx-stepper__description' }, step.description));
328
+ }
329
+ if (step.optional) {
330
+ text.push(h('span', { class: 'zx-stepper__optional' },
331
+ this._message('stepper.optional', 'Optional')));
332
+ }
333
+ const body = [marker, h('span', { class: 'zx-stepper__text' }, text), status];
334
+ const control = this.options.clickable === false
335
+ ? h('span', { class: 'zx-stepper__control' }, body)
336
+ : h('button', {
337
+ class: 'zx-stepper__control',
338
+ type: 'button',
339
+ dataset: { step: step.name }
340
+ }, body);
341
+ return {
342
+ step,
343
+ item: /** @type {HTMLLIElement} */ (h('li', { class: 'zx-stepper__step' }, control)),
344
+ control,
345
+ marker,
346
+ status
347
+ };
348
+ }
349
+
350
+ /**
351
+ * Pushes the current state onto every rendered step and the counter line.
352
+ * @returns {void}
353
+ */
354
+ _sync() {
355
+ this._records.forEach((record, index) => {
356
+ const state = this._stateFor(record.step, index);
357
+ record.item.dataset.state = state;
358
+ if (index === this._activeIndex) record.item.setAttribute('aria-current', 'step');
359
+ else record.item.removeAttribute('aria-current');
360
+ record.item.toggleAttribute('data-disabled', record.step.disabled);
361
+ record.marker.replaceChildren(...this._markerContent(state, index));
362
+ record.status.textContent = this._statusText(state);
363
+ if (record.control.tagName === 'BUTTON') {
364
+ /** @type {HTMLButtonElement} */ (record.control).disabled = !this._isClickable(record.step);
365
+ }
366
+ });
367
+ this._syncCounter();
368
+ }
369
+
370
+ /**
371
+ * Resolves a step's rendered state.
372
+ * @param {NormalizedStep} step Normalized step.
373
+ * @param {number} index Zero-based index.
374
+ * @returns {StepperStepState}
375
+ */
376
+ _stateFor(step, index) {
377
+ return stepState(step, index, {
378
+ activeIndex: this._activeIndex,
379
+ completed: this._completed,
380
+ errored: this._errored
381
+ });
382
+ }
383
+
384
+ /**
385
+ * @param {StepperStepState} state Rendered state.
386
+ * @param {number} index Zero-based index.
387
+ * @returns {Array<Node|string>}
388
+ */
389
+ _markerContent(state, index) {
390
+ if (state === 'complete') return [icon('check', { size: 12 })];
391
+ if (state === 'error') return [icon('warning', { size: 12 })];
392
+ return this.options.showNumbers ? [String(index + 1)] : [];
393
+ }
394
+
395
+ /**
396
+ * @param {StepperStepState} state Rendered state.
397
+ * @returns {string} Text announced in place of the decorative marker.
398
+ */
399
+ _statusText(state) {
400
+ if (state === 'complete') return this._message('stepper.completed', 'Completed');
401
+ if (state === 'error') return this._message('stepper.error', 'Error');
402
+ if (state === 'active') return this._message('stepper.current', 'Current step');
403
+ return '';
404
+ }
405
+
406
+ /**
407
+ * Whether a step's button is enabled. The active step stays enabled when it qualifies (under
408
+ * `all`, or under `completed` once it has been visited), because disabling the button the user
409
+ * just clicked would throw their focus back to the document body; activating the active step is
410
+ * a no-op anyway.
411
+ * @param {NormalizedStep} step Normalized step.
412
+ * @returns {boolean}
413
+ */
414
+ _isClickable(step) {
415
+ if (step.disabled) return false;
416
+ if (this.options.clickable === 'all') return true;
417
+ if (this.options.clickable === 'completed') return this._completed.has(step.name);
418
+ return false;
419
+ }
420
+
421
+ /** @returns {void} */
422
+ _syncCounter() {
423
+ if (!this.options.counter) return;
424
+ const counter = this.refs.counter;
425
+ counter.hidden = this._activeIndex < 0;
426
+ counter.textContent = this._activeIndex < 0 ? '' :
427
+ this._message('stepper.counter', 'Step %1 of %2', this._activeIndex + 1, this._steps.length);
428
+ }
429
+
430
+ /**
431
+ * @param {string} name Step name.
432
+ * @returns {NormalizedStep}
433
+ * @throws {RangeError} When no step carries that name.
434
+ */
435
+ _require(name) {
436
+ const index = stepIndex(this._steps, name);
437
+ if (index < 0) throw new RangeError(`Unknown step: ${name}`);
438
+ return this._steps[index];
439
+ }
440
+
441
+ /**
442
+ * Resolves a message through the host translator, falling back to the built-in English text.
443
+ * @param {string} key Message key.
444
+ * @param {string} fallback Built-in text, with `%1`-style placeholders.
445
+ * @param {...unknown} args Interpolation values.
446
+ * @returns {string}
447
+ */
448
+ _message(key, fallback, ...args) {
449
+ const message = this.msg(key, ...args);
450
+ return message === key ? printf(fallback, args) : message;
451
+ }
452
+ }
453
+
454
+ /**
455
+ * Fired before the active step changes. Calling `preventDefault()` vetoes the move, which is how a
456
+ * wizard blocks a step whose form has not validated.
457
+ * @event Stepper#change
458
+ * @type {CustomEvent<StepperChangeDetail>}
459
+ */