@zeyos/zx 2.2.1 → 2.3.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 (39) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +28 -2
  3. package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +67 -8
  4. package/dist/types/components/permission/permission.d.ts +3 -0
  5. package/dist/types/components/select/select.d.ts +36 -1
  6. package/dist/types/components/tabbox/tabbox.d.ts +12 -3
  7. package/dist/zx-compat.esm.js +302 -34
  8. package/dist/zx-compat.esm.min.js +1 -1
  9. package/dist/zx-compat.global.js +302 -34
  10. package/dist/zx-compat.global.min.js +3 -3
  11. package/dist/zx-zeyos.esm.js +280 -27
  12. package/dist/zx-zeyos.esm.min.js +1 -1
  13. package/dist/zx.css +159 -33
  14. package/dist/zx.esm.js +302 -34
  15. package/dist/zx.esm.min.js +3 -3
  16. package/dist/zx.global.js +302 -34
  17. package/dist/zx.global.min.js +3 -3
  18. package/dist/zx.min.css +1 -1
  19. package/docs/api.json +420 -17
  20. package/docs/llms.md +54 -12
  21. package/package.json +1 -1
  22. package/src/components/date-picker/date-range-picker.css +7 -0
  23. package/src/components/fieldset/fieldset.css +7 -0
  24. package/src/components/form/form.css +7 -0
  25. package/src/components/multi-value-editor/multi-value-editor.css +99 -15
  26. package/src/components/multi-value-editor/multi-value-editor.js +235 -18
  27. package/src/components/navigation-bar/navigation-bar.css +7 -0
  28. package/src/components/permission/permission.js +3 -0
  29. package/src/components/search/search.css +10 -2
  30. package/src/components/select/select.css +6 -0
  31. package/src/components/select/select.js +99 -11
  32. package/src/components/stepper/stepper.css +12 -2
  33. package/src/components/tabbox/tabbox.css +13 -3
  34. package/src/components/tabbox/tabbox.js +34 -10
  35. package/styles/tokens/dark.css +13 -10
  36. package/styles/tokens/global.css +12 -0
  37. package/styles/tokens/semantic.css +9 -5
  38. package/styles/tokens/themes.css +72 -0
  39. package/styles/zx.css +1 -0
package/docs/llms.md CHANGED
@@ -53,8 +53,27 @@ Load `zx.css`, then set theme/density on any ancestor (usually `<html>`):
53
53
  <html data-zx-theme="dark" data-zx-density="compact">
