@jxsuite/ui 0.0.0 → 0.1.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 (105) hide show
  1. package/components/jx-accordion-item.json +281 -0
  2. package/components/jx-accordion.json +33 -0
  3. package/components/jx-action-button.json +261 -33
  4. package/components/jx-action-group.json +123 -0
  5. package/components/jx-button.json +133 -25
  6. package/components/jx-checkbox.json +215 -0
  7. package/components/jx-color-area.json +263 -0
  8. package/components/jx-color-field.json +565 -0
  9. package/components/jx-color-slider.json +216 -0
  10. package/components/jx-combobox.json +544 -0
  11. package/components/jx-dialog.json +416 -0
  12. package/components/jx-divider.json +52 -0
  13. package/components/jx-dot.json +38 -0
  14. package/components/jx-field.json +162 -0
  15. package/components/jx-kbd.json +24 -0
  16. package/components/jx-listbox.json +76 -0
  17. package/components/jx-menu-item.json +5 -12
  18. package/components/jx-menu.json +45 -6
  19. package/components/jx-number-field.json +441 -0
  20. package/components/jx-option.json +350 -0
  21. package/components/jx-popover.json +227 -0
  22. package/components/jx-select.json +637 -0
  23. package/components/jx-spinner.json +129 -0
  24. package/components/jx-split.json +221 -0
  25. package/components/jx-swatch-group.json +107 -0
  26. package/components/jx-swatch.json +262 -0
  27. package/components/jx-switch.json +253 -0
  28. package/components/jx-tab-panel.json +58 -0
  29. package/components/jx-tab.json +296 -0
  30. package/components/jx-tabs.json +124 -0
  31. package/components/jx-textfield.json +639 -0
  32. package/components/jx-toast-host.json +121 -0
  33. package/components/jx-toast.json +255 -0
  34. package/components/jx-toolbar.json +81 -0
  35. package/components/jx-tooltip.json +207 -0
  36. package/components/jx-tree-item.json +325 -0
  37. package/components/jx-tree.json +202 -0
  38. package/icons/list.json +4 -0
  39. package/icons/manifest.json +12 -0
  40. package/package.json +27 -5
  41. package/project.json +164 -153
  42. package/src/behaviors/action-group.ts +255 -0
  43. package/src/behaviors/color-area.ts +303 -0
  44. package/src/behaviors/color-field.ts +465 -0
  45. package/src/behaviors/color-slider.ts +154 -0
  46. package/src/behaviors/combobox.ts +474 -0
  47. package/src/behaviors/dialog.ts +137 -0
  48. package/src/behaviors/listbox.ts +118 -0
  49. package/src/behaviors/menu.ts +80 -48
  50. package/src/behaviors/number-field.ts +161 -0
  51. package/src/behaviors/popover.ts +399 -0
  52. package/src/behaviors/select.ts +154 -0
  53. package/src/behaviors/split.ts +478 -0
  54. package/src/behaviors/swatch-group.ts +297 -0
  55. package/src/behaviors/tabs.ts +319 -0
  56. package/src/behaviors/textfield.ts +164 -0
  57. package/src/behaviors/toast-host.ts +241 -0
  58. package/src/behaviors/toast.ts +261 -0
  59. package/src/behaviors/toolbar.ts +413 -0
  60. package/src/behaviors/tooltip.ts +715 -0
  61. package/src/behaviors/tree.ts +830 -0
  62. package/src/color.ts +433 -0
  63. package/src/documents.ts +91 -1
  64. package/src/index.ts +41 -1
  65. package/src/loaders.ts +47 -0
  66. package/src/testing/anchor-support.ts +28 -0
  67. package/src/testing/popover-shim.ts +39 -6
  68. package/src/theme.ts +30 -6
  69. package/stylebook/jx-accordion-item.json +195 -0
  70. package/stylebook/jx-accordion.json +220 -0
  71. package/stylebook/jx-action-button.json +191 -23
  72. package/stylebook/jx-action-group.json +335 -0
  73. package/stylebook/jx-button.json +129 -26
  74. package/stylebook/jx-checkbox.json +245 -0
  75. package/stylebook/jx-color-area.json +272 -0
  76. package/stylebook/jx-color-field.json +261 -0
  77. package/stylebook/jx-color-slider.json +220 -0
  78. package/stylebook/jx-combobox.json +332 -0
  79. package/stylebook/jx-dialog.json +186 -0
  80. package/stylebook/jx-divider.json +125 -0
  81. package/stylebook/jx-dot.json +92 -0
  82. package/stylebook/jx-field.json +313 -0
  83. package/stylebook/jx-icon.json +4 -0
  84. package/stylebook/jx-kbd.json +90 -0
  85. package/stylebook/jx-listbox.json +337 -0
  86. package/stylebook/jx-menu.json +0 -1
  87. package/stylebook/jx-number-field.json +203 -0
  88. package/stylebook/jx-option.json +285 -0
  89. package/stylebook/jx-popover.json +123 -0
  90. package/stylebook/jx-select.json +407 -0
  91. package/stylebook/jx-spinner.json +111 -0
  92. package/stylebook/jx-split.json +170 -0
  93. package/stylebook/jx-swatch-group.json +175 -0
  94. package/stylebook/jx-swatch.json +249 -0
  95. package/stylebook/jx-switch.json +240 -0
  96. package/stylebook/jx-tab-panel.json +147 -0
  97. package/stylebook/jx-tab.json +201 -0
  98. package/stylebook/jx-tabs.json +278 -0
  99. package/stylebook/jx-textfield.json +309 -0
  100. package/stylebook/jx-toast-host.json +157 -0
  101. package/stylebook/jx-toast.json +266 -0
  102. package/stylebook/jx-toolbar.json +435 -0
  103. package/stylebook/jx-tooltip.json +162 -0
  104. package/stylebook/jx-tree-item.json +258 -0
  105. package/stylebook/jx-tree.json +429 -0
