@zeyos/zx 4.4.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/README.md +44 -11
  3. package/dist/types/components/account-menu/account-menu.d.ts +8 -0
  4. package/dist/types/components/activity-list/activity-list.d.ts +31 -1
  5. package/dist/types/components/app-icon/app-icon.d.ts +14 -2
  6. package/dist/types/components/app-sidebar/app-sidebar.d.ts +293 -17
  7. package/dist/types/components/avatar/avatar.d.ts +46 -7
  8. package/dist/types/components/badge/badge.d.ts +21 -1
  9. package/dist/types/components/button/button.d.ts +15 -0
  10. package/dist/types/components/calendar/calendar.d.ts +67 -5
  11. package/dist/types/components/card-view/card-view.d.ts +7 -4
  12. package/dist/types/components/card-view/record-card.d.ts +11 -0
  13. package/dist/types/components/chart/chart.d.ts +10 -0
  14. package/dist/types/components/checklist/checklist.d.ts +9 -0
  15. package/dist/types/components/code-editor/code-editor.d.ts +6 -17
  16. package/dist/types/components/context-menu/context-menu.d.ts +6 -2
  17. package/dist/types/components/data-filter/data-filter.d.ts +4 -4
  18. package/dist/types/components/datebox/date-range-box.d.ts +5 -1
  19. package/dist/types/components/datebox/datebox.d.ts +7 -2
  20. package/dist/types/components/dialog/dialog.d.ts +46 -6
  21. package/dist/types/components/dropdown/dropdown.d.ts +24 -9
  22. package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
  23. package/dist/types/components/field/field.d.ts +54 -2
  24. package/dist/types/components/fieldset/external-adapter.d.ts +83 -0
  25. package/dist/types/components/fieldset/fieldset.d.ts +26 -1
  26. package/dist/types/components/file-item/file-item.d.ts +34 -6
  27. package/dist/types/components/file-list/file-list.d.ts +35 -3
  28. package/dist/types/components/filter/filter.d.ts +127 -3
  29. package/dist/types/components/filter-panel/filter-panel.d.ts +36 -7
  30. package/dist/types/components/form/form.d.ts +12 -1
  31. package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
  32. package/dist/types/components/launcher/launcher.d.ts +268 -15
  33. package/dist/types/components/list-toolbar/list-toolbar.d.ts +16 -7
  34. package/dist/types/components/menu-button/menu-button.d.ts +39 -5
  35. package/dist/types/components/message/message.d.ts +132 -17
  36. package/dist/types/components/modal/modal.d.ts +43 -2
  37. package/dist/types/components/module-bar/module-bar.d.ts +395 -0
  38. package/dist/types/components/pagination/pagination.d.ts +17 -3
  39. package/dist/types/components/select/color.d.ts +12 -0
  40. package/dist/types/components/select/glyphs.d.ts +124 -0
  41. package/dist/types/components/select/select.d.ts +17 -3
  42. package/dist/types/components/sheet/sheet.d.ts +15 -0
  43. package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
  44. package/dist/types/components/tabbox/tabbox.d.ts +9 -0
  45. package/dist/types/components/table/table.d.ts +311 -18
  46. package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
  47. package/dist/types/components/tree/tree.d.ts +13 -0
  48. package/dist/types/components/view/record-view.d.ts +59 -2
  49. package/dist/types/components/view-settings/view-settings.d.ts +879 -0
  50. package/dist/types/components/workspace-switcher/workspace-switcher.d.ts +327 -0
  51. package/dist/types/core/breakpoint.d.ts +1 -1
  52. package/dist/types/core/component.d.ts +12 -2
  53. package/dist/types/core/i18n.d.ts +27 -3
  54. package/dist/types/core/icons.d.ts +23 -25
  55. package/dist/types/core/keyboard.d.ts +98 -1
  56. package/dist/types/core/messages.d.ts +382 -0
  57. package/dist/types/core/motion.d.ts +59 -0
  58. package/dist/types/core/overlay-host.d.ts +42 -1
  59. package/dist/types/core/overlay-stack.d.ts +260 -0
  60. package/dist/types/index.d.ts +10 -5
  61. package/dist/types/internal/app-rail.d.ts +38 -0
  62. package/dist/types/internal/menu-items.d.ts +71 -3
  63. package/dist/types/zeyos/connect.d.ts +6 -0
  64. package/dist/types/zeyos/form.d.ts +50 -9
  65. package/dist/types/zeyos/index.d.ts +2 -1
  66. package/dist/types/zeyos/launcher.d.ts +9 -0
  67. package/dist/types/zeyos/module-bar.d.ts +176 -0
  68. package/dist/types/zeyos/query.d.ts +110 -5
  69. package/dist/types/zeyos/schema.d.ts +24 -1
  70. package/dist/types/zeyos/table.d.ts +345 -22
  71. package/dist/types/zeyos/view.d.ts +26 -2
  72. package/dist/zx-zeyos.esm.js +6178 -1008
  73. package/dist/zx-zeyos.esm.min.js +53 -5
  74. package/dist/zx.css +2861 -827
  75. package/dist/zx.esm.js +8909 -1480
  76. package/dist/zx.esm.min.js +55 -7
  77. package/dist/zx.global.js +8911 -1482
  78. package/dist/zx.global.min.js +55 -7
  79. package/dist/zx.min.css +1 -1
  80. package/docs/api.json +1079 -117
  81. package/docs/llms.md +956 -154
  82. package/docs/llms.txt +9 -5
  83. package/package.json +1 -1
  84. package/src/components/account-menu/account-menu.css +35 -10
  85. package/src/components/account-menu/account-menu.js +12 -0
  86. package/src/components/activity-item/activity-item.css +1 -1
  87. package/src/components/activity-item/activity-item.js +3 -0
  88. package/src/components/activity-list/activity-list.css +38 -3
  89. package/src/components/activity-list/activity-list.js +61 -1
  90. package/src/components/app-icon/app-icon.css +14 -3
  91. package/src/components/app-icon/app-icon.js +11 -1
  92. package/src/components/app-rail/app-rail.css +59 -79
  93. package/src/components/app-sidebar/app-sidebar.css +304 -168
  94. package/src/components/app-sidebar/app-sidebar.js +667 -46
  95. package/src/components/aurora/aurora.css +5 -3
  96. package/src/components/avatar/avatar.css +32 -7
  97. package/src/components/avatar/avatar.js +72 -8
  98. package/src/components/badge/badge.css +54 -2
  99. package/src/components/badge/badge.js +29 -2
  100. package/src/components/breadcrumb/breadcrumb.css +1 -1
  101. package/src/components/button/button.css +67 -9
  102. package/src/components/button/button.js +38 -0
  103. package/src/components/calendar/calendar.css +47 -47
  104. package/src/components/calendar/calendar.js +26 -52
  105. package/src/components/card/card.css +4 -4
  106. package/src/components/card-view/card-view.css +14 -12
  107. package/src/components/card-view/card-view.js +13 -9
  108. package/src/components/card-view/record-card.js +14 -3
  109. package/src/components/chart/chart.css +4 -1
  110. package/src/components/chart/chart.js +12 -0
  111. package/src/components/check-button/check-button.css +10 -2
  112. package/src/components/checklist/checklist.css +9 -2
  113. package/src/components/checklist/checklist.js +11 -5
  114. package/src/components/checklist/field-adapter.js +3 -1
  115. package/src/components/code-editor/code-editor.css +7 -6
  116. package/src/components/code-editor/code-editor.js +19 -1
  117. package/src/components/code-editor/field-adapter.js +9 -2
  118. package/src/components/context-menu/context-menu.css +9 -4
  119. package/src/components/context-menu/context-menu.js +8 -2
  120. package/src/components/copy/copy-button.css +6 -0
  121. package/src/components/copy/copy-input.css +9 -4
  122. package/src/components/data-filter/data-filter.css +5 -4
  123. package/src/components/data-filter/data-filter.js +2 -2
  124. package/src/components/date-picker/date-picker.css +6 -6
  125. package/src/components/date-picker/date-range-picker.css +3 -3
  126. package/src/components/date-picker/time-picker.css +3 -3
  127. package/src/components/datebox/date-range-box.css +79 -2
  128. package/src/components/datebox/date-range-box.js +13 -2
  129. package/src/components/datebox/datebox.css +79 -2
  130. package/src/components/datebox/datebox.js +13 -2
  131. package/src/components/dialog/dialog.css +26 -0
  132. package/src/components/dialog/dialog.js +99 -17
  133. package/src/components/dock/dock.css +5 -4
  134. package/src/components/dropdown/dropdown.css +1 -1
  135. package/src/components/dropdown/dropdown.js +159 -72
  136. package/src/components/empty-state/empty-state.css +2 -2
  137. package/src/components/entity-ref/entity-ref.css +1 -1
  138. package/src/components/entity-ref/entity-ref.js +1 -1
  139. package/src/components/field/field.css +28 -9
  140. package/src/components/field/field.js +101 -29
  141. package/src/components/field-upload/field-upload.css +1 -1
  142. package/src/components/fieldset/external-adapter.js +363 -0
  143. package/src/components/fieldset/fieldset.css +5 -3
  144. package/src/components/fieldset/fieldset.js +38 -64
  145. package/src/components/file-item/file-item.js +144 -10
  146. package/src/components/file-list/file-list.js +144 -21
  147. package/src/components/filter/filter.css +86 -2
  148. package/src/components/filter/filter.js +307 -11
  149. package/src/components/filter-panel/filter-panel.css +1 -1
  150. package/src/components/filter-panel/filter-panel.js +149 -32
  151. package/src/components/finder/finder.css +16 -14
  152. package/src/components/form/form.css +5 -4
  153. package/src/components/form/form.js +11 -2
  154. package/src/components/grid/grid.css +1 -1
  155. package/src/components/groupbox/groupbox.css +1 -1
  156. package/src/components/kanban-view/kanban-card.js +11 -3
  157. package/src/components/kanban-view/kanban-view.css +42 -18
  158. package/src/components/kanban-view/kanban-view.js +9 -6
  159. package/src/components/launcher/launcher.css +108 -26
  160. package/src/components/launcher/launcher.js +530 -42
  161. package/src/components/list-toolbar/list-toolbar.css +13 -8
  162. package/src/components/list-toolbar/list-toolbar.js +40 -14
  163. package/src/components/master-panel/master-panel.css +2 -0
  164. package/src/components/master-panel/master-panel.js +0 -1
  165. package/src/components/menu-button/menu-button.css +72 -4
  166. package/src/components/menu-button/menu-button.js +160 -18
  167. package/src/components/message/message.css +33 -3
  168. package/src/components/message/message.js +264 -22
  169. package/src/components/modal/modal.css +1 -1
  170. package/src/components/modal/modal.js +83 -15
  171. package/src/components/module-bar/module-bar.css +486 -0
  172. package/src/components/module-bar/module-bar.js +1074 -0
  173. package/src/components/multi-value-editor/multi-value-editor.css +1 -1
  174. package/src/components/navigation-bar/navigation-bar.css +18 -9
  175. package/src/components/number-field/field-adapter.js +3 -1
  176. package/src/components/number-field/number-field.css +70 -0
  177. package/src/components/number-field/number-field.js +2 -0
  178. package/src/components/pagination/pagination.css +13 -11
  179. package/src/components/pagination/pagination.js +20 -4
  180. package/src/components/panel/panel.css +4 -3
  181. package/src/components/questionnaire/questionnaire.css +4 -4
  182. package/src/components/rating/field-adapter.js +2 -1
  183. package/src/components/rating/rating.css +8 -1
  184. package/src/components/search/search.css +4 -1
  185. package/src/components/select/color.js +22 -0
  186. package/src/components/select/field-adapter.js +2 -0
  187. package/src/components/select/glyphs.js +165 -0
  188. package/src/components/select/select.css +138 -45
  189. package/src/components/select/select.js +92 -47
  190. package/src/components/sheet/sheet.css +12 -9
  191. package/src/components/sheet/sheet.js +74 -32
  192. package/src/components/slider/field-adapter.js +6 -1
  193. package/src/components/slider/slider.css +18 -4
  194. package/src/components/slider/slider.js +1 -1
  195. package/src/components/sort-control/sort-control.css +4 -1
  196. package/src/components/split-view/split-view.css +1 -1
  197. package/src/components/stat-tile/stat-tile.css +42 -47
  198. package/src/components/stat-tile/stat-tile.js +17 -6
  199. package/src/components/stepper/stepper.css +1 -1
  200. package/src/components/tabbox/tabbox.css +41 -32
  201. package/src/components/tabbox/tabbox.js +5 -1
  202. package/src/components/table/table.css +187 -37
  203. package/src/components/table/table.js +718 -73
  204. package/src/components/tag-picker/field-adapter.js +2 -1
  205. package/src/components/tag-picker/tag-picker.css +83 -5
  206. package/src/components/tag-picker/tag-picker.js +25 -8
  207. package/src/components/timebox/timebox.css +22 -2
  208. package/src/components/toggle/toggle.css +12 -1
  209. package/src/components/toolbar/toolbar.js +2 -2
  210. package/src/components/tooltip/tooltip.css +4 -0
  211. package/src/components/tooltip/tooltip.js +44 -19
  212. package/src/components/tree/tree.css +26 -17
  213. package/src/components/tree/tree.js +31 -0
  214. package/src/components/value-list/value-list.css +3 -3
  215. package/src/components/view/record-view.css +1 -1
  216. package/src/components/view/record-view.js +178 -18
  217. package/src/components/view-settings/view-settings.css +285 -0
  218. package/src/components/view-settings/view-settings.js +1830 -0
  219. package/src/components/workspace-switcher/workspace-switcher.css +188 -0
  220. package/src/components/workspace-switcher/workspace-switcher.js +463 -0
  221. package/src/core/breakpoint.js +1 -1
  222. package/src/core/component.js +88 -17
  223. package/src/core/fontawesome.js +5 -1
  224. package/src/core/i18n.js +47 -4
  225. package/src/core/icons.js +204 -61
  226. package/src/core/keyboard.js +206 -11
  227. package/src/core/messages.js +427 -0
  228. package/src/core/motion.js +147 -0
  229. package/src/core/overlay-host.js +77 -3
  230. package/src/core/overlay-stack.js +487 -0
  231. package/src/core/position.js +22 -3
  232. package/src/index.js +10 -5
  233. package/src/internal/app-rail.js +135 -55
  234. package/src/internal/menu-items.js +61 -3
  235. package/src/zeyos/connect.js +6 -2
  236. package/src/zeyos/form.js +69 -27
  237. package/src/zeyos/index.js +4 -1
  238. package/src/zeyos/launcher.js +8 -3
  239. package/src/zeyos/module-bar.js +181 -0
  240. package/src/zeyos/query.js +197 -13
  241. package/src/zeyos/schema.js +144 -27
  242. package/src/zeyos/table.js +287 -84
  243. package/src/zeyos/view.js +49 -93
  244. package/styles/base.css +72 -1
  245. package/styles/icons.css +11 -1
  246. package/styles/print.css +1 -1
  247. package/styles/tokens/dark.css +117 -37
  248. package/styles/tokens/global.css +121 -27
  249. package/styles/tokens/semantic.css +140 -43
  250. package/styles/tokens/themes.css +7 -6
  251. package/styles/zx.css +3 -1
  252. package/styles/tokens/density.css +0 -9
