@fusion.dev/helix-headless 0.154.0 → 0.155.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 (59) hide show
  1. package/custom-elements.json +1427 -88
  2. package/dist/accordion/item/accordion-item.d.ts +15 -0
  3. package/dist/accordion/item/accordion-item.js +12 -1
  4. package/dist/accordion/root/accordion-root.d.ts +2 -1
  5. package/dist/accordion/root/accordion-root.js +1 -0
  6. package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +36 -3
  7. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +42 -2
  8. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +15 -5
  9. package/dist/breadcrumbs/root/breadcrumbs-root.js +29 -4
  10. package/dist/button-group/root/button-group-root.d.ts +2 -0
  11. package/dist/button-group/root/button-group-root.js +1 -0
  12. package/dist/color-input/root/color-input-root.d.ts +9 -3
  13. package/dist/color-input/root/color-input-root.js +3 -1
  14. package/dist/color-picker/channels.d.ts +5 -3
  15. package/dist/color-picker/channels.js +79 -21
  16. package/dist/color-picker/root/color-picker-root.d.ts +58 -5
  17. package/dist/color-picker/root/color-picker-root.js +75 -13
  18. package/dist/date-input/root/date-input-root.d.ts +1 -0
  19. package/dist/date-input/root/date-input-root.js +2 -1
  20. package/dist/date-picker/root/date-picker-root.d.ts +43 -13
  21. package/dist/date-picker/root/date-picker-root.js +54 -31
  22. package/dist/element-events.generated.js +2 -1
  23. package/dist/navigation/root/navigation-root.d.ts +26 -0
  24. package/dist/navigation/root/navigation-root.js +58 -1
  25. package/dist/select/root/select-root.d.ts +29 -3
  26. package/dist/select/root/select-root.js +34 -9
  27. package/dist/slider/root/slider-root.d.ts +71 -9
  28. package/dist/slider/root/slider-root.js +129 -42
  29. package/dist/speed-dial/item/speed-dial-item.d.ts +29 -1
  30. package/dist/speed-dial/item/speed-dial-item.js +48 -7
  31. package/dist/speed-dial/root/speed-dial-root.d.ts +32 -6
  32. package/dist/speed-dial/root/speed-dial-root.js +50 -5
  33. package/dist/splitter/root/splitter-root.d.ts +22 -0
  34. package/dist/splitter/root/splitter-root.js +36 -7
  35. package/dist/steps/reach.d.ts +5 -1
  36. package/dist/steps/reach.js +2 -2
  37. package/dist/steps/root/steps-root.d.ts +37 -2
  38. package/dist/steps/root/steps-root.js +49 -7
  39. package/dist/steps/step/step.d.ts +6 -0
  40. package/dist/steps/step/step.js +4 -0
  41. package/dist/tabs/root/tabs-root.d.ts +37 -0
  42. package/dist/tabs/root/tabs-root.js +92 -2
  43. package/dist/tabs/tab/tab.d.ts +38 -2
  44. package/dist/tabs/tab/tab.js +45 -4
  45. package/dist/tag/group/tag-group.d.ts +29 -3
  46. package/dist/tag/group/tag-group.js +42 -11
  47. package/dist/toggle/root/toggle-root.d.ts +2 -0
  48. package/dist/toggle/root/toggle-root.js +1 -1
  49. package/dist/tree/item/tree-item.d.ts +10 -0
  50. package/dist/tree/item/tree-item.js +12 -0
  51. package/dist/tree/root/tree-root.d.ts +40 -1
  52. package/dist/tree/root/tree-root.js +120 -4
  53. package/dist/tree/selection.d.ts +6 -0
  54. package/dist/tree/selection.js +11 -0
  55. package/lint.json +1 -1
  56. package/package.json +5 -5
  57. package/types/jsx.d.ts +984 -151
  58. package/types/vue.d.ts +221 -49
  59. package/types/words.d.ts +36 -1
