@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,715 @@
1
+ import { CheckButton } from '../components/check-button/check-button.js';
2
+ import { Checklist } from '../components/checklist/checklist.js';
3
+ import { Datebox } from '../components/datebox/datebox.js';
4
+ import { Dialog } from '../components/dialog/dialog.js';
5
+ import { Groupbox } from '../components/groupbox/groupbox.js';
6
+ import { Search } from '../components/search/search.js';
7
+ import { Select } from '../components/select/select.js';
8
+ import { Tabbox } from '../components/tabbox/tabbox.js';
9
+ import { Table } from '../components/table/table.js';
10
+ import { Timebox } from '../components/timebox/timebox.js';
11
+ import { Toggle } from '../components/toggle/toggle.js';
12
+ import { formatDate, parseDate } from '../core/date.js';
13
+ import { INVALID_ATTRIBUTE, coerceAttribute, serializeAttribute } from './reflect.js';
14
+
15
+ /** @typedef {'string'|'number'|'boolean'|'json'} AttributeType */
16
+ /**
17
+ * @typedef {Object} ElementAttribute
18
+ * @property {AttributeType} [type='string'] Attribute representation.
19
+ * @property {string|null} [option] Component option name; null keeps the value on the host only.
20
+ * @property {string} [property] Host property name; defaults to camel-cased attribute name.
21
+ * @property {unknown} [default] Property value used while the attribute is absent.
22
+ * @property {boolean} [always=false] Pass the default to the component during initial creation.
23
+ * @property {boolean} [rebuild=false] Recreate the component when this value changes.
24
+ * @property {(value: unknown, host: HTMLElement) => unknown} [normalize] Value adapter.
25
+ * @property {(value: unknown, host: HTMLElement) => string|null|typeof INVALID_ATTRIBUTE} [serialize]
26
+ * Property serializer override.
27
+ * @property {(component: InstanceType<typeof import('../core/component.js').Component>, value: any, host: HTMLElement) => void} [set]
28
+ * Live component setter.
29
+ * @property {(component: InstanceType<typeof import('../core/component.js').Component>, host: HTMLElement) => unknown} [get]
30
+ * Live component getter.
31
+ */
32
+ /** @typedef {Record<string, AttributeType|ElementAttribute>} ElementAttributeMap */
33
+ /**
34
+ * @typedef {Object} ElementWrapperOptions
35
+ * @property {boolean} [formAssociated=false] Whether the element participates in native forms.
36
+ * @property {ElementAttributeMap} [attrs={}] Reflected attribute definitions.
37
+ */
38
+
39
+ const COMPONENT_EVENTS = Object.freeze([
40
+ 'change', 'input', 'submit', 'clear', 'open', 'close', 'cancel', 'invalid',
41
+ 'query', 'loaded', 'rowclick', 'rowdblclick', 'sort', 'selectionchange', 'datachange'
42
+ ]);
43
+
44
+ const TOGGLE_ATTRS = attributes({
45
+ checked: bool({ always: true, set: (component, value) => component.set(value, { silent: true }), get: (component) => component.get() }),
46
+ value: text({ default: true }),
47
+ label: text({ default: null, rebuild: true }),
48
+ disabled: bool({ always: true, set: setDisabled })
49
+ });
50
+
51
+ const CHECK_BUTTON_ATTRS = attributes({
52
+ checked: bool({ always: true, set: (component, value) => component.set(value, { silent: true }), get: (component) => component.get() }),
53
+ value: text({ option: null, default: 'on' }),
54
+ label: text({ default: '', set: (component, value) => component.setLabel(value) }),
55
+ icon: bool({ rebuild: true }),
56
+ disabled: bool({ always: true, set: setDisabled })
57
+ });
58
+
59
+ const SELECT_ATTRS = attributes({
60
+ items: json({ default: [], set: (component, value) => component.setItems(value) }),
61
+ value: text({ default: null, set: (component, value) => component.set(value, { silent: true }), get: (component) => component.value }),
62
+ 'value-key': text({ property: 'valueKey', default: 'ID', rebuild: true }),
63
+ 'label-key': text({ property: 'labelKey', default: 'name', rebuild: true }),
64
+ placeholder: text({ default: '', set: (component, value) => { component.refs.input.placeholder = value; } }),
65
+ clearable: bool({ default: false, rebuild: true }),
66
+ filter: text({ default: false, normalize: normalizeFilter, rebuild: true }),
67
+ 'search-keys': json({ property: 'searchKeys', default: null, rebuild: true }),
68
+ 'min-query': number({ property: 'minQuery', default: 0, rebuild: true }),
69
+ debounce: number({ default: 200, rebuild: true }),
70
+ 'list-height': number({ property: 'listHeight', default: 280, rebuild: true }),
71
+ disabled: bool({ always: true, set: setDisabled }),
72
+ required: bool({ option: null, always: true }),
73
+ name: text({ option: null, default: '' })
74
+ });
75
+
76
+ const CHECKLIST_ATTRS = attributes({
77
+ items: json({ default: [], set: (component, value) => component.setItems(value) }),
78
+ value: json({ option: null, default: [], set: (component, value) => component.setValues(value), get: (component) => component.getValues() }),
79
+ 'value-key': text({ property: 'valueKey', default: 'ID', rebuild: true }),
80
+ 'label-key': text({ property: 'labelKey', default: 'name', rebuild: true }),
81
+ 'checked-key': text({ property: 'checkedKey', default: 'on', rebuild: true }),
82
+ search: bool({ rebuild: true }),
83
+ height: number({ default: 280, rebuild: true }),
84
+ 'default-checked': bool({ property: 'defaultChecked', default: false, rebuild: true }),
85
+ disabled: bool({ option: null, always: true, set: setChecklistDisabled }),
86
+ name: text({ option: null, default: '' })
87
+ });
88
+
89
+ const DATEBOX_ATTRS = attributes({
90
+ format: text({ default: '%d.%m.%Y', rebuild: true }),
91
+ time: bool({ always: true, rebuild: true }),
92
+ value: text({ option: null, default: null, normalize: normalizeDateValue, set: (component, value) => component.set(value, { silent: true }), get: (component, host) => dateValue(component.get(), Boolean(host.time)), serialize: serializeDateValue }),
93
+ placeholder: text({ default: null, rebuild: true }),
94
+ clearable: bool({ rebuild: true }),
95
+ disabled: bool({ always: true, set: setDisabled }),
96
+ required: bool({ option: null, always: true }),
97
+ name: text({ option: null, default: '' })
98
+ });
99
+
100
+ const TIMEBOX_ATTRS = attributes({
101
+ value: number({ default: 0, set: (component, value) => component.set(value, { silent: true }), get: (component) => component.get() }),
102
+ unit: text({ default: 'minutes', rebuild: true }),
103
+ seconds: bool({ always: true, rebuild: true }),
104
+ signed: bool({ always: true, rebuild: true }),
105
+ disabled: bool({ always: true, set: setDisabled }),
106
+ name: text({ option: null, default: '' })
107
+ });
108
+
109
+ const SEARCH_ATTRS = attributes({
110
+ value: text({ default: '', set: (component, value) => component.set(value, { silent: true }), get: (component) => component.get() }),
111
+ placeholder: text({ default: '', set: (component, value) => { component.refs.input.placeholder = value; } }),
112
+ clearable: bool({ rebuild: true }),
113
+ debounce: number({ default: 250, rebuild: true })
114
+ });
115
+
116
+ const GROUPBOX_ATTRS = attributes({
117
+ title: text({ default: '', set: (component, value) => component.setTitle(value) }),
118
+ open: bool({ default: false, always: true, set: (component, value) => value ? component.open() : component.close(), get: (component) => component.isOpen() })
119
+ });
120
+
121
+ const TABBOX_ATTRS = attributes({
122
+ tabs: json({ default: [], normalize: normalizeTabs, rebuild: true }),
123
+ active: text({ default: null, set: (component, value) => { if (value !== null) component.openTab(value); }, get: (component) => component.getActive() }),
124
+ variant: text({ default: 'divided', rebuild: true }),
125
+ 'keep-alive': bool({ property: 'keepAlive', rebuild: true })
126
+ });
127
+
128
+ const TABLE_ATTRS = attributes({
129
+ columns: json({ default: [], rebuild: true }),
130
+ data: json({ default: [], set: (component, value) => component.setData(value), get: (component) => component.getData() }),
131
+ 'row-id': text({ property: 'rowId', default: 'ID', rebuild: true }),
132
+ sort: json({ default: null, rebuild: true }),
133
+ 'sort-mode': text({ property: 'sortMode', default: 'local', rebuild: true }),
134
+ selectable: text({ default: false, normalize: normalizeSelectable, rebuild: true }),
135
+ 'sticky-header': bool({ property: 'stickyHeader', rebuild: true }),
136
+ height: text({ default: null, normalize: normalizeNumberOrString, rebuild: true }),
137
+ 'empty-text': text({ property: 'emptyText', default: null, rebuild: true }),
138
+ zebra: bool({ rebuild: true })
139
+ });
140
+
141
+ const DIALOG_ATTRS = attributes({
142
+ title: text({ default: '', set: (component, value) => component.setTitle(value) }),
143
+ size: text({ default: 'md', normalize: normalizeNumberOrString, rebuild: true }),
144
+ buttons: json({ default: [], set: (component, value) => component.setButtons(value) }),
145
+ closable: bool({ rebuild: true }),
146
+ open: bool({ option: null, default: false, always: true, set: (component, value) => value ? component.open() : component.close(), get: (component) => component.isOpen() })
147
+ });
148
+
149
+ const ELEMENTS = Object.freeze([
150
+ ['toggle', Toggle, { formAssociated: true, attrs: TOGGLE_ATTRS }],
151
+ ['check-button', CheckButton, { formAssociated: true, attrs: CHECK_BUTTON_ATTRS }],
152
+ ['select', Select, { formAssociated: true, attrs: SELECT_ATTRS }],
153
+ ['checklist', Checklist, { formAssociated: true, attrs: CHECKLIST_ATTRS }],
154
+ ['datebox', Datebox, { formAssociated: true, attrs: DATEBOX_ATTRS }],
155
+ ['timebox', Timebox, { formAssociated: true, attrs: TIMEBOX_ATTRS }],
156
+ ['search', Search, { formAssociated: false, attrs: SEARCH_ATTRS }],
157
+ ['groupbox', Groupbox, { formAssociated: false, attrs: GROUPBOX_ATTRS }],
158
+ ['tabbox', Tabbox, { formAssociated: false, attrs: TABBOX_ATTRS }],
159
+ ['table', Table, { formAssociated: false, attrs: TABLE_ATTRS }],
160
+ ['dialog', Dialog, { formAssociated: false, attrs: DIALOG_ATTRS }]
161
+ ]);
162
+
163
+ /**
164
+ * Creates a light-DOM custom-element class backed by a Zx component.
165
+ * Groupbox and Dialog wrappers adopt their host's initial children as component content;
166
+ * other wrappers leave unrelated host children untouched and append their component root.
167
+ *
168
+ * @param {typeof import('../core/component.js').Component} ComponentClass Component constructor.
169
+ * @param {ElementWrapperOptions} [wrapperOptions={}] Wrapper behavior.
170
+ * @returns {typeof HTMLElement} Custom-element constructor.
171
+ */
172
+ export function elementFor(ComponentClass, { formAssociated = false, attrs = {} } = {}) {
173
+ const definitions = normalizeAttributes(attrs);
174
+ const attributeNames = Object.freeze(Object.keys(definitions));
175
+ const contentElement = ComponentClass === Groupbox || ComponentClass === Dialog;
176
+
177
+ class ZxElement extends HTMLElement {
178
+ static formAssociated = Boolean(formAssociated);
179
+
180
+ /** @returns {string[]} Attributes observed by this wrapper. */
181
+ static get observedAttributes() {
182
+ return attributeNames;
183
+ }
184
+
185
+ constructor() {
186
+ super();
187
+ this._component = null;
188
+ this._bridgeAbort = null;
189
+ this._reflecting = new Set();
190
+ this._propertyValues = new Map();
191
+ this._defaults = null;
192
+ this._contentNodes = null;
193
+ this._disabledByForm = false;
194
+ this._applying = false;
195
+ this._internals = formAssociated && typeof this.attachInternals === 'function' ? this.attachInternals() : null;
196
+ }
197
+
198
+ /** Instantiates and mounts the backing component. @returns {void} */
199
+ connectedCallback() {
200
+ if (this._component) return;
201
+ this._upgradeProperties(definitions);
202
+ if (contentElement && this._contentNodes === null) this._contentNodes = Array.from(this.childNodes);
203
+ if (formAssociated && this._defaults === null) this._captureDefaults(definitions);
204
+ this._createComponent(ComponentClass, definitions, contentElement);
205
+ }
206
+
207
+ /** Destroys the backing component and all bridged listeners. @returns {void} */
208
+ disconnectedCallback() {
209
+ this._destroyComponent();
210
+ }
211
+
212
+ /**
213
+ * Applies a reflected attribute change through its mapped component setter.
214
+ * @param {string} name Attribute name.
215
+ * @param {string|null} _oldValue Previous serialized value.
216
+ * @param {string|null} newValue Next serialized value.
217
+ * @returns {void}
218
+ */
219
+ attributeChangedCallback(name, _oldValue, newValue) {
220
+ if (this._reflecting.has(name)) return;
221
+ const definition = definitions[name];
222
+ if (!definition) return;
223
+ const coerced = coerceAttribute(newValue, definition.type);
224
+ if (coerced === INVALID_ATTRIBUTE) return;
225
+ this._propertyValues.delete(definition.property);
226
+ const value = coerced === undefined ? cloneDefault(definition.default) : coerced;
227
+ this._applyValue(name, definition, value);
228
+ }
229
+
230
+ /**
231
+ * Applies effective disabled state supplied by a disabled fieldset or form owner.
232
+ * @param {boolean} disabled Effective form-disabled state.
233
+ * @returns {void}
234
+ */
235
+ formDisabledCallback(disabled) {
236
+ this._disabledByForm = Boolean(disabled);
237
+ this._syncEffectiveDisabled(definitions);
238
+ this._syncFormState(ComponentClass);
239
+ }
240
+
241
+ /** Restores the control value captured at initial connection. @returns {void} */
242
+ formResetCallback() {
243
+ if (!this._defaults) return;
244
+ const property = formControlProperty(ComponentClass);
245
+ if (!property || !this._defaults.has(property)) return;
246
+ this._applyControlValue(definitions, property, cloneDefault(this._defaults.get(property)));
247
+ }
248
+
249
+ /**
250
+ * Restores browser-saved form state.
251
+ * @param {File|string|FormData|null} state Saved state supplied by the browser.
252
+ * @param {'restore'|'autocomplete'} _mode Restoration reason.
253
+ * @returns {void}
254
+ */
255
+ formStateRestoreCallback(state, _mode) {
256
+ if (typeof state !== 'string') return;
257
+ const restored = restoredFormValue(ComponentClass, state);
258
+ if (restored === INVALID_ATTRIBUTE) return;
259
+ const property = formControlProperty(ComponentClass);
260
+ if (property) this._applyControlValue(definitions, property, restored);
261
+ }
262
+
263
+ /** @returns {InstanceType<typeof import('../core/component.js').Component>|null} Backing component. */
264
+ get component() {
265
+ return this._component;
266
+ }
267
+
268
+ /** @returns {HTMLFormElement|null} Associated native form. */
269
+ get form() {
270
+ return this._internals?.form ?? null;
271
+ }
272
+
273
+ /** @returns {NodeList|null} Labels associated with this form control. */
274
+ get labels() {
275
+ return this._internals?.labels ?? null;
276
+ }
277
+
278
+ /** @returns {ValidityState|null} Current constraint-validation state. */
279
+ get validity() {
280
+ return this._internals?.validity ?? null;
281
+ }
282
+
283
+ /** @returns {string} Current constraint-validation message. */
284
+ get validationMessage() {
285
+ return this._internals?.validationMessage ?? '';
286
+ }
287
+
288
+ /** @returns {boolean} Whether this control is a constraint-validation candidate. */
289
+ get willValidate() {
290
+ return this._internals?.willValidate ?? false;
291
+ }
292
+
293
+ /** @returns {boolean} Whether the form control satisfies its constraints. */
294
+ checkValidity() {
295
+ return this._internals?.checkValidity() ?? true;
296
+ }
297
+
298
+ /** @returns {boolean} Whether the form control satisfies its constraints. */
299
+ reportValidity() {
300
+ return this._internals?.reportValidity() ?? true;
301
+ }
302
+
303
+ /** @param {Record<string, ElementAttribute>} defs @returns {void} */
304
+ _upgradeProperties(defs) {
305
+ for (const definition of Object.values(defs)) {
306
+ const property = definition.property;
307
+ if (!Object.prototype.hasOwnProperty.call(this, property)) continue;
308
+ const value = this[property];
309
+ delete this[property];
310
+ this[property] = value;
311
+ }
312
+ }
313
+
314
+ /** @param {Record<string, ElementAttribute>} defs @returns {void} */
315
+ _captureDefaults(defs) {
316
+ this._defaults = new Map();
317
+ const property = formControlProperty(ComponentClass);
318
+ if (property) {
319
+ const entry = Object.values(defs).find((definition) => definition.property === property);
320
+ if (entry) this._defaults.set(property, cloneDefault(this._readProperty(entry)));
321
+ }
322
+ }
323
+
324
+ /**
325
+ * @param {typeof import('../core/component.js').Component} Constructor
326
+ * @param {Record<string, ElementAttribute>} defs
327
+ * @param {boolean} adoptsContent
328
+ * @returns {void}
329
+ */
330
+ _createComponent(Constructor, defs, adoptsContent) {
331
+ const options = {};
332
+ for (const [name, definition] of Object.entries(defs)) {
333
+ const supplied = this._propertyValues.has(definition.property) || this.hasAttribute(name) || definition.always;
334
+ if (!supplied || definition.option === null) continue;
335
+ const value = this._currentValue(name, definition);
336
+ if (value === INVALID_ATTRIBUTE || value === undefined) continue;
337
+ options[definition.option] = normalizeValue(definition, value, this);
338
+ }
339
+
340
+ let content = null;
341
+ if (adoptsContent) {
342
+ content = document.createDocumentFragment();
343
+ content.append(...this._contentNodes);
344
+ options.content = content;
345
+ }
346
+
347
+ const component = new Constructor(null, options);
348
+ this._component = component;
349
+ if (ComponentClass === Groupbox && content) component.setContent(content);
350
+ this.append(component.el);
351
+ this._bridgeEvents(component, ComponentClass);
352
+
353
+ for (const [name, definition] of Object.entries(defs)) {
354
+ if (definition.option !== null || !definition.set) continue;
355
+ const supplied = this._propertyValues.has(definition.property) || this.hasAttribute(name) || definition.always;
356
+ if (!supplied) continue;
357
+ const value = this._currentValue(name, definition);
358
+ if (value !== INVALID_ATTRIBUTE && value !== undefined) this._applyValue(name, definition, value, true);
359
+ }
360
+ this._syncEffectiveDisabled(defs);
361
+ this._syncFormState(ComponentClass);
362
+ }
363
+
364
+ /**
365
+ * @param {InstanceType<typeof import('../core/component.js').Component>} component
366
+ * @param {typeof import('../core/component.js').Component} Constructor
367
+ * @returns {void}
368
+ */
369
+ _bridgeEvents(component, Constructor) {
370
+ this._bridgeAbort?.abort();
371
+ this._bridgeAbort = new AbortController();
372
+ for (const type of COMPONENT_EVENTS) {
373
+ component.on(type, (event) => {
374
+ if (this._applying) return;
375
+ this._syncFormState(Constructor);
376
+ const bridged = new CustomEvent(`zx-${type}`, {
377
+ detail: event.detail ?? {},
378
+ bubbles: true,
379
+ composed: true,
380
+ cancelable: true
381
+ });
382
+ this.dispatchEvent(bridged);
383
+ if (bridged.defaultPrevented) event.preventDefault();
384
+ });
385
+ component.el.addEventListener(`zx-${type}`, (event) => event.stopPropagation(), {
386
+ signal: this._bridgeAbort.signal
387
+ });
388
+ }
389
+ }
390
+
391
+ /** @returns {void} */
392
+ _destroyComponent() {
393
+ this._bridgeAbort?.abort();
394
+ this._bridgeAbort = null;
395
+ if (!this._component) return;
396
+ this._component.destroy();
397
+ this._component = null;
398
+ }
399
+
400
+ /** @param {Record<string, ElementAttribute>} defs @param {boolean} adoptsContent @returns {void} */
401
+ _rebuildComponent(defs, adoptsContent = contentElement) {
402
+ if (!this._component) return;
403
+ if (adoptsContent && this._contentNodes) {
404
+ const fragment = document.createDocumentFragment();
405
+ fragment.append(...this._contentNodes);
406
+ }
407
+ this._destroyComponent();
408
+ this._createComponent(ComponentClass, defs, adoptsContent);
409
+ }
410
+
411
+ /**
412
+ * @param {string} name
413
+ * @param {ElementAttribute} definition
414
+ * @param {unknown} value
415
+ * @param {boolean} [initial=false]
416
+ * @returns {void}
417
+ */
418
+ _applyValue(name, definition, value, initial = false) {
419
+ if (!this._component) return;
420
+ if (definition.rebuild && !initial) {
421
+ this._rebuildComponent(definitions);
422
+ return;
423
+ }
424
+ if (!definition.set) {
425
+ this._syncFormState(ComponentClass);
426
+ return;
427
+ }
428
+ this._applying = true;
429
+ try {
430
+ definition.set(this._component, normalizeValue(definition, value, this), this);
431
+ } finally {
432
+ this._applying = false;
433
+ }
434
+ if (name !== 'disabled') this._syncEffectiveDisabled(definitions);
435
+ this._syncFormState(ComponentClass);
436
+ }
437
+
438
+ /** @param {Record<string, ElementAttribute>} defs @returns {void} */
439
+ _syncEffectiveDisabled(defs) {
440
+ if (!this._component || !defs.disabled?.set) return;
441
+ this._applying = true;
442
+ try {
443
+ defs.disabled.set(this._component, this.hasAttribute('disabled') || this._disabledByForm, this);
444
+ } finally {
445
+ this._applying = false;
446
+ }
447
+ }
448
+
449
+ /** @param {typeof import('../core/component.js').Component} Constructor @returns {void} */
450
+ _syncFormState(Constructor) {
451
+ if (!this._internals || !this._component) return;
452
+ const { formValue, state, empty } = currentFormValue(Constructor, this._component, this);
453
+ this._internals.setFormValue(formValue, state);
454
+ if ((Constructor === Select || Constructor === Datebox) && this.hasAttribute('required') && empty) {
455
+ const message = Constructor === Datebox ? 'Please enter a date.' : 'Please select a value.';
456
+ this._internals.setValidity({ valueMissing: true }, message, this._component.refs.input);
457
+ } else {
458
+ this._internals.setValidity({});
459
+ }
460
+ }
461
+
462
+ /**
463
+ * @param {Record<string, ElementAttribute>} defs
464
+ * @param {string} property
465
+ * @param {unknown} value
466
+ * @returns {void}
467
+ */
468
+ _applyControlValue(defs, property, value) {
469
+ const entry = Object.entries(defs).find(([, definition]) => definition.property === property);
470
+ if (!entry) return;
471
+ this._applyValue(entry[0], entry[1], value);
472
+ }
473
+
474
+ /** @param {string} name @param {ElementAttribute} definition @returns {unknown} */
475
+ _currentValue(name, definition) {
476
+ if (this._propertyValues.has(definition.property)) return this._propertyValues.get(definition.property);
477
+ const value = coerceAttribute(this.getAttribute(name), definition.type);
478
+ return value === undefined ? definition.default : value;
479
+ }
480
+
481
+ /** @param {ElementAttribute} definition @returns {unknown} */
482
+ _readProperty(definition) {
483
+ if (this._component && definition.get) return definition.get(this._component, this);
484
+ if (this._propertyValues.has(definition.property)) return this._propertyValues.get(definition.property);
485
+ const value = coerceAttribute(this.getAttribute(definition.attribute), definition.type);
486
+ if (value === INVALID_ATTRIBUTE || value === undefined) return cloneDefault(definition.default);
487
+ return value;
488
+ }
489
+
490
+ /** @param {ElementAttribute} definition @param {unknown} value @returns {void} */
491
+ _writeProperty(definition, value) {
492
+ this._propertyValues.set(definition.property, value);
493
+ const serialized = definition.serialize ?
494
+ definition.serialize(value, this) : serializeAttribute(value, definition.type);
495
+ if (serialized !== INVALID_ATTRIBUTE) {
496
+ this._reflecting.add(definition.attribute);
497
+ try {
498
+ if (serialized === null) this.removeAttribute(definition.attribute);
499
+ else this.setAttribute(definition.attribute, serialized);
500
+ } finally {
501
+ this._reflecting.delete(definition.attribute);
502
+ }
503
+ }
504
+ this._applyValue(definition.attribute, definition, value);
505
+ }
506
+ }
507
+
508
+ for (const definition of Object.values(definitions)) {
509
+ if (Object.prototype.hasOwnProperty.call(ZxElement.prototype, definition.property)) continue;
510
+ Object.defineProperty(ZxElement.prototype, definition.property, {
511
+ configurable: true,
512
+ enumerable: true,
513
+ get() { return this._readProperty(definition); },
514
+ set(value) { this._writeProperty(definition, value); }
515
+ });
516
+ }
517
+
518
+ return ZxElement;
519
+ }
520
+
521
+ /**
522
+ * Registers the supported Zx custom elements. Repeated calls and pre-registered names are safe.
523
+ * This function is deliberately not called by the module.
524
+ *
525
+ * @param {string} [prefix='zx'] Custom-element name prefix.
526
+ * @returns {void}
527
+ */
528
+ export function defineElements(prefix = 'zx') {
529
+ if (typeof customElements === 'undefined') {
530
+ throw new Error('Custom elements are not available in this environment');
531
+ }
532
+ const normalizedPrefix = String(prefix).trim();
533
+ for (const [suffix, Constructor, options] of ELEMENTS) {
534
+ const name = `${normalizedPrefix}-${suffix}`;
535
+ if (!customElements.get(name)) customElements.define(name, elementFor(Constructor, options));
536
+ }
537
+ }
538
+
539
+ /** @param {ElementAttributeMap} attrs @returns {Readonly<Record<string, ElementAttribute>>} */
540
+ function normalizeAttributes(attrs) {
541
+ const normalized = {};
542
+ for (const [attribute, definition] of Object.entries(attrs)) {
543
+ const details = typeof definition === 'string' ? { type: definition } : { ...definition };
544
+ const property = details.property ?? camelCase(attribute);
545
+ normalized[attribute] = Object.freeze({
546
+ type: details.type ?? 'string',
547
+ option: Object.prototype.hasOwnProperty.call(details, 'option') ? details.option : property,
548
+ property,
549
+ default: details.default,
550
+ always: Boolean(details.always),
551
+ rebuild: Boolean(details.rebuild),
552
+ normalize: details.normalize,
553
+ serialize: details.serialize,
554
+ set: details.set,
555
+ get: details.get,
556
+ attribute
557
+ });
558
+ }
559
+ return Object.freeze(normalized);
560
+ }
561
+
562
+ /** @param {Record<string, ElementAttribute>} value @returns {Readonly<Record<string, ElementAttribute>>} */
563
+ function attributes(value) {
564
+ return normalizeAttributes(value);
565
+ }
566
+
567
+ /** @param {Partial<ElementAttribute>} [options={}] @returns {ElementAttribute} */
568
+ function text(options = {}) {
569
+ return { type: 'string', ...options };
570
+ }
571
+
572
+ /** @param {Partial<ElementAttribute>} [options={}] @returns {ElementAttribute} */
573
+ function number(options = {}) {
574
+ return { type: 'number', ...options };
575
+ }
576
+
577
+ /** @param {Partial<ElementAttribute>} [options={}] @returns {ElementAttribute} */
578
+ function bool(options = {}) {
579
+ return { type: 'boolean', default: false, always: true, ...options };
580
+ }
581
+
582
+ /** @param {Partial<ElementAttribute>} [options={}] @returns {ElementAttribute} */
583
+ function json(options = {}) {
584
+ return { type: 'json', ...options };
585
+ }
586
+
587
+ /** @param {string} value @returns {string} */
588
+ function camelCase(value) {
589
+ return value.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
590
+ }
591
+
592
+ /** @param {ElementAttribute} definition @param {unknown} value @param {HTMLElement} host @returns {unknown} */
593
+ function normalizeValue(definition, value, host) {
594
+ return definition.normalize ? definition.normalize(value, host) : value;
595
+ }
596
+
597
+ /** @param {any} component @param {boolean} disabled @returns {void} */
598
+ function setDisabled(component, disabled) {
599
+ if (disabled) component.disable();
600
+ else component.enable();
601
+ }
602
+
603
+ /** @param {any} component @param {boolean} disabled @returns {void} */
604
+ function setChecklistDisabled(component, disabled) {
605
+ component.el.setAttribute('aria-disabled', String(disabled));
606
+ if (component.refs.search) component.refs.search.disabled = disabled;
607
+ for (const input of component.refs.list?.querySelectorAll('input') ?? []) input.disabled = disabled;
608
+ }
609
+
610
+ /** @param {unknown} value @returns {unknown} */
611
+ function normalizeFilter(value) {
612
+ if (value === '' || value === 'true') return 'local';
613
+ if (value === 'false') return false;
614
+ return value;
615
+ }
616
+
617
+ /** @param {unknown} value @returns {unknown} */
618
+ function normalizeSelectable(value) {
619
+ if (value === '' || value === 'true') return 'single';
620
+ if (value === 'false') return false;
621
+ return value;
622
+ }
623
+
624
+ /** @param {unknown} value @returns {unknown} */
625
+ function normalizeNumberOrString(value) {
626
+ if (typeof value !== 'string' || value.trim() === '') return value;
627
+ const numeric = Number(value);
628
+ return Number.isFinite(numeric) ? numeric : value;
629
+ }
630
+
631
+ /** @param {unknown} value @returns {unknown} */
632
+ function normalizeTabs(value) {
633
+ if (!Array.isArray(value)) return value;
634
+ return value.map((tab) => {
635
+ if (!tab || typeof tab !== 'object' || isNode(tab.content) || typeof tab.content === 'function') return tab;
636
+ return { ...tab, content: document.createTextNode(String(tab.content ?? '')) };
637
+ });
638
+ }
639
+
640
+ /** @param {unknown} value @returns {boolean} */
641
+ function isNode(value) {
642
+ return Boolean(value && typeof value === 'object' && typeof value.nodeType === 'number');
643
+ }
644
+
645
+ /** @param {unknown} value @returns {unknown} */
646
+ function normalizeDateValue(value) {
647
+ if (typeof value !== 'string') return value;
648
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(value)) return parseDate(value, '%Y-%m-%dT%H:%M');
649
+ if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return parseDate(value, '%Y-%m-%d');
650
+ return value;
651
+ }
652
+
653
+ /** @param {unknown} value @param {HTMLElement} host @returns {string|null|typeof INVALID_ATTRIBUTE} */
654
+ function serializeDateValue(value, host) {
655
+ if (value instanceof Date) return dateValue(value, Boolean(host.time));
656
+ return serializeAttribute(value, 'string');
657
+ }
658
+
659
+ /** @param {Date|null|unknown} value @param {boolean} includesTime @returns {string} */
660
+ function dateValue(value, includesTime) {
661
+ if (!(value instanceof Date) || Number.isNaN(value.getTime())) return '';
662
+ return formatDate(value, includesTime ? '%Y-%m-%dT%H:%M' : '%Y-%m-%d');
663
+ }
664
+
665
+ /** @param {typeof import('../core/component.js').Component} Constructor @returns {string|null} */
666
+ function formControlProperty(Constructor) {
667
+ if (Constructor === Toggle || Constructor === CheckButton) return 'checked';
668
+ if ([Select, Checklist, Datebox, Timebox].includes(Constructor)) return 'value';
669
+ return null;
670
+ }
671
+
672
+ /**
673
+ * @param {typeof import('../core/component.js').Component} Constructor
674
+ * @param {any} component
675
+ * @param {HTMLElement} host
676
+ * @returns {{formValue: string|null, state: string, empty: boolean}}
677
+ */
678
+ function currentFormValue(Constructor, component, host) {
679
+ if (Constructor === Toggle || Constructor === CheckButton) {
680
+ const checked = component.get();
681
+ let configured = Constructor === Toggle ? true : 'on';
682
+ if (host._propertyValues.has('value')) configured = host._propertyValues.get('value');
683
+ else if (host.hasAttribute('value')) configured = host.getAttribute('value');
684
+ return { formValue: checked ? String(configured) : null, state: String(checked), empty: !checked };
685
+ }
686
+ if (Constructor === Select) {
687
+ const value = component.value;
688
+ return { formValue: value === null ? null : String(value), state: JSON.stringify(value), empty: value === null || value === '' };
689
+ }
690
+ if (Constructor === Checklist) {
691
+ const value = JSON.stringify(component.getValues());
692
+ return { formValue: value, state: value, empty: value === '[]' };
693
+ }
694
+ if (Constructor === Datebox) {
695
+ const value = dateValue(component.get(), Boolean(host.time));
696
+ return { formValue: value || null, state: value, empty: value === '' };
697
+ }
698
+ const value = String(component.get());
699
+ return { formValue: value, state: value, empty: false };
700
+ }
701
+
702
+ /** @param {typeof import('../core/component.js').Component} Constructor @param {string} state @returns {unknown|typeof INVALID_ATTRIBUTE} */
703
+ function restoredFormValue(Constructor, state) {
704
+ if (Constructor === Toggle || Constructor === CheckButton) return state === 'true';
705
+ if (Constructor === Select || Constructor === Checklist) return coerceAttribute(state, 'json');
706
+ if (Constructor === Timebox) return coerceAttribute(state, 'number');
707
+ return state;
708
+ }
709
+
710
+ /** @param {unknown} value @returns {unknown} */
711
+ function cloneDefault(value) {
712
+ if (Array.isArray(value)) return value.slice();
713
+ if (value instanceof Date) return new Date(value.getTime());
714
+ return value;
715
+ }