@threadlabs/looma 0.10.2 → 0.10.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.10.2",
3
+ "version": "0.10.4",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -1,4 +1,13 @@
1
1
  /* Generated by HTML Next 1.0.0-alpha.5. Do not edit. */
2
+ @keyframes ui-tree-item-marquee {
3
+ from {
4
+ transform: translateX(0);
5
+ }
6
+
7
+ to {
8
+ transform: translateX(var(--_marquee-distance));
9
+ }
10
+ }
2
11
  @scope ([data-component~="ui-tree-item"]) to ([data-component], [data-slotted]) {
3
12
  :scope {
4
13
  --_tree-row-min-height: 32px;
@@ -144,11 +153,63 @@
144
153
  display: flex;
145
154
  align-items: center;
146
155
  min-inline-size: 0;
156
+ /* The cell clips: a name longer than the row slides inside it rather than over its icons. */
157
+ overflow: hidden;
147
158
  padding: var(--ui-tree-item-label-padding-block, var(--ui-space-1)) var(--ui-tree-item-label-padding-inline, var(--ui-space-2));
148
159
  }
149
160
 
150
161
  /* Slotted label content fills the cell, so a link is the row's hit area rather than sizing to
151
162
  its own text and reading as overflow. */
163
+ .label-text {
164
+ display: flex;
165
+ align-items: center;
166
+ /* Stretches, so slotted content can still be the row's full-height hit area. */
167
+ align-self: stretch;
168
+ block-size: 100%;
169
+ min-inline-size: 0;
170
+ inline-size: 100%;
171
+ white-space: nowrap;
172
+ }
173
+
174
+ /* A hovered row reads its whole name: the label slides far enough to show the end, waits
175
+ there, and comes back. Only when it actually overflows — the controller measures by how
176
+ much — after the controls have taken their place over its end — and hands over the distance
177
+ and a duration from it, so a longer name travels no faster. */
178
+ .row[data-ui-marquee] > .label > .label-text {
179
+ /* Once, and it stays at the end while the pointer is there: a name that keeps sliding back
180
+ and forth under the pointer is harder to read than one that arrives and holds. */
181
+ animation: ui-tree-item-marquee var(--_marquee-duration) linear 0.5s 1 normal forwards;
182
+ will-change: transform;
183
+ }
184
+
185
+ /* While it slides, the start fades too: the name is leaving the cell at both ends, and a hard
186
+ edge beside the icon reads as a collision rather than as more text. */
187
+ .row[data-ui-marquee] > .label {
188
+ mask-image: linear-gradient(
189
+ to right,
190
+ transparent 0,
191
+ #000 var(--ui-space-2),
192
+ #000 calc(100% - var(--_tree-actions-width, 1.75rem) - var(--ui-space-3)),
193
+ transparent calc(100% - var(--_tree-actions-width, 1.75rem))
194
+ );
195
+ }
196
+
197
+ .row[data-ui-marquee] > .label:dir(rtl) {
198
+ mask-image: linear-gradient(
199
+ to left,
200
+ transparent 0,
201
+ #000 var(--ui-space-2),
202
+ #000 calc(100% - var(--_tree-actions-width, 1.75rem) - var(--ui-space-3)),
203
+ transparent calc(100% - var(--_tree-actions-width, 1.75rem))
204
+ );
205
+ }
206
+
207
+ /* Reduced motion keeps the fade, which already says the name continues. */
208
+ @media (prefers-reduced-motion: reduce) {
209
+ .row[data-ui-marquee] > .label > .label-text {
210
+ animation: none;
211
+ }
212
+ }
152
213
 
153
214
  /* The controls sit over the label's end, so the text fades out where they begin. The fade is
154
215
  the truncation mark while it shows: an ellipsis under it would read as a second one. */
@@ -327,11 +388,24 @@
327
388
 
328
389
  /* Slotted label content fills the cell, so a link is the row's hit area rather than sizing to
329
390
  its own text and reading as overflow. */
330
- .label > :where([data-slotted], [data-slotted] *):is(*) {
391
+
392
+ .label-text > :where([data-slotted], [data-slotted] *):is(*) {
331
393
  flex: 1 1 auto;
332
394
  min-inline-size: 0;
333
395
  }
334
396
 
397
+ /* A hovered row reads its whole name: the label slides far enough to show the end, waits
398
+ there, and comes back. Only when it actually overflows — the controller measures by how
399
+ much — after the controls have taken their place over its end — and hands over the distance
400
+ and a duration from it, so a longer name travels no faster. */
401
+
402
+ /* While it slides, the start fades too: the name is leaving the cell at both ends, and a hard
403
+ edge beside the icon reads as a collision rather than as more text. */
404
+
405
+ /* Reduced motion keeps the fade, which already says the name continues. */
406
+ @media (prefers-reduced-motion: reduce) {
407
+ }
408
+
335
409
  /* The controls sit over the label's end, so the text fades out where they begin. The fade is
336
410
  the truncation mark while it shows: an ellipsis under it would read as a second one. */
337
411
 
@@ -1,6 +1,12 @@
1
1
  /* Generated by HTML Next 1.0.0-alpha.5. Do not edit. */
2
2
  @scope ([data-component~="ui-tree"]) to ([data-component], [data-slotted]) {
3
- :scope[data-ui-tree-state~="density=compact"] {
3
+ /* Reaches the items the way density does, so the setting lives on the tree the way a reader
4
+ thinks about it: this list scrolls its long names. */
5
+ :scope[data-ui-tree-state~="marquee"] {
6
+ --ui-tree-item-marquee: 1;
7
+ }
8
+
9
+ :scope[data-ui-tree-state~="density=compact"] {
4
10
  --ui-tree-item-min-block-size: var(--ui-control-size-sm);
5
11
  --ui-tree-item-font-size: var(--ui-font-size-sm);
6
12
  --ui-tree-item-label-padding-block: 0;
@@ -18,5 +24,8 @@
18
24
  }
19
25
  }
20
26
  @scope ([data-component~="ui-tree"]) to ([data-component]) {
21
- /* A component that sets its display still honours the hidden attribute on its root. */
27
+ /* Reaches the items the way density does, so the setting lives on the tree the way a reader
28
+ thinks about it: this list scrolls its long names. */
29
+
30
+ /* A component that sets its display still honours the hidden attribute on its root. */
22
31
  }
@@ -11,6 +11,7 @@ export interface UiTreeProps {
11
11
  density?: "comfortable" | "compact" | null;
12
12
  hoverExpandDelay?: number | null;
13
13
  label?: string | null;
14
+ marquee?: boolean | null;
14
15
  maxDepth?: number | null;
15
16
  attributes?: Readonly<Record<string, string | number | boolean | null | undefined>>;
16
17
  children?: readonly (string | Node)[];
package/vanilla/UiTree.js CHANGED
@@ -3,7 +3,7 @@ import { manageComponentLifecycle } from "@nextwebwg/html-next/runtime";
3
3
  import * as controller from "../components/ui-tree/ui-tree.js";
4
4
  import "../styles/ui-tree.css";
5
5
 
6
- const definition = {...{"contract":{"tag":"ui-tree","props":{"density":{"type":{"enum":["comfortable","compact"]},"required":false,"target":{"attribute":"density"},"default":"comfortable"},"hoverExpandDelay":{"type":"number","required":false,"target":{"attribute":"hoverexpanddelay"},"default":700},"label":{"type":"string","required":false,"target":{"attribute":"label"},"default":"Tree"},"maxDepth":{"type":"number","required":false,"target":{"attribute":"maxdepth"},"default":0}}},"template":{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"role","value":"tree"}],"children":[{"kind":"slot"}]},"declarations":[{"kind":"event","name":"reorder","type":"object({ sourceId: string, targetId: string, position: before | inside | after, sourceType: string, targetType: string, sourceScope: string, targetScope: string, trigger: keyboard | pointer | programmatic })","bubbles":true,"composed":true,"cancelable":false},{"kind":"event","name":"reorder-rejected","type":"object({ sourceId: string, targetId: string, position: before | inside | after, reason: max-depth, maxDepth: integer, resultingDepth: integer, trigger: keyboard | pointer | programmatic })","bubbles":true,"composed":true,"cancelable":false}],"root":{"kind":"native","element":"div","choices":["div"]}},source:{file:import.meta.url},css:""};
6
+ const definition = {...{"contract":{"tag":"ui-tree","props":{"density":{"type":{"enum":["comfortable","compact"]},"required":false,"target":{"attribute":"density"},"default":"comfortable"},"hoverExpandDelay":{"type":"number","required":false,"target":{"attribute":"hoverexpanddelay"},"default":700},"label":{"type":"string","required":false,"target":{"attribute":"label"},"default":"Tree"},"marquee":{"type":"boolean","required":false,"target":{"attribute":"marquee"},"default":false},"maxDepth":{"type":"number","required":false,"target":{"attribute":"maxdepth"},"default":0}}},"template":{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"role","value":"tree"}],"children":[{"kind":"slot"}]},"declarations":[{"kind":"event","name":"reorder","type":"object({ sourceId: string, targetId: string, position: before | inside | after, sourceType: string, targetType: string, sourceScope: string, targetScope: string, trigger: keyboard | pointer | programmatic })","bubbles":true,"composed":true,"cancelable":false},{"kind":"event","name":"reorder-rejected","type":"object({ sourceId: string, targetId: string, position: before | inside | after, reason: max-depth, maxDepth: integer, resultingDepth: integer, trigger: keyboard | pointer | programmatic })","bubbles":true,"composed":true,"cancelable":false}],"root":{"kind":"native","element":"div","choices":["div"]}},source:{file:import.meta.url},css:""};
7
7
 
