@zeyos/zx 5.2.0 → 5.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.
package/docs/api.json CHANGED
@@ -1914,6 +1914,30 @@
1914
1914
  "type": "string|null",
1915
1915
  "default": "null",
1916
1916
  "description": "Accessible collection label; null resolves message `cardView.label`, \"Card view\"."
1917
+ },
1918
+ {
1919
+ "name": "keyboard",
1920
+ "type": "'tabs'|'roving'",
1921
+ "default": "'tabs'",
1922
+ "description": "Keyboard model. `'tabs'` makes every card, title link, checkbox, and action its own Tab stop. `'roving'` — recommended for lists — follows the APG Grid pattern with row focus: the collection is one Tab stop, the arrow keys, Home/End, and PageUp/PageDown move between cards, Enter activates, Space selects, and the controls inside a card stay pointer targets with `tabindex=\"-1\"`."
1923
+ },
1924
+ {
1925
+ "name": "collapsibleGroups",
1926
+ "type": "boolean",
1927
+ "default": "false",
1928
+ "description": "Whether group headings carry a toggle that hides and shows the group's cards."
1929
+ },
1930
+ {
1931
+ "name": "collapsedGroups",
1932
+ "type": "unknown[]",
1933
+ "default": "[]",
1934
+ "description": "Group ids that start collapsed."
1935
+ },
1936
+ {
1937
+ "name": "renderGroupHeading",
1938
+ "type": "((context:CardGroupHeadingContext)=>Node|string|null|undefined)|null",
1939
+ "default": "null",
1940
+ "description": "Content of a group heading in place of its label. With `collapsibleGroups` it is placed inside the toggle button, so it must not contain interactive elements."
1917
1941
  }
1918
1942
  ],
1919
1943
  "events": [
@@ -1921,6 +1945,11 @@
1921
1945
  "name": "recordaction",
1922
1946
  "payload": "Record<string,unknown>",
1923
1947
  "description": ""
1948
+ },
1949
+ {
1950
+ "name": "groupcollapse",
1951
+ "payload": "{id:string,collapsed:boolean,collapsedGroups:string[]}",
1952
+ "description": ""
1924
1953
  }
1925
1954
  ],
1926
1955
  "methods": [
@@ -1936,6 +1965,18 @@
1936
1965
  "returns": "this",
1937
1966
  "description": "Restores shared and CardView configuration. Unknown shared fields reconcile in RecordView."
1938
1967
  },
1968
+ {
1969
+ "name": "setGroupCollapsed",
1970
+ "signature": "setGroupCollapsed(id, [collapsed], [options])",
1971
+ "returns": "this",
1972
+ "description": "Collapses or expands one group. A collapsed group keeps its heading and hides its cards."
1973
+ },
1974
+ {
1975
+ "name": "getCollapsedGroups",
1976
+ "signature": "getCollapsedGroups()",
1977
+ "returns": "string[]",
1978
+ "description": "Returns the collapsed group ids."
1979
+ },
1939
1980
  {
1940
1981
  "name": "destroy",
1941
1982
  "signature": "destroy()",
package/docs/llms.md CHANGED
@@ -1700,8 +1700,13 @@ portable state as TableView and KanbanView.
1700
1700
 
1701
1701
  - **Options** — the shared RecordView options plus `titleField`, `subtitleField`, `preview`,
1702
1702
  `previewAlt`, `link`, `actions`, `groupBy`, `groupOrder`, `minCardWidth`, `maxColumns`,
1703
- `variant: 'outlined'|'raised'|'filled'`, `loadingCount`, `headingLevel`, and `label` (null
1704
- resolves message `cardView.label`, "Card view").
1703
+ `variant: 'outlined'|'raised'|'filled'`, `loadingCount`, `headingLevel`, `label` (null
1704
+ resolves message `cardView.label`, "Card view"), `keyboard: 'tabs'|'roving'` (default `'tabs'`),
1705
+ `collapsibleGroups`, `collapsedGroups`, and `renderGroupHeading`.
1706
+ - **Methods** — the shared RecordView methods plus `setGroupCollapsed(id, collapsed, {silent})` and
1707
+ `getCollapsedGroups()`. **Events** — the shared set plus `recordaction` and
1708
+ `groupcollapse {id, collapsed, collapsedGroups}` (also a `statechange` with reason
1709
+ `'groupcollapse'`). The view state carries `collapsedGroups`.
1705
1710
  - **Messages** — `cardView.label`, `cardView.sort`, `cardView.sortCards`, `cardView.unsorted`,
1706
1711
  `cardView.sortAscending`/`cardView.sortDescending` (`%1` the field), `cardView.ungrouped`
1707
1712
  ("Ungrouped"), `cardView.groupEmpty` ("No records in this group."), `table.empty` for the empty
@@ -1728,6 +1733,29 @@ portable state as TableView and KanbanView.
1728
1733
  a stable fallback. Title/action links are normalized; `_blank` links receive `noopener`.
1729
1734
  - **Grouping and layout** — `groupBy` is a field ID or callback; `groupOrder` may deliberately show
1730
1735
  empty groups. Cards reflow through container queries and may cap columns without viewport logic.
1736
+ - **Keyboard model** — `keyboard: 'roving'` is the recommended setting for lists; `'tabs'` stays the
1737
+ default for compatibility, and makes every card, title link, checkbox, and action its own Tab
1738
+ stop (44 stops for 22 selectable cards). `'roving'` follows the WAI-ARIA APG **Grid** pattern with
1739
+ row focus, the same model as `Table`: the content is `role="grid"` (`aria-multiselectable` for
1740
+ `multi`), each card a `row` with `aria-selected` when selectable, and the collection is one Tab
1741
+ stop. Grid rather than listbox because a card holds a link and buttons, which a listbox option
1742
+ would flatten. ArrowUp/ArrowDown move to the card above or below — columns are measured from the
1743
+ rendered positions, and the column you travel in is remembered — ArrowLeft/ArrowRight within a
1744
+ row, Home/End to the first or last card, PageUp/PageDown by a visible page. Enter activates
1745
+ (`recordclick`), Space toggles selection in `selectable` modes (activates otherwise),
1746
+ Shift+movement extends a `multi` selection, Ctrl/Cmd+A selects every rendered card. Title links,
1747
+ checkboxes, and actions get `tabindex="-1"`: they still work with the pointer, and Enter on the
1748
+ card is the keyboard route. The current card is kept by record id across `setData`, sorting,
1749
+ grouping, and collapsing, and focus is restored with `preventScroll`. Focus shows as an inset
1750
+ `--zx-focus-ring` with a transparent outline for forced-colors mode.
1751
+ - **Group headings** — `renderGroupHeading({id, label, count, records, collapsed, toggle})` returns
1752
+ the heading content in place of the label. `collapsibleGroups: true` makes each heading an APG
1753
+ disclosure: a button with `aria-expanded` and `aria-controls` pointing at the group's list, which
1754
+ holds the label (and so the custom content, which must then not contain interactive elements).
1755
+ A collapsed group keeps its heading and renders no cards; `collapsedGroups` sets the initial
1756
+ ones. In the roving model a collapsible heading is a row in the arrow-key order: Enter or Space
1757
+ toggles it, ArrowLeft collapses, ArrowRight expands, and collapsing the group of the focused card
1758
+ moves focus to its heading.
1731
1759
  - **Interaction** — By default Enter activates a focused card; Space toggles selection. With
1732
1760
  `selectionTrigger:'card'`, Enter also toggles selection. Links, actions,
1733
1761
  selection controls, and selected text do not also activate the card. `recordaction` adds the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeyos/zx",
3
- "version": "5.2.0",
3
+ "version": "5.3.0",
4
4
  "description": "Dependency-free JavaScript implementation of the Xenon Design System.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -59,6 +59,77 @@
59
59
  line-height: 1.3;
60
60
  }