@@ -0,0 +1,325 @@
1
+ {
2
+ "$schema": "https://jxsuite.com/schema/v1",
3
+ "$id": "JxTreeItem",
4
+ "tagName": "jx-tree-item",
5
+ "$description": "One row of a `jx-tree`, and a row is ALL it is: it draws itself, announces itself, and knows nothing about the rows either side of it. THE HOST OWNS THE ROW SET AND THE COUNTS. `level`, `posinset` and `setsize` are props rather than anything derived, because a windowed tree's DOM holds one screen of a model that may be four thousand rows long: a row that counted its own siblings would count the ones that happen to be drawn, and a reader would be told there are eleven items in a directory holding ten thousand. So `aria-setsize` and `aria-posinset` describe the FULL set, they are the host's to supply, and this element will never invent them — a row given neither writes neither, which is ARIA's own way of saying \"ask the tree\", and is right for the one case where the DOM is the whole model. `aria-selected` is written only when the row IS selected: a `false` on every row is what tells assistive technology a tree supports multiple selection, the row cannot see whether its tree does, and `aria-multiselectable` on `jx-tree` is where that answer actually lives. The three `data-` attributes it styles but does not write — `data-dragging`, `data-drop` and the drag island generally — are the pointer half of moving a row, and they are deliberately not props: pragmatic-drag-and-drop attaches through the HOST (studio-ui-guidelines.md §9.4) and writes them directly, so a binding here would be a second writer that clobbers the island on the next repaint. What the element owes the non-drag half is `cut`, which draws a row already lifted, and a keyboard that lets go of every chord it does not own.",
6
+ "observedAttributes": [
7
+ "value",
8
+ "label",
9
+ "level",
10
+ "posinset",
11
+ "setsize",
12
+ "expanded",
13
+ "selected",
14
+ "disabled",
15
+ "cut",
16
+ "grip"
17
+ ],
18
+ "$elements": [
19
+ {
20
+ "$ref": "./jx-icon.json"
21
+ }
22
+ ],
23
+ "state": {
24
+ "value": {
25
+ "type": "string",
26
+ "default": "",
27
+ "attribute": "value",
28
+ "description": "What the row stands for: the detail of every event it provokes, and what the tree's `current` is matched against. A path, an id, a `JxPath` key — whatever the host's model already calls this row, because the tree hands it straight back and never parses it."
29
+ },
30
+ "label": {
31
+ "type": "string",
32
+ "default": "",
33
+ "attribute": "label",
34
+ "description": "The words on the row, its accessible name, and what typeahead matches. Written to `aria-label` as well as drawn, for the reason `jx-tab` gives: a `treeitem` names itself from its content, so a badge, a status pill and a row action would all join the row's name and a reader would hear \"div Hero More actions\". `aria-label` wins over name-from-content in accname's precedence order, so the name stays exactly the string the row draws and each slotted control keeps announcing its own. It is therefore REQUIRED once anything is slotted, and a consumer whose row is a badge PLUS a name composes both into this one string rather than hoping the concatenation comes out right."
35
+ },
36
+ "level": {
37
+ "type": "number",
38
+ "default": 1,
39
+ "attribute": "level",
40
+ "description": "How deep the row sits, one-based, exactly as `aria-level` counts. It is also the indent: the row's inline padding is `--jx-tree-indent` times one less than this, so the arithmetic that decides what a level LOOKS like lives with the declarations rather than being composed per row in a host's TypeScript. A flat projection is what makes this a prop at all — under windowing there is no ancestor in the DOM to count."
41
+ },
42
+ "posinset": {
43
+ "type": "number",
44
+ "default": 0,
45
+ "attribute": "posinset",
46
+ "description": "Which of its siblings this row is, one-based, IN THE MODEL. Zero writes no `aria-posinset` at all, which is how a small tree that draws every row says \"count me from the DOM\". A windowed tree must supply it: the drawn slice is not the set, and a position computed from it is a number a reader can act on and be wrong about."
47
+ },
48
+ "setsize": {
49
+ "type": "number",
50
+ "default": 0,
51
+ "attribute": "setsize",
52
+ "description": "How many siblings the row has, IN THE MODEL — the total under its parent, not the number drawn. Zero writes no `aria-setsize`, the same \"ask the DOM\" as `posinset`. The pair is what keeps ARIA true while the tree windows, and it is the whole reason the counts are the host's."
53
+ },
54
+ "expanded": {
55
+ "type": "string",
56
+ "default": "",
57
+ "attribute": "expanded",
58
+ "description": "Empty for a LEAF, which owns no `aria-expanded` and no twisty; `true` or `false` for a row that has children. Three values rather than a boolean because \"closed\" and \"cannot open\" are different things to a reader and to the keyboard: `ArrowRight` opens a closed row and steps into an open one, and does nothing at all on a leaf. The same string spelling `jx-menu-item`'s `checked` uses, for the same reason."
59
+ },
60
+ "selected": {
61
+ "type": "boolean",
62
+ "default": false,
63
+ "attribute": "selected",
64
+ "description": "Whether the row is in the selection. The HOST writes it, and it is the one row prop the tree is NOT the single writer of: a selection is a set, a windowed tree draws part of it, and an element that owned it would forget every row it scrolled past. Written to `aria-selected` only when true — see the element's description."
65
+ },
66
+ "disabled": {
67
+ "type": "boolean",
68
+ "default": false,
69
+ "attribute": "disabled",
70
+ "description": "A row that cannot be acted on. It stays DRAWN and stays counted, because a row that vanished would renumber its siblings, but the caret steps over it, a click on it does nothing, and it never takes the tree's tab stop."
71
+ },
72
+ "cut": {
73
+ "type": "boolean",
74
+ "default": false,
75
+ "attribute": "cut",
76
+ "description": "The row is on the clipboard, waiting for a paste to land it somewhere. Drawn faded, and deliberately invisible to assistive technology: cut and paste are the keyboard alternative to dragging (WCAG 2.2 SC 2.5.7, studio-ui-guidelines.md §8.2), both are host commands, and the host announces the move through its own live region — a reader is told what happened by the page, not by an opacity."
77
+ },
78
+ "grip": {
79
+ "type": "boolean",
80
+ "default": false,
81
+ "attribute": "grip",
82
+ "description": "Draws the drag handle. A plain `aria-hidden` span rather than a control, because it is the grab affordance for a pointer drag the drag island owns and NOT a second way to move the row: the keyboard path is cut and paste, and a focusable handle with no keyboard operation of its own would be an SC 2.1.1 failure dressed as an affordance."
83
+ },
84
+ "caret": {
85
+ "type": "boolean",
86
+ "default": false,
87
+ "description": "Whether this row holds the tree's one tab stop. `jx-tree` is the single writer of it — a host moves the caret by writing the TREE's `current`, never a row's. It is a prop rather than a `tabindex` the sidecar writes directly so that a repaint cannot lose it: the attribute is derived from state, so re-rendering the row restores the caret instead of quietly resetting it to -1."
88
+ },
89
+ "onTwisty": {
90
+ "$prototype": "Function",
91
+ "body": [
92
+ {
93
+ "stopPropagation": true
94
+ },
95
+ {
96
+ "dispatchEvent": "toggle",
97
+ "detail": {
98
+ "$ref": "#/state/value"
99
+ },
100
+ "bubbles": true
101
+ }
102
+ ],
103
+ "emits": [
104
+ {
105
+ "name": "toggle",
106
+ "description": "The row's twisty was pressed; detail is its value. The TREE's internal protocol: `jx-tree` catches it, works out which way the row should go, and answers with its own `expand`."
107
+ }
108
+ ],
109
+ "description": "The click stops at the twisty, so opening a row does not also select it — the judgement `jx-menu-item` makes for a submenu chevron and `jx-tab` for its close button."
110
+ },
111
+ "onActions": {
112
+ "$prototype": "Function",
113
+ "body": [
114
+ {
115
+ "stopPropagation": true
116
+ }
117
+ ],
118
+ "description": "A click inside the actions slot stops there, so pressing a control a consumer put on the row never ALSO moves the selection. It stops at the CONTAINER rather than at the host: `stopPropagation` halts an event at the node it is called on and every node after it, never the listeners already run below it, so the slotted control's own handler has fired and only the TREE's delegated click is cut off. Nothing is stopped for `icon` or `status`: those hold marks rather than controls, and a click on a mark is a click on the row."
119
+ }
120
+ },
121
+ "attributes": {
122
+ "role": "treeitem",
123
+ "tabindex": "${state.caret ? '0' : '-1'}",
124
+ "aria-label": "${state.label || null}",
125
+ "aria-level": "${state.level}",
126
+ "aria-posinset": "${state.posinset > 0 ? state.posinset : null}",
127
+ "aria-setsize": "${state.setsize > 0 ? state.setsize : null}",
128
+ "aria-expanded": "${state.expanded || null}",
129
+ "aria-selected": "${state.selected ? 'true' : null}",
130
+ "aria-disabled": "${state.disabled ? 'true' : null}",
131
+ "data-value": "${state.value}",
132
+ "data-cut": "${state.cut ? '' : null}"
133
+ },
134
+ "style": {
135
+ "$description": "The row's block size is a CONTRACT rather than a look: a tree windows, and a window can only reserve the right amount of scroll for the rows it did not draw if every row is exactly one row tall. `--jx-control-h` follows the density setting, which a hard-coded 24px could not. The indent is padding rather than a margin or a spacer element for the same reason — a row whose HEIGHT depended on its depth would make the scrollbar a lie.",
136
+ "display": "flex",
137
+ "&[hidden]": {
138
+ "display": "none"
139
+ },
140
+ "alignItems": "center",
141
+ "gap": "var(--jx-space-2)",
142
+ "boxSizing": "border-box",
143
+ "blockSize": "var(--jx-control-h)",
144
+ "paddingInline": "calc(var(--jx-space-3) + var(--jx-tree-indent, 16px) * ${state.level - 1}) var(--jx-space-3)",
145
+ "borderInlineStart": "2px solid transparent",
146
+ "color": "inherit",
147
+ "cursor": "default",
148
+ "userSelect": "none",
149
+ "whiteSpace": "nowrap",
150
+ "outline": "none",
151
+ ":hover": {
152
+ "background": "var(--jx-hover-bg)"
153
+ },
154
+ ":focus-visible": {
155
+ "outline": "var(--jx-focus-ring)",
156
+ "outlineOffset": "-2px"
157
+ },
158
+ "&[aria-selected=\"true\"]": {
159
+ "background": "var(--jx-accent-20)",
160
+ "borderInlineStartColor": "var(--jx-accent-solid)"
161
+ },
162
+ "&[aria-disabled=\"true\"]": {
163
+ "opacity": "0.5"
164
+ },
165
+ "&[data-cut]": {
166
+ "opacity": "0.5"
167
+ },
168
+ "&[data-dragging]": {
169
+ "$description": "The two attributes the DRAG ISLAND writes, styled here and bound nowhere. Pragmatic-drag-and-drop attaches to the rows through the host and sets them imperatively for the length of a gesture; a prop mirroring either would be a second writer, and the repaint that cleared it mid-drag is exactly the stale affordance a cancelled drag used to leave on screen.",
170
+ "opacity": "0.4"
171
+ },
172
+ "&[data-drop]": {
173
+ "background": "var(--jx-accent-15)",
174
+ "outline": "1px dashed var(--jx-accent-solid)",
175
+ "outlineOffset": "-1px"
176
+ },
177
+ "& [part=\"twisty\"]": {
178
+ "$description": "A leaf draws no chevron and still occupies the box, which is what lines every name in the tree up whether or not its row can be opened. It is `aria-hidden` and not a button: the row itself carries `aria-expanded`, `ArrowRight` and `ArrowLeft` operate it, and a focusable control inside a composite would put a second tab stop on every row.",
179
+ "display": "inline-flex",
180
+ "alignItems": "center",
181
+ "justifyContent": "center",
182
+ "inlineSize": "14px",
183
+ "flex": "none",
184
+ "color": "var(--jx-fg-dim)",
185
+ "--jx-icon-size": "12px"
186
+ },
187
+ "& [part=\"label\"]": {
188
+ "flex": "1",
189
+ "minWidth": "0",
190
+ "overflow": "hidden",
191
+ "textOverflow": "ellipsis",
192
+ "whiteSpace": "nowrap"
193
+ },
194
+ "& [part=\"icon\"], & [part=\"status\"], & [part=\"actions\"], & [part=\"grip-slot\"]": {
195
+ "$description": "The four consumer zones generate no box of their own. `display: contents` is what makes an EMPTY one cost nothing — as ordinary flex items they would each take the row's gap, which is dead space on every row that slotted nothing — and it is what puts slotted content in the ROW's flex line rather than one box deeper, so an icon, a language chip and a pin button line up with the name on one gap rather than on three.",
196
+ "display": "contents"
197
+ },
198
+ "& [part=\"grip\"]": {
199
+ "$description": "Revealed on hover, on the selected row, and whenever anything inside the row has focus. The third is what keeps it visible for a reader who arrived by keyboard, even though they will move the row with cut and paste rather than with this.",
200
+ "display": "none",
201
+ "flex": "none",
202
+ "color": "var(--jx-fg-dim)",
203
+ "fontSize": "var(--jx-text-sm)",
204
+ "cursor": "grab"
205
+ },
206
+ "&:hover [part=\"grip\"], &[aria-selected=\"true\"] [part=\"grip\"], &:focus-within [part=\"grip\"]": {
207
+ "display": "inline-block"
208
+ },
209
+ "@(forced-colors: active)": {
210
+ "&[aria-selected=\"true\"]": {
211
+ "$description": "The selected row, redrawn. Every author colour is replaced by one from the user's palette in forced colours, so the accent wash behind the row collapses into the page and the inline-start edge is the only marker left.",
212
+ "borderInlineStartColor": "Highlight"
213
+ }
214
+ }
215
+ },
216
+ "children": [
217
+ {
218
+ "tagName": "span",
219
+ "attributes": {
220
+ "part": "twisty",
221
+ "aria-hidden": "true"
222
+ },
223
+ "onclick": {
224
+ "$ref": "#/state/onTwisty"
225
+ },
226
+ "$switch": {
227
+ "$ref": "#/state/expanded"
228
+ },
229
+ "cases": {
230
+ "true": {
231
+ "tagName": "jx-icon",
232
+ "$props": {
233
+ "name": "caret-down",
234
+ "weight": "bold"
235
+ },
236
+ "attributes": {
237
+ "part": "twisty-icon"
238
+ }
239
+ },
240
+ "false": {
241
+ "tagName": "jx-icon",
242
+ "$props": {
243
+ "name": "caret-right",
244
+ "weight": "bold"
245
+ },
246
+ "attributes": {
247
+ "part": "twisty-icon"
248
+ }
249
+ }
250
+ }
251
+ },
252
+ {
253
+ "tagName": "span",
254
+ "attributes": {
255
+ "part": "icon"
256
+ },
257
+ "children": [
258
+ {
259
+ "tagName": "slot",
260
+ "attributes": {
261
+ "name": "icon"
262
+ }
263
+ }
264
+ ]
265
+ },
266
+ {
267
+ "tagName": "span",
268
+ "attributes": {
269
+ "part": "label"
270
+ },
271
+ "textContent": "${state.label}"
272
+ },
273
+ {
274
+ "tagName": "span",
275
+ "attributes": {
276
+ "part": "status"
277
+ },
278
+ "children": [
279
+ {
280
+ "tagName": "slot",
281
+ "attributes": {
282
+ "name": "status"
283
+ }
284
+ }
285
+ ]
286
+ },
287
+ {
288
+ "tagName": "span",
289
+ "attributes": {
290
+ "part": "grip-slot"
291
+ },
292
+ "$switch": {
293
+ "$ref": "#/state/grip"
294
+ },
295
+ "cases": {
296
+ "true": {
297
+ "tagName": "span",
298
+ "attributes": {
299
+ "part": "grip",
300
+ "aria-hidden": "true",
301
+ "title": "Drag to reorder"
302
+ },
303
+ "textContent": "⠿"
304
+ }
305
+ }
306
+ },
307
+ {
308
+ "tagName": "span",
309
+ "attributes": {
310
+ "part": "actions"
311
+ },
312
+ "onclick": {
313
+ "$ref": "#/state/onActions"
314
+ },
315
+ "children": [
316
+ {
317
+ "tagName": "slot",
318
+ "attributes": {
319
+ "name": "actions"
320
+ }
321
+ }
322
+ ]
323
+ }
324
+ ]
325
+ }
@@ -0,0 +1,202 @@
1
+ {
2
+ "$schema": "https://jxsuite.com/schema/v1",
3
+ "$id": "JxTree",
4
+ "tagName": "jx-tree",
5
+ "$description": "The `role=\"tree\"` itself, over a FLAT list of `jx-tree-item` rows. Four decisions are the whole design, and each of them is forced by the same fact: a Studio tree windows, so the DOM holds a SLICE and the element must never mistake it for the model. (1) THE HOST OWNS THE ROW SET. The element takes a flat projection — one row per drawn line, each carrying its own `level`, `posinset`, `setsize` and `expanded` — rather than a nested structure, because a nested one cannot be windowed at all: a subtree is a variable number of lines and a viewport is a fixed number of them. Both Studio trees already project flat rows for exactly this reason, and neither can go back. (2) THE COUNTS DESCRIBE THE FULL SET. `aria-setsize` and `aria-posinset` are the row's props and are never derived from `items.length`, which is how a reader ends up told a directory holds eleven files when it holds ten thousand. (3) MULTI-SELECT IS AN INTENT, NOT A SET. The tree owns the ANCHOR and dispatches `select` with a mode — `replace`, `toggle` or `range` — and the HOST resolves it, because resolving a range means naming every row between two of them and the rows between are precisely the ones the drawn slice does not have. (4) DRAG AND DROP IS AN ISLAND. Pragmatic-drag-and-drop attaches through the host (studio-ui-guidelines.md §9.4); the element's job is to make every row addressable (`data-value` on each) and to keep the non-drag alternative alive, which means the roving caret reaches every row and every chord the tree does not own — cut, paste, delete, rename, select-all — passes straight through to the host (WCAG 2.2 SC 2.5.7, guidelines §8.2). The windowing contract is `padtop` and `padbottom` plus the `move` and `typeahead` events, and it is deliberately NOT a second geometry engine: the pads are the element's only knowledge of what it cannot see, and a caret move past one — or a letter the slice cannot honestly answer — is dispatched rather than performed.",
6
+ "observedAttributes": ["label", "multiple", "current", "padtop", "padbottom"],
7
+ "state": {
8
+ "label": {
9
+ "type": "string",
10
+ "default": "Tree",
11
+ "attribute": "label",
12
+ "description": "The accessible name of the tree. A `tree` takes no name from its rows and nothing lints a missing one — a bound role switches the naming rules off — so name every tree. \"Document outline\", \"Project files\": what this tree is OF, since a window may hold two."
13
+ },
14
+ "multiple": {
15
+ "type": "boolean",
16
+ "default": false,
17
+ "attribute": "multiple",
18
+ "description": "Whether more than one row may be selected, announced as `aria-multiselectable`. It is also the gate on the modifiers: with it off, every `select` the element dispatches is `replace` however many keys the reader is holding, so a host that never asked for multi-select cannot be handed a `toggle` it has no code for. This is where the multi answer lives rather than on the rows, because a `aria-selected=\"false\"` on every row is the OTHER way to say it and a row cannot see its tree."
19
+ },
20
+ "current": {
21
+ "type": "string",
22
+ "default": "",
23
+ "attribute": "current",
24
+ "description": "The `value` of the row holding the roving caret. The tree is the single writer of every drawn row's `caret`, derived from this, so a host moves the caret by writing HERE and never a row's own state. Under windowing the row it names is routinely not drawn; the tab stop then falls back to the first drawn row so the tree cannot drop out of the tab order, and `current` itself is left exactly as the host wrote it."
25
+ },
26
+ "anchor": {
27
+ "type": "string",
28
+ "default": "",
29
+ "description": "The row a `Shift` range extends from. The tree owns it — it is a fact about the reader's gesture rather than about the document — and writes it on every `replace` and `toggle`. A host may seed it to restore a session; it never has to."
30
+ },
31
+ "padtop": {
32
+ "type": "number",
33
+ "default": 0,
34
+ "attribute": "padtop",
35
+ "description": "The height, in pixels, of the rows the window left ABOVE the drawn slice, reserved as a spacer so the scrollbar stays the length of the whole model. It is also the element's only knowledge that those rows exist: a caret move off the top of the slice dispatches `move` when this is non-zero and clamps when it is zero, which is how one code path serves a windowed tree and a small one that draws every row."
36
+ },
37
+ "padbottom": {
38
+ "type": "number",
39
+ "default": 0,
40
+ "attribute": "padbottom",
41
+ "description": "The same, below. Zero at both ends means the slice IS the model, and the ends of the drawn rows are the ends of the tree."
42
+ },
43
+ "applyCurrent": {
44
+ "$prototype": "Function",
45
+ "$src": "jx-ui:/behaviors/tree.ts",
46
+ "$export": "applyCurrent",
47
+ "parameters": ["scope"],
48
+ "description": "Writes the caret onto the rows and returns it. Idempotent: it runs inside a render effect."
49
+ },
50
+ "sync": "${state.applyCurrent(state)}",
51
+ "onKeydown": {
52
+ "$prototype": "Function",
53
+ "$src": "jx-ui:/behaviors/tree.ts",
54
+ "$export": "onTreeKeydown",
55
+ "description": "The ARIA tree keyboard contract: the arrows walk the drawn rows without wrapping, Home and End go to the ends, ArrowRight opens a closed row and steps into an open one, ArrowLeft closes an open row and steps out of a closed one, Enter activates, Space is a selection verb, and a printable character is typeahead — resolved over the drawn rows when they are the whole model and dispatched as `typeahead` when a pad says they are not. Shift extends from the anchor and Ctrl or Cmd moves the caret alone. Every other key is left alone, which is what keeps the host's cut, paste, delete and rename chords reachable from a focused row.",
56
+ "emits": [
57
+ {
58
+ "name": "change",
59
+ "description": "The caret moved to a drawn row; detail is that row's value. `current` is written before this is dispatched, so a listener reading `e.target.current` sees the new value."
60
+ },
61
+ {
62
+ "name": "select",
63
+ "description": "The reader meant something by landing there; detail is `{ value, mode, anchor }` where mode is `replace`, `toggle` or `range`. An INTENT: the host owns the selection set and resolves the range."
64
+ },
65
+ {
66
+ "name": "move",
67
+ "description": "The caret must reach a row the drawn slice does not have; detail is `{ from, key }`. The host scrolls, repaints and writes `current`. Dispatched only when a pad says there are rows that way."
68
+ },
69
+ {
70
+ "name": "typeahead",
71
+ "description": "A printable character, on a tree whose drawn slice is not the whole model; detail is `{ from, char }` and the host answers it against the model exactly as it answers `move`. Dispatched for EVERY such character rather than only when the slice holds no match: a move off the end of the slice announces itself, and a letter never does — a search over a slice always answers, and the answer is simply the wrong row. Not dispatched at all when both pads are zero, where the drawn rows ARE the tree and the element resolves the letter itself."
72
+ },
73
+ {
74
+ "name": "expand",
75
+ "description": "A row should be opened or closed; detail is `{ value, expanded }`, and `expanded` is the state it should be PUT INTO rather than the state it is in."
76
+ },
77
+ {
78
+ "name": "activate",
79
+ "description": "A row was activated with Enter; detail is its value. What that means — open the file, start the rename — is the host's."
80
+ }
81
+ ]
82
+ },
83
+ "onClick": {
84
+ "$prototype": "Function",
85
+ "$src": "jx-ui:/behaviors/tree.ts",
86
+ "$export": "onTreeClick",
87
+ "description": "A pointer landed on a row: the caret follows it and the modifiers say what it meant. Delegated on the TREE rather than dispatched by each row, which is the one place the kit departs from its own child-dispatches-select idiom — a selection intent IS the modifier state, and a row would have to copy three booleans out of the mouse event for the tree to read them back.",
88
+ "emits": [
89
+ {
90
+ "name": "change",
91
+ "description": "The caret moved to the clicked row; detail is its value."
92
+ },
93
+ {
94
+ "name": "select",
95
+ "description": "The selection intent the click carried; detail is `{ value, mode, anchor }`."
96
+ }
97
+ ]
98
+ },
99
+ "onDblClick": {
100
+ "$prototype": "Function",
101
+ "$src": "jx-ui:/behaviors/tree.ts",
102
+ "$export": "onTreeDblClick",
103
+ "description": "A double click means what Enter means. One verb for both, so a pointer-only reader and a keyboard-only reader reach the same thing.",
104
+ "emits": [
105
+ {
106
+ "name": "activate",
107
+ "description": "A row was activated with a double click; detail is its value."
108
+ }
109
+ ]
110
+ },
111
+ "onToggle": {
112
+ "$prototype": "Function",
113
+ "$src": "jx-ui:/behaviors/tree.ts",
114
+ "$export": "onTreeToggle",
115
+ "description": "A row's twisty was pressed. The row says only which row it was; the tree works out which way it should go and answers with its own `expand`, so that event has ONE dispatcher and `e.target` is the element the host wrote whether the reader used the twisty or ArrowLeft.",
116
+ "emits": [
117
+ {
118
+ "name": "expand",
119
+ "description": "The row should be opened or closed; detail is `{ value, expanded }`."
120
+ }
121
+ ]
122
+ },
123
+ "onReady": {
124
+ "$prototype": "Function",
125
+ "$src": "jx-ui:/behaviors/tree.ts",
126
+ "$export": "onTreeMount",
127
+ "description": "Gives the tree its first sight of itself, syncs the caret and starts watching the row set. Not optional: `applyAttributes` runs before `distributeSlots`, so no row exists when the root's bindings first evaluate — and a scroll replaces the whole slice, so the watch is what keeps one tab stop alive afterwards."
128
+ },
129
+ "onMount": {
130
+ "$prototype": "Function",
131
+ "body": [
132
+ {
133
+ "dispatchEvent": "jx-ready",
134
+ "bubbles": true
135
+ }
136
+ ],
137
+ "description": "Announces that the tree has rendered and taken its slotted rows, which is how onReady reaches the element itself."
138
+ }
139
+ },
140
+ "attributes": {
141
+ "role": "tree",
142
+ "aria-label": "${state.label}",
143
+ "aria-multiselectable": "${state.multiple ? 'true' : null}",
144
+ "tabindex": "-1",
145
+ "data-current": "${state.sync}"
146
+ },
147
+ "onkeydown": {
148
+ "$ref": "#/state/onKeydown"
149
+ },
150
+ "onclick": {
151
+ "$ref": "#/state/onClick"
152
+ },
153
+ "ondblclick": {
154
+ "$ref": "#/state/onDblClick"
155
+ },
156
+ "ontoggle": {
157
+ "$ref": "#/state/onToggle"
158
+ },
159
+ "onjx-ready": {
160
+ "$ref": "#/state/onReady"
161
+ },
162
+ "style": {
163
+ "$description": "The tree declares no `overflow` and owns no scrollbar, which is a decision rather than an omission: one Studio tree scrolls inside itself and the other scrolls inside the panel that also holds its toolbar, and a windowing observer resolves the nearest scrolling ancestor either way. Declaring one here would settle that question wrongly for one of them. The indent is a custom property on the CONTAINER so it inherits to every row and a consumer can retune the whole tree with one declaration.",
164
+ "display": "block",
165
+ "&[hidden]": {
166
+ "display": "none"
167
+ },
168
+ "boxSizing": "border-box",
169
+ "--jx-tree-indent": "16px",
170
+ "fontFamily": "var(--jx-font-sans)",
171
+ "fontSize": "var(--jx-text-md)",
172
+ "lineHeight": "var(--jx-leading-md)",
173
+ "color": "var(--jx-fg)",
174
+ "outline": "none",
175
+ "& [part=\"pad-top\"], & [part=\"pad-bottom\"]": {
176
+ "$description": "The rows the window left out, as the height they would have occupied, so the scrollbar stays the length of the whole model. `aria-hidden` because a `role=\"tree\"` owns `treeitem`s and a div that exists to be four thousand pixels tall is not one.",
177
+ "display": "block",
178
+ "flex": "none"
179
+ }
180
+ },
181
+ "children": [
182
+ {
183
+ "tagName": "div",
184
+ "attributes": {
185
+ "part": "pad-top",
186
+ "aria-hidden": "true",
187
+ "style": "block-size: ${state.padtop}px"
188
+ }
189
+ },
190
+ {
191
+ "tagName": "slot"
192
+ },
193
+ {
194
+ "tagName": "div",
195
+ "attributes": {
196
+ "part": "pad-bottom",
197
+ "aria-hidden": "true",
198
+ "style": "block-size: ${state.padbottom}px"
199
+ }
200
+ }
201
+ ]
202
+ }
package/icons/list.json CHANGED
@@ -18,6 +18,7 @@
18
18
  "arrow-up": ["regular", "bold"],