8
8
  export function createUiTree(options = {}) {
9
9
  const { attributes = {}, children = [], slots = {}, as, ...componentProps } = options;
@@ -16,6 +16,7 @@ export interface UiTreeItemProps {
16
16
  expanded?: boolean | null;
17
17
  itemId?: string | null;
18
18
  label?: string | null;
19
+ marquee?: boolean | null;
19
20
  selected?: boolean | null;
20
21
  sortable?: boolean | null;
21
22
  subtreeDepth?: number | null;
@@ -3,7 +3,7 @@ import { manageComponentLifecycle } from "@nextwebwg/html-next/runtime";
3
3
  import * as controller from "../components/ui-tree-item/ui-tree-item.js";
4
4
  import "../styles/ui-tree-item.css";
5
5
 
6
- const definition = {...{"contract":{"tag":"ui-tree-item","props":{"accepts":{"type":"string","required":false,"target":{"attribute":"data-accepts"},"default":""},"container":{"type":"boolean","required":false,"target":{"attribute":"container"},"default":false},"disabled":{"type":"boolean","required":false,"target":{"attribute":"aria-disabled"},"default":false},"dragType":{"type":"string","required":false,"target":{"attribute":"data-drag-type"},"default":"item"},"dropDepth":{"type":"number","required":false,"target":{"attribute":"data-drop-depth"}},"dropScope":{"type":"string","required":false,"target":{"attribute":"data-drop-scope"},"default":""},"expanded":{"type":"boolean","required":false,"target":{"attribute":"expanded"},"default":false},"itemId":{"type":"string","required":false,"target":{"attribute":"data-item-id"},"default":""},"label":{"type":"string","required":false,"target":{"attribute":"aria-label"},"default":""},"selected":{"type":"boolean","required":false,"target":{"attribute":"aria-selected"},"default":false},"sortable":{"type":"boolean","required":false,"target":{"attribute":"sortable"},"default":false},"subtreeDepth":{"type":"number","required":false,"target":{"attribute":"data-subtree-depth"}}}},"template":{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"role","value":"treeitem"},{"kind":"attribute","name":"aria-label","expression":"label","expressionPlan":{"source":"label","ast":{"kind":"id","name":"label"},"dependencies":["label"]}},{"kind":"attribute","name":"aria-level","expression":"structuralLevel","expressionPlan":{"source":"structuralLevel","ast":{"kind":"id","name":"structuralLevel"},"dependencies":["structuralLevel"]}},{"kind":"attribute","name":"aria-selected","expression":"selected","expressionPlan":{"source":"selected","ast":{"kind":"id","name":"selected"},"dependencies":["selected"]}},{"kind":"attribute","name":"aria-disabled","expression":"disabled","expressionPlan":{"source":"disabled","ast":{"kind":"id","name":"disabled"},"dependencies":["disabled"]}},{"kind":"attribute","name":"data-item-id","expression":"itemId","expressionPlan":{"source":"itemId","ast":{"kind":"id","name":"itemId"},"dependencies":["itemId"]}},{"kind":"attribute","name":"data-drag-type","expression":"dragType","expressionPlan":{"source":"dragType","ast":{"kind":"id","name":"dragType"},"dependencies":["dragType"]}},{"kind":"attribute","name":"data-drop-scope","expression":"dropScope","expressionPlan":{"source":"dropScope","ast":{"kind":"id","name":"dropScope"},"dependencies":["dropScope"]}},{"kind":"attribute","name":"data-accepts","expression":"accepts","expressionPlan":{"source":"accepts","ast":{"kind":"id","name":"accepts"},"dependencies":["accepts"]}},{"kind":"attribute","name":"data-drop-depth","expression":"dropDepth","expressionPlan":{"source":"dropDepth","ast":{"kind":"id","name":"dropDepth"},"dependencies":["dropDepth"]}},{"kind":"attribute","name":"data-subtree-depth","expression":"subtreeDepth","expressionPlan":{"source":"subtreeDepth","ast":{"kind":"id","name":"subtreeDepth"},"dependencies":["subtreeDepth"]}}],"children":[{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"class","value":"row"}],"children":[{"kind":"element","name":"button","attributes":[{"kind":"literal","name":"class","value":"drag-handle"},{"kind":"literal","name":"type","value":"button"},{"kind":"literal","name":"draggable","value":"true"},{"kind":"attribute","name":"aria-label","expression":"format('Drag %s to reorder', label)","expressionPlan":{"source":"format('Drag %s to reorder', label)","ast":{"kind":"call","fn":"format","args":[{"kind":"literal","value":"Drag %s to reorder"},{"kind":"id","name":"label"}]},"dependencies":["label"]}}],"children":[{"kind":"element","name":"svg","attributes":[{"kind":"literal","name":"aria-hidden","value":"true"},{"kind":"literal","name":"focusable","value":"false"},{"kind":"literal","name":"viewBox","value":"0 0 24 24"},{"kind":"literal","name":"fill","value":"none"},{"kind":"literal","name":"stroke","value":"currentColor"},{"kind":"literal","name":"stroke-width","value":"2"},{"kind":"literal","name":"stroke-linecap","value":"round"},{"kind":"literal","name":"stroke-linejoin","value":"round"}],"children":[{"kind":"element","name":"circle","attributes":[{"kind":"literal","name":"cx","value":"9"},{"kind":"literal","name":"cy","value":"12"},{"kind":"literal","name":"r","value":"1"}],"children":[]},{"kind":"element","name":"circle","attributes":[{"kind":"literal","name":"cx","value":"9"},{"kind":"literal","name":"cy","value":"5"},{"kind":"literal","name":"r","value":"1"}],"children":[]},{"kind":"element","name":"circle","attributes":[{"kind":"literal","name":"cx","value":"9"},{"kind":"literal","name":"cy","value":"19"},{"kind":"literal","name":"r","value":"1"}],"children":[]},{"kind":"element","name":"circle","attributes":[{"kind":"literal","name":"cx","value":"15"},{"kind":"literal","name":"cy","value":"12"},{"kind":"literal","name":"r","value":"1"}],"children":[]},{"kind":"element","name":"circle","attributes":[{"kind":"literal","name":"cx","value":"15"},{"kind":"literal","name":"cy","value":"5"},{"kind":"literal","name":"r","value":"1"}],"children":[]},{"kind":"element","name":"circle","attributes":[{"kind":"literal","name":"cx","value":"15"},{"kind":"literal","name":"cy","value":"19"},{"kind":"literal","name":"r","value":"1"}],"children":[]}]}],"flow":{"kind":"if","test":"sortable and not disabled","testPlan":{"source":"sortable and not disabled","ast":{"kind":"binary","op":"and","left":{"kind":"id","name":"sortable"},"right":{"kind":"unary","op":"not","operand":{"kind":"id","name":"disabled"}}},"dependencies":["disabled","sortable"]}},"ref":"dragHandle"},{"kind":"element","name":"button","attributes":[{"kind":"literal","name":"class","value":"disclosure"},{"kind":"literal","name":"type","value":"button"},{"kind":"attribute","name":"disabled","expression":"disabled","expressionPlan":{"source":"disabled","ast":{"kind":"id","name":"disabled"},"dependencies":["disabled"]}},{"kind":"attribute","name":"hidden","expression":"not isContainer","expressionPlan":{"source":"not isContainer","ast":{"kind":"unary","op":"not","operand":{"kind":"id","name":"isContainer"}},"dependencies":["isContainer"]}}],"children":[{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"disclosure-icon"},{"kind":"literal","name":"aria-hidden","value":"true"}],"children":[]}],"ref":"disclosure"},{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"leading"}],"children":[{"kind":"slot","fallback":[],"name":"leading"}]},{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"label"}],"children":[{"kind":"slot","fallback":[{"kind":"element","name":"template","attributes":[{"kind":"directive","name":"value","expression":"label","expressionPlan":{"source":"label","ast":{"kind":"id","name":"label"},"dependencies":["label"]}}],"children":[]}],"name":"label"}]},{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"actions"}],"children":[{"kind":"slot","fallback":[],"name":"actions"}],"ref":"actions"},{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"drop-indicator row-drop-indicator"},{"kind":"literal","name":"aria-hidden","value":"true"}],"children":[]}],"ref":"row"},{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"class","value":"children"},{"kind":"literal","name":"role","value":"group"},{"kind":"attribute","name":"hidden","expression":"not (isContainer and internalExpanded)","expressionPlan":{"source":"not (isContainer and internalExpanded)","ast":{"kind":"unary","op":"not","operand":{"kind":"binary","op":"and","left":{"kind":"id","name":"isContainer"},"right":{"kind":"id","name":"internalExpanded"}}},"dependencies":["internalExpanded","isContainer"]}}],"children":[{"kind":"slot"}],"ref":"children"},{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"drop-indicator subtree-drop-indicator"},{"kind":"literal","name":"aria-hidden","value":"true"}],"children":[]}]},"declarations":[{"kind":"state","name":"internalExpanded","expression":{"source":"false","ast":{"kind":"literal","value":false},"dependencies":[]}},{"kind":"state","name":"structuralLevel","expression":{"source":"1","ast":{"kind":"literal","value":1},"dependencies":[]}},{"kind":"state","name":"tabStop","expression":{"source":"false","ast":{"kind":"literal","value":false},"dependencies":[]}},{"kind":"state","name":"isContainer","expression":{"source":"false","ast":{"kind":"literal","value":false},"dependencies":[]}},{"kind":"event","name":"expand","type":"object({ id: string, expanded: boolean, trigger: keyboard | pointer | programmatic })","bubbles":false,"composed":true,"cancelable":false}],"root":{"kind":"native","element":"div","choices":["div"]}},source:{file:import.meta.url},css:""};
6
+ const definition = {...{"contract":{"tag":"ui-tree-item","props":{"accepts":{"type":"string","required":false,"target":{"attribute":"data-accepts"},"default":""},"container":{"type":"boolean","required":false,"target":{"attribute":"container"},"default":false},"disabled":{"type":"boolean","required":false,"target":{"attribute":"aria-disabled"},"default":false},"dragType":{"type":"string","required":false,"target":{"attribute":"data-drag-type"},"default":"item"},"dropDepth":{"type":"number","required":false,"target":{"attribute":"data-drop-depth"}},"dropScope":{"type":"string","required":false,"target":{"attribute":"data-drop-scope"},"default":""},"expanded":{"type":"boolean","required":false,"target":{"attribute":"expanded"},"default":false},"itemId":{"type":"string","required":false,"target":{"attribute":"data-item-id"},"default":""},"label":{"type":"string","required":false,"target":{"attribute":"aria-label"},"default":""},"marquee":{"type":"boolean","required":false,"target":{"attribute":"marquee"},"default":false},"selected":{"type":"boolean","required":false,"target":{"attribute":"aria-selected"},"default":false},"sortable":{"type":"boolean","required":false,"target":{"attribute":"sortable"},"default":false},"subtreeDepth":{"type":"number","required":false,"target":{"attribute":"data-subtree-depth"}}}},"template":{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"role","value":"treeitem"},{"kind":"attribute","name":"aria-label","expression":"label","expressionPlan":{"source":"label","ast":{"kind":"id","name":"label"},"dependencies":["label"]}},{"kind":"attribute","name":"aria-level","expression":"structuralLevel","expressionPlan":{"source":"structuralLevel","ast":{"kind":"id","name":"structuralLevel"},"dependencies":["structuralLevel"]}},{"kind":"attribute","name":"aria-selected","expression":"selected","expressionPlan":{"source":"selected","ast":{"kind":"id","name":"selected"},"dependencies":["selected"]}},{"kind":"attribute","name":"aria-disabled","expression":"disabled","expressionPlan":{"source":"disabled","ast":{"kind":"id","name":"disabled"},"dependencies":["disabled"]}},{"kind":"attribute","name":"data-item-id","expression":"itemId","expressionPlan":{"source":"itemId","ast":{"kind":"id","name":"itemId"},"dependencies":["itemId"]}},{"kind":"attribute","name":"data-drag-type","expression":"dragType","expressionPlan":{"source":"dragType","ast":{"kind":"id","name":"dragType"},"dependencies":["dragType"]}},{"kind":"attribute","name":"data-drop-scope","expression":"dropScope","expressionPlan":{"source":"dropScope","ast":{"kind":"id","name":"dropScope"},"dependencies":["dropScope"]}},{"kind":"attribute","name":"data-accepts","expression":"accepts","expressionPlan":{"source":"accepts","ast":{"kind":"id","name":"accepts"},"dependencies":["accepts"]}},{"kind":"attribute","name":"data-drop-depth","expression":"dropDepth","expressionPlan":{"source":"dropDepth","ast":{"kind":"id","name":"dropDepth"},"dependencies":["dropDepth"]}},{"kind":"attribute","name":"data-subtree-depth","expression":"subtreeDepth","expressionPlan":{"source":"subtreeDepth","ast":{"kind":"id","name":"subtreeDepth"},"dependencies":["subtreeDepth"]}}],"children":[{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"class","value":"row"}],"children":[{"kind":"element","name":"button","attributes":[{"kind":"literal","name":"class","value":"drag-handle"},{"kind":"literal","name":"type","value":"button"},{"kind":"literal","name":"draggable","value":"true"},{"kind":"attribute","name":"aria-label","expression":"format('Drag %s to reorder', label)","expressionPlan":{"source":"format('Drag %s to reorder', label)","ast":{"kind":"call","fn":"format","args":[{"kind":"literal","value":"Drag %s to reorder"},{"kind":"id","name":"label"}]},"dependencies":["label"]}}],"children":[{"kind":"element","name":"svg","attributes":[{"kind":"literal","name":"aria-hidden","value":"true"},{"kind":"literal","name":"focusable","value":"false"},{"kind":"literal","name":"viewBox","value":"0 0 24 24"},{"kind":"literal","name":"fill","value":"none"},{"kind":"literal","name":"stroke","value":"currentColor"},{"kind":"literal","name":"stroke-width","value":"2"},{"kind":"literal","name":"stroke-linecap","value":"round"},{"kind":"literal","name":"stroke-linejoin","value":"round"}],"children":[{"kind":"element","name":"circle","attributes":[{"kind":"literal","name":"cx","value":"9"},{"kind":"literal","name":"cy","value":"12"},{"kind":"literal","name":"r","value":"1"}],"children":[]},{"kind":"element","name":"circle","attributes":[{"kind":"literal","name":"cx","value":"9"},{"kind":"literal","name":"cy","value":"5"},{"kind":"literal","name":"r","value":"1"}],"children":[]},{"kind":"element","name":"circle","attributes":[{"kind":"literal","name":"cx","value":"9"},{"kind":"literal","name":"cy","value":"19"},{"kind":"literal","name":"r","value":"1"}],"children":[]},{"kind":"element","name":"circle","attributes":[{"kind":"literal","name":"cx","value":"15"},{"kind":"literal","name":"cy","value":"12"},{"kind":"literal","name":"r","value":"1"}],"children":[]},{"kind":"element","name":"circle","attributes":[{"kind":"literal","name":"cx","value":"15"},{"kind":"literal","name":"cy","value":"5"},{"kind":"literal","name":"r","value":"1"}],"children":[]},{"kind":"element","name":"circle","attributes":[{"kind":"literal","name":"cx","value":"15"},{"kind":"literal","name":"cy","value":"19"},{"kind":"literal","name":"r","value":"1"}],"children":[]}]}],"flow":{"kind":"if","test":"sortable and not disabled","testPlan":{"source":"sortable and not disabled","ast":{"kind":"binary","op":"and","left":{"kind":"id","name":"sortable"},"right":{"kind":"unary","op":"not","operand":{"kind":"id","name":"disabled"}}},"dependencies":["disabled","sortable"]}},"ref":"dragHandle"},{"kind":"element","name":"button","attributes":[{"kind":"literal","name":"class","value":"disclosure"},{"kind":"literal","name":"type","value":"button"},{"kind":"attribute","name":"disabled","expression":"disabled","expressionPlan":{"source":"disabled","ast":{"kind":"id","name":"disabled"},"dependencies":["disabled"]}},{"kind":"attribute","name":"hidden","expression":"not isContainer","expressionPlan":{"source":"not isContainer","ast":{"kind":"unary","op":"not","operand":{"kind":"id","name":"isContainer"}},"dependencies":["isContainer"]}}],"children":[{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"disclosure-icon"},{"kind":"literal","name":"aria-hidden","value":"true"}],"children":[]}],"ref":"disclosure"},{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"leading"}],"children":[{"kind":"slot","fallback":[],"name":"leading"}]},{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"label"}],"children":[{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"label-text"}],"children":[{"kind":"slot","fallback":[{"kind":"element","name":"template","attributes":[{"kind":"directive","name":"value","expression":"label","expressionPlan":{"source":"label","ast":{"kind":"id","name":"label"},"dependencies":["label"]}}],"children":[]}],"name":"label"}],"ref":"labelText"}]},{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"actions"}],"children":[{"kind":"slot","fallback":[],"name":"actions"}],"ref":"actions"},{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"drop-indicator row-drop-indicator"},{"kind":"literal","name":"aria-hidden","value":"true"}],"children":[]}],"ref":"row"},{"kind":"element","name":"div","attributes":[{"kind":"literal","name":"class","value":"children"},{"kind":"literal","name":"role","value":"group"},{"kind":"attribute","name":"hidden","expression":"not (isContainer and internalExpanded)","expressionPlan":{"source":"not (isContainer and internalExpanded)","ast":{"kind":"unary","op":"not","operand":{"kind":"binary","op":"and","left":{"kind":"id","name":"isContainer"},"right":{"kind":"id","name":"internalExpanded"}}},"dependencies":["internalExpanded","isContainer"]}}],"children":[{"kind":"slot"}],"ref":"children"},{"kind":"element","name":"span","attributes":[{"kind":"literal","name":"class","value":"drop-indicator subtree-drop-indicator"},{"kind":"literal","name":"aria-hidden","value":"true"}],"children":[]}]},"declarations":[{"kind":"state","name":"internalExpanded","expression":{"source":"false","ast":{"kind":"literal","value":false},"dependencies":[]}},{"kind":"state","name":"structuralLevel","expression":{"source":"1","ast":{"kind":"literal","value":1},"dependencies":[]}},{"kind":"state","name":"tabStop","expression":{"source":"false","ast":{"kind":"literal","value":false},"dependencies":[]}},{"kind":"state","name":"isContainer","expression":{"source":"false","ast":{"kind":"literal","value":false},"dependencies":[]}},{"kind":"event","name":"expand","type":"object({ id: string, expanded: boolean, trigger: keyboard | pointer | programmatic })","bubbles":false,"composed":true,"cancelable":false}],"root":{"kind":"native","element":"div","choices":["div"]}},source:{file:import.meta.url},css:""};
7
7
 