54
54
  ```
55
55
 
56
- Themes: `light` | `dark` | `auto` (follows OS). Density: `cozy` | `compact`. Define a product theme
57
- by overriding **semantic** tokens under a `[data-zx-theme="name"]` selector:
56
+ Themes: `light` | `dark` | `auto` (follows OS). Density: `cozy` | `compact`.
57
+
58
+ **Accent presets.** A second, orthogonal attribute picks the accent ramp:
59
+ `data-zx-preset="zx" | "zeyos" | "ocean" | "violet" | "rose" | "slate"` (`zx` is the default and
60
+ need not be set). A preset repoints five tier-1 tokens — `--zx-accent-300` … `--zx-accent-700` —
61
+ and the semantic tier takes its own stop per polarity (600/700 on light surfaces, 300/400/500 on
62
+ dark), so one five-line block rethemes light, dark, and auto together. Status colours are not part
63
+ of a preset. Define your own the same way, or build one at `/theme.html` (the theme studio, which
64
+ renders every component live and exports the CSS):
65
+
66
+ ```css
67
+ :root {
68
+ --zx-accent-300: #c4b4ff; --zx-accent-400: #a684ff; --zx-accent-500: #8e51ff;
69
+ --zx-accent-600: #7f22fe; --zx-accent-700: #7008e7;
70
+ }
71
+ ```
72
+
73
+ Keep the 600 stop at 4.5:1 or better against white and the 400 stop at 4.5:1 against the dark page
74
+ — that is what makes `--zx-color-on-accent` legible without being restated per theme.
75
+
76
+ Define a product theme by overriding **semantic** tokens under a `[data-zx-theme="name"]` selector:
58
77
  `--zx-color-bg-page/surface/raised/control/hover/selected`, `--zx-color-border(-strong/-control)`,
59
78
  `--zx-color-text(-muted/-placeholder)`, `--zx-color-accent(-hover)/on-accent`,
60
79
  `--zx-color-danger/warning/success/info(+ -bg)`, `--zx-focus-ring`, `--zx-control-height/-radius`,
@@ -404,16 +423,26 @@ Search input with embedded search and clear buttons (`role="search"`).
404
423
 
405
424
  Unifies single-select, local filtering, and async loading.
406
425
 
407
- - **Options** — `items: []`, `valueKey: 'ID'` (string key or `(item)=>id`), `labelKey: 'name'`
408
- (string or `(item)=>string`), `renderItem`, `renderValue`, `value`, `disabled`, `placeholder`,
409
- `clearable: false`, `filter: false | 'local' | async (query)=>items`, `searchKeys`,
410
- `minQuery: 0`, `debounce: 200`, `listHeight: 280`, `groupKey`.
426
+ - **Options** — `items: []`, `fixedItems: []`, `valueKey: 'ID'` (string key or `(item)=>id`),
427
+ `labelKey: 'name'` (string or `(item)=>string`), `renderItem`, `renderValue`, `value`,
428
+ `disabled`, `placeholder`, `clearable: false`, `filter: false | 'local' | async (query)=>items`,
429
+ `searchKeys`, `minQuery: 0`, `debounce: 200`, `listHeight: 280`, `groupKey`.
411
430
  - **Getters** — `.value`, `.selected`.
412
- - **Methods** — `set(id, {silent})`, `setItems()`, `reset()`, `open()`/`close()`,
431
+ - **Methods** — `set(id, {silent})`, `setItems()`, `setFixedItems()`, `reset()`, `open()`/`close()`,
413
432
  `enable()`/`disable()`, `focus()`.
414
433
  - **Events** — `change {value, item}` (`item` is null on clear), `open`, `close`, `query {query}`,
415
434
  `loaded {items}`.
416
- - **Preset** — `Select.priority(target, opts)`, a 5-level priority picker.
435
+ - **Fixed choices** — `fixedItems` pins entries to the top of the list, separated by a rule (the
436
+ rule is dropped when `groupKey` is on, since a heading already divides them). They belong to the
437
+ control, not to its data: a `filter` function replaces `items` on every query, while the pinned
438
+ entries are narrowed locally against the same query and stay resolvable by `set()`. They survive
439
+ `setItems()` and are shown below `minQuery`. Use them for choices with no row in the source —
440
+ "Unassigned", "Everyone", "Private".
441
+ - **Presets** — `Select.priority(target, opts)`, a 5-level priority picker;
442
+ `Select.permission(target, {groups, value})`, the record-access picker with Private and Public
443
+ pinned above the groups. Its value is the tri-state ZeyOS stores (`false` private, `true` public,
444
+ or a group ID), `groups` takes an array or a loader `(query)=>items`, and the labels come from
445
+ `msg` (`permission.private`, `permission.public`). Replaces the deprecated `Permission`.
417
446
  - **Keyboard** (APG combobox) — ArrowDown/Alt+ArrowDown open; arrows navigate and wrap; Home/End;
418
447
  Enter selects; Esc closes; Tab closes; printable characters filter (editable) or run a typeahead
419
448
  (readonly). `aria-activedescendant` tracks the active option.
@@ -917,9 +946,13 @@ or Ctrl+Arrow reorders.
917
946
  Ordered value editor with explicit rows (add / remove / move).
918
947
 
919
948
  - **Options** — `values`, `options` (allowed values, which turns the rows into selects),
920
- `addLabel`.
949
+ `addLabel`, `reorder: 'both'` (`'both'` | `'buttons'` | `'drag'` | `'none'` — a drag handle, the
950
+ up/down buttons, both, or neither).
921
951
  - **Methods** — `getValues()`, `setValues()`.
922
952
  - **Events** — `change {values}`.
953
+ - Dragging goes through a dedicated handle, so text selection inside a row's input still works. The
954
+ handle is also a keyboard control (Arrow keys, Home, End), which is what keeps `reorder: 'drag'`
955
+ operable without a pointer.
923
956
  <!-- /doc -->
924
957
 
925
958
  <!-- doc:field-upload -->
@@ -961,7 +994,9 @@ yet. Field type: `tagpicker`.
961
994
  <!-- doc:permission -->
962
995
  ### Permission
963
996
 
964
- Private / public / group record-permission selector.
997
+ Private / public / group record-permission selector. **Deprecated** — use
998
+ `Select.permission(target, {groups})`, which carries the same tri-state value in one control and
999
+ can query the groups remotely. This class stays for `gx.zeyos.Permission` and is removed in 3.0.
965
1000
 
966
1001
  - **Options** — `value: true|false|groupId`, `groups: []`, `groupsValueKey: 'ID'`,
967
1002
  `groupsLabelKey: 'name'`.
@@ -1058,8 +1093,13 @@ restores rounded corners without forking the stylesheet.
1058
1093
  - **Methods** — `addTab()`, `removeTab()`, `openTab(name)`, `getActive()`, `setTitle()`,
1059
1094
  `setBadge(name, text|null)`, `enableTab()`/`disableTab()`.
1060
1095
  - **Events** — `change {name, previous}` (preventable), `close {name}`.
1096
+ - **Behaviour** — a `closable` tab renders a × that closes it on click, and `close` fires for that
1097
+ gesture just as it does for Delete. `removeTab()` is the silent programmatic path. Closing the
1098
+ active tab activates its neighbour through the usual `change` event, so a `preventDefault()` on
1099
+ that change also vetoes the close.
1061
1100
  - **Keyboard** — ArrowLeft/Right and Home/End move focus, Enter/Space activate, Delete closes a
1062
- closable tab.
1101
+ closable tab. The × is a plain glyph rather than a nested button (buttons cannot nest), so
1102
+ keyboard and screen-reader users close through Delete; focus lands on the neighbouring tab.
1063
1103
  <!-- /doc -->
1064
1104
 
1065
1105
  <!-- doc:stepper -->
@@ -1068,7 +1108,9 @@ restores rounded corners without forking the stylesheet.
1068
1108
  Linear progress through a multi-step flow: a numbered `<ol>` where each step is upcoming, active,
1069
1109
  complete, or errored. It promotes the markup the checkout and record wizards used to hand-roll.
1070
1110
  Advancing past a step marks it complete automatically, and only steps the `clickable` policy allows
1071
- are rendered as real buttons — the rest are not focusable at all.
1111
+ are rendered as real buttons — the rest are not focusable at all. A horizontal rail fills the width
1112
+ it is given: the labels keep their own width and the connectors stretch to take the slack, wrapping
1113
+ onto more lines only when the container is too narrow to hold them.
1072
1114
 
1073
1115
  - **Options** — `steps: [{name, title, description?, optional?, disabled?}]`, `active` (defaults to
1074
1116
  the first enabled step), `orientation: 'horizontal'|'vertical'`, `clickable:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeyos/zx",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "Dependency-free, accessible UI component library for ZeyOS business applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,6 +3,13 @@