19
19
  "arrows-clockwise": ["regular"],
20
20
  "arrows-horizontal": ["regular"],
21
+ "arrows-in-line-horizontal": ["regular"],
21
22
  "arrows-out": ["regular"],
22
23
  "arrows-out-line-horizontal": ["regular"],
23
24
  "arrows-out-line-vertical": ["regular"],
@@ -43,6 +44,7 @@
43
44
  "export": ["regular"],
44
45
  "eye": ["regular", "fill"],
45
46
  "eye-slash": ["regular"],
47
+ "eyedropper": ["regular"],
46
48
  "file": ["regular", "fill"],
47
49
  "file-code": ["regular"],
48
50
  "file-text": ["regular"],
@@ -61,6 +63,7 @@
61
63
  "lightning": ["regular", "fill"],
62
64
  "link": ["regular"],
63
65
  "list": ["regular"],
66
+ "lock-simple": ["regular"],
64
67
  "list-bullets": ["regular"],
65
68
  "magnifying-glass": ["regular"],
66
69
  "minus": ["regular", "bold"],
@@ -82,6 +85,7 @@
82
85
  "stack": ["regular", "fill"],
83
86
  "stop": ["regular", "fill"],
84
87
  "tag": ["regular", "fill"],
88
+ "text-a-underline": ["regular"],
85
89
  "text-aa": ["regular"],