8
8
  export function createUiTreeItem(options = {}) {
9
9
  const { attributes = {}, children = [], slots = {}, as, ...componentProps } = options;
@@ -113,49 +113,52 @@ export function createUiTreeItem(options = {}) {
113
113
  element0.append(element11);
114
114
  const element12 = document.createElement("span");
115
115
  element12.setAttribute("class", "label");
116
+ const element13 = document.createElement("span");
117
+ element13.setAttribute("class", "label-text");
116
118
  if (slots["label"] ?? [].length > 0) {
117
119
  for (const child of slots["label"] ?? []) {
118
120
  const node = typeof child === "string" ? document.createTextNode(child) : child;
119
121
  projected.push([node, "label"]);
120
- element12.append(node);
122
+ element13.append(node);
121
123
  }
122
124
  } else {
123
- const element13 = document.createElement("template");
124
- element12.append(element13);
125
+ const element14 = document.createElement("template");
126
+ element13.append(element14);
125
127
  }
128
+ element12.append(element13);
126
129
  element0.append(element12);
127
- const element14 = document.createElement("span");
128
- element14.setAttribute("class", "actions");
130
+ const element15 = document.createElement("span");
131
+ element15.setAttribute("class", "actions");
129
132
  if (slots["actions"] ?? [].length > 0) {
130
133
  for (const child of slots["actions"] ?? []) {
131
134
  const node = typeof child === "string" ? document.createTextNode(child) : child;
132
135
  projected.push([node, "actions"]);
133
- element14.append(node);
136
+ element15.append(node);
134
137
  }
135
138
  } else {
136
139
  }
137
- element0.append(element14);
138
- const element15 = document.createElement("span");
139
- element15.setAttribute("class", "drop-indicator row-drop-indicator");
140
- element15.setAttribute("aria-hidden", "true");
141
140
  element0.append(element15);
141
+ const element16 = document.createElement("span");
142
+ element16.setAttribute("class", "drop-indicator row-drop-indicator");
143
+ element16.setAttribute("aria-hidden", "true");
144
+ element0.append(element16);
142
145
  element.append(element0);
143
- const element16 = document.createElement("div");
144
- element16.setAttribute("class", "children");
145
- element16.setAttribute("role", "group");
146
+ const element17 = document.createElement("div");
147
+ element17.setAttribute("class", "children");
148
+ element17.setAttribute("role", "group");
146
149
  if (children.length > 0) {
147
150
  for (const child of children) {
148
151
  const node = typeof child === "string" ? document.createTextNode(child) : child;
149
152
  projected.push([node, ""]);
150
- element16.append(node);
153
+ element17.append(node);
151
154
  }
152
155
  } else {
153
156
  }
154
- element.append(element16);
155
- const element17 = document.createElement("span");
156
- element17.setAttribute("class", "drop-indicator subtree-drop-indicator");
157
- element17.setAttribute("aria-hidden", "true");
158
157
  element.append(element17);
158
+ const element18 = document.createElement("span");
159
+ element18.setAttribute("class", "drop-indicator subtree-drop-indicator");
160
+ element18.setAttribute("aria-hidden", "true");
161
+ element.append(element18);
159
162
  manageComponentLifecycle(element, definition, { props: componentProps, projected,
160
163
  controller,
161
164
  });
package/vue/UiTree.d.ts CHANGED
@@ -2,6 +2,7 @@ type __VLS_Props = {
2
2
  density?: 'comfortable' | 'compact';
3
3
  hoverExpandDelay?: number;
4
4
  label?: string;
5
+ marquee?: boolean;
5
6
  maxDepth?: number;
6
7
  };
7
8
  declare var __VLS_1: {};
@@ -52,6 +53,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
52
53
  }) => any) | undefined;