@@ -39,7 +39,7 @@
39
39
  overflow: hidden;
40
40
  border-color: transparent;
41
41
  background: transparent;
42
- font-weight: 650;
42
+ font-weight: var(--zx-weight-semibold);
43
43
  text-overflow: ellipsis;
44
44
  text-wrap: nowrap;
45
45
  }
@@ -47,7 +47,7 @@
47
47
  .zx-month-picker__year,
48
48
  .zx-date-picker__quick-year {
49
49
  text-align: center;
50
- font-weight: 650;
50
+ font-weight: var(--zx-weight-semibold);
51
51
  }
52
52
 
53
53
  .zx-date-picker__grid {
@@ -62,7 +62,7 @@
62
62
  padding: 0;
63
63
  color: var(--zx-color-text-muted);
64
64
  font-size: var(--zx-text-xs);
65
- font-weight: 600;
65
+ font-weight: var(--zx-weight-medium);
66
66
  text-align: center;
67
67
  }
68
68
 
@@ -94,7 +94,7 @@
94
94
  border-color: var(--zx-color-accent);
95
95
  background: var(--zx-color-accent);
96
96
  color: var(--zx-color-on-accent);
97
- font-weight: 700;
97
+ font-weight: var(--zx-weight-semibold);
98
98
  }
