@zeyos/zx 2.1.0 → 2.2.1

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 +51 -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 +375 -39
  120. package/dist/zx-compat.esm.min.js +1 -1
  121. package/dist/zx-compat.global.js +372 -73
  122. package/dist/zx-compat.global.min.js +3 -3
  123. package/dist/zx-zeyos.esm.js +355 -49
  124. package/dist/zx-zeyos.esm.min.js +1 -1
  125. package/dist/zx.css +68 -6
  126. package/dist/zx.esm.js +359 -73
  127. package/dist/zx.esm.min.js +3 -3
  128. package/dist/zx.global.js +359 -73
  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 +31 -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 +1711 -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,136 @@
1
+ import { h } from '../../core/dom.js';
2
+
3
+ /**
4
+ * @typedef {Object} StackOptions
5
+ * @property {'column'|'row'} [direction='column'] Flow direction.
6
+ * @property {number|string} [gap=4] Space between children. A number 1–8 picks the matching
7
+ * `--zx-space-*` step; a string is used as-is.
8
+ * @property {'start'|'center'|'end'|'stretch'|'baseline'} [align] Cross-axis alignment.
9
+ * @property {'start'|'center'|'end'|'between'|'around'} [justify] Main-axis distribution.
10
+ * @property {boolean} [wrap=false] Whether a row wraps.
11
+ * @property {boolean} [inline=false] Whether the stack is laid out inline.
12
+ * @property {string} [class=''] Extra class names.
13
+ */
14
+
15
+ /**
16
+ * @typedef {Object} GridOptions
17
+ * @property {number|null} [columns=null] Column count at full width. Null fills the row with as
18
+ * many tracks of `min` width as fit.
19
+ * @property {number|string} [min='16rem'] Narrowest a track may get before the grid drops a
20
+ * column. A number is read as pixels.
21
+ * @property {number|string} [gap=4] Space between cells, as in `stack`.
22
+ * @property {'start'|'center'|'end'|'stretch'} [align] Block-axis alignment of each cell.
23
+ * @property {string} [class=''] Extra class names.
24
+ */
25
+
26
+ /**
27
+ * @typedef {Object} AspectOptions
28
+ * @property {string} [ratio='16 / 9'] Aspect ratio of the box.
29
+ * @property {'cover'|'contain'|'fill'} [fit='cover'] How the single child fills the box.
30
+ * @property {string} [class=''] Extra class names.
31
+ */
32
+
33
+ /** @typedef {import('../../core/dom.js').DomChild} DomChild */
34
+
35
+ /**
36
+ * Creates a one-dimensional flow of children with consistent spacing.
37
+ *
38
+ * Zx ships no page grid on purpose: an ERP screen is a shell of panels, and `SplitView`,
39
+ * `MasterPanel`, and `Panel` already own that layer. `stack` and `grid` are the small pieces that
40
+ * were missing under them — the spacing every form section, toolbar row, and card list was
41
+ * otherwise re-inventing with an ad-hoc flex rule.
42
+ *
43
+ * @param {StackOptions} [options={}] Stack options.
44
+ * @param {...DomChild} children Children.
45
+ * @returns {HTMLDivElement}
46
+ */
47
+ export function stack(options = {}, ...children) {
48
+ const element = /** @type {HTMLDivElement} */ (h('div', {
49
+ class: ['zx-stack', options.class ?? ''],
50
+ dataset: {
51
+ direction: options.direction === 'row' ? 'row' : null,
52
+ align: ALIGN.has(options.align) ? options.align : null,
53
+ justify: JUSTIFY.has(options.justify) ? options.justify : null,
54
+ wrap: options.wrap ? 'true' : null,
55
+ inline: options.inline ? 'true' : null
56
+ },
57
+ style: gapStyle('--zx-stack-gap', options.gap)
58
+ }, children));
59
+ return element;
60
+ }
61
+
62
+ /**
63
+ * Creates a grid that keeps its tracks readable, dropping a column when the container gets too
64
+ * narrow to hold them at `min` width.
65
+ *
66
+ * The reflow is intrinsic — `auto-fit` against a `min` track width — so a grid behaves the same
67
+ * in a full-width page, a split pane, and a modal. Nothing here consults the viewport, which is
68
+ * the usual reason a "responsive" grid still breaks inside a narrow panel.
69
+ *
70
+ * @param {GridOptions} [options={}] Grid options.
71
+ * @param {...DomChild} children Cells.
72
+ * @returns {HTMLDivElement}
73
+ */
74
+ export function grid(options = {}, ...children) {
75
+ const columns = Number(options.columns);
76
+ const style = {
77
+ ...gapStyle('--zx-grid-gap', options.gap),
78
+ '--zx-grid-min': length(options.min ?? '16rem')
79
+ };
80
+ if (Number.isFinite(columns) && columns >= 1) {
81
+ style['--zx-grid-columns'] = String(Math.trunc(columns));
82
+ }
83
+ return /** @type {HTMLDivElement} */ (h('div', {
84
+ class: ['zx-grid', options.class ?? ''],
85
+ dataset: {
86
+ columns: Number.isFinite(columns) && columns >= 1 ? 'true' : null,
87
+ align: ALIGN.has(options.align) ? options.align : null
88
+ },
89
+ style
90
+ }, children));
91
+ }
92
+
93
+ /**
94
+ * Creates a box that holds a fixed aspect ratio whatever its width, so an image, a map, or a
95
+ * chart placeholder reserves its space before it loads.
96
+ * @param {AspectOptions} [options={}] Aspect options.
97
+ * @param {...DomChild} children The single child to fit.
98
+ * @returns {HTMLDivElement}
99
+ */
100
+ export function aspect(options = {}, ...children) {
101
+ return /** @type {HTMLDivElement} */ (h('div', {
102
+ class: ['zx-aspect', options.class ?? ''],
103
+ style: {
104
+ '--zx-aspect-ratio': String(options.ratio ?? '16 / 9'),
105
+ '--zx-aspect-fit': FIT.has(options.fit) ? options.fit : 'cover'
106
+ }
107
+ }, children));
108
+ }
109
+
110
+ const ALIGN = new Set(['start', 'center', 'end', 'stretch', 'baseline']);
111
+ const JUSTIFY = new Set(['start', 'center', 'end', 'between', 'around']);
112
+ const FIT = new Set(['cover', 'contain', 'fill']);
113
+
114
+ /**
115
+ * Resolves a gap into a custom property, mapping 1–8 onto the spacing scale.
116
+ * @param {string} property Custom property to set.
117
+ * @param {number|string|undefined} gap Gap option.
118
+ * @returns {Record<string, string>}
119
+ */
120
+ function gapStyle(property, gap) {
121
+ if (gap === undefined || gap === null || gap === '') return {};
122
+ const step = Number(gap);
123
+ if (Number.isInteger(step) && step >= 1 && step <= 8) {
124
+ return { [property]: `var(--zx-space-${step})` };
125
+ }
126
+ return { [property]: String(gap) };
127
+ }
128
+
129
+ /**
130
+ * Normalizes a CSS length, reading a bare number as pixels.
131
+ * @param {string|number} value Length.
132
+ * @returns {string}
133
+ */
134
+ function length(value) {
135
+ return typeof value === 'number' ? `${value}px` : String(value);
136
+ }
@@ -0,0 +1,34 @@
1
+ .zx-inline-loading {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: var(--zx-space-2);
5
+ color: var(--zx-color-text-muted);
6
+ font-size: var(--zx-control-font-size);
7
+ vertical-align: middle;
8
+ }
9
+
10
+ .zx-inline-loading__indicator {
11
+ display: inline-flex;
12
+ align-items: center;
13
+ flex: none;
14
+ }
15
+
16
+ .zx-inline-loading__indicator:empty {
17
+ display: none;
18
+ }
19
+
20
+ .zx-inline-loading[data-status="success"] {
21
+ color: var(--zx-color-success);
22
+ }
23
+
24
+ .zx-inline-loading[data-status="error"] {
25
+ color: var(--zx-color-danger);
26
+ }
27
+
28
+ .zx-inline-loading[data-status="inactive"] {
29
+ color: var(--zx-color-text-placeholder);
30
+ }
31
+
32
+ .zx-inline-loading[data-size="md"] {
33
+ font-size: var(--zx-text-lg);
34
+ }
@@ -0,0 +1,140 @@
1
+ import { Component } from '../../core/component.js';
2
+ import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
3
+ import { icon as createIcon } from '../../core/icons.js';
4
+ import { spinner } from './spinner.js';
5
+
6
+ /**
7
+ * @typedef {Object} InlineLoadingOptions
8
+ * @property {'inactive'|'active'|'success'|'error'} [status='active'] Initial state.
9
+ * @property {string} [description=''] Text shown beside the indicator.
10
+ * @property {'sm'|'md'} [size='sm'] Indicator size.
11
+ * @property {boolean} [live=true] Whether state changes are announced through `aria-live`.
12
+ * @property {(event: CustomEvent<{status: string, description: string}>) => void} [onstatuschange]
13
+ * State-change listener.
14
+ */
15
+
16
+ const STATUSES = new Set(['inactive', 'active', 'success', 'error']);
17
+
18
+ /**
19
+ * The status line that replaces a spinner once the wait resolves — "Saving…" becoming "Saved" or
20
+ * "Could not save" in the same place, beside the control that started the work.
21
+ *
22
+ * The whole element is one polite live region, so a screen reader hears the outcome without the
23
+ * focus moving. Reach for `Message` instead when the outcome deserves a dismissible notification
24
+ * rather than a line of text that stays put.
25
+ *
26
+ * @fires InlineLoading#statuschange
27
+ * @extends {Component<InlineLoadingOptions>}
28
+ */
29
+ export class InlineLoading extends Component {
30
+ static cssName = 'inline-loading';
31
+
32
+ /** @type {InlineLoadingOptions} */
33
+ static defaults = {
34
+ status: 'active',
35
+ description: '',
36
+ size: 'sm',
37
+ live: true
38
+ };
39
+
40
+ /** @returns {HTMLElement} */
41
+ render() {
42
+ // render() runs inside the base constructor, before class-field initializers would run.
43
+ this._status = 'inactive';
44
+ this._description = '';
45
+ this._createdRoot = this.el === null;
46
+ this._snapshot = this._createdRoot ? null : snapshotTarget(this.el);
47
+
48
+ const root = /** @type {HTMLElement} */ (this.el ?? h('span'));
49
+ this.el = root;
50
+
51
+ root.replaceChildren(
52
+ h('span', { ref: 'indicator', class: 'zx-inline-loading__indicator' }),
53
+ h('span', { ref: 'description', class: 'zx-inline-loading__description' })
54
+ );
55
+ root.dataset.size = this.options.size === 'md' ? 'md' : 'sm';
56
+ if (this.options.live !== false) {
57
+ root.setAttribute('role', 'status');
58
+ root.setAttribute('aria-live', 'polite');
59
+ }
60
+
61
+ this.set(this.options.status, this.options.description, { silent: true });
62
+ return root;
63
+ }
64
+
65
+ /**
66
+ * Returns the current state.
67
+ * @returns {'inactive'|'active'|'success'|'error'}
68
+ */
69
+ get() {
70
+ return this._status;
71
+ }
72
+
73
+ /**
74
+ * Returns the current description.
75
+ * @returns {string}
76
+ */
77
+ getDescription() {
78
+ return this._description;
79
+ }
80
+
81
+ /**
82
+ * Moves to a state, optionally replacing the description with it.
83
+ * @param {'inactive'|'active'|'success'|'error'} status Next state.
84
+ * @param {string} [description] Replacement description; the current one is kept when omitted.
85
+ * @param {{silent?: boolean}} [options={}] Set `silent` to suppress `statuschange`.
86
+ * @returns {this}
87
+ * @fires InlineLoading#statuschange
88
+ */
89
+ set(status, description, { silent = false } = {}) {
90
+ const next = STATUSES.has(status) ? status : 'inactive';
91
+ if (description !== undefined) this._description = String(description ?? '');
92
+ const changed = next !== this._status;
93
+ this._status = next;
94
+ this._sync();
95
+ if (changed && !silent) {
96
+ this.emit('statuschange', { status: this._status, description: this._description });
97
+ }
98
+ return this;
99
+ }
100
+
101
+ /**
102
+ * Replaces the description without changing the state.
103
+ * @param {string} description Text beside the indicator.
104
+ * @returns {this}
105
+ */
106
+ setDescription(description) {
107
+ this._description = String(description ?? '');
108
+ this.refs.description.textContent = this._description;
109
+ return this;
110
+ }
111
+
112
+ /** Paints the indicator and the description for the current state. @returns {void} */
113
+ _sync() {
114
+ this.el.dataset.status = this._status;
115
+ this.refs.description.textContent = this._description;
116
+ this.refs.indicator.replaceChildren(...this._indicator());
117
+ }
118
+
119
+ /** @returns {Node[]} */
120
+ _indicator() {
121
+ const size = this.el.dataset.size === 'md' ? 18 : 16;
122
+ if (this._status === 'active') return [spinner({ size: 'sm', kind: 'current' })];
123
+ if (this._status === 'success') return [createIcon('success', { size })];
124
+ if (this._status === 'error') return [createIcon('error', { size })];
125
+ return [];
126
+ }
127
+
128
+ /** Restores an enhanced target to the markup it had before the takeover. @returns {void} */
129
+ destroy() {
130
+ const root = this.el;
131
+ super.destroy();
132
+ if (!this._createdRoot && root) restoreTarget(root, this._snapshot);
133
+ }
134
+ }
135
+
136
+ /**
137
+ * State change.
138
+ * @event InlineLoading#statuschange
139
+ * @type {CustomEvent<{status: string, description: string}>}
140
+ */
@@ -0,0 +1,115 @@
1
+ .zx-progress-bar {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--zx-space-1);
5
+ inline-size: 100%;
6
+ }
7
+
8
+ .zx-progress-bar__head {
9
+ display: flex;
10
+ align-items: baseline;
11
+ justify-content: space-between;
12
+ gap: var(--zx-space-3);
13
+ font-size: var(--zx-control-font-size);
14
+ }
15
+
16
+ .zx-progress-bar__label {
17
+ color: var(--zx-color-text);
18
+ font-weight: 500;
19
+ }
20
+
21
+ .zx-progress-bar__value {
22
+ display: inline-flex;
23
+ align-items: center;
24
+ gap: var(--zx-space-1);
25
+ color: var(--zx-color-text-muted);
26
+ font-variant-numeric: tabular-nums;
27
+ }
28
+
29
+ .zx-progress-bar__track {
30
+ position: relative;
31
+ block-size: var(--zx-progress-thickness, 8px);
32
+ background: var(--zx-color-bg-muted);
33
+ border-radius: var(--zx-radius-full);
34
+ overflow: hidden;
35
+ }
36
+
37
+ .zx-progress-bar__fill {
38
+ block-size: 100%;
39
+ inline-size: var(--zx-progress-fill, 0%);
40
+ background: var(--zx-color-accent);
41
+ border-radius: inherit;
42
+ }
43
+
44
+ .zx-progress-bar__helper {
45
+ margin: 0;
46
+ color: var(--zx-color-text-muted);
47
+ font-size: var(--zx-text-sm);
48
+ }
49
+
50
+ /* An empty head or helper must not add its line box to the stack. */
51
+ .zx-progress-bar__helper:empty,
52
+ .zx-progress-bar__head:not(:has(.zx-progress-bar__label:not(:empty), .zx-progress-bar__value:not(:empty))) {
53
+ display: none;
54
+ }
55
+
56
+ .zx-progress-bar[data-size="sm"] {
57
+ --zx-progress-thickness: 4px;
58
+ }
59
+
60
+ .zx-progress-bar[data-hide-label] .zx-progress-bar__label {
61
+ position: absolute;
62
+ clip-path: inset(50%);
63
+ inline-size: 1px;
64
+ block-size: 1px;
65
+ overflow: hidden;
66
+ white-space: nowrap;
67
+ }
68
+
69
+ .zx-progress-bar[data-status="success"] .zx-progress-bar__fill {
70
+ background: var(--zx-color-success);
71
+ }
72
+
73
+ .zx-progress-bar[data-status="success"] .zx-progress-bar__value {
74
+ color: var(--zx-color-success);
75
+ }
76
+
77
+ .zx-progress-bar[data-status="error"] .zx-progress-bar__fill {
78
+ background: var(--zx-color-danger);
79
+ }
80
+
81
+ .zx-progress-bar[data-status="error"] .zx-progress-bar__value {
82
+ color: var(--zx-color-danger);
83
+ }
84
+
85
+ /*
86
+ * With the share unknown there is nothing to fill, so a short band travels the track instead.
87
+ * Without motion the band would sit still and read as a stalled 30% bar, so reduced motion gets a
88
+ * full-width muted track — visibly busy, never a wrong number.
89
+ */
90
+ .zx-progress-bar[data-indeterminate] .zx-progress-bar__fill {
91
+ inline-size: 100%;
92
+ background: color-mix(in srgb, var(--zx-color-accent) 30%, var(--zx-color-bg-muted));
93
+ }
94
+
95
+ @media (prefers-reduced-motion: no-preference) {
96
+ .zx-progress-bar__fill {
97
+ transition: inline-size var(--zx-dur-base) var(--zx-ease);
98
+ }
99
+
100
+ .zx-progress-bar[data-indeterminate] .zx-progress-bar__fill {
101
+ inline-size: 35%;
102
+ background: var(--zx-color-accent);
103
+ animation: zx-progress-slide 1200ms var(--zx-ease) infinite;
104
+ }
105
+ }
106
+
107
+ @keyframes zx-progress-slide {
108
+ from {
109
+ transform: translateX(-100%);
110
+ }
111
+
112
+ to {
113
+ transform: translateX(calc(100% / 0.35));
114
+ }
115
+ }
@@ -0,0 +1,259 @@
1
+ import { Component } from '../../core/component.js';
2
+ import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
3
+ import { icon as createIcon } from '../../core/icons.js';
4
+ import { clamp, uid } from '../../core/util.js';
5
+
6
+ /**
7
+ * @typedef {Object} ProgressBarOptions
8
+ * @property {number} [value=0] Completed amount, clamped to `[0, max]`.
9
+ * @property {number} [max=100] Amount that counts as finished.
10
+ * @property {string} [label=''] Visible name of the task.
11
+ * @property {string} [helperText=''] Supporting line below the track.
12
+ * @property {'active'|'success'|'error'} [status='active'] Visual state of the track.
13
+ * @property {boolean} [indeterminate=false] Whether the amount done is unknown.
14
+ * @property {'md'|'sm'} [size='md'] Track thickness.
15
+ * @property {boolean} [hideLabel=false] Whether the label is kept for assistive technology only.
16
+ * @property {boolean} [showValue=true] Whether the formatted value is drawn opposite the label.
17
+ * @property {((value: number, max: number) => string)|null} [formatValue=null] Replaces the
18
+ * default percentage readout.
19
+ * @property {(event: CustomEvent<{value: number, percent: number}>) => void} [onchange] Value listener.
20
+ * @property {(event: CustomEvent<{value: number}>) => void} [oncomplete] Listener for reaching `max`.
21
+ */
22
+
23
+ /**
24
+ * Determinate progress track for work whose completed share is known — a file upload, a batch
25
+ * posting run, an import.
26
+ *
27
+ * The track carries `role="progressbar"` and its ARIA value attributes, which is what a screen
28
+ * reader reports; an indeterminate bar drops `aria-valuenow` rather than reporting a fake number.
29
+ * `status` is a display concern only: a finished-with-errors bar still reads 100%.
30
+ *
31
+ * @fires ProgressBar#change
32
+ * @fires ProgressBar#complete
33
+ * @extends {Component<ProgressBarOptions>}
34
+ */
35
+ export class ProgressBar extends Component {
36
+ static cssName = 'progress-bar';
37
+
38
+ /** @type {ProgressBarOptions} */
39
+ static defaults = {
40
+ value: 0,
41
+ max: 100,
42
+ label: '',
43
+ helperText: '',
44
+ status: 'active',
45
+ indeterminate: false,
46
+ size: 'md',
47
+ hideLabel: false,
48
+ showValue: true,
49
+ formatValue: null
50
+ };
51
+
52
+ /** @returns {HTMLElement} */
53
+ render() {
54
+ // render() runs inside the base constructor, before class-field initializers would run.
55
+ this._value = 0;
56
+ this._createdRoot = this.el === null;
57
+ this._snapshot = this._createdRoot ? null : snapshotTarget(this.el);
58
+
59
+ const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
60
+ this.el = root;
61
+
62
+ this._max = Math.max(1, Number(this.options.max) || 100);
63
+ this._status = normalizeStatus(this.options.status);
64
+ this._indeterminate = Boolean(this.options.indeterminate);
65
+ this._labelId = uid('zx-progress-label');
66
+
67
+ const head = h('div', { ref: 'head', class: 'zx-progress-bar__head' },
68
+ h('span', { ref: 'label', class: 'zx-progress-bar__label', id: this._labelId },
69
+ String(this.options.label ?? '')),
70
+ h('span', { ref: 'value', class: 'zx-progress-bar__value' })
71
+ );
72
+ const track = h('div', {
73
+ ref: 'track',
74
+ class: 'zx-progress-bar__track',
75
+ role: 'progressbar',
76
+ ariaValuemin: '0',
77
+ ariaValuemax: String(this._max),
78
+ ariaLabelledby: this._labelId
79
+ }, h('div', { ref: 'fill', class: 'zx-progress-bar__fill' }));
80
+
81
+ root.replaceChildren(head, track,
82
+ h('p', { ref: 'helper', class: 'zx-progress-bar__helper' },
83
+ String(this.options.helperText ?? '')));
84
+ root.dataset.size = this.options.size === 'sm' ? 'sm' : 'md';
85
+ root.toggleAttribute('data-hide-label', Boolean(this.options.hideLabel));
86
+
87
+ this.set(this.options.value, { silent: true });
88
+ return root;
89
+ }
90
+
91
+ /** Current value. @returns {number} */
92
+ get value() {
93
+ return this._value;
94
+ }
95
+
96
+ /** @param {number} next Next value. */
97
+ set value(next) {
98
+ this.set(next);
99
+ }
100
+
101
+ /**
102
+ * Returns the current value.
103
+ * @returns {number}
104
+ */
105
+ get() {
106
+ return this._value;
107
+ }
108
+
109
+ /**
110
+ * Sets the completed amount, clamped to `[0, max]`.
111
+ * @param {number} next Completed amount.
112
+ * @param {{silent?: boolean}} [options={}] Set `silent` to suppress events.
113
+ * @returns {this}
114
+ * @fires ProgressBar#change
115
+ * @fires ProgressBar#complete
116
+ */
117
+ set(next, { silent = false } = {}) {
118
+ const raw = Number(next);
119
+ const value = clamp(Number.isFinite(raw) ? raw : 0, 0, this._max);
120
+ const changed = value !== this._value;
121
+ const reached = changed && value >= this._max;
122
+ this._value = value;
123
+ this._sync();
124
+ if (!silent && changed) this.emit('change', { value, percent: this.percent() });
125
+ if (!silent && reached) this.emit('complete', { value });
126
+ return this;
127
+ }
128
+
129
+ /**
130
+ * Completed share of the track, as a number between 0 and 100.
131
+ * @returns {number}
132
+ */
133
+ percent() {
134
+ return (this._value / this._max) * 100;
135
+ }
136
+
137
+ /**
138
+ * Replaces the upper bound, re-clamping the current value against it.
139
+ * @param {number} max New maximum.
140
+ * @returns {this}
141
+ */
142
+ setMax(max) {
143
+ this._max = Math.max(1, Number(max) || 1);
144
+ this.refs.track.setAttribute('aria-valuemax', String(this._max));
145
+ return this.set(this._value, { silent: true });
146
+ }
147
+
148
+ /**
149
+ * Sets the visual state, optionally replacing the helper line with it.
150
+ * @param {'active'|'success'|'error'} status Track state.
151
+ * @param {string} [helperText] Replacement helper line.
152
+ * @returns {this}
153
+ */
154
+ setStatus(status, helperText) {
155
+ this._status = normalizeStatus(status);
156
+ if (helperText !== undefined) this.setHelperText(helperText);
157
+ this._sync();
158
+ return this;
159
+ }
160
+
161
+ /**
162
+ * Replaces the visible task name.
163
+ * @param {string} label Task name.
164
+ * @returns {this}
165
+ */
166
+ setLabel(label) {
167
+ this.refs.label.textContent = String(label ?? '');
168
+ return this;
169
+ }
170
+
171
+ /**
172
+ * Replaces the supporting line below the track.
173
+ * @param {string} helperText Supporting line.
174
+ * @returns {this}
175
+ */
176
+ setHelperText(helperText) {
177
+ this.refs.helper.textContent = String(helperText ?? '');
178
+ return this;
179
+ }
180
+
181
+ /**
182
+ * Switches between a known and an unknown completed share.
183
+ * @param {boolean} [indeterminate=true] Whether the amount done is unknown.
184
+ * @returns {this}
185
+ */
186
+ setIndeterminate(indeterminate = true) {
187
+ this._indeterminate = Boolean(indeterminate);
188
+ this._sync();
189
+ return this;
190
+ }
191
+
192
+ /** Paints the track, the readout, and the ARIA value attributes. @returns {void} */
193
+ _sync() {
194
+ const { track, fill, value } = this.refs;
195
+ this.el.dataset.status = this._status;
196
+ this.el.toggleAttribute('data-indeterminate', this._indeterminate);
197
+
198
+ if (this._indeterminate) {
199
+ track.removeAttribute('aria-valuenow');
200
+ track.removeAttribute('aria-valuetext');
201
+ fill.style.removeProperty('--zx-progress-fill');
202
+ value.textContent = '';
203
+ return;
204
+ }
205
+
206
+ const text = this._format();
207
+ track.setAttribute('aria-valuenow', String(this._value));
208
+ track.setAttribute('aria-valuetext', text);
209
+ fill.style.setProperty('--zx-progress-fill', `${this.percent()}%`);
210
+ value.replaceChildren(...this._readout(text));
211
+ }
212
+
213
+ /** @returns {string} */
214
+ _format() {
215
+ const formatter = this.options.formatValue;
216
+ if (typeof formatter === 'function') return String(formatter(this._value, this._max));
217
+ return `${Math.round(this.percent())}%`;
218
+ }
219
+
220
+ /**
221
+ * The readout beside the label: the formatted value, prefixed by a status glyph once the run
222
+ * has finished one way or the other.
223
+ * @param {string} text Formatted value.
224
+ * @returns {Node[]}
225
+ */
226
+ _readout(text) {
227
+ if (!this.options.showValue) return [];
228
+ const nodes = [];
229
+ if (this._status !== 'active') {
230
+ nodes.push(createIcon(this._status === 'success' ? 'success' : 'error', { size: 14 }));
231
+ }
232
+ nodes.push(h('span', { class: 'zx-progress-bar__percent' }, text));
233
+ return nodes;
234
+ }
235
+
236
+ /** Restores an enhanced target to the markup it had before the takeover. @returns {void} */
237
+ destroy() {
238
+ const root = this.el;
239
+ super.destroy();
240
+ if (!this._createdRoot && root) restoreTarget(root, this._snapshot);
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Value change.
246
+ * @event ProgressBar#change
247
+ * @type {CustomEvent<{value: number, percent: number}>}
248
+ */
249
+
250
+ /**
251
+ * Fired once the value reaches `max`.
252
+ * @event ProgressBar#complete
253
+ * @type {CustomEvent<{value: number}>}
254
+ */
255
+
256
+ /** @param {unknown} value @returns {'active'|'success'|'error'} */
257
+ function normalizeStatus(value) {
258
+ return value === 'success' || value === 'error' ? value : 'active';
259
+ }