53
54
  }>, {
54
55
  label: string;
56
+ marquee: boolean;
55
57
  density: "comfortable" | "compact";
56
58
  hoverExpandDelay: number;
57
59
  maxDepth: number;
package/vue/UiTree.vue CHANGED
@@ -11,12 +11,14 @@ const props = withDefaults(
11
11
  density?: 'comfortable' | 'compact'
12
12
  hoverExpandDelay?: number
13
13
  label?: string
14
+ marquee?: boolean
14
15
  maxDepth?: number
15
16
  }>(),
16
17
  {
17
18
  density: 'comfortable',
18
19
  hoverExpandDelay: 700,
19
20
  label: 'Tree',
21
+ marquee: false,
20
22
  maxDepth: 0,
21
23
  },
22
24
  )
@@ -52,6 +54,7 @@ const root = ref<HTMLElement | null>(null)
52
54
  /** The values the styles' :host-state() rules test. */
53
55
  const hostState = computed(() =>
54
56
  [
57
+ props.marquee && 'marquee',
55
58
  props.density && `density density=${props.density}`,
56
59
  ].filter(Boolean).join(' ')
57
60
  )
@@ -127,6 +130,12 @@ useComponentHost(controllerModule.default, {
127
130
  </template>
128
131
 
129
132
  <style scoped>
133
+ /* Reaches the items the way density does, so the setting lives on the tree the way a reader
134
+ thinks about it: this list scrolls its long names. */
135
+ [data-component~="ui-tree"][data-ui-tree-state~="marquee"] {
136
+ --ui-tree-item-marquee: 1;
137
+ }
138
+
130
139
  [data-component~="ui-tree"][data-ui-tree-state~="density=compact"] {
131
140
  --ui-tree-item-min-block-size: var(--ui-control-size-sm);
132
141
  --ui-tree-item-font-size: var(--ui-font-size-sm);
@@ -8,6 +8,7 @@ type __VLS_Props = {
8
8
  expanded?: boolean;
9
9
  itemId?: string;
10
10
  label?: string;
11
+ marquee?: boolean;
11
12
  selected?: boolean;
12
13
  sortable?: boolean;
13
14
  subtreeDepth?: number;
@@ -38,6 +39,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
38
39
  }) => any) | undefined;
39
40
  }>, {
40
41
  label: string;
42
+ marquee: boolean;
41
43
  disabled: boolean;
42
44
  selected: boolean;
43
45
  expanded: boolean;
@@ -17,6 +17,7 @@ const props = withDefaults(
17
17
  expanded?: boolean
18
18
  itemId?: string
19
19
  label?: string
20
+ marquee?: boolean
20
21
  selected?: boolean
21
22
  sortable?: boolean
22
23
  subtreeDepth?: number
@@ -30,6 +31,7 @@ const props = withDefaults(
30
31
  expanded: false,
31
32
  itemId: '',
32
33
  label: '',
34
+ marquee: false,
33
35
  selected: false,
34
36
  sortable: false,
35
37
  },
@@ -49,6 +51,7 @@ const root = ref<HTMLElement | null>(null)
49
51
  const rowElement = useTemplateRef<HTMLElement>('row')
50
52
  const dragHandleElement = useTemplateRef<HTMLElement>('dragHandle')
51
53
  const disclosureElement = useTemplateRef<HTMLElement>('disclosure')
54
+ const labelTextElement = useTemplateRef<HTMLElement>('labelText')
52
55
  const actionsElement = useTemplateRef<HTMLElement>('actions')
53
56
  const childrenElement = useTemplateRef<HTMLElement>('children')
54
57
  const internalExpanded = ref(false)
@@ -88,6 +91,7 @@ useComponentHost(controllerModule.default, {
88
91
  row: rowElement,
89
92
  dragHandle: dragHandleElement,
90
93
  disclosure: disclosureElement,
94
+ labelText: labelTextElement,
91
95
  actions: actionsElement,
92
96
  children: childrenElement,
93
97
  },
@@ -150,7 +154,9 @@ useComponentHost(controllerModule.default, {
150
154
  <span class="disclosure-icon" aria-hidden="true"></span>
151
155
  </button>
152
156
  <span class="leading"><slot name="leading" /></span>
153
- <span class="label"><slot name="label">{{ label }}</slot></span>
157
+ <span class="label"><span class="label-text" ref="labelText"><slot name="label">{{
158
+ label
159
+ }}</slot></span></span>
154
160
  <span class="actions" ref="actions"><slot name="actions" /></span>
155
161
  <span class="drop-indicator row-drop-indicator" aria-hidden="true"></span>
156
162
  </div>
@@ -313,17 +319,80 @@ useComponentHost(controllerModule.default, {
313
319
  display: flex;
314
320
  align-items: center;
315
321
  min-inline-size: 0;
322
+ /* The cell clips: a name longer than the row slides inside it rather than over its icons. */
323
+ overflow: hidden;
316
324
  padding: var(--ui-tree-item-label-padding-block, var(--ui-space-1))
317
325
  var(--ui-tree-item-label-padding-inline, var(--ui-space-2));
318
326
  }
319
327
 
320
328
  /* Slotted label content fills the cell, so a link is the row's hit area rather than sizing to
321
329
  its own text and reading as overflow. */
322
- .label > :slotted(*) {
330
+ .label-text {
331
+ display: flex;
332
+ align-items: center;
333
+ /* Stretches, so slotted content can still be the row's full-height hit area. */
334
+ align-self: stretch;
335
+ block-size: 100%;
336
+ min-inline-size: 0;
337
+ inline-size: 100%;
338
+ white-space: nowrap;
339
+ }
340
+
341
+ .label-text > :slotted(*) {
323
342
  flex: 1 1 auto;
324
343
  min-inline-size: 0;
325
344
  }
326
345
 
346
+ /* A hovered row reads its whole name: the label slides far enough to show the end, waits
347
+ there, and comes back. Only when it actually overflows — the controller measures by how
348
+ much — after the controls have taken their place over its end — and hands over the distance
349
+ and a duration from it, so a longer name travels no faster. */
350
+ .row[data-ui-marquee] > .label > .label-text {
351
+ /* Once, and it stays at the end while the pointer is there: a name that keeps sliding back
352
+ and forth under the pointer is harder to read than one that arrives and holds. */
353
+ animation: ui-tree-item-marquee var(--_marquee-duration) linear 0.5s 1 normal forwards;
354
+ will-change: transform;
355
+ }
356
+
357
+ /* While it slides, the start fades too: the name is leaving the cell at both ends, and a hard
358
+ edge beside the icon reads as a collision rather than as more text. */
359
+ .row[data-ui-marquee] > .label {
360
+ mask-image: linear-gradient(
361
+ to right,
362
+ transparent 0,
363
+ #000 var(--ui-space-2),
364
+ #000 calc(100% - var(--_tree-actions-width, 1.75rem) - var(--ui-space-3)),
365
+ transparent calc(100% - var(--_tree-actions-width, 1.75rem))
366
+ );
367
+ }
368
+
369
+ .row[data-ui-marquee] > .label:dir(rtl) {
370
+ mask-image: linear-gradient(
371
+ to left,
372
+ transparent 0,
373
+ #000 var(--ui-space-2),
374
+ #000 calc(100% - var(--_tree-actions-width, 1.75rem) - var(--ui-space-3)),
375
+ transparent calc(100% - var(--_tree-actions-width, 1.75rem))
376
+ );
377
+ }
378
+
379
+ @keyframes ui-tree-item-marquee {
380
+ from {
381
+ transform: translateX(0);
382
+ }
383
+
384
+ to {
385
+ transform: translateX(var(--_marquee-distance));
386
+ }
387
+ }
388
+
389
+ /* Reduced motion keeps the fade, which already says the name continues. */
390
+ @media (prefers-reduced-motion: reduce) {
391
+ .row[data-ui-marquee] > .label > .label-text {
392
+ animation: none;
393
+ }
394
+ }
395
+
327
396
  /* The controls sit over the label's end, so the text fades out where they begin. The fade is
328
397
  the truncation mark while it shows: an ellipsis under it would read as a second one. */
329
398
  .row:hover > .label,
@@ -13,6 +13,10 @@ var UiTree_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE_
13
13
  density: { default: "comfortable" },
14
14
  hoverExpandDelay: { default: 700 },
15
15
  label: { default: "Tree" },
16
+ marquee: {
17
+ type: Boolean,
18
+ default: false
19
+ },
16
20
  maxDepth: { default: 0 }
17
21
  },
18
22
  emits: [
@@ -25,7 +29,7 @@ var UiTree_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE_
25
29
  const emit = __emit;
26
30
  const root = ref(null);
27
31
  /** The values the styles' :host-state() rules test. */
28
- const hostState = computed(() => [props.density && `density density=${props.density}`].filter(Boolean).join(" "));
32
+ const hostState = computed(() => [props.marquee && "marquee", props.density && `density density=${props.density}`].filter(Boolean).join(" "));
29
33
  const isReorderDetail = (detail) => detail !== null && typeof detail === "object" && !Array.isArray(detail) && typeof detail["sourceId"] === "string" && typeof detail["targetId"] === "string" && (detail["position"] === "before" || detail["position"] === "inside" || detail["position"] === "after") && typeof detail["sourceType"] === "string" && typeof detail["targetType"] === "string" && typeof detail["sourceScope"] === "string" && typeof detail["targetScope"] === "string" && (detail["trigger"] === "keyboard" || detail["trigger"] === "pointer" || detail["trigger"] === "programmatic") && Object.keys(detail).every((key) => [
30
34
  "sourceId",
31
35
  "targetId",
@@ -68,6 +72,6 @@ var UiTree_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE_
68
72
  }), [renderSlot(_ctx.$slots, "default", {}, void 0, true)], 16, _hoisted_1);
69
73
  };
70
74
  }
71
- }), [["__scopeId", "data-v-a2bb2dca"]]);
75
+ }), [["__scopeId", "data-v-5c1143dd"]]);
72
76
  //#endregion