86
90
  "text-align-center": ["regular"],
87
91
  "text-align-justify": ["regular"],
@@ -61,6 +61,9 @@
61
61
  "arrows-horizontal": {
62
62
  "regular": "M237.66,133.66l-32,32a8,8,0,0,1-11.32-11.32L212.69,136H43.31l18.35,18.34a8,8,0,0,1-11.32,11.32l-32-32a8,8,0,0,1,0-11.32l32-32a8,8,0,0,1,11.32,11.32L43.31,120H212.69l-18.35-18.34a8,8,0,0,1,11.32-11.32l32,32A8,8,0,0,1,237.66,133.66Z"
63
63
  },
64
+ "arrows-in-line-horizontal": {
65
+ "regular": "M136,40V216a8,8,0,0,1-16,0V40a8,8,0,0,1,16,0ZM69.66,90.34a8,8,0,0,0-11.32,11.32L76.69,120H16a8,8,0,0,0,0,16H76.69L58.34,154.34a8,8,0,0,0,11.32,11.32l32-32a8,8,0,0,0,0-11.32ZM240,120H179.31l18.35-18.34a8,8,0,0,0-11.32-11.32l-32,32a8,8,0,0,0,0,11.32l32,32a8,8,0,0,0,11.32-11.32L179.31,136H240a8,8,0,0,0,0-16Z"
66
+ },
64
67
  "arrows-out": {
65
68
  "regular": "M216,48V96a8,8,0,0,1-16,0V67.31l-42.34,42.35a8,8,0,0,1-11.32-11.32L188.69,56H160a8,8,0,0,1,0-16h48A8,8,0,0,1,216,48ZM98.34,146.34,56,188.69V160a8,8,0,0,0-16,0v48a8,8,0,0,0,8,8H96a8,8,0,0,0,0-16H67.31l42.35-42.34a8,8,0,0,0-11.32-11.32ZM208,152a8,8,0,0,0-8,8v28.69l-42.34-42.35a8,8,0,0,0-11.32,11.32L188.69,200H160a8,8,0,0,0,0,16h48a8,8,0,0,0,8-8V160A8,8,0,0,0,208,152ZM67.31,56H96a8,8,0,0,0,0-16H48a8,8,0,0,0-8,8V96a8,8,0,0,0,16,0V67.31l42.34,42.35a8,8,0,0,0,11.32-11.32Z"
66
69
  },
