@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,105 @@
1
+ /*
2
+ * Breadcrumb — the ancestor trail above a record or a browsed hierarchy.
3
+ *
4
+ * Ancestors are quiet (muted, no chrome until hovered) and the current page is the only thing at
5
+ * full strength, so the trail reads as context rather than as a row of buttons. Separators are
6
+ * decorative and hidden from assistive technology.
7
+ */
8
+
9
+ .zx-breadcrumb {
10
+ min-inline-size: 0;
11
+ }
12
+
13
+ .zx-breadcrumb__list {
14
+ display: flex;
15
+ flex-wrap: wrap;
16
+ align-items: center;
17
+ gap: var(--zx-space-1);
18
+ margin: 0;
19
+ padding: 0;
20
+ list-style: none;
21
+ font-size: var(--zx-text-sm);
22
+ }
23
+
24
+ .zx-breadcrumb__item {
25
+ display: inline-flex;
26
+ align-items: center;
27
+ gap: var(--zx-space-1);
28
+ min-inline-size: 0;
29
+ }
30
+
31
+ .zx-breadcrumb__separator {
32
+ display: inline-flex;
33
+ align-items: center;
34
+ color: var(--zx-color-text-placeholder);
35
+ }
36
+
37
+ .zx-breadcrumb__link {
38
+ display: inline-flex;
39
+ align-items: center;
40
+ gap: var(--zx-space-2);
41
+ min-inline-size: 0;
42
+ min-block-size: 0;
43
+ border: 0;
44
+ border-radius: var(--zx-radius-md);
45
+ background: none;
46
+ padding: var(--zx-space-1) var(--zx-space-2);
47
+ color: var(--zx-color-text-muted);
48
+ font: inherit;
49
+ font-size: inherit;
50
+ font-weight: 500;
51
+ text-decoration: none;
52
+ cursor: pointer;
53
+ }
54
+
55
+ .zx-breadcrumb__link:hover {
56
+ background: var(--zx-color-bg-hover);
57
+ color: var(--zx-color-text);
58
+ }
59
+
60
+ .zx-breadcrumb__link:focus-visible {
61
+ outline: 0;
62
+ box-shadow: var(--zx-focus-ring);
63
+ }
64
+
65
+ .zx-breadcrumb__current {
66
+ display: inline-flex;
67
+ align-items: center;
68
+ gap: var(--zx-space-2);
69
+ min-inline-size: 0;
70
+ padding: var(--zx-space-1) var(--zx-space-2);
71
+ color: var(--zx-color-text);
72
+ font-weight: 600;
73
+ }
74
+
75
+ .zx-breadcrumb__label {
76
+ overflow: hidden;
77
+ text-overflow: ellipsis;
78
+ white-space: nowrap;
79
+ }
80
+
81
+ .zx-breadcrumb__icon {
82
+ display: inline-flex;
83
+ flex: none;
84
+ align-items: center;
85
+ color: var(--zx-color-text-muted);
86
+ }
87
+
88
+ /* The collapsed-levels trigger is a MenuButton, shrunk to sit on the trail's baseline. */
89
+ .zx-breadcrumb__more.zx-btn {
90
+ min-block-size: 0;
91
+ border-radius: var(--zx-radius-md);
92
+ padding: var(--zx-space-1) var(--zx-space-2);
93
+ color: var(--zx-color-text-muted);
94
+ }
95
+
96
+ .zx-breadcrumb__more .zx-btn__label:empty {
97
+ display: none;
98
+ }
99
+
100
+ @media (prefers-reduced-motion: no-preference) {
101
+ .zx-breadcrumb__link {
102
+ transition: color var(--zx-dur-fast) var(--zx-ease),
103
+ background-color var(--zx-dur-fast) var(--zx-ease);
104
+ }
105
+ }
@@ -0,0 +1,331 @@
1
+ import { Component } from '../../core/component.js';
2
+ import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
3
+ import { printf } from '../../core/i18n.js';
4
+ import { icon } from '../../core/icons.js';
5
+ import { MenuButton } from '../menu-button/menu-button.js';
6
+
7
+ /** @typedef {'chevron'|'slash'} BreadcrumbSeparator */
8
+
9
+ /**
10
+ * @typedef {Object} BreadcrumbItem
11
+ * @property {string} name Stable item name, unique within the trail.
12
+ * @property {string} label Visible label.
13
+ * @property {string} [icon] Icon name passed to `icon()`, rendered before the label.
14
+ * @property {string} [href] Link destination; items without one render as buttons.
15
+ */
16
+
17
+ /**
18
+ * @typedef {Object} NormalizedBreadcrumbItem
19
+ * @property {string} name Stable item name.
20
+ * @property {string} label Visible label.
21
+ * @property {string|null} icon Icon name, or null.
22
+ * @property {string|null} href Link destination, or null.
23
+ */
24
+
25
+ /**
26
+ * @typedef {Object} BreadcrumbOptions
27
+ * @property {BreadcrumbItem[]} [items=[]] Trail from the root to the current page.
28
+ * @property {number} [maxVisible=0] Number of items rendered inline; 0 renders all of them.
29
+ * Anything in between collapses into an ellipsis menu, always keeping the first and last item.
30
+ * Values below 2 behave as 2.
31
+ * @property {BreadcrumbSeparator} [separator='chevron'] Glyph drawn between items.
32
+ * @property {string|null} [rootIcon=null] Icon for the first item when it carries none itself.
33
+ * @property {Record<string, string>|Record<string, Record<string, string>>} [msg] Localized messages.
34
+ * @property {(event: CustomEvent<BreadcrumbSelectDetail>) => void} [onselect] Selection listener.
35
+ */
36
+
37
+ /**
38
+ * @typedef {Object} BreadcrumbSelectDetail
39
+ * @property {string} name Selected item name.
40
+ * @property {NormalizedBreadcrumbItem} item Copy of the selected item.
41
+ * @property {number} index Zero-based index in the full trail, collapsed items included.
42
+ */
43
+
44
+ /** Separator glyphs. */
45
+ const SEPARATORS = new Set(['chevron', 'slash']);
46
+
47
+ /**
48
+ * The trail of ancestors above the current page. Pairs with {@link Finder} and {@link TreeView}:
49
+ * feed `setItems()` from a `change` event's `nodes`, and turn a `select` back into a path.
50
+ *
51
+ * The last item is the page you are on, so it is plain text marked `aria-current="page"` and never
52
+ * interactive. Every other item is a real `<a>` when it carries an `href` — its navigation is left
53
+ * alone — and a `<button>` otherwise, for trails an application resolves in JavaScript.
54
+ * @fires Breadcrumb#select
55
+ * @extends {Component<BreadcrumbOptions>}
56
+ */
57
+ export class Breadcrumb extends Component {
58
+ static cssName = 'breadcrumb';
59
+
60
+ /** @type {Readonly<BreadcrumbOptions>} */
61
+ static defaults = {
62
+ items: [],
63
+ maxVisible: 0,
64
+ separator: 'chevron',
65
+ rootIcon: null
66
+ };
67
+
68
+ /**
69
+ * Creates or enhances a breadcrumb trail.
70
+ * @param {Element|string|null} target Existing container, selector, or null.
71
+ * @param {BreadcrumbOptions} [options={}] Breadcrumb options.
72
+ */
73
+ constructor(target, options = {}) {
74
+ super(target, options);
75
+ }
76
+
77
+ /**
78
+ * Builds the trail. Runs inside the base constructor, so instance state is initialized here.
79
+ * @returns {HTMLElement}
80
+ */
81
+ render() {
82
+ this._createdRoot = this.el === null;
83
+ this._snapshot = this._createdRoot ? null : snapshotTarget(this.el);
84
+ this._destroyed = false;
85
+ /** @type {NormalizedBreadcrumbItem[]} */
86
+ this._items = [];
87
+ /** @type {MenuButton|null} */
88
+ this._overflow = null;
89
+
90
+ if (!SEPARATORS.has(this.options.separator)) {
91
+ throw new RangeError(`Unknown breadcrumb separator: ${this.options.separator}`);
92
+ }
93
+
94
+ const root = /** @type {HTMLElement} */ (this.el ?? h('nav'));
95
+ this.el = root;
96
+ if (root.tagName !== 'NAV') root.setAttribute('role', 'navigation');
97
+ root.setAttribute('aria-label', this._message('breadcrumb.label', 'Breadcrumb'));
98
+
99
+ const list = h('ol', { class: 'zx-breadcrumb__list', ref: 'list' });
100
+ root.replaceChildren(list);
101
+ this.setItems(this.options.items);
102
+
103
+ this.listen(list, 'click', (event) => {
104
+ const control = /** @type {Element} */ (event.target).closest?.('[data-crumb]');
105
+ if (!control || !list.contains(control)) return;
106
+ // A crumb with an href is an ordinary link: report the selection and let the browser follow.
107
+ this._select(/** @type {HTMLElement} */ (control).dataset.crumb);
108
+ });
109
+ return root;
110
+ }
111
+
112
+ /**
113
+ * Replaces the whole trail.
114
+ * @param {BreadcrumbItem[]} list Items from the root to the current page.
115
+ * @returns {this}
116
+ * @throws {TypeError} When the list or an item is malformed.
117
+ * @throws {RangeError} When two items share a name.
118
+ */
119
+ setItems(list) {
120
+ if (!Array.isArray(list)) throw new TypeError('Breadcrumb items must be an array');
121
+ const names = new Set();
122
+ this._items = list.map((item) => {
123
+ if (!item || typeof item !== 'object' || Array.isArray(item)) {
124
+ throw new TypeError('Breadcrumb item must be an object');
125
+ }
126
+ if (typeof item.name !== 'string' || item.name === '') {
127
+ throw new TypeError('Breadcrumb item name must be a non-empty string');
128
+ }
129
+ if (names.has(item.name)) throw new RangeError(`Breadcrumb item already exists: ${item.name}`);
130
+ names.add(item.name);
131
+ return {
132
+ name: item.name,
133
+ label: String(item.label ?? ''),
134
+ icon: item.icon ? String(item.icon) : null,
135
+ href: item.href == null ? null : String(item.href)
136
+ };
137
+ });
138
+ this._build();
139
+ return this;
140
+ }
141
+
142
+ /**
143
+ * Returns a copy of the trail.
144
+ * @returns {NormalizedBreadcrumbItem[]}
145
+ */
146
+ getItems() {
147
+ return this._items.map((item) => ({ ...item }));
148
+ }
149
+
150
+ /**
151
+ * Appends one level, which becomes the current page.
152
+ * @param {BreadcrumbItem} item Item to append.
153
+ * @returns {this}
154
+ */
155
+ push(item) {
156
+ return this.setItems([...this._items, item]);
157
+ }
158
+
159
+ /**
160
+ * Removes the deepest level.
161
+ * @returns {NormalizedBreadcrumbItem|null} The removed item, or null when the trail was empty.
162
+ */
163
+ pop() {
164
+ if (this._items.length === 0) return null;
165
+ const removed = this._items[this._items.length - 1];
166
+ this.setItems(this._items.slice(0, -1));
167
+ return removed;
168
+ }
169
+
170
+ /**
171
+ * Drops every level below a named item, making it the current page.
172
+ * @param {string} name Item name.
173
+ * @returns {this}
174
+ * @throws {RangeError} When no item carries that name.
175
+ */
176
+ truncateTo(name) {
177
+ const index = this._items.findIndex((item) => item.name === name);
178
+ if (index < 0) throw new RangeError(`Unknown breadcrumb item: ${name}`);
179
+ return this.setItems(this._items.slice(0, index + 1));
180
+ }
181
+
182
+ /**
183
+ * Destroys the overflow menu, aborts listeners, and restores an enhanced target.
184
+ * @returns {void}
185
+ */
186
+ destroy() {
187
+ if (this._destroyed) return;
188
+ this._destroyed = true;
189
+ this._overflow?.destroy();
190
+ this._overflow = null;
191
+ const root = this.el;
192
+ super.destroy();
193
+ if (!this._createdRoot && root) restoreTarget(root, this._snapshot);
194
+ }
195
+
196
+ /* ------------------------------------------------------------------ internals -- */
197
+
198
+ /**
199
+ * Rebuilds the list from the current items.
200
+ * @returns {void}
201
+ */
202
+ _build() {
203
+ const { head, hidden, tail } = this._split();
204
+ const last = this._items[this._items.length - 1] ?? null;
205
+ /** @type {HTMLElement[]} */
206
+ const entries = [];
207
+
208
+ for (const item of head) entries.push(this._renderItem(item, item === last));
209
+ if (hidden.length > 0) entries.push(this._renderOverflow(hidden));
210
+ for (const item of tail) entries.push(this._renderItem(item, item === last));
211
+
212
+ this.refs.list.replaceChildren(...entries.map((content, index) => h('li', {
213
+ class: 'zx-breadcrumb__item'
214
+ }, index === 0 ? content : [this._renderSeparator(), content])));
215
+ if (hidden.length === 0) this._overflow?.toElement().remove();
216
+ }
217
+
218
+ /**
219
+ * Splits the trail into the inline head, the collapsed middle, and the inline tail.
220
+ * @returns {{head: NormalizedBreadcrumbItem[], hidden: NormalizedBreadcrumbItem[], tail: NormalizedBreadcrumbItem[]}}
221
+ */
222
+ _split() {
223
+ const items = this._items;
224
+ const max = Math.trunc(Number(this.options.maxVisible) || 0);
225
+ if (max <= 0 || items.length <= max) return { head: items, hidden: [], tail: [] };
226
+ // Collapsing needs at least the first and the last item to survive.
227
+ const visible = Math.max(2, max);
228
+ if (items.length <= visible) return { head: items, hidden: [], tail: [] };
229
+ const tailCount = visible - 1;
230
+ return {
231
+ head: items.slice(0, 1),
232
+ hidden: items.slice(1, items.length - tailCount),
233
+ tail: items.slice(items.length - tailCount)
234
+ };
235
+ }
236
+
237
+ /**
238
+ * @param {NormalizedBreadcrumbItem} item Item to render.
239
+ * @param {boolean} current Whether this is the page the user is on.
240
+ * @returns {HTMLElement}
241
+ */
242
+ _renderItem(item, current) {
243
+ const children = [];
244
+ const glyph = item.icon ?? (item === this._items[0] ? this.options.rootIcon : null);
245
+ if (glyph) {
246
+ children.push(h('span', { class: 'zx-breadcrumb__icon', ariaHidden: 'true' },
247
+ icon(String(glyph), { size: 13 })));
248
+ }
249
+ children.push(h('span', { class: 'zx-breadcrumb__label' }, item.label));
250
+
251
+ if (current) {
252
+ return h('span', { class: 'zx-breadcrumb__current', ariaCurrent: 'page' }, children);
253
+ }
254
+ if (item.href !== null) {
255
+ return h('a', {
256
+ class: 'zx-breadcrumb__link',
257
+ href: item.href,
258
+ dataset: { crumb: item.name }
259
+ }, children);
260
+ }
261
+ return h('button', {
262
+ class: 'zx-breadcrumb__link',
263
+ type: 'button',
264
+ dataset: { crumb: item.name }
265
+ }, children);
266
+ }
267
+
268
+ /**
269
+ * Renders (and, on first use, creates) the ellipsis menu holding the collapsed levels. The
270
+ * MenuButton is kept for the component's lifetime because its dropdown panel lives on
271
+ * `document.body`; rebuilding the trail only refills its items.
272
+ * @param {NormalizedBreadcrumbItem[]} hidden Collapsed items.
273
+ * @returns {HTMLElement}
274
+ */
275
+ _renderOverflow(hidden) {
276
+ if (!this._overflow) {
277
+ this._overflow = new MenuButton(null, {
278
+ label: '',
279
+ icon: 'dots',
280
+ kind: 'ghost',
281
+ placement: 'bottom-start'
282
+ });
283
+ const trigger = this._overflow.toElement();
284
+ trigger.classList.add('zx-breadcrumb__more');
285
+ trigger.setAttribute('aria-label', this._message('breadcrumb.more', 'Show hidden levels'));
286
+ this._overflow.on('select', (event) => this._select(String(event.detail.value)));
287
+ }
288
+ this._overflow.setItems(hidden.map((item) => ({
289
+ label: item.label,
290
+ value: item.name,
291
+ icon: item.icon ?? undefined
292
+ })));
293
+ return /** @type {HTMLElement} */ (this._overflow.toElement());
294
+ }
295
+
296
+ /** @returns {HTMLElement} */
297
+ _renderSeparator() {
298
+ return h('span', { class: 'zx-breadcrumb__separator', ariaHidden: 'true' },
299
+ this.options.separator === 'slash' ? '/' : icon('chevron-right', { size: 11 }));
300
+ }
301
+
302
+ /**
303
+ * @param {string|undefined} name Item name from a control's `data-crumb`.
304
+ * @returns {void}
305
+ * @fires Breadcrumb#select
306
+ */
307
+ _select(name) {
308
+ const index = this._items.findIndex((item) => item.name === name);
309
+ if (index < 0) return;
310
+ this.emit('select', { name: this._items[index].name, item: { ...this._items[index] }, index });
311
+ }
312
+
313
+ /**
314
+ * Resolves a message through the host translator, falling back to the built-in English text.
315
+ * @param {string} key Message key.
316
+ * @param {string} fallback Built-in text, with `%1`-style placeholders.
317
+ * @param {...unknown} args Interpolation values.
318
+ * @returns {string}
319
+ */
320
+ _message(key, fallback, ...args) {
321
+ const message = this.msg(key, ...args);
322
+ return message === key ? printf(fallback, args) : message;
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Fired when an interactive crumb is chosen, from the trail itself or from the overflow menu. A
328
+ * crumb with an `href` still navigates: the event reports the choice, it does not gate it.
329
+ * @event Breadcrumb#select
330
+ * @type {CustomEvent<BreadcrumbSelectDetail>}
331
+ */
@@ -0,0 +1,104 @@
1
+ .zx-btn {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ gap: var(--zx-space-2);
6
+ min-block-size: var(--zx-control-height);
7
+ border: 1px solid var(--zx-color-border-control);
8
+ border-radius: var(--zx-button-radius, var(--zx-control-radius));
9
+ background: var(--zx-color-bg-control);
10
+ color: var(--zx-color-text);
11
+ padding-inline: var(--zx-control-pad-x);
12
+ font: inherit;
13
+ font-size: var(--zx-control-font-size);
14
+ font-weight: var(--zx-control-font-weight);
15
+ line-height: 1;
16
+ cursor: pointer;
17
+ transition: color var(--zx-dur-fast) var(--zx-ease),
18
+ background-color var(--zx-dur-fast) var(--zx-ease),
19
+ border-color var(--zx-dur-fast) var(--zx-ease),
20
+ box-shadow var(--zx-dur-fast) var(--zx-ease);
21
+ }
22
+
23
+ .zx-btn:hover:not(:disabled) {
24
+ background: var(--zx-color-bg-hover);
25
+ }
26
+
27
+ .zx-btn:active:not(:disabled) {
28
+ border-color: var(--zx-color-border-strong);
29
+ }
30
+
31
+ .zx-btn:focus-visible {
32
+ box-shadow: var(--zx-focus-ring);
33
+ }
34
+
35
+ .zx-btn:disabled {
36
+ opacity: 0.55;
37
+ cursor: not-allowed;
38
+ }
39
+
40
+ .zx-btn[data-kind="primary"] {
41
+ border-color: var(--zx-color-accent);
42
+ background: var(--zx-color-accent);
43
+ color: var(--zx-color-on-accent);
44
+ }
45
+
46
+ .zx-btn[data-kind="primary"]:hover:not(:disabled) {
47
+ border-color: var(--zx-color-accent-hover);
48
+ background: var(--zx-color-accent-hover);
49
+ }
50
+
51
+ .zx-btn[data-kind="primary"]:active:not(:disabled) {
52
+ border-color: var(--zx-color-accent-active);
53
+ background: var(--zx-color-accent-active);
54
+ }
55
+
56
+ .zx-btn[data-kind="danger"] {
57
+ border-color: var(--zx-color-danger);
58
+ background: var(--zx-color-danger);
59
+ color: var(--zx-color-on-accent);
60
+ }
61
+
62
+ .zx-btn[data-kind="danger"]:hover:not(:disabled) {
63
+ background: color-mix(in srgb, var(--zx-color-danger) 88%, var(--zx-color-text));
64
+ }
65
+
66
+ .zx-btn[data-kind="ghost"] {
67
+ border-color: transparent;
68
+ background: none;
69
+ }
70
+
71
+ .zx-btn[data-kind="ghost"]:hover:not(:disabled) {
72
+ border-color: transparent;
73
+ background: var(--zx-color-bg-hover);
74
+ }
75
+
76
+ .zx-btn[data-size="sm"] {
77
+ min-block-size: calc(var(--zx-control-height) - var(--zx-space-1));
78
+ padding-inline: var(--zx-space-2);
79
+ font-size: var(--zx-text-sm);
80
+ }
81
+
82
+ .zx-btn > svg,
83
+ .zx-btn > .zx-icon {
84
+ flex: none;
85
+ }
86
+
87
+ .zx-btn-group {
88
+ display: inline-flex;
89
+ }
90
+
91
+ .zx-btn-group > .zx-btn:not(:first-child) {
92
+ margin-inline-start: -1px;
93
+ border-start-start-radius: 0;
94
+ border-end-start-radius: 0;
95
+ }
96
+
97
+ .zx-btn-group > .zx-btn:not(:last-child) {
98
+ border-start-end-radius: 0;
99
+ border-end-end-radius: 0;
100
+ }
101
+
102
+ .zx-btn-group > .zx-btn:focus-visible {
103
+ z-index: 1;
104
+ }
@@ -0,0 +1,76 @@
1
+ import { h } from '../../core/dom.js';
2
+ import { icon as createIcon } from '../../core/icons.js';
3
+
4
+ /**
5
+ * @typedef {Object} ButtonOptions
6
+ * @property {string} [label=''] Visible button label.
7
+ * @property {string|null} [icon=null] Icon name from `icons.js`.
8
+ * @property {'default'|'primary'|'danger'|'ghost'} [kind='default'] Visual intent.
9
+ * @property {'md'|'sm'} [size='md'] Control size.
10
+ * @property {boolean} [disabled=false] Whether the button is disabled.
11
+ * @property {string} [title] Native title text.
12
+ * @property {(event: MouseEvent) => void} [onclick] Click callback.
13
+ */
14
+
15
+ /** @type {Readonly<ButtonOptions>} */
16
+ const defaults = Object.freeze({
17
+ label: '',
18
+ icon: null,
19
+ kind: 'default',
20
+ size: 'md',
21
+ disabled: false
22
+ });
23
+
24
+ /**
25
+ * Creates a styled native button.
26
+ * @param {ButtonOptions} [opts={}] Button options.
27
+ * @returns {HTMLButtonElement}
28
+ */
29
+ export function button(opts = {}) {
30
+ const options = { ...defaults, ...opts };
31
+ const element = /** @type {HTMLButtonElement} */ (h('button', {
32
+ class: 'zx-btn',
33
+ type: 'button',
34
+ disabled: Boolean(options.disabled),
35
+ title: options.title,
36
+ dataset: {
37
+ kind: normalizeKind(options.kind),
38
+ size: normalizeSize(options.size)
39
+ }
40
+ }));
41
+
42
+ if (options.icon) {
43
+ element.append(createIcon(options.icon, { size: 16 }));
44
+ }
45
+ if (options.label !== '') {
46
+ element.append(h('span', { class: 'zx-btn__label' }, String(options.label)));
47
+ }
48
+ if (!options.label && options.title) element.setAttribute('aria-label', options.title);
49
+ if (typeof options.onclick === 'function') element.onclick = options.onclick;
50
+ return element;
51
+ }
52
+
53
+ /**
54
+ * Groups buttons into a joined control.
55
+ * @param {HTMLButtonElement[]} buttons Buttons to group.
56
+ * @returns {HTMLDivElement}
57
+ */
58
+ export function buttonGroup(buttons) {
59
+ const group = /** @type {HTMLDivElement} */ (h('div', {
60
+ class: 'zx-btn-group',
61
+ role: 'group'
62
+ }));
63
+ group.append(...buttons);
64
+ return group;
65
+ }
66
+
67
+ /** @param {unknown} value @returns {'default'|'primary'|'danger'|'ghost'} */
68
+ function normalizeKind(value) {
69
+ return ['primary', 'danger', 'ghost'].includes(String(value)) ?
70
+ /** @type {'primary'|'danger'|'ghost'} */ (value) : 'default';
71
+ }
72
+
73
+ /** @param {unknown} value @returns {'md'|'sm'} */
74
+ function normalizeSize(value) {
75
+ return value === 'sm' ? 'sm' : 'md';
76
+ }
@@ -0,0 +1,66 @@
1
+ .zx-check-button {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ gap: var(--zx-space-2);
6
+ min-block-size: var(--zx-control-height);
7
+ border: 1px solid var(--zx-color-border-control);
8
+ border-radius: var(--zx-control-radius);
9
+ background: var(--zx-color-bg-control);
10
+ color: var(--zx-color-text);
11
+ padding-inline: var(--zx-control-pad-x);
12
+ font: inherit;
13
+ font-size: var(--zx-control-font-size);
14
+ cursor: pointer;
15
+ }
16
+
17
+ .zx-check-button:hover:not(:disabled) {
18
+ background: var(--zx-color-bg-hover);
19
+ }
20
+
21
+ .zx-check-button[aria-pressed="true"] {
22
+ border-color: var(--zx-color-accent);
23
+ background: var(--zx-color-bg-selected);
24
+ color: var(--zx-color-accent);
25
+ }
26
+
27
+ .zx-check-button:focus-visible {
28
+ box-shadow: var(--zx-focus-ring);
29
+ }
30
+
31
+ .zx-check-button:disabled {
32
+ opacity: 0.55;
33
+ cursor: not-allowed;
34
+ }
35
+
36
+ /* Both glyphs occupy the same grid cell, so swapping them never shifts the label. */
37
+ .zx-check-button__icon {
38
+ display: inline-grid;
39
+ flex: none;
40
+ grid-template-areas: "state";
41
+ place-items: center;
42
+ }
43
+
44
+ .zx-check-button__off,
45
+ .zx-check-button__on {
46
+ display: inline-grid;
47
+ grid-area: state;
48
+ place-items: center;
49
+ }
50
+
51
+ .zx-check-button__off {
52
+ color: var(--zx-color-text-muted);
53
+ }
54
+
55
+ .zx-check-button__on,
56
+ .zx-check-button[aria-pressed="true"] .zx-check-button__off {
57
+ visibility: hidden;
58
+ }
59
+
60
+ .zx-check-button[aria-pressed="true"] .zx-check-button__on {
61
+ visibility: visible;
62
+ }
63
+
64
+ .zx-check-button__icon .zx-icon {
65
+ display: block;
66
+ }