@@ -146,6 +146,11 @@ export declare class HelixHeadlessTreeItem extends HelixHeadlessTreeItem_base {
146
146
  * tree: the item being moved, or the one the drop indicator is beside.
147
147
  */
148
148
  move?: TreeItemMove | undefined;
149
+ /**
150
+ * @internal The item's place among its siblings, counting from one, written
151
+ * by the tree for `aria-posinset`, for the reason `level` gives.
152
+ */
153
+ position?: number | undefined;
149
154
  /**
150
155
  * The link's `rel` value, which describes how the linked page relates to this
151
156
  * one.
@@ -168,6 +173,11 @@ export declare class HelixHeadlessTreeItem extends HelixHeadlessTreeItem_base {
168
173
  * row is not part of the tree React hydrates.
169
174
  */
170
175
  selected: boolean;
176
+ /**
177
+ * @internal How many siblings the item has, itself included, written by the
178
+ * tree for `aria-setsize`, for the reason `level` gives.
179
+ */
180
+ setSize?: number | undefined;
171
181
  /**
172
182
  * Where the link opens. Use it for a row that leaves the site.
173
183
  */
@@ -174,6 +174,8 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
174
174
  const linked = this.href !== '';
175
175
  const level = this.level ?? 1;
176
176
  const depth = this.level ?? nothing;
177
+ const position = this.position ?? nothing;
178
+ const size = this.setSize ?? nothing;
177
179
  const expanded = this.isBranch ? (this.open ? 'true' : 'false') : nothing;
178
180
  const chosen = this.checkable ? undefined : this.selected ? 'true' : 'false';
179
181
  const checked = this.checkable
@@ -193,6 +195,8 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
193
195
  aria-disabled=${linked ? nothing : off}
194
196
  aria-busy=${linked ? nothing : busy}
195
197
  aria-level=${linked ? nothing : depth}
198
+ aria-posinset=${linked ? nothing : position}
199
+ aria-setsize=${linked ? nothing : size}
196
200
  tabindex=${linked ? nothing : tabIndex}
197
201
  >
198
202
  ${linked
@@ -208,6 +212,8 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
208
212
  aria-disabled=${off}
209
213
  aria-busy=${busy}
210
214
  aria-level=${depth}
215
+ aria-posinset=${position}
216
+ aria-setsize=${size}
211
217
  tabindex=${tabIndex}
212
218
  style="--tree-level: ${level}"
213
219
  @click=${this.handleRowClick}
@@ -255,12 +261,18 @@ __decorate([
255
261
  __decorate([
256
262
  property({ attribute: false })
257
263
  ], HelixHeadlessTreeItem.prototype, "move", void 0);
264
+ __decorate([
265
+ property({ attribute: false, type: Number })
266
+ ], HelixHeadlessTreeItem.prototype, "position", void 0);
258
267
  __decorate([
259
268
  property()
260
269
  ], HelixHeadlessTreeItem.prototype, "rel", void 0);
261
270
  __decorate([
262
271
  property({ type: Boolean })
263
272
  ], HelixHeadlessTreeItem.prototype, "selected", void 0);
273
+ __decorate([
274
+ property({ attribute: false, type: Number })
275
+ ], HelixHeadlessTreeItem.prototype, "setSize", void 0);
264
276
  __decorate([
265
277
  property()
266
278
  ], HelixHeadlessTreeItem.prototype, "target", void 0);
@@ -35,6 +35,13 @@ declare const HelixHeadlessTree_base: (new (...args: any[]) => import("@fusion.d
35
35
  * or open, and the tree's one tab stop stays on the item last reached. In a
36
36
  * tree of check marks, Control+A (Command+A on an Apple keyboard) checks every
37
37
  * item or clears them all, and Shift with Up or Down Arrow moves and checks.
38
+ * Shift+Space checks every item from the one last checked to the focused one,
39
+ * and Control+Shift+Home or End checks every item from the focused one to the
40
+ * first or the last, then moves there, as the tree pattern's range keys do.
41
+ *
42
+ * A tree with no name of its own is announced as "Tree", which says nothing
43
+ * about what it holds, so a development build warns once with the kind
44
+ * `missing-name` where neither `label` nor an `aria-label` names it.
38
45
  *
39
46
  * Removing the item that has focus moves focus to the item after it among its
40
47
  * siblings, or the one before, or its parent, so focus stays in the tree.
@@ -50,7 +57,15 @@ declare const HelixHeadlessTree_base: (new (...args: any[]) => import("@fusion.d
50
57
  * `helix-before-reorder`, which you can refuse, and then `helix-reorder`, whose
51
58
  * `keys` hold the siblings in the new order. **You move the items**: the tree
52
59
  * moves no element of yours, and focus follows the moved item once you have.
53
- * Moving an item into another branch is not part of this.
60
+ *
61
+ * **Alt with the arrow along the line moves an item into another branch.**
62
+ * Alt with the arrow that opens a branch (Right on a left-to-right page) puts
63
+ * the focused item last inside the item before it, and opens that item; Alt
64
+ * with the other arrow puts it after its parent, a level out. The same
65
+ * `helix-before-reorder` and `helix-reorder` are sent, with `parent` naming
66
+ * the branch it moves into by its key, `''` for the top level, and `keys`
67
+ * holding that branch's items in their new order. The tree says the move with
68
+ * `moved-into-label`.
54
69
  *
55
70
  * @since 0.1.0
56
71
  * @status stable
@@ -92,6 +107,14 @@ export declare class HelixHeadlessTree extends HelixHeadlessTree_base {
92
107
  * wins over this.
93
108
  */
94
109
  label: string;
110
+ /**
111
+ * What screen readers hear when an item moves into another branch, in a
112
+ * `reorderable` tree. `{name}` is the item's words, `{parent}` the words of
113
+ * the branch it moved into, or the tree's `label` for the top level,
114
+ * `{position}` its place there, counting from 1, and `{count}` the number of
115
+ * items there.
116
+ */
117
+ movedIntoLabel: string;
95
118
  /**
96
119
  * What screen readers hear each time an item moves, in a `reorderable`
97
120
  * tree. `{name}` is the item's words, `{position}` its place among its
@@ -188,6 +211,8 @@ export declare class HelixHeadlessTree extends HelixHeadlessTree_base {
188
211
  private watcher;
189
212
  /** The item that last had focus, so its removal can hand focus on. */
190
213
  private focused;
214
+ /** The item last checked or unchecked, where a range Shift+Space checks starts. */
215
+ private anchor;
191
216
  private readonly handleFocusIn;
192
217
  /**
193
218
  * Puts focus back in the tree where the item that had it was removed and
@@ -253,6 +278,20 @@ export declare class HelixHeadlessTree extends HelixHeadlessTree_base {
253
278
  * Returns whether the press was one of those.
254
279
  */
255
280
  private handleMultiple;
281
+ /** Checks every item of a range, and keeps the ones already checked. */
282
+ private checkRange;
283
+ /**
284
+ * Moves the focused item into another branch, in a `reorderable` tree: last
285
+ * inside the item before it, `toward` the line's end, or after its parent,
286
+ * toward its start. Asks the page, which moves the item, and says where it
287
+ * went or that the move was refused. Returns whether the key moved anything.
288
+ */
289
+ private nest;
290
+ /**
291
+ * Gives focus back to a moved item once the page has moved it, where moving
292
+ * it sent focus to the body, as a move among siblings does.
293
+ */
294
+ private follow;
256
295
  private move;
257
296
  private focusOn;
258
297
  private readonly handleKeydown;
@@ -4,12 +4,12 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { AriaForwardController, closestInstance, commaList, customElement, deepActiveElement, HelixElement, inlineStep, keyClaimed, MixinCollection, MixinControllerActive, ReorderController, scrollItemIntoView, selectsAll, slottedWords, Typeahead, treeArrow, typesCharacter, whenHydrated, words, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, auditName, closestInstance, commaList, customElement, deepActiveElement, formatWords, HelixElement, inlineStep, keyClaimed, MixinCollection, MixinControllerActive, ReorderController, requestReorder, scrollItemIntoView, selectsAll, slottedWords, Typeahead, treeArrow, typesCharacter, whenHydrated, words, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { fromGrip } from '../grip.js';
11
11
  import { HelixHeadlessTreeItem } from '../item/tree-item.js';
12
- import { checkedState, pressRow } from '../selection.js';
12
+ import { checkedState, checkRows, pressRow } from '../selection.js';
13
13
  const KEYS = new Set([
14
14
  'ArrowDown',
15
15
  'ArrowUp',
@@ -26,6 +26,7 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
26
26
  this.canceledLabel = 'Canceled';
27
27
  this.droppedLabel = 'Dropped';
28
28
  this.label = 'Tree';
29
+ this.movedIntoLabel = '{name} moved into {parent}, position {position} of {count}';
29
30
  this.movedLabel = '{name} moved to position {position} of {count}';
30
31
  this.aria = new AriaForwardController(this, {
31
32
  label: true,
@@ -66,6 +67,7 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
66
67
  this.requestActive(this.multiple
67
68
  ? pressRow(this.rowOf(origin), this.values, this.order).join(',')
68
69
  : event.detail.value);
70
+ this.anchor = origin;
69
71
  }
70
72
  this.syncItems();
71
73
  };
@@ -87,8 +89,15 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
87
89
  }
88
90
  this.typeahead.reset();
89
91
  const along = inlineStep(this, event.key);
92
+ if (event.altKey && along !== 0 && this.reorderable) {
93
+ if (!current.disabled && this.nest(current, along)) {
94
+ event.preventDefault();
95
+ }
96
+ return;
97
+ }
90
98
  if (this.multiple && event.key === ' ') {
91
99
  current.select();
100
+ this.anchor = current;
92
101
  event.preventDefault();
93
102
  return;
94
103
  }
@@ -240,8 +249,18 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
240
249
  const heads = this.nesting === 'heading' || this.selectionMode === 'leaf';
241
250
  const multiple = this.multiple;
242
251
  const checked = new Set(this.values);
252
+ const places = new Map();
253
+ for (const item of this.items) {
254
+ if (!places.has(item)) {
255
+ const siblings = this.siblingsOf(item);
256
+ siblings.forEach((one, at) => {
257
+ places.set(one, { position: at + 1, size: siblings.length });
258
+ });
259
+ }
260
+ }
243
261
  for (const item of this.items) {
244
262
  const key = this.keyOf(item);
263
+ const place = places.get(item);
245
264
  const selected = !multiple && item.value !== '' && item.value === this.active;
246
265
  const state = multiple ? checkedState(this.rowOf(item), checked) : false;
247
266
  const focusable = item === stop;
@@ -259,6 +278,8 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
259
278
  item.checked = state;
260
279
  item.focusable = focusable;
261
280
  item.level = level;
281
+ item.position = place?.position;
282
+ item.setSize = place?.size;
262
283
  item.heads = heads;
263
284
  item.grip = this.reorderable;
264
285
  item.move = this.reorder.moveOf(key);
@@ -308,9 +329,24 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
308
329
  this.syncItems();
309
330
  return true;
310
331
  }
332
+ const visible = this.visibleItems;
333
+ const at = visible.indexOf(current);
334
+ if (event.shiftKey && event.key === ' ') {
335
+ const from = this.anchor ? visible.indexOf(this.anchor) : -1;
336
+ const range = from < 0 ? [current] : visible.slice(Math.min(from, at), Math.max(from, at) + 1);
337
+ this.checkRange(range);
338
+ return true;
339
+ }
340
+ if (event.shiftKey &&
341
+ (event.ctrlKey || event.metaKey) &&
342
+ (event.key === 'Home' || event.key === 'End')) {
343
+ const range = event.key === 'Home' ? visible.slice(0, at + 1) : visible.slice(at);
344
+ this.checkRange(range);
345
+ this.focusOn(event.key === 'Home' ? range[0] : range.at(-1));
346
+ return true;
347
+ }
311
348
  if (event.shiftKey && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
312
- const visible = this.visibleItems;
313
- const next = visible[visible.indexOf(current) + (event.key === 'ArrowDown' ? 1 : -1)];
349
+ const next = visible[at + (event.key === 'ArrowDown' ? 1 : -1)];
314
350
  if (next) {
315
351
  this.focusOn(next);
316
352
  next.select();
@@ -319,6 +355,81 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
319
355
  }
320
356
  return false;
321
357
  }
358
+ checkRange(range) {
359
+ this.requestActive(checkRows(range.map((item) => this.rowOf(item)), this.values, this.order).join(','));
360
+ this.syncItems();
361
+ }
362
+ nest(current, toward) {
363
+ const siblings = this.siblingsOf(current);
364
+ const from = siblings.indexOf(current);
365
+ const key = this.keyOf(current);
366
+ let into;
367
+ let keys;
368
+ if (toward > 0) {
369
+ const before = siblings[from - 1];
370
+ if (!before || before.disabled) {
371
+ return false;
372
+ }
373
+ into = before;
374
+ const inside = [...before.children].filter((node) => node instanceof HelixHeadlessTreeItem);
375
+ keys = [...inside.map((one) => this.keyOf(one)), key];
376
+ }
377
+ else {
378
+ const parent = closestInstance(current.parentElement, HelixHeadlessTreeItem);
379
+ if (!(parent instanceof HelixHeadlessTreeItem) || !this.items.includes(parent)) {
380
+ return false;
381
+ }
382
+ const above = closestInstance(parent.parentElement, HelixHeadlessTreeItem);
383
+ into =
384
+ above instanceof HelixHeadlessTreeItem && this.items.includes(above) ? above : undefined;
385
+ keys = this.siblingsOf(parent).map((one) => this.keyOf(one));
386
+ keys.splice(keys.indexOf(this.keyOf(parent)) + 1, 0, key);
387
+ }
388
+ const to = keys.indexOf(key);
389
+ const stood = requestReorder(this, {
390
+ from,
391
+ key,
392
+ keys,
393
+ kind: 'item',
394
+ parent: into ? this.keyOf(into) : '',
395
+ to,
396
+ value: keys.join(','),
397
+ });
398
+ const name = slottedWords(current, ['']) || key;
399
+ this.announced = stood
400
+ ? formatWords(this, 'movedIntoLabel', {
401
+ count: keys.length,
402
+ name,
403
+ parent: into ? slottedWords(into, ['']) || this.keyOf(into) : this.label,
404
+ position: to + 1,
405
+ })
406
+ : this.canceledLabel;
407
+ this.requestUpdate();
408
+ if (stood) {
409
+ if (into && !into.open) {
410
+ into.requestOpen(true);
411
+ }
412
+ this.follow(key);
413
+ }
414
+ return true;
415
+ }
416
+ follow(key) {
417
+ const lost = () => {
418
+ const active = deepActiveElement();
419
+ return !active || active === document.body;
420
+ };
421
+ if (lost()) {
422
+ this.focusOn(this.itemFor(key));
423
+ return;
424
+ }
425
+ const watch = new MutationObserver(() => {
426
+ watch.disconnect();
427
+ if (lost()) {
428
+ this.focusOn(this.itemFor(key));
429
+ }
430
+ });
431
+ watch.observe(this, { childList: true, subtree: true });
432
+ }
322
433
  move(from, delta) {
323
434
  const visible = this.visibleItems;
324
435
  const index = visible.indexOf(from);
@@ -359,6 +470,7 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
359
470
  firstUpdated(changed) {
360
471
  super.firstUpdated(changed);
361
472
  this.syncItems();
473
+ auditName(this, () => null, 'Give it a `label` that says what it holds, such as "Files".', () => this.hasAttribute('label') || this.label !== 'Tree');
362
474
  }
363
475
  updated(changed) {
364
476
  super.updated(changed);
@@ -400,6 +512,10 @@ __decorate([
400
512
  words,
401
513
  property()
402
514
  ], HelixHeadlessTree.prototype, "label", void 0);
515
+ __decorate([
516
+ words,
517
+ property({ attribute: 'moved-into-label' })
518
+ ], HelixHeadlessTree.prototype, "movedIntoLabel", void 0);
403
519
  __decorate([
404
520
  words,
405
521
  property({ attribute: 'moved-label' })
@@ -14,6 +14,12 @@ export interface CheckableRow {
14
14
  * a branch by its leaves, `mixed` where some of them are.
15
15
  */
16
16
  export declare const checkedState: (row: CheckableRow, chosen: ReadonlySet<string>) => boolean | 'mixed';
17
+ /**
18
+ * The checked values after checking every row in `rows`, in `order`: a range
19
+ * the reader extends with Shift and Space, or with Control, Shift and Home or
20
+ * End. Rows already checked stay checked, and disabled rows are passed over.
21
+ */
22
+ export declare const checkRows: (rows: readonly CheckableRow[], chosen: readonly string[], order: readonly string[]) => string[];
17
23
  /**
18
24
  * The checked values after a press on `row`, in `order`.
19
25
  *
@@ -8,6 +8,17 @@ export const checkedState = (row, chosen) => {
8
8
  }
9
9
  return count === values.length ? true : 'mixed';
10
10
  };
11
+ export const checkRows = (rows, chosen, order) => {
12
+ const added = new Set(chosen);
13
+ for (const row of rows) {
14
+ if (!row.disabled) {
15
+ for (const value of valuesOf(row)) {
16
+ added.add(value);
17
+ }
18
+ }
19
+ }
20
+ return order.filter((value) => added.has(value));
21
+ };
11
22
  export const pressRow = (row, chosen, order) => {
12
23
  return row.disabled ? [...chosen] : toggleEvery(chosen, valuesOf(row), order);
13
24
  };