61
61
 
62
+ /* The heading row: a plain wrapper in the tab model, a grid row in the roving one. */
63
+ .zx-card-view__group-header {
64
+ border-radius: var(--zx-radius-sm);
65
+ }
66
+
67
+ .zx-card-view__group-header:focus-visible {
68
+ outline: 2px solid transparent;
69
+ box-shadow: inset var(--zx-focus-ring);
70
+ }
71
+
72
+ /* A collapsible heading is an APG disclosure: the button carries the label, the chevron turns. */
73
+ .zx-card-view__group-toggle {
74
+ display: inline-flex;
75
+ align-items: center;
76
+ gap: var(--zx-space-1);
77
+ min-block-size: 0;
78
+ margin-inline-start: calc(-1 * var(--zx-space-1));
79
+ padding: var(--zx-space-1);
80
+ border: 0;
81
+ border-radius: var(--zx-radius-sm);
82
+ background: none;
83
+ color: inherit;
84
+ font: inherit;
85
+ cursor: pointer;
86
+ }
87
+
88
+ .zx-card-view__group-toggle:hover {
89
+ background: var(--zx-color-bg-hover);
90
+ }
91
+
92
+ .zx-card-view__group-toggle:focus-visible {
93
+ outline: 2px solid transparent;
94
+ box-shadow: var(--zx-focus-ring);
95
+ }
96
+
97
+ .zx-card-view__group-toggle > .zx-icon {
98
+ flex: none;
99
+ color: var(--zx-color-text-subtle);
100
+ }
101
+
102
+ .zx-card-view__group-toggle[aria-expanded="false"] > .zx-icon {
103
+ transform: rotate(-90deg);
104
+ }
105
+
106
+ .zx-card-view__group[data-collapsed="true"] .zx-card-view__group-heading {
107
+ margin-block-end: 0;
108
+ }
109
+
110
+ .zx-card-view__list[hidden] {
111
+ display: none;
112
+ }
113
+
114
+ /* Roving focus sits inside the card, so a host that restyles cards into flush rows still shows
115
+ it; the transparent outline is what forced-colors mode paints. */
116
+ .zx-card-view[data-keyboard="roving"] .zx-record-card:focus-visible {
117
+ outline: 2px solid transparent;
118
+ outline-offset: -2px;
119
+ box-shadow: inset var(--zx-focus-ring);
120
+ }
121
+
122
+ .zx-card-view__content[data-empty-focus]:focus-visible {
123
+ outline: 2px solid transparent;
124
+ box-shadow: inset var(--zx-focus-ring);
125
+ }
126
+
127
+ @media (prefers-reduced-motion: no-preference) {
128
+ .zx-card-view__group-toggle > .zx-icon {
129
+ transition: transform var(--zx-dur-fast) var(--zx-ease);
130
+ }
131
+ }
132
+
62
133
  .zx-card-view__list {
63
134
  display: grid;
64
135
  grid-template-columns: repeat(auto-fill,