73
77
  export { UiTree_default as t };
@@ -25,10 +25,14 @@ var _hoisted_4 = ["disabled", "hidden"];
25
25
  var _hoisted_5 = { class: "leading" };
26
26
  var _hoisted_6 = { class: "label" };
27
27
  var _hoisted_7 = {
28
+ class: "label-text",
29
+ ref: "labelText"
30
+ };
31
+ var _hoisted_8 = {
28
32
  class: "actions",
29
33
  ref: "actions"
30
34
  };
31
- var _hoisted_8 = ["hidden"];
35
+ var _hoisted_9 = ["hidden"];
32
36
  //#endregion
33
37
  //#region .build/vue/UiTreeItem.vue
34
38
  var UiTreeItem_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -53,6 +57,10 @@ var UiTreeItem_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
53
57
  },
54
58
  itemId: { default: "" },
55
59
  label: { default: "" },
60
+ marquee: {
61
+ type: Boolean,
62
+ default: false
63
+ },
56
64
  selected: {
57
65
  type: Boolean,
58
66
  default: false
@@ -71,6 +79,7 @@ var UiTreeItem_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
71
79
  const rowElement = useTemplateRef("row");
72
80
  const dragHandleElement = useTemplateRef("dragHandle");
73
81
  const disclosureElement = useTemplateRef("disclosure");
82
+ const labelTextElement = useTemplateRef("labelText");
74
83
  const actionsElement = useTemplateRef("actions");
75
84
  const childrenElement = useTemplateRef("children");
76
85
  const internalExpanded = ref(false);
@@ -101,6 +110,7 @@ var UiTreeItem_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
101
110
  row: rowElement,
102
111
  dragHandle: dragHandleElement,
103
112
  disclosure: disclosureElement,
113
+ labelText: labelTextElement,
104
114
  actions: actionsElement,
105
115
  children: childrenElement
106
116
  },