3
3
  .zx-date-range-picker {
4
4
  display: block;
5
5
  container-type: inline-size;
6
+ /* A size container takes no width from its own contents, so in a shrink-to-fit host — a flex
7
+ row, an inline-block, a grid `auto` track — it would collapse to nothing and spill. Only the
8
+ host can size it, so it always fills the host. That covers every host with a width of its
9
+ own; a host that shrink-wraps *its* contents still collapses, because 100% then resolves
10
+ against a containing block this element's own containment zeroed. Dropping containment is the
11
+ only fix for that — see multi-value-editor.css. */
12
+ inline-size: 100%;
6
13
  color: var(--zx-color-text);
7
14
  font-size: var(--zx-control-font-size);
8
15
  }
@@ -6,6 +6,13 @@
6
6
  border-radius: var(--zx-radius-lg);
7
7
  background: var(--zx-color-bg-surface);
8
8
  container-type: inline-size;
9
+ /* A size container takes no width from its own contents, so in a shrink-to-fit host — a flex
10
+ row, an inline-block, a grid `auto` track — it would collapse to nothing and spill. Only the
11
+ host can size it, so it always fills the host. That covers every host with a width of its
12
+ own; a host that shrink-wraps *its* contents still collapses, because 100% then resolves
13
+ against a containing block this element's own containment zeroed. Dropping containment is the
14
+ only fix for that — see multi-value-editor.css. */
15
+ inline-size: 100%;
9
16
  }