99
99
 
100
100
  .zx-date-picker__day[aria-disabled="true"] {
@@ -166,13 +166,13 @@
166
166
  .zx-date-picker__quick-month[aria-selected="true"] {
167
167
  background: var(--zx-color-overlay-selected);
168
168
  color: var(--zx-color-accent);
169
- font-weight: 700;
169
+ font-weight: var(--zx-weight-semibold);
170
170
  }
171
171
 
172
172
  .zx-month-picker__month[aria-selected="true"] {
173
173
  background: var(--zx-color-bg-selected);
174
174
  color: var(--zx-color-accent);
175
- font-weight: 700;
175
+ font-weight: var(--zx-weight-semibold);
176
176
  }
177
177
 
178
178
  .zx-month-picker__month[aria-disabled="true"] {
@@ -83,7 +83,7 @@
83
83
 
84
84
  .zx-date-range-picker__caption {
85
85
  overflow: hidden;
86
- font-weight: 650;
86
+ font-weight: var(--zx-weight-semibold);
87
87
  text-align: center;
88
88
  text-overflow: ellipsis;
89
89
  text-wrap: nowrap;
@@ -147,7 +147,7 @@
147
147
  padding: 0;
148
148
  color: var(--zx-color-text-muted);
149
149
  font-size: var(--zx-text-xs);
150
- font-weight: 600;
150
+ font-weight: var(--zx-weight-medium);
151
151
  text-align: center;
152
152
  }
153
153
 
@@ -223,7 +223,7 @@
223
223
  border-radius: var(--zx-radius-full);
224
224
  background: var(--zx-color-accent);
225
225
  color: var(--zx-color-on-accent);
226
- font-weight: 700;
226
+ font-weight: var(--zx-weight-semibold);
227
227
  }
228
228
 
229
229
  .zx-date-range-picker__grid td[data-range="middle"] > .zx-date-range-picker__day,
@@ -23,7 +23,7 @@
23
23
 
24
24
  .zx-time-picker__separator {
25
25
  color: var(--zx-color-text-muted);
26
- font-weight: 700;
26
+ font-weight: var(--zx-weight-semibold);
27
27
  }
28
28
 
29
29
  .zx-time-picker__clock-toggle {
@@ -84,7 +84,7 @@
84
84
  }
85
85
 
86
86
  .zx-time-picker__clock-unit:focus-visible {
87
- outline: 0;
87
+ outline-color: transparent;
88
88
  box-shadow: var(--zx-focus-ring);
89
89
  }
90
90
 
@@ -146,7 +146,7 @@
146
146
  }