@@ -190,8 +200,8 @@ var UiTreeItem_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
190
200
  "aria-hidden": "true"
191
201
  }, null, -1)])], 8, _hoisted_4),
192
202
  createElementVNode("span", _hoisted_5, [renderSlot(_ctx.$slots, "leading")]),
193
- createElementVNode("span", _hoisted_6, [renderSlot(_ctx.$slots, "label", {}, () => [createTextVNode(toDisplayString(__props.label), 1)])]),
194
- createElementVNode("span", _hoisted_7, [renderSlot(_ctx.$slots, "actions")], 512),
203
+ createElementVNode("span", _hoisted_6, [createElementVNode("span", _hoisted_7, [renderSlot(_ctx.$slots, "label", {}, () => [createTextVNode(toDisplayString(__props.label), 1)])], 512)]),
204
+ createElementVNode("span", _hoisted_8, [renderSlot(_ctx.$slots, "actions")], 512),
195
205
  _cache[2] || (_cache[2] = createElementVNode("span", {
196
206
  class: "drop-indicator row-drop-indicator",
197
207
  "aria-hidden": "true"
@@ -202,7 +212,7 @@ var UiTreeItem_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
202
212
  role: "group",
203
213
  hidden: !(isContainer.value && internalExpanded.value),
204
214
  ref: "children"
205
- }, [renderSlot(_ctx.$slots, "default")], 8, _hoisted_8),
215
+ }, [renderSlot(_ctx.$slots, "default")], 8, _hoisted_9),
206
216
  _cache[3] || (_cache[3] = createElementVNode("span", {
207
217
  class: "drop-indicator subtree-drop-indicator",
208
218
  "aria-hidden": "true"
@@ -210,6 +220,6 @@ var UiTreeItem_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
210
220
  ], 16, _hoisted_1);
211
221
  };
212
222
  }
213
- }), [["__scopeId", "data-v-48465a46"]]);
223
+ }), [["__scopeId", "data-v-171d033f"]]);
214
224
  //#endregion
215
225
  export { UiTreeItem_default as t };