10
17
 
11
18
  .zx-fieldset__legend {
@@ -5,6 +5,13 @@
5
5
  min-inline-size: 0;
6
6
  }
7
7
 
8
+ /* Fieldsets are size containers, and a contained descendant contributes nothing to its ancestors'
9
+ intrinsic width either — so a form in a shrink-to-fit host would shrink-wrap to its action
10
+ buttons and leave the fields spilling out of a sliver. The form fills its host instead. */
11
+ .zx-form {
12
+ inline-size: 100%;
13
+ }
14
+
8
15
  .zx-form__actions {
9
16
  display: flex;
10
17
  flex-wrap: wrap;
@@ -1,12 +1,22 @@
1
+ /* Deliberately not a size container. Inline-size containment removes an element's own intrinsic
2
+ width, so every host that sizes to its contents — a flex row, an inline-block, a grid `auto`
3
+ track, a table cell — measured this editor as nothing and the rows spilled out of a 26px sliver.
4
+ `inline-size: 100%` does not rescue it either: the percentage resolves against a containing
5
+ block that was itself measured from the contribution containment had already zeroed. The row
6
+ below reflows on its own instead, which reads the width that actually matters — the row's.
7
+
8
+ Without containment `inline-size: 100%` does work: a percentage that has no definite containing
9
+ block to resolve against counts as `auto` while the host measures its contents, so the editor
10
+ contributes its real width and then fills whatever the host settled on. */
1
11
  .zx-multi-value-editor {
2
12
  display: grid;
3
13
  gap: var(--zx-space-3);
4
14
  min-inline-size: 0;
15
+ inline-size: 100%;
5
16
  padding: var(--zx-space-3);
6
17
  border: 1px solid var(--zx-color-border);
7
18
  border-radius: var(--zx-radius-lg);
8
19
  background: var(--zx-color-bg-surface);
9
- container-type: inline-size;
10
20
  }
11
21
 
12
22
  .zx-multi-value-editor__rows {
@@ -15,19 +25,32 @@
15
25
  }
16
26
 
17
27
  .zx-multi-value-editor__row {
18
- display: grid;
19
- grid-template-columns: minmax(0, 1fr) auto;
28
+ position: relative;
29
+ display: flex;
30
+ flex-wrap: wrap;
31
+ /* A grid item refuses to go under its min-content width unless told to, which in a pane narrower
32
+ than the control cluster pushed the whole row back out through the editor's side. */
33
+ min-inline-size: 0;
20
34
  gap: var(--zx-space-2);
21
35
  align-items: center;
22
36
  }
23
37
 
38
+ /* Below roughly this basis the controls wrap onto a line of their own, which is what the container
39
+ query used to do — except a row measures the space the row actually has. */
24
40
  .zx-multi-value-editor__value {
25
- inline-size: 100%;
41
+ flex: 1 1 11rem;
42
+ min-inline-size: 0;
26
43
  }
27
44
 
45
+ /* Wide enough and this keeps its natural size at the end of the row; narrower than the buttons it
46
+ holds, and they wrap among themselves rather than out through the side. */
28
47
  .zx-multi-value-editor__controls {
29
48
  display: inline-flex;
49
+ flex: 0 1 auto;
50
+ flex-wrap: wrap;
51
+ min-inline-size: 0;
30
52
  gap: var(--zx-space-1);
53
+ margin-inline-start: auto;
31
54
  }
32
55
 
33
56
  .zx-multi-value-editor__controls .zx-btn {
@@ -35,22 +58,83 @@
35
58
  padding-inline: var(--zx-space-2);
36
59
  }
37
60
 
38
- .zx-multi-value-editor__controls .zx-btn:last-child {
61
+ .zx-multi-value-editor__controls .zx-btn[data-action="remove"] {
39
62
  color: var(--zx-color-danger);
40
63
  }
41
64
 
65
+ /* A row at either end keeps its move button focusable and merely announces that it cannot go
66
+ further, so pressing it repeatedly never drops focus to the document. */
67
+ .zx-multi-value-editor__controls .zx-btn[aria-disabled="true"] {
68
+ opacity: 0.55;
69
+ cursor: not-allowed;
70
+ }
71
+
72
+ .zx-multi-value-editor__controls .zx-btn[aria-disabled="true"]:hover {
73
+ background: var(--zx-color-bg-control);
74
+ }
75
+
76
+ .zx-multi-value-editor__handle {
77
+ display: inline-flex;
78
+ flex: none;
79
+ align-items: center;
80
+ justify-content: center;
81
+ min-block-size: var(--zx-control-height);
82
+ min-inline-size: var(--zx-space-6);
83
+ padding: 0;
84
+ border: 0;
85
+ border-radius: var(--zx-control-radius);
86
+ background: none;
87
+ color: var(--zx-color-text-muted);
88
+ cursor: grab;
89
+ touch-action: none;
90
+ transition: color var(--zx-dur-fast) var(--zx-ease),
91
+ background-color var(--zx-dur-fast) var(--zx-ease);
92
+ }
93
+
94
+ .zx-multi-value-editor__handle:hover:not(:disabled) {
95
+ background: var(--zx-color-bg-hover);
96
+ color: var(--zx-color-text);
97
+ }
98
+
99
+ .zx-multi-value-editor__handle:focus-visible {
100
+ outline: none;
101
+ box-shadow: var(--zx-focus-ring);
102
+ }
103
+
104
+ .zx-multi-value-editor__handle:disabled {
105
+ opacity: 0.55;
106
+ cursor: default;
107
+ }
108
+
109
+ .zx-multi-value-editor[data-state="dragging"] .zx-multi-value-editor__handle {
110
+ cursor: grabbing;
111
+ }
112
+
113
+ .zx-multi-value-editor__row[data-dragging="true"] {
114
+ opacity: 0.45;
115
+ }
116
+
117
+ /* The line sits in the gap between two rows, so it reads as "it lands here", not "it replaces
118
+ this row". */
119
+ .zx-multi-value-editor__row[data-drop]::after {
120
+ content: '';
121
+ position: absolute;
122
+ inset-inline: 0;
123
+ block-size: 2px;
124
+ border-radius: var(--zx-radius-full);
125
+ background: var(--zx-color-accent);
126
+ }
127
+
128
+ .zx-multi-value-editor__row[data-drop="before"]::after {
129
+ inset-block-start: calc(var(--zx-space-2) / -2 - 1px);
130
+ }
131
+
132
+ .zx-multi-value-editor__row[data-drop="after"]::after {
133
+ inset-block-end: calc(var(--zx-space-2) / -2 - 1px);
134
+ }
135
+
42
136
  .zx-multi-value-editor__add {
43
137
  justify-self: start;
44
138
  border-color: var(--zx-color-accent);
45
139
  color: var(--zx-color-accent);
46
140
  }
47
-
48
- @container (max-width: 24rem) {
49
- .zx-multi-value-editor__row {
50
- grid-template-columns: minmax(0, 1fr);
51
- }
52
-
53
- .zx-multi-value-editor__controls {
54
- justify-self: end;
55
- }
56
- }