@@ -147,6 +150,9 @@
147
150
  "eye-slash": {
148
151
  "regular": "M53.92,34.62A8,8,0,1,0,42.08,45.38L61.32,66.55C25,88.84,9.38,123.2,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208a127.11,127.11,0,0,0,52.07-10.83l22,24.21a8,8,0,1,0,11.84-10.76Zm47.33,75.84,41.67,45.85a32,32,0,0,1-41.67-45.85ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.16,133.16,0,0,1,25,128c4.69-8.79,19.66-33.39,47.35-49.38l18,19.75a48,48,0,0,0,63.66,70l14.73,16.2A112,112,0,0,1,128,192Zm6-95.43a8,8,0,0,1,3-15.72,48.16,48.16,0,0,1,38.77,42.64,8,8,0,0,1-7.22,8.71,6.39,6.39,0,0,1-.75,0,8,8,0,0,1-8-7.26A32.09,32.09,0,0,0,134,96.57Zm113.28,34.69c-.42.94-10.55,23.37-33.36,43.8a8,8,0,1,1-10.67-11.92A132.77,132.77,0,0,0,231.05,128a133.15,133.15,0,0,0-23.12-30.77C185.67,75.19,158.78,64,128,64a118.37,118.37,0,0,0-19.36,1.57A8,8,0,1,1,106,49.79,134,134,0,0,1,128,48c34.88,0,66.57,13.26,91.66,38.35,18.83,18.83,27.3,37.62,27.65,38.41A8,8,0,0,1,247.31,131.26Z"
149
152
  },
