@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,167 @@
1
+ import { Component } from '../../core/component.js';
2
+ import { h } from '../../core/dom.js';
3
+ import { icon } from '../../core/icons.js';
4
+
5
+ /**
6
+ * @typedef {Object} CheckButtonOptions
7
+ * @property {string|[string, string]} [label=''] A shared label or `[onLabel, offLabel]`.
8
+ * @property {boolean} [checked=false] Initial pressed state.
9
+ * @property {boolean} [icon=true] Whether to show the check indicator.
10
+ * @property {boolean} [disabled=false] Initial disabled state.
11
+ * @property {(event: CustomEvent<{checked: boolean}>) => void} [onchange] Change callback.
12
+ */
13
+
14
+ /**
15
+ * Two-state native press button.
16
+ * @fires CheckButton#change
17
+ * @extends {Component<CheckButtonOptions>}
18
+ */
19
+ export class CheckButton extends Component {
20
+ static cssName = 'check-button';
21
+
22
+ /** @type {Readonly<CheckButtonOptions>} */
23
+ static defaults = {
24
+ label: '',
25
+ checked: false,
26
+ icon: true,
27
+ disabled: false
28
+ };
29
+
30
+ /** @returns {HTMLButtonElement} */
31
+ render() {
32
+ const created = !this.el;
33
+ const root = /** @type {HTMLButtonElement} */ (this.el ?? h('button'));
34
+ this.el = root;
35
+ if (root.localName !== 'button') {
36
+ throw new TypeError('CheckButton target must be a button element');
37
+ }
38
+
39
+ this._createdRoot = created;
40
+ this._original = created ? null : snapshot(root);
41
+ this._checked = Boolean(this.options.checked);
42
+ this._disabled = Boolean(this.options.disabled);
43
+ this._labels = normalizeLabels(this.options.label);
44
+ root.type = 'button';
45
+ const children = [];
46
+ if (this.options.icon) {
47
+ // Both states carry a glyph — an empty box when off, a check when on — so the control still
48
+ // reads as a two-state control while pressed, unpressed, or disabled.
49
+ children.push(h('span', { class: 'zx-check-button__icon', ariaHidden: 'true' },
50
+ h('span', { class: 'zx-check-button__off' }, icon('square', { size: 15 })),
51
+ h('span', { class: 'zx-check-button__on' }, icon('check', { size: 15 }))));
52
+ }
53
+ children.push(h('span', { class: 'zx-check-button__label', ref: 'label' }));
54
+ root.replaceChildren(...children);
55
+ this._sync();
56
+ this.listen(root, 'click', () => this.toggle());
57
+ return root;
58
+ }
59
+
60
+ /**
61
+ * Returns the current pressed state.
62
+ * @returns {boolean}
63
+ */
64
+ get() {
65
+ return this._checked;
66
+ }
67
+
68
+ /**
69
+ * Sets the pressed state.
70
+ * @param {boolean} checked Next state.
71
+ * @param {{silent?: boolean}} [options={}] Set `silent` to suppress `change`.
72
+ * @returns {this}
73
+ * @fires CheckButton#change
74
+ */
75
+ set(checked, { silent = false } = {}) {
76
+ const next = Boolean(checked);
77
+ if (next === this._checked) return this;
78
+ this._checked = next;
79
+ this._sync();
80
+ if (!silent) this.emit('change', { checked: this._checked });
81
+ return this;
82
+ }
83
+
84
+ /**
85
+ * Toggles the pressed state.
86
+ * @returns {this}
87
+ * @fires CheckButton#change
88
+ */
89
+ toggle() {
90
+ return this.set(!this._checked);
91
+ }
92
+
93
+ /**
94
+ * Replaces the state labels.
95
+ * @param {string|[string, string]} label A shared label or `[onLabel, offLabel]`.
96
+ * @returns {this}
97
+ */
98
+ setLabel(label) {
99
+ this._labels = normalizeLabels(label);
100
+ this._sync();
101
+ return this;
102
+ }
103
+
104
+ /**
105
+ * Enables user interaction.
106
+ * @returns {this}
107
+ */
108
+ enable() {
109
+ this._disabled = false;
110
+ this.el.removeAttribute('disabled');
111
+ return this;
112
+ }
113
+
114
+ /**
115
+ * Disables user interaction.
116
+ * @returns {this}
117
+ */
118
+ disable() {
119
+ this._disabled = true;
120
+ this.el.setAttribute('disabled', '');
121
+ return this;
122
+ }
123
+
124
+ /** @returns {void} */
125
+ destroy() {
126
+ if (this._cleaned) return;
127
+ this._cleaned = true;
128
+ super.destroy();
129
+ if (!this._createdRoot && this._original) restore(this.el, this._original);
130
+ }
131
+
132
+ /** @returns {void} */
133
+ _sync() {
134
+ this.el.setAttribute('aria-pressed', String(this._checked));
135
+ this.el.toggleAttribute('disabled', this._disabled);
136
+ this.refs.label.textContent = this._checked ? this._labels.on : this._labels.off;
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Change emitted after a real state transition.
142
+ * @event CheckButton#change
143
+ * @type {CustomEvent<{checked: boolean}>}
144
+ */
145
+
146
+ /** @param {string|[string, string]} label @returns {{on: string, off: string}} */
147
+ function normalizeLabels(label) {
148
+ if (Array.isArray(label)) {
149
+ return { on: String(label[0] ?? ''), off: String(label[1] ?? '') };
150
+ }
151
+ return { on: String(label ?? ''), off: String(label ?? '') };
152
+ }
153
+
154
+ /** @param {Element} element @returns {{attributes: [string, string][], children: Node[]}} */
155
+ function snapshot(element) {
156
+ return {
157
+ attributes: Array.from(element.attributes, (attribute) => [attribute.name, attribute.value]),
158
+ children: Array.from(element.childNodes)
159
+ };
160
+ }
161
+
162
+ /** @param {Element} element @param {{attributes: [string, string][], children: Node[]}} state */
163
+ function restore(element, state) {
164
+ for (const attribute of Array.from(element.attributes)) element.removeAttribute(attribute.name);
165
+ for (const [name, value] of state.attributes) element.setAttribute(name, value);
166
+ element.replaceChildren(...state.children);
167
+ }
@@ -0,0 +1,65 @@
1
+ .zx-checklist {
2
+ display: grid;
3
+ gap: var(--zx-space-2);
4
+ min-inline-size: 0;
5
+ color: var(--zx-color-text);
6
+ font-size: var(--zx-control-font-size);
7
+ }
8
+
9
+ .zx-checklist__label {
10
+ font-weight: 650;
11
+ }
12
+
13
+ .zx-checklist__search {
14
+ inline-size: 100%;
15
+ }
16
+
17
+ .zx-checklist__search[hidden] {
18
+ display: none;
19
+ }
20
+
21
+ .zx-checklist__list {
22
+ overflow-y: auto;
23
+ overscroll-behavior: contain;
24
+ border: 1px solid var(--zx-color-border);
25
+ border-radius: var(--zx-radius-md);
26
+ background: var(--zx-color-bg-surface);
27
+ padding: var(--zx-space-1);
28
+ }
29
+
30
+ .zx-checklist__item {
31
+ display: flex;
32
+ align-items: center;
33
+ gap: var(--zx-space-2);
34
+ min-block-size: var(--zx-control-height);
35
+ border-radius: var(--zx-radius-sm);
36
+ cursor: pointer;
37
+ padding: var(--zx-space-1) var(--zx-space-2);
38
+ }
39
+
40
+ .zx-checklist__item:hover {
41
+ background: var(--zx-color-bg-hover);
42
+ }
43
+
44
+ .zx-checklist__item:has(input:checked) {
45
+ background: var(--zx-color-bg-selected);
46
+ }
47
+
48
+ .zx-checklist__item input {
49
+ flex: none;
50
+ accent-color: var(--zx-color-accent);
51
+ }
52
+
53
+ .zx-checklist__item:has(input:focus-visible) {
54
+ box-shadow: var(--zx-focus-ring);
55
+ }
56
+
57
+ .zx-checklist__empty {
58
+ color: var(--zx-color-text-muted);
59
+ padding: var(--zx-space-3);
60
+ text-align: center;
61
+ }
62
+
63
+ .zx-checklist[data-state="loading"] .zx-checklist__list {
64
+ color: var(--zx-color-text-muted);
65
+ }
@@ -0,0 +1,282 @@
1
+ import { Component } from '../../core/component.js';
2
+ import { h } from '../../core/dom.js';
3
+ import { uid } from '../../core/util.js';
4
+ import { matchItems } from '../select/filter.js';
5
+
6
+ /** @typedef {Record<string, any>|string|number|boolean|symbol|bigint|null|undefined} ChecklistItem */
7
+ /**
8
+ * @typedef {Object} ChecklistOptions
9
+ * @property {ChecklistItem[]} [items=[]] Available checkbox items.
10
+ * @property {string|((item: ChecklistItem) => unknown)} [valueKey='ID'] Item ID property or reader.
11
+ * @property {string|((item: ChecklistItem) => string)} [labelKey='name'] Item label property or reader.
12
+ * @property {string|((item: ChecklistItem) => unknown)} [checkedKey='on'] Initial checked-state property or reader.
13
+ * @property {boolean} [search=true] Whether the search field is shown.
14
+ * @property {number} [height=280] Maximum list height in pixels.
15
+ * @property {boolean} [defaultChecked=false] Fallback initial checked state.
16
+ * @property {(() => Promise<ChecklistItem[]>|ChecklistItem[])|null} [load=null] Async item loader.
17
+ * @property {Record<string, string>|Record<string, Record<string, string>>} [msg] Localized messages.
18
+ * @property {(event: CustomEvent<ChecklistChangeDetail>) => void} [onchange] Change listener.
19
+ * @property {(event: CustomEvent<ChecklistLoadedDetail>) => void} [onloaded] Loaded listener.
20
+ */
21
+ /** @typedef {{values: unknown[]}} ChecklistChangeDetail */
22
+ /** @typedef {{items: ChecklistItem[]}} ChecklistLoadedDetail */
23
+
24
+ /**
25
+ * Searchable native-checkbox group.
26
+ *
27
+ * @fires Checklist#change
28
+ * @fires Checklist#loaded
29
+ * @extends {Component<ChecklistOptions>}
30
+ */
31
+ export class Checklist extends Component {
32
+ static cssName = 'checklist';
33
+
34
+ /** @type {ChecklistOptions} */
35
+ static defaults = {
36
+ items: [],
37
+ valueKey: 'ID',
38
+ labelKey: 'name',
39
+ checkedKey: 'on',
40
+ search: true,
41
+ height: 280,
42
+ defaultChecked: false,
43
+ load: null
44
+ };
45
+
46
+
47
+ /** @returns {HTMLElement} */
48
+ render() {
49
+ // Instance state initialized here because render() runs inside the base
50
+ // constructor, before class-field initializers would run (and clobber it).
51
+ this._items = []; this._visibleItems = []; this._checked = new Set(); this._query = ''; this._loadSequence = 0; this._createdRoot = false; this._active = true; this._originalChildren = []; this._originalAttributes = new Map();
52
+ this._createdRoot = this.el === null;
53
+ const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
54
+ this.el = root;
55
+ if (!this._createdRoot) this._originalChildren = Array.from(root.childNodes);
56
+ for (const name of ['role', 'aria-labelledby', 'data-state']) {
57
+ this._originalAttributes.set(name, root.getAttribute(name));
58
+ }
59
+
60
+ const labelId = uid('zx-checklist-label');
61
+ const title = h('div', {
62
+ class: 'zx-checklist__label',
63
+ id: labelId
64
+ }, this._message('checklist.label', 'Checklist'));
65
+ const search = h('input', {
66
+ ref: 'search',
67
+ class: 'zx-checklist__search',
68
+ type: 'search',
69
+ autocomplete: 'off',
70
+ placeholder: this._message('checklist.search', 'Search'),
71
+ ariaLabel: this._message('checklist.search', 'Search checklist'),
72
+ hidden: !this.options.search
73
+ });
74
+ const list = h('div', {
75
+ ref: 'list',
76
+ class: 'zx-checklist__list'
77
+ });
78
+ list.style.maxBlockSize = `${Math.max(0, Number(this.options.height) || 0)}px`;
79
+ root.replaceChildren(title, search, list);
80
+ root.setAttribute('role', 'group');
81
+ root.setAttribute('aria-labelledby', labelId);
82
+
83
+ this.listen(search, 'input', () => this.search(/** @type {HTMLInputElement} */ (search).value));
84
+ this.listen(list, 'change', (event) => {
85
+ const input = /** @type {HTMLInputElement|null} */ (event.target);
86
+ if (!input?.matches?.('input[type="checkbox"][data-index]')) return;
87
+ const item = this._visibleItems[Number(input.dataset.index)];
88
+ if (item === undefined) return;
89
+ const value = this._itemValue(item);
90
+ if (input.checked) this._checked.add(value);
91
+ else this._checked.delete(value);
92
+ this.emit('change', { values: this.getValues() });
93
+ });
94
+
95
+ this._replaceItems(this.options.items);
96
+ if (typeof this.options.load === 'function') {
97
+ queueMicrotask(() => {
98
+ if (this._active) void this.reload().catch(() => {});
99
+ });
100
+ }
101
+ return root;
102
+ }
103
+
104
+ /**
105
+ * Replaces items and derives checked state from `checkedKey` and `defaultChecked`.
106
+ * @param {ChecklistItem[]} items New items.
107
+ * @returns {this}
108
+ */
109
+ setItems(items) {
110
+ this._replaceItems(items);
111
+ return this;
112
+ }
113
+
114
+ /** Returns checked IDs in item order. @returns {unknown[]} */
115
+ getValues() {
116
+ return this._items
117
+ .map((item) => this._itemValue(item))
118
+ .filter((value) => this._checked.has(value));
119
+ }
120
+
121
+ /**
122
+ * Replaces the checked IDs.
123
+ * @param {Iterable<unknown>|unknown[]} ids Checked item IDs.
124
+ * @returns {this}
125
+ * @fires Checklist#change
126
+ */
127
+ setValues(ids) {
128
+ const requested = new Set(ids == null ? [] : Array.from(ids));
129
+ const next = new Set(this._items
130
+ .map((item) => this._itemValue(item))
131
+ .filter((value) => requested.has(value)));
132
+ this._replaceChecked(next);
133
+ return this;
134
+ }
135
+
136
+ /** Checks every item. @returns {this} @fires Checklist#change */
137
+ checkAll() {
138
+ this._replaceChecked(new Set(this._items.map((item) => this._itemValue(item))));
139
+ return this;
140
+ }
141
+
142
+ /** Unchecks every item. @returns {this} @fires Checklist#change */
143
+ uncheckAll() {
144
+ this._replaceChecked(new Set());
145
+ return this;
146
+ }
147
+
148
+ /**
149
+ * Filters visible items by label.
150
+ * @param {string} query Search text.
151
+ * @returns {this}
152
+ */
153
+ search(query) {
154
+ this._query = String(query ?? '');
155
+ const input = /** @type {HTMLInputElement} */ (this.refs.search);
156
+ if (input.value !== this._query) input.value = this._query;
157
+ this._visibleItems = matchItems(this._items, this._query, [this.options.labelKey]);
158
+ this._renderItems();
159
+ return this;
160
+ }
161
+
162
+ /**
163
+ * Reloads items through the configured async loader.
164
+ * @returns {Promise<this>}
165
+ * @fires Checklist#loaded
166
+ */
167
+ async reload() {
168
+ if (!this._active || typeof this.options.load !== 'function') return this;
169
+ const sequence = ++this._loadSequence;
170
+ this.el.setAttribute('data-state', 'loading');
171
+ this.refs.list.setAttribute('aria-busy', 'true');
172
+ try {
173
+ const items = await this.options.load();
174
+ if (sequence !== this._loadSequence) return this;
175
+ this._replaceItems(items);
176
+ this.emit('loaded', { items: this._items.slice() });
177
+ } finally {
178
+ if (sequence === this._loadSequence) {
179
+ this.el.removeAttribute('data-state');
180
+ this.refs.list.setAttribute('aria-busy', 'false');
181
+ }
182
+ }
183
+ return this;
184
+ }
185
+
186
+ /** Restores an enhanced target and invalidates pending loads. @returns {void} */
187
+ destroy() {
188
+ this._active = false;
189
+ this._loadSequence += 1;
190
+ const root = /** @type {HTMLElement} */ (this.el);
191
+ if (!this._createdRoot && root) {
192
+ root.replaceChildren(...this._originalChildren);
193
+ for (const [name, value] of this._originalAttributes) {
194
+ if (value === null) root.removeAttribute(name);
195
+ else root.setAttribute(name, value);
196
+ }
197
+ }
198
+ super.destroy();
199
+ }
200
+
201
+ /** @param {ChecklistItem[]} items @returns {void} */
202
+ _replaceItems(items) {
203
+ this._items = Array.isArray(items) ? items.slice() : [];
204
+ this._checked = new Set(this._items
205
+ .filter((item) => Boolean(this._initialChecked(item)))
206
+ .map((item) => this._itemValue(item)));
207
+ this._visibleItems = matchItems(this._items, this._query, [this.options.labelKey]);
208
+ this._renderItems();
209
+ }
210
+
211
+ /** @returns {void} */
212
+ _renderItems() {
213
+ if (!this.refs.list) return;
214
+ const rows = this._visibleItems.map((item, index) => {
215
+ const checkboxId = uid('zx-checklist-item');
216
+ const input = h('input', {
217
+ type: 'checkbox',
218
+ id: checkboxId,
219
+ checked: this._checked.has(this._itemValue(item)),
220
+ dataset: { index }
221
+ });
222
+ return h('label', {
223
+ class: 'zx-checklist__item',
224
+ for: checkboxId
225
+ }, input, h('span', {}, this._itemLabel(item)));
226
+ });
227
+ if (rows.length === 0) {
228
+ rows.push(h('div', {
229
+ class: 'zx-checklist__empty',
230
+ role: 'status'
231
+ }, this._message('checklist.empty', 'No matches')));
232
+ }
233
+ this.refs.list.replaceChildren(...rows);
234
+ }
235
+
236
+ /** @param {Set<unknown>} next @returns {void} */
237
+ _replaceChecked(next) {
238
+ if (setsEqual(this._checked, next)) return;
239
+ this._checked = next;
240
+ this._renderItems();
241
+ this.emit('change', { values: this.getValues() });
242
+ }
243
+
244
+ /** @param {ChecklistItem} item @returns {unknown} */
245
+ _itemValue(item) {
246
+ return this._read(item, this.options.valueKey);
247
+ }
248
+
249
+ /** @param {ChecklistItem} item @returns {string} */
250
+ _itemLabel(item) {
251
+ return String(this._read(item, this.options.labelKey) ?? '');
252
+ }
253
+
254
+ /** @param {ChecklistItem} item @returns {unknown} */
255
+ _initialChecked(item) {
256
+ let value;
257
+ if (typeof this.options.checkedKey === 'function') value = this.options.checkedKey(item);
258
+ else if (item !== null && typeof item === 'object') value = item[this.options.checkedKey];
259
+ return value == null ? this.options.defaultChecked : value;
260
+ }
261
+
262
+ /** @param {ChecklistItem} item @param {string|((item: ChecklistItem) => unknown)} reader @returns {unknown} */
263
+ _read(item, reader) {
264
+ if (typeof reader === 'function') return reader(item);
265
+ if (item === null || typeof item !== 'object') return item;
266
+ return item[reader];
267
+ }
268
+
269
+ /** @param {string} key @param {string} fallback @returns {string} */
270
+ _message(key, fallback) {
271
+ const message = this.msg(key);
272
+ return message === key ? fallback : message;
273
+ }
274
+ }
275
+
276
+ /** Checked-value change. @event Checklist#change @type {CustomEvent<ChecklistChangeDetail>} */
277
+ /** Async-load completion. @event Checklist#loaded @type {CustomEvent<ChecklistLoadedDetail>} */
278
+
279
+ /** @param {Set<unknown>} left @param {Set<unknown>} right @returns {boolean} */
280
+ function setsEqual(left, right) {
281
+ return left.size === right.size && Array.from(left).every((value) => right.has(value));
282
+ }
@@ -0,0 +1,46 @@
1
+ import { Field } from '../field/field.js';
2
+ import { Checklist } from './checklist.js';
3
+
4
+ /**
5
+ * Registers the Checklist-backed `checklist` field type.
6
+ * @returns {void}
7
+ */
8
+ export function registerChecklistFieldAdapter() {
9
+ Field.register('checklist', (field, options) => {
10
+ const checklist = field.own(new Checklist(null, { ...(options.props ?? {}) }));
11
+ let disabled = false;
12
+ let setting = false;
13
+ const syncDisabled = () => {
14
+ checklist.el.setAttribute('aria-disabled', String(disabled));
15
+ for (const control of checklist.el.querySelectorAll('input, button, select, textarea')) {
16
+ control.disabled = disabled;
17
+ }
18
+ };
19
+ field.listen(checklist.el, 'change', (event) => event.stopImmediatePropagation());
20
+ checklist.on('change', (event) => {
21
+ if (!setting) field.emit('change', { value: event.detail.values });
22
+ });
23
+ checklist.on('loaded', syncDisabled);
24
+ return {
25
+ el: checklist.el,
26
+ get: () => checklist.getValues(),
27
+ set: (value) => {
28
+ setting = true;
29
+ try {
30
+ checklist.setValues(Array.isArray(value) ? value : []);
31
+ } finally {
32
+ setting = false;
33
+ }
34
+ syncDisabled();
35
+ },
36
+ focus: () => {
37
+ const search = checklist.el.querySelector('.zx-checklist__search:not([hidden]):not([disabled])');
38
+ (search ?? checklist.el.querySelector('input[type="checkbox"]:not([disabled])'))?.focus();
39
+ },
40
+ setDisabled: (value) => {
41
+ disabled = Boolean(value);
42
+ syncDisabled();
43
+ }
44
+ };
45
+ });
46
+ }
@@ -0,0 +1,54 @@
1
+ /*
2
+ * The anchor is a zero-sized element parked at the pointer, not a visible part of the menu: the
3
+ * panel positions against it exactly as a Dropdown positions against its trigger, so a menu opened
4
+ * at the pointer flips near a viewport edge like every other floating panel in the library.
5
+ */
6
+ .zx-context-menu__anchor {
7
+ position: fixed;
8
+ inline-size: 0;
9
+ block-size: 0;
10
+ pointer-events: none;
11
+ }
12
+
13
+ .zx-context-menu {
14
+ min-inline-size: 180px;
15
+ padding: var(--zx-space-1);
16
+ }
17
+
18
+ .zx-context-menu__item {
19
+ display: flex;
20
+ align-items: center;
21
+ gap: var(--zx-space-2);
22
+ inline-size: 100%;
23
+ min-block-size: var(--zx-control-height);
24
+ border-color: var(--zx-color-bg-raised);
25
+ background: var(--zx-color-bg-raised);
26
+ padding-inline: var(--zx-control-pad-x);
27
+ text-align: start;
28
+ }
29
+
30
+ .zx-context-menu__item:hover,
31
+ .zx-context-menu__item:focus-visible {
32
+ background: var(--zx-color-bg-hover);
33
+ }
34
+
35
+ .zx-context-menu__item[data-danger="true"] {
36
+ color: var(--zx-color-danger);
37
+ }
38
+
39
+ .zx-context-menu__item[aria-disabled="true"] {
40
+ color: var(--zx-color-text-muted);
41
+ cursor: not-allowed;
42
+ opacity: 0.65;
43
+ }
44
+
45
+ .zx-context-menu__icon {
46
+ display: inline-flex;
47
+ flex: none;
48
+ }
49
+
50
+ .zx-context-menu__separator {
51
+ block-size: 1px;
52
+ margin: var(--zx-space-1) var(--zx-space-2);
53
+ background: var(--zx-color-border);
54
+ }