147
147
 
148
148
  .zx-time-picker__mark:focus-visible {
149
- outline: 0;
149
+ outline-color: transparent;
150
150
  box-shadow: var(--zx-focus-ring);
151
151
  }
152
152
 
@@ -10,6 +10,12 @@
10
10
  align-items: stretch;
11
11
  }
12
12
 
13
+ /* The box draws the frame, so the input and buttons inside it are one border shorter on each side:
14
+ the control as a whole is one control height, like a text input beside it. */
15
+ .zx-date-range-box__control > :is(input, button) {
16
+ min-block-size: calc(var(--zx-control-height) - 2px);
17
+ }
18
+
13
19
  .zx-date-range-box__control {
14
20
  position: relative;
15
21
  border: 1px solid var(--zx-color-border-control);
@@ -26,16 +32,40 @@
26
32
  background: var(--zx-color-danger-bg);
27
33
  }
28
34
 
29
- /* The input always reserves room for the clear button, which floats over its trailing edge —
30
- showing or hiding the button must not change the control's total width. */
35
+ /* A clearable input always reserves room for the clear button, which floats over its trailing
36
+ edge — showing or hiding the button must not change the control's total width. Without
37
+ `clearable` there is no button, and no room is kept for one. */
31
38
  .zx-date-range-box__input {
32
39
  min-inline-size: 16em;
33
40
  border: 0;
34
41
  border-radius: var(--zx-control-radius) 0 0 var(--zx-control-radius);
35
42
  background: inherit;
43
+ }
44
+
45
+ .zx-date-range-box[data-clearable] .zx-date-range-box__input {
36
46
  padding-inline-end: var(--zx-control-height);
37
47
  }
38
48
 
49
+ /* In a Field the control fills its column, as a text input does, and gives way in a narrow one
50
+ instead of holding a minimum width that overflows a two-column row. */
51
+ .zx-field__control > .zx-date-range-box {
52
+ display: block;
53
+ }
54
+
55
+ .zx-field__control > .zx-date-range-box :is(.zx-date-range-box__content, .zx-date-range-box__control) {
56
+ display: flex;
57
+ }
58
+
59
+ .zx-field__control > .zx-date-range-box .zx-date-range-box__control {
60
+ flex: 1 1 auto;
61
+ min-inline-size: 0;
62
+ }
63
+
64
+ .zx-field__control > .zx-date-range-box .zx-date-range-box__input {
65
+ flex: 1 1 auto;
66
+ min-inline-size: 0;
67
+ }
68
+
39
69
  /* Focused controls lift above their siblings so the ring is never clipped. */
40
70
  .zx-date-range-box__input:focus-visible,
41
71
  .zx-date-range-box__toggle:focus-visible {
@@ -141,3 +171,50 @@
141
171
  box-shadow: var(--zx-overlay-shadow);
142
172
  }
143
173
  }
174
+
175
+ /*
176
+ * Quiet fields. Inside `[data-zx-fields="quiet"]` the date reads as text: no border, no fill, and
177
+ * the calendar button and its divider hidden. Hover brings the hover fill and the button back;
178
+ * focus brings back the border, the control fill and the focus ring. An invalid value keeps its
179
+ * danger border, quiet or not. A disabled control never lights up.
180
+ */
181
+ [data-zx-fields="quiet"] .zx-date-range-box:not([data-state="invalid"]) .zx-date-range-box__control {
182
+ border-color: transparent;
183
+ background: transparent;
184
+ }
185
+
186
+ [data-zx-fields="quiet"] .zx-date-range-box__toggle {
187
+ border-inline-start-color: transparent;
188
+ opacity: 0;
189
+ }
190
+
191
+ [data-zx-fields="quiet"] .zx-date-range-box__clear {
192
+ opacity: 0;
193
+ }
194
+
195
+ [data-zx-fields="quiet"] .zx-date-range-box:not([aria-disabled="true"], [data-state="invalid"]):hover .zx-date-range-box__control {
196
+ background: var(--zx-color-bg-hover);
197
+ }
198
+
199
+ [data-zx-fields="quiet"] .zx-date-range-box:not([aria-disabled="true"]):is(:hover, :focus-within) :is(.zx-date-range-box__toggle, .zx-date-range-box__clear) {
200
+ opacity: 1;
201
+ }
202
+
203
+ [data-zx-fields="quiet"] .zx-date-range-box:not([aria-disabled="true"]):focus-within .zx-date-range-box__toggle {
204
+ border-inline-start-color: var(--zx-color-border-control);
205
+ }
206
+
207
+ [data-zx-fields="quiet"] .zx-date-range-box:not([aria-disabled="true"], [data-state="invalid"]):focus-within .zx-date-range-box__control {
208
+ border-color: var(--zx-color-border-control);
209
+ background: var(--zx-color-bg-control);
210
+ }
211
+
212
+ @media (prefers-reduced-motion: no-preference) {
213
+ [data-zx-fields="quiet"] .zx-date-range-box__control {
214
+ transition: background-color var(--zx-dur-fast) var(--zx-ease), border-color var(--zx-dur-fast) var(--zx-ease);
215
+ }
216
+
217
+ [data-zx-fields="quiet"] :is(.zx-date-range-box__toggle, .zx-date-range-box__clear) {
218
+ transition: opacity var(--zx-dur-fast) var(--zx-ease), border-color var(--zx-dur-fast) var(--zx-ease);
219
+ }
220
+ }
@@ -19,7 +19,8 @@ import { DateRangePicker, clampRange, normalizeRange } from '../date-picker/date
19
19
  * @property {string} [format='%d.%m.%Y'] Kernel date format used for both endpoints.
