@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,450 @@
1
+ import { Component } from '../../core/component.js';
2
+ import { h } from '../../core/dom.js';
3
+ import { isElement, uid } from '../../core/util.js';
4
+ import { button } from '../button/button.js';
5
+ import { MenuButton } from '../menu-button/menu-button.js';
6
+
7
+ /** @typedef {Node|Component|(() => Node|Component)} NavigationPanelContent */
8
+
9
+ /**
10
+ * @typedef {Object} NavigationItem
11
+ * @property {string} name Stable item name.
12
+ * @property {string} title Visible item title.
13
+ * @property {string|null} [badge=null] Optional badge text.
14
+ * @property {NavigationPanelContent} [content] Optional panel content; its presence enables tab semantics.
15
+ * @property {string} [href] Optional link destination for plain navigation.
16
+ * @property {boolean} [disabled=false] Whether the item is unavailable.
17
+ * @property {(name: string, item: NavigationItem, navigation: NavigationBar) => void} [onselect] Selection callback.
18
+ */
19
+
20
+ /**
21
+ * @typedef {Object} NavigationActionDescriptor
22
+ * @property {string} [label=''] Visible button label.
23
+ * @property {string|null} [icon=null] Icon name from the kernel icon set.
24
+ * @property {'default'|'primary'|'danger'|'ghost'} [kind='default'] Visual intent.
25
+ * @property {'md'|'sm'} [size='md'] Control size.
26
+ * @property {boolean} [disabled=false] Whether the action is disabled.
27
+ * @property {string} [title] Native title text.
28
+ * @property {(event: MouseEvent) => void} [onclick] Click callback.
29
+ */
30
+ /** @typedef {Element|NavigationActionDescriptor} NavigationAction */
31
+
32
+ /**
33
+ * @typedef {Object} NavigationBarOptions
34
+ * @property {string} [title=''] Brand or application title.
35
+ * @property {NavigationItem[]} [items=[]] Navigation items.
36
+ * @property {string|null} [active=null] Initially active item, or the first enabled item.
37
+ * @property {NavigationAction[]} [actions=[]] Right-aligned action elements or descriptors.
38
+ * @property {(event: CustomEvent<{name: string}>) => void} [onchange] Change callback.
39
+ */
40
+
41
+ /**
42
+ * @typedef {Object} NavigationRecord
43
+ * @property {NavigationItem} definition Normalized item definition.
44
+ * @property {HTMLElement} item Rendered link or button.
45
+ * @property {HTMLElement} badge Badge element.
46
+ * @property {HTMLElement|null} panel Optional tab panel.
47
+ * @property {boolean} built Whether panel content has been mounted.
48
+ */
49
+
50
+ /**
51
+ * Responsive application navigation with MenuButton overflow.
52
+ * @fires NavigationBar#change
53
+ * @extends {Component<NavigationBarOptions>}
54
+ */
55
+ export class NavigationBar extends Component {
56
+ static cssName = 'navigation-bar';
57
+
58
+ /** @type {Readonly<NavigationBarOptions>} */
59
+ static defaults = {
60
+ title: '',
61
+ items: [],
62
+ active: null,
63
+ actions: []
64
+ };
65
+
66
+ /**
67
+ * Creates or enhances an application navigation bar.
68
+ * @param {Element|string|null} target Existing container, selector, or null.
69
+ * @param {NavigationBarOptions} [options={}] Navigation options.
70
+ */
71
+ constructor(target, options = {}) {
72
+ super(target, options);
73
+ this._overflow = new MenuButton(this.refs.moreTrigger, {
74
+ label: 'More',
75
+ icon: 'dots',
76
+ kind: 'ghost',
77
+ items: [],
78
+ placement: 'bottom-end'
79
+ });
80
+ this._overflow.on('select', (event) => {
81
+ const record = this._find(String(event.detail.value));
82
+ if (!record || record.definition.disabled) return;
83
+ this.setActive(record.definition.name);
84
+ record.definition.onselect?.(record.definition.name, record.definition, this);
85
+ if (record.definition.href) globalThis.location?.assign(record.definition.href);
86
+ });
87
+ this._syncOverflow();
88
+ }
89
+
90
+ /** @returns {HTMLElement} */
91
+ render() {
92
+ const created = !this.el;
93
+ const root = /** @type {HTMLElement} */ (this.el ?? h('section'));
94
+ this.el = root;
95
+ this._createdRoot = created;
96
+ this._original = created ? null : snapshot(root);
97
+ this._cleaned = false;
98
+ /** @type {NavigationRecord[]} */
99
+ this._items = [];
100
+ this._activeName = null;
101
+ this._tabMode = false;
102
+ this._overflow = null;
103
+
104
+ const bar = h('header', { class: 'zx-navigation-bar__bar' },
105
+ h('div', { class: 'zx-navigation-bar__brand', ref: 'title' }),
106
+ h('nav', {
107
+ class: 'zx-navigation-bar__navigation',
108
+ ariaLabel: 'Primary navigation'
109
+ },
110
+ h('div', { class: 'zx-navigation-bar__items', ref: 'items' }),
111
+ h('div', { class: 'zx-navigation-bar__more', ref: 'more' },
112
+ h('button', { class: 'zx-navigation-bar__more-trigger', ref: 'moreTrigger' })
113
+ )),
114
+ h('div', {
115
+ class: 'zx-navigation-bar__actions',
116
+ ref: 'actions',
117
+ role: 'group',
118
+ ariaLabel: 'Application actions'
119
+ })
120
+ );
121
+ const panels = h('div', {
122
+ class: 'zx-navigation-bar__panels',
123
+ ref: 'panels',
124
+ hidden: true
125
+ });
126
+ root.replaceChildren(bar, panels);
127
+ this.setTitle(this.options.title);
128
+ this.setItems(this.options.items);
129
+ this.setActions(this.options.actions);
130
+ this.listen(this.refs.items, 'keydown', (event) => this._onTabKeydown(event));
131
+ return root;
132
+ }
133
+
134
+ /**
135
+ * Replaces the brand or application title.
136
+ * @param {string} title Next title.
137
+ * @returns {this}
138
+ */
139
+ setTitle(title) {
140
+ this.refs.title.textContent = String(title ?? '');
141
+ return this;
142
+ }
143
+
144
+ /**
145
+ * Replaces navigation items.
146
+ * @param {NavigationItem[]} list Item definitions.
147
+ * @returns {this}
148
+ */
149
+ setItems(list) {
150
+ if (!Array.isArray(list)) throw new TypeError('NavigationBar items must be an array');
151
+ const previous = this._activeName;
152
+ const names = new Set();
153
+ const definitions = list.map((item) => {
154
+ if (!item || typeof item !== 'object' || Array.isArray(item)) {
155
+ throw new TypeError('Navigation item must be an object');
156
+ }
157
+ if (typeof item.name !== 'string' || item.name === '') {
158
+ throw new TypeError('Navigation item name must be a non-empty string');
159
+ }
160
+ if (names.has(item.name)) throw new RangeError(`Navigation item already exists: ${item.name}`);
161
+ names.add(item.name);
162
+ if (Object.hasOwn(item, 'content') && !isPanelContent(item.content)) {
163
+ throw new TypeError(`Navigation panel content for ${item.name} is invalid`);
164
+ }
165
+ const definition = {
166
+ name: item.name,
167
+ title: String(item.title ?? ''),
168
+ badge: item.badge == null ? null : String(item.badge),
169
+ href: item.href == null ? undefined : String(item.href),
170
+ disabled: Boolean(item.disabled),
171
+ onselect: typeof item.onselect === 'function' ? item.onselect : undefined
172
+ };
173
+ if (Object.hasOwn(item, 'content')) definition.content = item.content;
174
+ return definition;
175
+ });
176
+
177
+ const tabMode = definitions.some((item) => Object.hasOwn(item, 'content'));
178
+ if (tabMode && definitions.some((item) => !Object.hasOwn(item, 'content'))) {
179
+ throw new TypeError('NavigationBar panel items must all define content');
180
+ }
181
+ this._items = [];
182
+ this._activeName = null;
183
+ this._tabMode = tabMode;
184
+ this.refs.items.replaceChildren();
185
+ this.refs.panels.replaceChildren();
186
+ this.refs.panels.hidden = !this._tabMode;
187
+ if (this._tabMode) {
188
+ this.refs.items.setAttribute('role', 'tablist');
189
+ this.refs.items.setAttribute('aria-orientation', 'horizontal');
190
+ } else {
191
+ this.refs.items.removeAttribute('role');
192
+ this.refs.items.removeAttribute('aria-orientation');
193
+ }
194
+
195
+ for (const definition of definitions) this._appendItem(definition);
196
+ const requested = previous ?? this.options.active;
197
+ const initial = requested === null ? null : this._find(requested);
198
+ const active = initial && !initial.definition.disabled ? initial : this._enabledItems()[0];
199
+ if (active) this._applyActive(active);
200
+ this._syncOverflow();
201
+ return this;
202
+ }
203
+
204
+ /**
205
+ * Selects a navigation item.
206
+ * @param {string} name Item name.
207
+ * @returns {this}
208
+ * @fires NavigationBar#change
209
+ */
210
+ setActive(name) {
211
+ const record = this._find(name);
212
+ if (!record || record.definition.disabled || name === this._activeName) return this;
213
+ this._applyActive(record);
214
+ this.emit('change', { name: record.definition.name });
215
+ return this;
216
+ }
217
+
218
+ /**
219
+ * Sets or clears an item badge.
220
+ * @param {string} name Item name.
221
+ * @param {string|null} text Badge text, or null to remove it.
222
+ * @returns {this}
223
+ */
224
+ setBadge(name, text) {
225
+ const record = this._require(name);
226
+ record.definition.badge = text === null ? null : String(text);
227
+ record.badge.textContent = record.definition.badge ?? '';
228
+ record.badge.hidden = record.definition.badge === null;
229
+ this._syncOverflow();
230
+ return this;
231
+ }
232
+
233
+ /**
234
+ * Replaces right-aligned application actions.
235
+ * @param {NavigationAction[]} list Action elements or button descriptors.
236
+ * @returns {this}
237
+ */
238
+ setActions(list) {
239
+ if (!Array.isArray(list)) throw new TypeError('NavigationBar actions must be an array');
240
+ const elements = list.map((item) => {
241
+ if (isElement(item)) return item;
242
+ if (!item || typeof item !== 'object' || Array.isArray(item)) {
243
+ throw new TypeError('NavigationBar actions must be Elements or button descriptors');
244
+ }
245
+ const descriptor = { ...item };
246
+ const onclick = descriptor.onclick;
247
+ delete descriptor.onclick;
248
+ const element = button(descriptor);
249
+ if (typeof onclick === 'function') this.listen(element, 'click', onclick);
250
+ return element;
251
+ });
252
+ this.refs.actions.replaceChildren(...elements);
253
+ return this;
254
+ }
255
+
256
+ /** @returns {void} */
257
+ destroy() {
258
+ if (this._cleaned) return;
259
+ this._cleaned = true;
260
+ this._overflow?.destroy();
261
+ super.destroy();
262
+ if (!this._createdRoot && this._original) restore(this.el, this._original);
263
+ }
264
+
265
+ /** @param {NavigationItem} definition @returns {void} */
266
+ _appendItem(definition) {
267
+ const tabId = uid('zx-navigation-item');
268
+ const panelId = uid('zx-navigation-panel');
269
+ const badge = h('span', {
270
+ class: 'zx-navigation-bar__badge',
271
+ hidden: definition.badge === null
272
+ }, definition.badge ?? '');
273
+ const properties = {
274
+ class: 'zx-navigation-bar__item',
275
+ id: tabId,
276
+ ariaDisabled: definition.disabled ? 'true' : null
277
+ };
278
+ let item;
279
+ if (this._tabMode) {
280
+ item = h('button', {
281
+ ...properties,
282
+ type: 'button',
283
+ role: 'tab',
284
+ tabindex: '-1',
285
+ ariaControls: panelId,
286
+ ariaSelected: 'false'
287
+ }, h('span', { class: 'zx-navigation-bar__label' }, definition.title), badge);
288
+ } else if (definition.href) {
289
+ item = h('a', {
290
+ ...properties,
291
+ href: definition.href
292
+ }, h('span', { class: 'zx-navigation-bar__label' }, definition.title), badge);
293
+ } else {
294
+ item = h('button', {
295
+ ...properties,
296
+ type: 'button'
297
+ }, h('span', { class: 'zx-navigation-bar__label' }, definition.title), badge);
298
+ }
299
+
300
+ let panel = null;
301
+ if (this._tabMode) {
302
+ panel = h('div', {
303
+ class: 'zx-navigation-bar__panel',
304
+ id: panelId,
305
+ role: 'tabpanel',
306
+ tabindex: '0',
307
+ ariaLabelledby: tabId,
308
+ hidden: true
309
+ });
310
+ this.refs.panels.append(panel);
311
+ }
312
+ /** @type {NavigationRecord} */
313
+ const record = { definition, item, badge, panel, built: false };
314
+ this._items.push(record);
315
+ this.refs.items.append(item);
316
+ this.listen(item, 'click', (event) => {
317
+ if (record.definition.disabled) {
318
+ event.preventDefault();
319
+ return;
320
+ }
321
+ this.setActive(record.definition.name);
322
+ record.definition.onselect?.(record.definition.name, record.definition, this);
323
+ });
324
+ }
325
+
326
+ /** @param {NavigationRecord} record @returns {void} */
327
+ _applyActive(record) {
328
+ if (this._tabMode) this._ensurePanel(record);
329
+ for (const item of this._items) {
330
+ const active = item === record;
331
+ if (this._tabMode) {
332
+ item.item.setAttribute('aria-selected', String(active));
333
+ item.item.tabIndex = active ? 0 : -1;
334
+ if (item.panel) item.panel.hidden = !active;
335
+ } else if (active) {
336
+ item.item.setAttribute('aria-current', 'page');
337
+ } else {
338
+ item.item.removeAttribute('aria-current');
339
+ }
340
+ }
341
+ this._activeName = record.definition.name;
342
+ }
343
+
344
+ /** @param {NavigationRecord} record @returns {void} */
345
+ _ensurePanel(record) {
346
+ if (!record.panel || record.built) return;
347
+ const source = record.definition.content;
348
+ const content = typeof source === 'function' ? source() : source;
349
+ const node = content instanceof Component ? content.toElement() : content;
350
+ if (!node || typeof node.nodeType !== 'number') {
351
+ throw new TypeError(`Navigation panel factory for ${record.definition.name} must return a Node or Component`);
352
+ }
353
+ record.panel.append(node);
354
+ record.built = true;
355
+ }
356
+
357
+ /** @param {KeyboardEvent} event @returns {void} */
358
+ _onTabKeydown(event) {
359
+ if (!this._tabMode) return;
360
+ const item = event.target.closest?.('[role="tab"]');
361
+ if (!item || !this.refs.items.contains(item)) return;
362
+ const current = this._items.find((record) => record.item === item);
363
+ if (!current) return;
364
+ const enabled = this._enabledItems();
365
+ if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
366
+ event.preventDefault();
367
+ const offset = event.key === 'ArrowRight' ? 1 : -1;
368
+ const index = enabled.indexOf(current);
369
+ const next = enabled[(index + offset + enabled.length) % enabled.length];
370
+ if (next) this._focusTab(next);
371
+ return;
372
+ }
373
+ if (event.key === 'Home' || event.key === 'End') {
374
+ event.preventDefault();
375
+ const next = event.key === 'Home' ? enabled[0] : enabled[enabled.length - 1];
376
+ if (next) this._focusTab(next);
377
+ return;
378
+ }
379
+ if (event.key === 'Enter' || event.key === ' ') {
380
+ event.preventDefault();
381
+ this.setActive(current.definition.name);
382
+ }
383
+ }
384
+
385
+ /** @returns {void} */
386
+ _syncOverflow() {
387
+ if (!this._overflow) return;
388
+ this.refs.more.dataset.empty = this._items.length === 0 ? 'true' : 'false';
389
+ this._overflow.setItems(this._items.map((record) => ({
390
+ label: record.definition.badge === null ? record.definition.title :
391
+ `${record.definition.title} (${record.definition.badge})`,
392
+ value: record.definition.name,
393
+ disabled: record.definition.disabled
394
+ })));
395
+ }
396
+
397
+ /** @param {NavigationRecord} record @returns {void} */
398
+ _focusTab(record) {
399
+ for (const item of this._items) item.item.tabIndex = item === record ? 0 : -1;
400
+ record.item.focus();
401
+ }
402
+
403
+ /** @returns {NavigationRecord[]} */
404
+ _enabledItems() {
405
+ return this._items.filter((record) => !record.definition.disabled);
406
+ }
407
+
408
+ /** @param {string} name @returns {NavigationRecord|null} */
409
+ _find(name) {
410
+ return this._items.find((record) => record.definition.name === name) ?? null;
411
+ }
412
+
413
+ /** @param {string} name @returns {NavigationRecord} */
414
+ _require(name) {
415
+ const record = this._find(name);
416
+ if (!record) throw new RangeError(`Unknown navigation item: ${name}`);
417
+ return record;
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Fired after the active item changes.
423
+ * @event NavigationBar#change
424
+ * @type {CustomEvent<{name: string}>}
425
+ */
426
+
427
+ /** @param {unknown} content @returns {content is NavigationPanelContent} */
428
+ function isPanelContent(content) {
429
+ if (typeof content === 'function' || content instanceof Component) return true;
430
+ return Boolean(content && typeof content === 'object' && typeof content.nodeType === 'number');
431
+ }
432
+
433
+ /** @param {Element} element @returns {{attributes: Array<[string, string]>, children: Node[]}} */
434
+ function snapshot(element) {
435
+ return {
436
+ attributes: Array.from(element.attributes, (attribute) => [attribute.name, attribute.value]),
437
+ children: Array.from(element.childNodes)
438
+ };
439
+ }
440
+
441
+ /**
442
+ * @param {Element} element
443
+ * @param {{attributes: Array<[string, string]>, children: Node[]}} original
444
+ * @returns {void}
445
+ */
446
+ function restore(element, original) {
447
+ for (const attribute of Array.from(element.attributes)) element.removeAttribute(attribute.name);
448
+ for (const [name, value] of original.attributes) element.setAttribute(name, value);
449
+ element.replaceChildren(...original.children);
450
+ }
@@ -0,0 +1,40 @@
1
+ import { Field } from '../field/field.js';
2
+ import { NumberField } from './number-field.js';
3
+
4
+ /**
5
+ * Registers the NumberField-backed `number` field type.
6
+ * @returns {void}
7
+ */
8
+ export function registerNumberFieldAdapter() {
9
+ Field.register('number', (field, options) => {
10
+ const numberField = field.own(new NumberField(null, {
11
+ label: options.label ? String(options.label) : undefined,
12
+ placeholder: options.placeholder,
13
+ required: options.required,
14
+ ...(options.props ?? {})
15
+ }));
16
+ let setting = false;
17
+ // The inner <input> also fires a native `change`; without this the Field would see two
18
+ // changes per edit, the first still carrying the previous committed value.
19
+ field.listen(numberField.el, 'change', (event) => event.stopImmediatePropagation());
20
+ numberField.on('change', (event) => {
21
+ if (!setting) field.emit('change', { value: event.detail.value });
22
+ });
23
+ return {
24
+ el: numberField.el,
25
+ get: () => numberField.get(),
26
+ set: (value) => {
27
+ setting = true;
28
+ try {
29
+ numberField.set(value === '' || value === null || value === undefined ? null : value, {
30
+ silent: true
31
+ });
32
+ } finally {
33
+ setting = false;
34
+ }
35
+ },
36
+ focus: () => numberField.focus(),
37
+ setDisabled: (value) => (value ? numberField.disable() : numberField.enable())
38
+ };
39
+ });
40
+ }
@@ -0,0 +1,77 @@
1
+ .zx-number-field {
2
+ display: inline-flex;
3
+ align-items: stretch;
4
+ min-block-size: var(--zx-control-height);
5
+ border: 1px solid var(--zx-color-border-control);
6
+ border-radius: var(--zx-control-radius);
7
+ background: var(--zx-color-bg-control);
8
+ color: var(--zx-color-text);
9
+ font-size: var(--zx-control-font-size);
10
+ }
11
+
12
+ .zx-number-field:focus-within {
13
+ box-shadow: var(--zx-focus-ring);
14
+ }
15
+
16
+ .zx-number-field[data-disabled] {
17
+ opacity: 0.55;
18
+ }
19
+
20
+ .zx-number-field__input {
21
+ min-inline-size: 5em;
22
+ min-block-size: 0;
23
+ flex: 1 1 auto;
24
+ border: 0;
25
+ border-radius: 0;
26
+ background: none;
27
+ padding-inline: var(--zx-space-2);
28
+ font-variant-numeric: tabular-nums;
29
+ text-align: end;
30
+ }
31
+
32
+ .zx-number-field__input:focus-visible {
33
+ box-shadow: none;
34
+ }
35
+
36
+ .zx-number-field__unit {
37
+ display: grid;
38
+ place-items: center;
39
+ padding-inline-end: var(--zx-space-2);
40
+ color: var(--zx-color-text-muted);
41
+ font-size: var(--zx-text-sm);
42
+ }
43
+
44
+ .zx-number-field__step {
45
+ display: inline-grid;
46
+ place-items: center;
47
+ min-block-size: 0;
48
+ inline-size: calc(var(--zx-control-height) - 6px);
49
+ flex: none;
50
+ padding: 0;
51
+ border: 0;
52
+ border-radius: 0;
53
+ background: none;
54
+ color: var(--zx-color-text-muted);
55
+ cursor: pointer;
56
+ }
57
+
58
+ .zx-number-field__step:hover:not(:disabled) {
59
+ background: var(--zx-color-bg-hover);
60
+ color: var(--zx-color-text);
61
+ }
62
+
63
+ .zx-number-field__step:disabled {
64
+ cursor: not-allowed;
65
+ }
66
+
67
+ .zx-number-field__step[data-step="down"] {
68
+ border-start-start-radius: var(--zx-control-radius);
69
+ border-end-start-radius: var(--zx-control-radius);
70
+ border-inline-end: 1px solid var(--zx-color-border-control);
71
+ }
72
+
73
+ .zx-number-field__step[data-step="up"] {
74
+ border-start-end-radius: var(--zx-control-radius);
75
+ border-end-end-radius: var(--zx-control-radius);
76
+ border-inline-start: 1px solid var(--zx-color-border-control);
77
+ }