153
+ "eyedropper": {
154
+ "regular": "M224,67.3a35.79,35.79,0,0,0-11.26-25.66c-14-13.28-36.72-12.78-50.62,1.13L142.8,62.2a24,24,0,0,0-33.14.77l-9,9a16,16,0,0,0,0,22.64l2,2.06-51,51a39.75,39.75,0,0,0-10.53,38l-8,18.41A13.68,13.68,0,0,0,36,219.3a15.92,15.92,0,0,0,17.71,3.35L71.23,215a39.89,39.89,0,0,0,37.06-10.75l51-51,2.06,2.06a16,16,0,0,0,22.62,0l9-9a24,24,0,0,0,.74-33.18l19.75-19.87A35.75,35.75,0,0,0,224,67.3ZM97,193a24,24,0,0,1-24,6,8,8,0,0,0-5.55.31l-18.1,7.91L57,189.41a8,8,0,0,0,.25-5.75A23.88,23.88,0,0,1,63,159l51-51,33.94,34ZM202.13,82l-25.37,25.52a8,8,0,0,0,0,11.3l4.89,4.89a8,8,0,0,1,0,11.32l-9,9L112,83.26l9-9a8,8,0,0,1,11.31,0l4.89,4.89a8,8,0,0,0,11.33,0l24.94-25.09c7.81-7.82,20.5-8.18,28.29-.81a20,20,0,0,1,.39,28.7Z"
155
+ },
150
156
  "file": {
151
157
  "regular": "M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Z",
152
158
  "fill": "M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM152,88V44l44,44Z"
@@ -212,6 +218,9 @@
212
218
  "list-bullets": {
213
219
  "regular": "M80,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H88A8,8,0,0,1,80,64Zm136,56H88a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm0,64H88a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM44,52A12,12,0,1,0,56,64,12,12,0,0,0,44,52Zm0,64a12,12,0,1,0,12,12A12,12,0,0,0,44,116Zm0,64a12,12,0,1,0,12,12A12,12,0,0,0,44,180Z"
214
220
  },
221
+ "lock-simple": {
222
+ "regular": "M208,80H176V56a48,48,0,0,0-96,0V80H48A16,16,0,0,0,32,96V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V96A16,16,0,0,0,208,80ZM96,56a32,32,0,0,1,64,0V80H96ZM208,208H48V96H208V208Z"
223
+ },
215
224
  "magnifying-glass": {
216
225
  "regular": "M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z"
217
226
  },
@@ -288,6 +297,9 @@
288
297
  "regular": "M243.31,136,144,36.69A15.86,15.86,0,0,0,132.69,32H40a8,8,0,0,0-8,8v92.69A15.86,15.86,0,0,0,36.69,144L136,243.31a16,16,0,0,0,22.63,0l84.68-84.68a16,16,0,0,0,0-22.63Zm-96,96L48,132.69V48h84.69L232,147.31ZM96,84A12,12,0,1,1,84,72,12,12,0,0,1,96,84Z",
289
298
  "fill": "M243.31,136,144,36.69A15.86,15.86,0,0,0,132.69,32H40a8,8,0,0,0-8,8v92.69A15.86,15.86,0,0,0,36.69,144L136,243.31a16,16,0,0,0,22.63,0l84.68-84.68a16,16,0,0,0,0-22.63ZM84,96A12,12,0,1,1,96,84,12,12,0,0,1,84,96Z"
290
299
  },