20
20
  * @property {string} [separator=' – '] Text placed between the two formatted dates.
21
21
  * @property {string|null} [placeholder=null] Input placeholder; null derives one from the format.
22
- * @property {boolean} [clearable=true] Show a clear button while a range is present.
22
+ * @property {boolean} [clearable=true] Show a clear button while a range is present and the box is
23
+ * enabled; without it the input keeps no room for one.
23
24
  * @property {Date|null} [min=null] Earliest selectable local calendar day.
24
25
  * @property {Date|null} [max=null] Latest selectable local calendar day.
25
26
  * @property {number} [months=2] Month panels in the popover; narrow ones show only the first.
@@ -132,6 +133,8 @@ export class DateRangeBox extends Component {
132
133
  this.el = root;
133
134
  this._originalState = root.getAttribute('data-state');
134
135
  this._originalAriaDisabled = root.getAttribute('aria-disabled');
136
+ this._originalClearable = root.getAttribute('data-clearable');
137
+ root.toggleAttribute('data-clearable', Boolean(this.options.clearable));
135
138
  this._format = String(this.options.format || '%d.%m.%Y');
136
139
  this._separator = String(this.options.separator ?? ' – ');
137
140
  this._disabled = Boolean(this.options.disabled);
@@ -336,6 +339,7 @@ export class DateRangeBox extends Component {
336
339
  this._content?.remove();
337
340
  restoreAttribute(this.el, 'data-state', this._originalState);
338
341
  restoreAttribute(this.el, 'aria-disabled', this._originalAriaDisabled);
342
+ restoreAttribute(this.el, 'data-clearable', this._originalClearable);
339
343
  super.destroy();
340
344
  }
341
345
 
@@ -435,7 +439,13 @@ export class DateRangeBox extends Component {
435
439
  /** @returns {void} */
436
440
  _syncDisplay() {
437
441
  this.refs.input.value = formatRangeText(this._range, this._format, this._separator);
438
- this.refs.clear.hidden = !this.options.clearable || !(this._range.start || this._range.end);
442
+ this._syncClear();
443
+ }
444
+
445
+ /** A disabled box offers no clear button: there is nothing it could do. @returns {void} */
446
+ _syncClear() {
447
+ this.refs.clear.hidden = !this.options.clearable || !(this._range.start || this._range.end)
448
+ || this._disabled;
439
449
  }
440
450
 
441
451
  /** @returns {void} */
@@ -444,6 +454,7 @@ export class DateRangeBox extends Component {
444
454
  this.refs.toggle.disabled = this._disabled;
445
455
  this.refs.clear.disabled = this._disabled;
446
456
  this.el.setAttribute('aria-disabled', String(this._disabled));
457
+ this._syncClear();
447
458
  if (this._disabled) this._picker?.disable();
448
459
  else this._picker?.enable();
449
460
  }
@@ -10,6 +10,12 @@
10
10
  align-items: stretch;
11
11
  }
12
12
 
13
+ /* The box draws the frame, so the input and buttons inside it are one border shorter on each side:
14
+ the control as a whole is one control height, like a text input beside it. */
15
+ .zx-datebox__control > :is(input, button) {
16
+ min-block-size: calc(var(--zx-control-height) - 2px);
17
+ }
18
+
13
19
  .zx-datebox__control {
14
20
  position: relative;
15
21
  border: 1px solid var(--zx-color-border-control);
@@ -26,16 +32,40 @@
26
32
  background: var(--zx-color-danger-bg);
27
33
  }
28
34
 
29
- /* The input always reserves room for the clear button, which floats over its trailing edge —
30
- showing or hiding the button must not change the control's total width. */
35
+ /* A clearable input always reserves room for the clear button, which floats over its trailing
36
+ edge — showing or hiding the button must not change the control's total width. Without
37
+ `clearable` there is no button, and no room is kept for one. */
31
38
  .zx-datebox__input {
32
39
  min-inline-size: 10em;
33
40
  border: 0;
34
41
  border-radius: var(--zx-control-radius) 0 0 var(--zx-control-radius);
35
42
  background: inherit;
43
+ }
44
+
45
+ .zx-datebox[data-clearable] .zx-datebox__input {
36
46
  padding-inline-end: var(--zx-control-height);
37
47
  }
38
48
 
49
+ /* In a Field the control fills its column, as a text input does, and gives way in a narrow one
50
+ instead of holding a minimum width that overflows a two-column row. */
51
+ .zx-field__control > .zx-datebox {
52
+ display: block;
53
+ }
54
+
55
+ .zx-field__control > .zx-datebox :is(.zx-datebox__content, .zx-datebox__control) {
56
+ display: flex;
57
+ }
58
+
59
+ .zx-field__control > .zx-datebox .zx-datebox__control {
60
+ flex: 1 1 auto;
61
+ min-inline-size: 0;
62
+ }
63
+
64
+ .zx-field__control > .zx-datebox .zx-datebox__input {
65
+ flex: 1 1 auto;
66
+ min-inline-size: 0;
67
+ }
68
+
39
69
  /* Focused controls lift above their siblings so the ring is never clipped. */
40
70
  .zx-datebox__input:focus-visible,
41
71
  .zx-datebox__toggle:focus-visible {
@@ -121,3 +151,50 @@
121
151
  box-shadow: var(--zx-overlay-shadow);
122
152
  }
123
153
  }
154
+
155
+ /*
156
+ * Quiet fields. Inside `[data-zx-fields="quiet"]` the date reads as text: no border, no fill, and
157
+ * the calendar button and its divider hidden. Hover brings the hover fill and the button back;
158
+ * focus brings back the border, the control fill and the focus ring. An invalid value keeps its
159
+ * danger border, quiet or not. A disabled control never lights up.
160
+ */
161
+ [data-zx-fields="quiet"] .zx-datebox:not([data-state="invalid"]) .zx-datebox__control {
162
+ border-color: transparent;
163
+ background: transparent;
164
+ }
165
+
166
+ [data-zx-fields="quiet"] .zx-datebox__toggle {
167
+ border-inline-start-color: transparent;
168
+ opacity: 0;
169
+ }
170
+
171
+ [data-zx-fields="quiet"] .zx-datebox__clear {
172
+ opacity: 0;
173
+ }
174
+
175
+ [data-zx-fields="quiet"] .zx-datebox:not([aria-disabled="true"], [data-state="invalid"]):hover .zx-datebox__control {
176
+ background: var(--zx-color-bg-hover);
177
+ }
178
+
179
+ [data-zx-fields="quiet"] .zx-datebox:not([aria-disabled="true"]):is(:hover, :focus-within) :is(.zx-datebox__toggle, .zx-datebox__clear) {
180
+ opacity: 1;
181
+ }
182
+
183
+ [data-zx-fields="quiet"] .zx-datebox:not([aria-disabled="true"]):focus-within .zx-datebox__toggle {
184
+ border-inline-start-color: var(--zx-color-border-control);
185
+ }
186
+
187
+ [data-zx-fields="quiet"] .zx-datebox:not([aria-disabled="true"], [data-state="invalid"]):focus-within .zx-datebox__control {
188
+ border-color: var(--zx-color-border-control);
189
+ background: var(--zx-color-bg-control);
190
+ }
191
+
192
+ @media (prefers-reduced-motion: no-preference) {
193
+ [data-zx-fields="quiet"] .zx-datebox__control {
194
+ transition: background-color var(--zx-dur-fast) var(--zx-ease), border-color var(--zx-dur-fast) var(--zx-ease);
195
+ }
196
+
197
+ [data-zx-fields="quiet"] :is(.zx-datebox__toggle, .zx-datebox__clear) {
198
+ transition: opacity var(--zx-dur-fast) var(--zx-ease), border-color var(--zx-dur-fast) var(--zx-ease);
199
+ }
200
+ }
@@ -14,7 +14,8 @@ import { DatePicker } from '../date-picker/date-picker.js';
14
14
  * @property {Date|null} [min=null] Earliest selectable date.
15
15
  * @property {Date|null} [max=null] Latest selectable date.
16
16
  * @property {string|null} [placeholder=null] Input placeholder; null derives one from the format.
17
- * @property {boolean} [clearable=true] Show a clear button when a value is present.
17
+ * @property {boolean} [clearable=true] Show a clear button while a value is present and the box is
18
+ * enabled; without it the input keeps no room for one.
18
19
  * @property {boolean} [disabled=false] Disable all controls.
19
20
  * @property {(event: CustomEvent<{date: Date|null}>) => void} [onchange] Change listener.
20
21
  * @property {(event: CustomEvent<{text: string}>) => void} [oninvalid] Invalid-input listener.