300
+ "text-a-underline": {
301
+ "regular": "M60.59,175.24a8,8,0,0,0,10.65-3.83L87.9,136h80.2l16.66,35.41a8,8,0,1,0,14.48-6.82l-64-136a8,8,0,0,0-14.48,0l-64,136A8,8,0,0,0,60.59,175.24ZM128,50.79,160.57,120H95.43ZM224,216a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16H216A8,8,0,0,1,224,216Z"
302
+ },
291
303
  "text-aa": {
292
304
  "regular": "M87.24,52.59a8,8,0,0,0-14.48,0l-64,136a8,8,0,1,0,14.48,6.81L39.9,160h80.2l16.66,35.4a8,8,0,1,0,14.48-6.81ZM47.43,144,80,74.79,112.57,144ZM200,96c-12.76,0-22.73,3.47-29.63,10.32a8,8,0,0,0,11.26,11.36c3.8-3.77,10-5.68,18.37-5.68,13.23,0,24,9,24,20v3.22A42.76,42.76,0,0,0,200,128c-22.06,0-40,16.15-40,36s17.94,36,40,36a42.73,42.73,0,0,0,24-7.25,8,8,0,0,0,16-.75V132C240,112.15,222.06,96,200,96Zm0,88c-13.23,0-24-9-24-20s10.77-20,24-20,24,9,24,20S213.23,184,200,184Z"
293
305
  },