@@ -53,6 +54,8 @@ export class Datebox extends Component {
53
54
  this.el = root;
54
55
  this._originalState = root.getAttribute('data-state');
55
56
  this._originalAriaDisabled = root.getAttribute('aria-disabled');
57
+ this._originalClearable = root.getAttribute('data-clearable');
58
+ root.toggleAttribute('data-clearable', Boolean(this.options.clearable));
56
59
  this._format = effectiveFormat(this.options.format, this.options.time);
57
60
  this._disabled = Boolean(this.options.disabled);
58
61
  this._open = false;
@@ -230,6 +233,8 @@ export class Datebox extends Component {
230
233
  else this.el.setAttribute('data-state', this._originalState);
231
234
  if (this._originalAriaDisabled === null) this.el.removeAttribute('aria-disabled');
232
235
  else this.el.setAttribute('aria-disabled', this._originalAriaDisabled);
236
+ if (this._originalClearable === null) this.el.removeAttribute('data-clearable');
237
+ else this.el.setAttribute('data-clearable', this._originalClearable);
233
238
  super.destroy();
234
239
  }
235
240
 
@@ -278,7 +283,12 @@ export class Datebox extends Component {
278
283
  /** @returns {void} */
279
284
  _syncDisplay() {
280
285
  this.refs.input.value = this._value ? formatDate(this._value, this._format) : '';
281
- this.refs.clear.hidden = !this.options.clearable || !this._value;
286
+ this._syncClear();
287
+ }
288
+
289
+ /** A disabled box offers no clear button: there is nothing it could do. @returns {void} */
290
+ _syncClear() {
291
+ this.refs.clear.hidden = !this.options.clearable || !this._value || this._disabled;
282
292
  }
283
293
 
284
294
  /** @returns {void} */
@@ -287,6 +297,7 @@ export class Datebox extends Component {
287
297
  this.refs.toggle.disabled = this._disabled;
288
298
  this.refs.clear.disabled = this._disabled;
289
299
  this.el.setAttribute('aria-disabled', String(this._disabled));
300
+ this._syncClear();
290
301
  }
291
302
 
292
303
  /** @returns {void} */
@@ -24,6 +24,8 @@
24
24
  .zx-dialog__title {
25
25
  margin: 0;
26
26
  font-size: var(--zx-text-xl);
27
+ font-weight: 650;
28
+ letter-spacing: var(--zx-tracking-title);
27
29
  line-height: 1.3;
28
30
  }
29
31
 
@@ -61,6 +63,30 @@
61
63
  inline-size: 100%;
62
64
  }
63
65
 
66
+ /* Type-to-confirm: the message, then the label naming the text to type, then the input. */
67
+ .zx-dialog__confirm {
68
+ display: grid;
69
+ gap: var(--zx-space-2);
70
+ }
71
+
72
+ .zx-dialog__confirm > .zx-dialog__prompt-message {
73
+ margin-block-end: var(--zx-space-2);
74
+ }
75
+
76
+ .zx-dialog__confirm-label {
77
+ color: var(--zx-color-text-muted);
78
+ font-size: var(--zx-text-sm);
79
+ }
80
+
81
+ .zx-dialog__confirm-label > b {
82
+ color: var(--zx-color-text);
83
+ font-weight: var(--zx-weight-semibold);
84
+ }
85
+
86
+ .zx-dialog__confirm-input {
87
+ inline-size: 100%;
88
+ }
89
+
64
90
  @media (prefers-reduced-motion: no-preference) {
65
91
  .zx-dialog__view[aria-hidden="false"] {
66
92
  animation: zx-dialog-view-enter var(--zx-dur-fast) var(--zx-ease);
@@ -20,7 +20,7 @@ import { button } from '../button/button.js';
20
20
  * @property {DialogButton[]} [buttons=[]] Footer buttons.
21
21
  * @property {boolean} [closable=true] Whether Escape and the header button may close the dialog.
22
22
  * @property {Node|string|number|{toElement: () => Node|null}|null} [content=null] Body content.
23
- * @property {Element|string|null} [scope=null] Element whose nearest Zx theme scope owns the overlay; defaults to the opener.
23
+ * @property {Element|string|null} [scope=null] Element whose nearest Zx theme scope owns the overlay; defaults to the opener. Resolved on every open, so a scope connected after construction still owns it.
24
24
  * @property {(event: CustomEvent<Record<string, never>>) => void} [onopen] Open event listener.
25
25
  * @property {(event: CustomEvent<{result: unknown}>) => void} [onclose] Close event listener.
26
26
  * @property {(event: CustomEvent<Record<string, never>>) => void} [oncancel] Cancel event listener.
@@ -51,6 +51,11 @@ import { button } from '../button/button.js';
51
51
  * @property {string} [okLabel='OK'] Confirmation label.
52
52
  * @property {string} [cancelLabel='Cancel'] Cancellation label.
53
53
  * @property {boolean} [danger=false] Whether the confirmation action is dangerous.
54
+ * @property {string|null} [confirmText=null] Text the reader must type before the confirm button
55
+ * enables — the name of what is about to be deleted, say. The dialog adds an input labelled
56
+ * "Type <confirmText> to confirm" (message `dialog.typeToConfirm`, `%1` standing for the text),
57
+ * focuses it, and enables the confirm button, and Enter, only while its value matches exactly,
58
+ * ignoring surrounding spaces.
54
59
  */
55
60
  /**
56
61
  * @typedef {Object} PromptOptions
@@ -58,6 +63,8 @@ import { button } from '../button/button.js';
58
63
  * @property {Node|string|number|null} [message=''] Prompt message.
59
64
  * @property {string} [value=''] Initial input value.
60
65
  * @property {string} [placeholder=''] Input placeholder.
66
+ * @property {string} [okLabel='OK'] Confirmation label.
67
+ * @property {string} [cancelLabel='Cancel'] Cancellation label.
61
68
  */
62
69
 
63
70
  /**
@@ -138,7 +145,7 @@ export class Dialog extends Modal {
138
145
  h('footer', { class: 'zx-dialog__footer', ref: 'footer' })
139
146
  ));
140
147
  dialog.dataset.state = 'closed';
141
- this.mountTarget().append(dialog);
148
+ this._mountInitial(dialog);
142
149
  return dialog;
143
150
  }
144
151
 
@@ -281,12 +288,13 @@ export class Dialog extends Modal {
281
288
  }
282
289
 
283
290
  /**
284
- * Opens a confirmation dialog.
291
+ * Opens a confirmation dialog. With `confirmText` it becomes a type-to-confirm dialog: the
292
+ * confirm button stays disabled until the reader has typed that text.
285
293
  * @param {ConfirmOptions} [options={}] Confirmation options.
286
- * @returns {Promise<boolean>}
294
+ * @returns {Promise<boolean>} True when confirmed; false on Cancel, Escape, or the close button.
287
295
  */
288
296
  static confirm({
289
- title = '', message = '', okLabel, cancelLabel, danger = false
297
+ title = '', message = '', okLabel, cancelLabel, danger = false, confirmText = null
290
298
  } = {}) {
291
299
  return new Promise((resolve) => {
292
300
  let settled = false;
@@ -296,16 +304,58 @@ export class Dialog extends Modal {
296
304
  dialog.close(value);
297
305
  resolve(value);
298
306
  };
299
- const dialog = new Dialog(null, { title, content: message });
300
- dialog.setButtons([
301
- { label: cancelLabel ?? dialog._message('dialog.cancel', 'Cancel'), action: () => finish(false) },
302
- {
303
- label: okLabel ?? dialog._message('dialog.ok', 'OK'),
304
- kind: danger ? 'danger' : 'primary',
305
- action: () => finish(true),
307
+ const expected = confirmText == null ? '' : String(confirmText).trim();
308
+ const dialog = new Dialog(null, { title, content: expected ? null : message });
309
+ // A destructive confirmation starts on the least destructive choice, as the APG alert dialog
310
+ // has it; type-to-confirm starts in its input.
311
+ const cancel = {
312
+ label: cancelLabel ?? dialog._message('dialog.cancel', 'Cancel'),
313
+ action: () => finish(false),
314
+ autofocus: danger && !expected
315
+ };
316
+ const accept = {
317
+ label: okLabel ?? dialog._message('dialog.ok', 'OK'),
318
+ kind: /** @type {DialogButtonKind} */ (danger ? 'danger' : 'primary'),
319
+ action: () => finish(true),
320
+ autofocus: !danger && !expected
321
+ };
322
+ if (expected) {
323
+ const input = /** @type {HTMLInputElement} */ (h('input', {
324
+ class: 'zx-dialog__confirm-input',
325
+ id: uid('zx-dialog-confirm'),
326
+ type: 'text',
327
+ autocomplete: 'off',
328
+ autocapitalize: 'off',
329
+ spellcheck: false,
306
330
  autofocus: true
307
- }
308
- ]);
331
+ }));
332
+ const [before, after] = typeToConfirmParts(
333
+ dialog._message('dialog.typeToConfirm', 'Type %1 to confirm'), expected);
334
+ const label = h('label', { class: 'zx-dialog__confirm-label', for: input.id },
335
+ before, h('b', {}, expected), after);
336
+ dialog.setContent(h('div', { class: 'zx-dialog__confirm' },
337
+ isEmptyContent(message) ? null : h('div', { class: 'zx-dialog__prompt-message' }, message),
338
+ label,
339
+ input));
340
+ let matched = false;
341
+ const settle = () => dialog.setButtons([cancel, { ...accept, autofocus: false, disabled: !matched }]);
342
+ settle();
343
+ dialog.listen(input, 'input', () => {
344
+ const next = typedConfirmation(input.value, expected);
345
+ if (next === matched) return;
346
+ matched = next;
347
+ settle();
348
+ });
349
+ // Enter confirms only a match; otherwise it does nothing, as the disabled button would.
350
+ dialog.listen(input, 'keydown', (event) => {
351
+ const key = /** @type {KeyboardEvent} */ (event);
352
+ if (key.key !== 'Enter' || key.isComposing) return;
353
+ key.preventDefault();
354
+ if (typedConfirmation(input.value, expected)) finish(true);
355
+ });
356
+ } else {
357
+ dialog.setButtons([cancel, accept]);
358
+ }
309
359
  dialog.once('close', () => {
310
360
  if (!settled) {
311
361
  settled = true;
@@ -322,7 +372,9 @@ export class Dialog extends Modal {
322
372
  * @param {PromptOptions} [options={}] Prompt options.
323
373
  * @returns {Promise<string|null>}
324
374
  */
325
- static prompt({ title = '', message = '', value = '', placeholder = '' } = {}) {
375
+ static prompt({
376
+ title = '', message = '', value = '', placeholder = '', okLabel, cancelLabel
377
+ } = {}) {
326
378
  return new Promise((resolve) => {
327
379
  let settled = false;
328
380
  const input = h('input', {
@@ -344,8 +396,8 @@ export class Dialog extends Modal {
344
396
  const dialog = new Dialog(null, { title, content });
345
397
  input.setAttribute('aria-label', String(title || dialog._message('dialog.promptValue', 'Prompt value')));
346
398
  dialog.setButtons([
347
- { label: dialog._message('dialog.cancel', 'Cancel'), action: () => finish(null) },
348
- { label: dialog._message('dialog.ok', 'OK'), kind: 'primary', action: () => finish(input.value) }
399
+ { label: cancelLabel ?? dialog._message('dialog.cancel', 'Cancel'), action: () => finish(null) },
400
+ { label: okLabel ?? dialog._message('dialog.ok', 'OK'), kind: 'primary', action: () => finish(input.value) }
349
401
  ]);
350
402
  dialog.listen(input, 'keydown', (event) => {
351
403
  if (event.key !== 'Enter') return;
@@ -428,6 +480,36 @@ export class Dialog extends Modal {
428
480
  }
429
481
  }
430
482
 
483
+ /**
484
+ * Reports whether what the reader typed confirms a type-to-confirm dialog: the same text, exactly
485
+ * — case and inner spacing included — once surrounding spaces are ignored on both sides.
486
+ * @param {unknown} typed Input value.
487
+ * @param {unknown} expected Text the dialog asks for.
488
+ * @returns {boolean}
489
+ */
490
+ export function typedConfirmation(typed, expected) {
491
+ const target = expected == null ? '' : String(expected).trim();
492
+ return target !== '' && String(typed ?? '').trim() === target;
493
+ }
494
+
495
+ /**
496
+ * Splits the type-to-confirm message around `%1`, where the text to type goes. A translation that
497
+ * leaves `%1` out gets the text after it, following a space; a second `%1` is filled in as text.
498
+ * @param {string} template Message such as "Type %1 to confirm".
499
+ * @param {string} expected Text to type.
500
+ * @returns {[string, string]} The text before and after the text to type.
501
+ */
502
+ export function typeToConfirmParts(template, expected) {
503
+ const parts = String(template).split('%1');
504
+ if (parts.length === 1) return [`${parts[0]} `, ''];
505
+ return [parts[0], parts.slice(1).join(expected)];
506
+ }
507
+
508
+ /** @param {unknown} content @returns {boolean} */
509
+ function isEmptyContent(content) {
510
+ return content == null || content === '';
511
+ }
512
+
431
513
  /**
432
514
  * Copies footer-button descriptors without mutating the caller's array or objects, and settles
433
515
  * the two booleans the footer renders from.
@@ -83,7 +83,7 @@
83
83
  .zx-dock__toggle:disabled .zx-dock__chevron { visibility: hidden; }
84
84
 
85
85
  .zx-dock__toggle:focus-visible {
86
- outline: none;
86
+ outline-color: transparent;
87
87
  box-shadow: var(--zx-focus-ring);
88
88
  }
89
89
 
@@ -141,7 +141,7 @@
141
141
  }
142
142
 
143
143
  .zx-dock__tab:focus-visible {
144
- outline: none;
144
+ outline-color: transparent;
145
145
  box-shadow: var(--zx-focus-ring);
146
146
  }
147
147
 
@@ -172,7 +172,8 @@
172
172
  }
173
173
 
174
174
  .zx-dock__panel:focus-visible {
175
- outline: none;
175
+ outline-color: transparent;
176
+ outline-offset: -2px;
176
177
  box-shadow: inset var(--zx-focus-ring);
177
178
  }
178
179
 
@@ -222,7 +223,7 @@
222
223
  }
223
224
 
224
225
  .zx-dock__divider:focus-visible {
225
- outline: none;
226
+ outline-color: transparent;
226
227
  z-index: 1;
227
228
  box-shadow: var(--zx-focus-ring);
228
229
  }