@fusion.dev/helix-headless 0.162.1 → 0.164.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 (45) hide show
  1. package/custom-elements.json +1041 -47
  2. package/dist/callout/root/callout-root.d.ts +43 -4
  3. package/dist/callout/root/callout-root.js +58 -2
  4. package/dist/canvas/index.d.ts +1 -1
  5. package/dist/canvas/root/canvas-document.d.ts +33 -0
  6. package/dist/canvas/root/canvas-root.d.ts +50 -2
  7. package/dist/canvas/root/canvas-root.js +98 -3
  8. package/dist/chat/composer/chat-composer.js +1 -1
  9. package/dist/chat/root/chat-root.d.ts +15 -4
  10. package/dist/chat/root/chat-root.js +19 -8
  11. package/dist/code-block/patch.d.ts +42 -0
  12. package/dist/code-block/patch.js +118 -0
  13. package/dist/code-block/root/code-block-root.d.ts +37 -1
  14. package/dist/code-block/root/code-block-root.js +57 -9
  15. package/dist/color-picker/root/color-picker-root.d.ts +7 -3
  16. package/dist/color-picker/root/color-picker-root.js +7 -4
  17. package/dist/combobox/root/combobox-root.d.ts +1 -0
  18. package/dist/combobox/root/combobox-root.js +10 -1
  19. package/dist/data-table/root/data-table-root.d.ts +1 -0
  20. package/dist/data-table/root/data-table-root.js +1 -0
  21. package/dist/element-events.generated.js +7 -1
  22. package/dist/map/root/map-root.d.ts +8 -1
  23. package/dist/map/root/map-root.js +12 -0
  24. package/dist/menu/item/menu-item.d.ts +38 -7
  25. package/dist/menu/item/menu-item.js +20 -9
  26. package/dist/menu/root/menu-root.d.ts +10 -1
  27. package/dist/menu/root/menu-root.js +8 -3
  28. package/dist/object-inspector/root/object-inspector-root.d.ts +23 -0
  29. package/dist/object-inspector/root/object-inspector-root.js +19 -1
  30. package/dist/steps/root/steps-root.d.ts +7 -1
  31. package/dist/steps/root/steps-root.js +6 -1
  32. package/dist/table/grid-controller.d.ts +7 -0
  33. package/dist/table/grid-controller.js +15 -1
  34. package/dist/table/root/table-root.d.ts +10 -0
  35. package/dist/table/root/table-root.js +18 -1
  36. package/dist/video/root/video-root.js +2 -1
  37. package/dist/video-playlist/item/video-playlist-item.d.ts +8 -0
  38. package/dist/video-playlist/item/video-playlist-item.js +7 -0
  39. package/dist/video-playlist/root/video-playlist-root.d.ts +60 -0
  40. package/dist/video-playlist/root/video-playlist-root.js +55 -1
  41. package/lint.json +1 -1
  42. package/package.json +5 -5
  43. package/types/jsx.d.ts +502 -40
  44. package/types/vue.d.ts +126 -14
  45. package/types/words.d.ts +58 -0
@@ -4,7 +4,7 @@ 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 { ariaChord, auditName, customElement, deepActiveElement, HelixBeforeToggleEvent, HelixElement, HelixSelectEvent, isRtl, onApple, PopoverController, requestChange, slotFilled, } from '@fusion.dev/helix-core';
7
+ import { ariaChord, auditName, customElement, deepActiveElement, HelixBeforeToggleEvent, HelixElement, HelixSelectEvent, isRtl, onApple, onUnlessFalse, PopoverController, requestChange, slotFilled, } from '@fusion.dev/helix-core';
8
8
  import { caretRight, check } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -29,6 +29,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
29
29
  this.target = '';
30
30
  this.value = '';
31
31
  this.isMenuItem = true;
32
+ this.keepsOpen = false;
32
33
  this.submenu = false;
33
34
  this.start = false;
34
35
  this.end = false;
@@ -58,8 +59,10 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
58
59
  surface: () => (this.flyout ? this.partNamed('surface') : null),
59
60
  });
60
61
  this.handleClick = () => {
61
- this.choose();
62
+ this.choose(this.spacePressed);
63
+ this.spacePressed = false;
62
64
  };
65
+ this.spacePressed = false;
63
66
  this.handleKeydown = (event) => {
64
67
  if (event.key !== 'Enter' && event.key !== ' ') {
65
68
  return;
@@ -70,12 +73,17 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
70
73
  }
71
74
  event.preventDefault();
72
75
  if (link) {
76
+ this.spacePressed = true;
73
77
  link.click();
78
+ this.spacePressed = false;
74
79
  return;
75
80
  }
76
- this.handleClick();
81
+ this.choose(event.key === ' ');
77
82
  };
78
83
  }
84
+ get choice() {
85
+ return this.checkable || this.checked || this.exclusive;
86
+ }
79
87
  get hasSubmenu() {
80
88
  return this.submenu;
81
89
  }
@@ -93,9 +101,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
93
101
  const rows = this.knownChildren.filter((child) => child.slot === 'submenu' && child.isMenuItem);
94
102
  this.toggleState('submenu-start', this.flyout && rows.some((row) => row.querySelector(':scope > [slot="start"]') !== null));
95
103
  this.toggleState('submenu-mark', this.flyout &&
96
- rows.some((row) => row.checkable ||
97
- row.checked ||
98
- [...row.children].some((child) => child.slot === 'submenu')));
104
+ rows.some((row) => row.choice || [...row.children].some((child) => child.slot === 'submenu')));
99
105
  }
100
106
  firstUpdated(changed) {
101
107
  super.firstUpdated(changed);
@@ -152,7 +158,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
152
158
  return this.active;
153
159
  }
154
160
  tick() {
155
- if (!(this.checkable || this.checked) || this.hasSubmenu) {
161
+ if (!this.choice || this.hasSubmenu) {
156
162
  return;
157
163
  }
158
164
  const next = this.exclusive ? true : !this.checked;
@@ -188,10 +194,12 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
188
194
  }
189
195
  return peers;
190
196
  }
191
- choose() {
197
+ choose(space = false) {
192
198
  if (this.disabled) {
193
199
  return;
194
200
  }
201
+ const spaceKeeps = space && this.choice && !this.hasSubmenu;
202
+ this.keepsOpen = this.closeOnSelect === undefined ? spaceKeeps : !this.closeOnSelect;
195
203
  this.tick();
196
204
  this.dispatchEvent(new HelixSelectEvent({ value: this.value }));
197
205
  }
@@ -221,7 +229,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
221
229
  }
222
230
  const submenu = this.hasSubmenu;
223
231
  const linked = this.href !== '' && !this.disabled;
224
- const ticks = this.checkable || this.checked;
232
+ const ticks = this.choice;
225
233
  const role = ticks ? (this.exclusive ? 'menuitemradio' : 'menuitemcheckbox') : 'menuitem';
226
234
  const checkedState = ticks ? (this.checked ? 'true' : 'false') : nothing;
227
235
  const keys = this.shortcut === '' ? nothing : ariaChord(this.shortcut, !this.hydrating && onApple());
@@ -281,6 +289,9 @@ __decorate([
281
289
  __decorate([
282
290
  property({ type: Boolean })
283
291
  ], HelixHeadlessMenuItem.prototype, "checked", void 0);
292
+ __decorate([
293
+ property({ attribute: 'close-on-select', converter: onUnlessFalse })
294
+ ], HelixHeadlessMenuItem.prototype, "closeOnSelect", void 0);
284
295
  __decorate([
285
296
  property({ reflect: true, type: Boolean })
286
297
  ], HelixHeadlessMenuItem.prototype, "disabled", void 0);
@@ -40,7 +40,8 @@ declare const HelixHeadlessMenu_base: (new (...args: any[]) => import("@fusion.d
40
40
  * Keys: Enter, Space or Down Arrow on the trigger opens the menu on its first
41
41
  * row, and Up Arrow on its last. In the menu, Up and Down Arrow move between
42
42
  * rows, Home and End go to the first and the last, a typed letter moves to
43
- * the next row whose name starts with it, Enter and Space choose, Right Arrow
43
+ * the next row whose name starts with it, Enter and Space choose (Space on a
44
+ * choice row changes it and leaves the menu open), Right Arrow
44
45
  * opens a row's submenu and Left Arrow goes back, the two swapping on a
45
46
  * right-to-left page. Left Arrow on the top page does nothing. Escape closes
46
47
  * the menu and returns focus to the trigger. Tab closes it and moves on.
@@ -109,6 +110,14 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
109
110
  * The text of the row that returns from a submenu.
110
111
  */
111
112
  backLabel: string;
113
+ /**
114
+ * Whether choosing a row closes the menu.
115
+ *
116
+ * On by default, and a Space on a choice row leaves the menu open either way.
117
+ * Write `close-on-select="false"` to keep the menu open after every choice, as
118
+ * Ark UI's `closeOnSelect` does. A row's own `close-on-select` wins over this.
119
+ */
120
+ closeOnSelect: boolean;
112
121
  /**
113
122
  * Whether the menu is the trigger's context menu: it opens where the reader
114
123
  * asks for one, with a right-click, a long press, Shift+F10 or the menu key.
@@ -4,7 +4,7 @@ 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, anchorElement, customElement, DrilldownController, deepActiveElement, deepContains, focusTarget, HelixElement, inlineStep, KeyboardNavigationController, linkDescription, MixinCollection, MixinControllerOpen, PopoverController, scrollItemIntoView, sideOf, slottedWords, tellDisclosure, typeaheadBy, unlinkDescription, words, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, anchorElement, customElement, DrilldownController, deepActiveElement, deepContains, focusTarget, HelixElement, inlineStep, KeyboardNavigationController, linkDescription, MixinCollection, MixinControllerOpen, onUnlessFalse, PopoverController, scrollItemIntoView, sideOf, slottedWords, tellDisclosure, typeaheadBy, unlinkDescription, words, } from '@fusion.dev/helix-core';
8
8
  import { caretLeft } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property } from 'lit/decorators.js';
@@ -31,6 +31,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
31
31
  constructor() {
32
32
  super(...arguments);
33
33
  this.backLabel = 'Back';
34
+ this.closeOnSelect = true;
34
35
  this.context = false;
35
36
  this.label = '';
36
37
  this.longPressLabel = 'Long press or press Alt+Down to show the menu';
@@ -119,8 +120,9 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
119
120
  this.enter(chosen);
120
121
  return;
121
122
  }
123
+ const keep = chosen.keepsOpen || (chosen.closeOnSelect === undefined && !this.closeOnSelect);
122
124
  queueMicrotask(() => {
123
- if (!event.defaultPrevented) {
125
+ if (!(event.defaultPrevented || keep)) {
124
126
  this.hide();
125
127
  }
126
128
  });
@@ -538,7 +540,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
538
540
  markColumns() {
539
541
  const rows = this.flies ? this.topRows : this.drilldown.rows;
540
542
  this.toggleState('start', rows.some((row) => row.querySelector(':scope > [slot="start"]') !== null));
541
- this.toggleState('mark', rows.some((row) => row.checkable || row.checked || this.drilldown.hasPage(row)));
543
+ this.toggleState('mark', rows.some((row) => row.choice || this.drilldown.hasPage(row)));
542
544
  }
543
545
  mark(item, onPage) {
544
546
  const page = this.drilldown.page;
@@ -608,6 +610,9 @@ __decorate([
608
610
  words,
609
611
  property({ attribute: 'back-label' })
610
612
  ], HelixHeadlessMenu.prototype, "backLabel", void 0);
613
+ __decorate([
614
+ property({ attribute: 'close-on-select', converter: onUnlessFalse })
615
+ ], HelixHeadlessMenu.prototype, "closeOnSelect", void 0);
611
616
  __decorate([
612
617
  property({ reflect: true, type: Boolean })
613
618
  ], HelixHeadlessMenu.prototype, "context", void 0);
@@ -99,6 +99,8 @@ export type ObjectInspectorRenderer = (value: unknown, entry: ObjectInspectorEnt
99
99
  * @aliases JSON viewer, Object viewer, Inspector, Data explorer
100
100
  * @documentation https://fusion.dev/helix/components/object-inspector
101
101
  *
102
+ * @slot empty - Shown in the tree while it has no entries to show. Defaults to `empty-label`, or `no-matches-label` while a filter matched none.
103
+ *
102
104
  * @event {HelixActivateEvent} helix-activate - The active row moved, by a key or a click. The detail's `value` is its path.
103
105
  * @event {HelixBeforeActivateEvent} helix-before-activate - The active row is about to move, with its new path. Cancelable: cancel it to keep the row where it is.
104
106
  * @event {HelixBeforeCloseEvent} helix-before-close - A reader is about to close the active branch. Cancelable: cancel it to keep the branch open.
@@ -128,6 +130,7 @@ export type ObjectInspectorRenderer = (value: unknown, entry: ObjectInspectorEnt
128
130
  * @csspart copy-tooltip-right - The tooltip while it is right of the copy button. Set alongside `copy-tooltip`.
129
131
  * @csspart copy-tooltip-top - The tooltip while it is above the copy button. Set alongside `copy-tooltip`.
130
132
  * @csspart count - The number of entries a branch holds.
133
+ * @csspart empty - The row shown in the tree while it has no entries to show: no value, or a filter that matched none. It holds the `empty` slot.
131
134
  * @csspart frame - The tree and the copy button beside it.
132
135
  * @csspart header - The bar that holds the label, when there is one.
133
136
  * @csspart key - A property name or an array index.
@@ -146,6 +149,10 @@ export type ObjectInspectorRenderer = (value: unknown, entry: ObjectInspectorEnt
146
149
  * @csspart value - What `renderValue` drew for an entry, in place of its own drawing.
147
150
  *
148
151
  * @cssproperty --object-inspector-level - How deep a row is, from 0 at the root, for indenting it.
152
+ *
153
+ * @cssstate empty - While the tree has no entries to show: no value, or a filter that matched none.
154
+ * @cssstate filtered - While `filter` holds text.
155
+ * @cssstate matched - While `filter` holds text and matched at least one entry.
149
156
  */
150
157
  export declare class HelixHeadlessObjectInspector extends HelixElement {
151
158
  /**
@@ -183,6 +190,11 @@ export declare class HelixHeadlessObjectInspector extends HelixElement {
183
190
  * wall of text this exists to avoid.
184
191
  */
185
192
  defaultDepth: number;
193
+ /**
194
+ * What the tree says while there is no value to show, such as before a
195
+ * fetch answers. The `empty` slot replaces it.
196
+ */
197
+ emptyLabel: string;
186
198
  /**
187
199
  * The open branches, by path, in no set order.
188
200
  *
@@ -254,6 +266,11 @@ export declare class HelixHeadlessObjectInspector extends HelixElement {
254
266
  * reach a value.
255
267
  */
256
268
  noCopy: boolean;
269
+ /**
270
+ * What the tree says while a filter matched no entry. The `empty` slot
271
+ * replaces it.
272
+ */
273
+ noMatchesLabel: string;
257
274
  /**
258
275
  * Draws an entry's value your way, given the value and its entry. Set it as
259
276
  * a property.
@@ -358,6 +375,12 @@ export declare class HelixHeadlessObjectInspector extends HelixElement {
358
375
  private seed;
359
376
  /** Opens the branches around each path, where the value has them. */
360
377
  private openAround;
378
+ /**
379
+ * Whether the tree has no entries to show. Not while a value named by `for`
380
+ * is still to be read, as on a server and while hydrating what one sent,
381
+ * where it says nothing yet about the value.
382
+ */
383
+ private get blank();
361
384
  protected willUpdate(changed: PropertyValues): void;
362
385
  /**
363
386
  * A count in its plural message, `items-label`, `keys-label` or
@@ -32,6 +32,7 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
32
32
  this.copyLabel = 'Copy {name}';
33
33
  this.copyValueLabel = 'Copy value';
34
34
  this.defaultDepth = 1;
35
+ this.emptyLabel = 'Nothing to show';
35
36
  this.filter = '';
36
37
  this.filterable = false;
37
38
  this.filterLabel = 'Filter';
@@ -40,6 +41,7 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
40
41
  this.keysLabel = '{count, plural, one {# key} other {# keys}}';
41
42
  this.label = '';
42
43
  this.noCopy = false;
44
+ this.noMatchesLabel = 'No matches';
43
45
  this.resultsLabel = '{count, plural, one {# result} other {# results}}';
44
46
  this.treeLabel = 'Value';
45
47
  this.value = undefined;
@@ -213,6 +215,9 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
213
215
  openAround(paths) {
214
216
  this.opened = withAncestors(this.opened, this.branches, paths);
215
217
  }
218
+ get blank() {
219
+ return this.rows.length === 0 && (this.for === '' || this.loadedFor === this.for);
220
+ }
216
221
  willUpdate(changed) {
217
222
  super.willUpdate(changed);
218
223
  this.hadFocus = this.hasFocusedRow();
@@ -248,6 +253,9 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
248
253
  if (changed.has('filter') && this.hasUpdated) {
249
254
  this.tell();
250
255
  }
256
+ this.toggleState('empty', this.blank);
257
+ this.toggleState('filtered', this.found !== undefined);
258
+ this.toggleState('matched', (this.found?.size ?? 0) > 0);
251
259
  }
252
260
  counted(which, count) {
253
261
  const one = this[`${which}Label`];
@@ -526,7 +534,9 @@ let HelixHeadlessObjectInspector = class HelixHeadlessObjectInspector extends He
526
534
  @scroll=${() => this.virtualize.measure()}
527
535
  >
528
536
  ${this.windowed ? html `<div part="before" role="presentation" style="block-size: ${before}px"></div>` : nothing}
529
- ${repeat(shown, ([row]) => row.path, ([row, at]) => this.renderRow(row, at))}
537
+ ${repeat(shown, ([row]) => row.path, ([row, at]) => this.renderRow(row, at))}${this.blank
538
+ ? html `<div part="empty" role="row"><div role="gridcell"><slot name="empty">${this.found ? this.noMatchesLabel : this.emptyLabel}</slot></div></div>`
539
+ : nothing}
530
540
  ${this.windowed ? html `<div part="after" role="presentation" style="block-size: ${after}px"></div>` : nothing}
531
541
  </div>
532
542
  ${this.noCopy ? nothing : this.renderCopyButton(rows[this.cursor])}
@@ -548,6 +558,10 @@ __decorate([
548
558
  __decorate([
549
559
  property({ attribute: 'default-depth', type: Number })
550
560
  ], HelixHeadlessObjectInspector.prototype, "defaultDepth", void 0);
561
+ __decorate([
562
+ words,
563
+ property({ attribute: 'empty-label' })
564
+ ], HelixHeadlessObjectInspector.prototype, "emptyLabel", void 0);
551
565
  __decorate([
552
566
  property({ attribute: false })
553
567
  ], HelixHeadlessObjectInspector.prototype, "expanded", null);
@@ -584,6 +598,10 @@ __decorate([
584
598
  __decorate([
585
599
  property({ attribute: 'no-copy', type: Boolean })
586
600
  ], HelixHeadlessObjectInspector.prototype, "noCopy", void 0);
601
+ __decorate([
602
+ words,
603
+ property({ attribute: 'no-matches-label' })
604
+ ], HelixHeadlessObjectInspector.prototype, "noMatchesLabel", void 0);
587
605
  __decorate([
588
606
  property({ attribute: false })
589
607
  ], HelixHeadlessObjectInspector.prototype, "renderValue", void 0);
@@ -150,7 +150,13 @@ export declare class HelixHeadlessSteps extends HelixHeadlessSteps_base {
150
150
  protected readonly keys: KeyboardNavigationController;
151
151
  /** The steps, in the order the consumer wrote them. */
152
152
  private get steps();
153
- /** The index of the stage the reader is on, or `-1`. */
153
+ /**
154
+ * The index of the stage the reader is on, or `-1`.
155
+ *
156
+ * Read from `active` once anything has set it, because a move writes
157
+ * `active` at once and each step's `current` only on the next update: two
158
+ * calls to `next()` in one task then move two stages.
159
+ */
154
160
  private get at();
155
161
  /**
156
162
  * Moves to the next stage that can be gone to, as Ark UI's `goToNextStep`
@@ -58,7 +58,12 @@ let HelixHeadlessSteps = class HelixHeadlessSteps extends MixinControllerActive(
58
58
  return [...this.querySelectorAll(':scope > *')].filter((one) => 'current' in one);
59
59
  }
60
60
  get at() {
61
- return this.steps.findIndex((step) => step.current);
61
+ const steps = this.steps;
62
+ if (this.active === '') {
63
+ return steps.findIndex((step) => step.current);
64
+ }
65
+ const item = this.resolveActive(steps);
66
+ return item ? steps.indexOf(item) : -1;
62
67
  }
63
68
  next() {
64
69
  return this.step(1);
@@ -42,6 +42,13 @@ export interface TableGridOptions {
42
42
  * carry the selection selects the row there.
43
43
  */
44
44
  arrive?: (to: number, control: boolean) => void;
45
+ /**
46
+ * Right or Left on the disclosure cell of drawn row `row`, `forward` for
47
+ * the key that moves on along the line. Says that it opened or shut the
48
+ * row, names the drawn row to move to, or says it did neither, which moves
49
+ * along the row as the key does on any cell.
50
+ */
51
+ disclose?: (row: number, forward: boolean) => boolean | number;
45
52
  /** Whether the table is a grid now. */
46
53
  on: () => boolean;
47
54
  }
@@ -71,10 +71,24 @@ export class TableGridController {
71
71
  }
72
72
  return;
73
73
  }
74
+ const step = inlineStep(this.host, event.key);
75
+ if (step !== 0 &&
76
+ place.row >= 0 &&
77
+ !(event.shiftKey || event.ctrlKey || event.metaKey) &&
78
+ this.options.lead()[place.column] === 'expand') {
79
+ const done = this.options.disclose?.(place.row, step > 0) ?? false;
80
+ if (done !== false) {
81
+ event.preventDefault();
82
+ if (typeof done === 'number') {
83
+ this.moveTo({ column: place.column, row: done });
84
+ }
85
+ return;
86
+ }
87
+ }
74
88
  const to = gridMove(event.key, place, { last: this.options.rows().length - 1, top: this.top, width: this.widthOf(place.row) }, {
75
89
  control: event.ctrlKey || event.metaKey,
76
90
  page: event.key.startsWith('Page') ? this.pageFrom(place.cell) : 1,
77
- step: inlineStep(this.host, event.key),
91
+ step,
78
92
  });
79
93
  if (!to) {
80
94
  return;
@@ -111,6 +111,8 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
111
111
  * | Enter, Space on a sortable heading | Sorts by the column. |
112
112
  * | Space on a row, Enter or Space on its checkbox cell | Selects the row, in a `selectable` table. |
113
113
  * | Enter or Space on a disclosure cell | Shows or hides the rows under the row, in a table whose rows nest. |
114
+ * | Right on a disclosure cell | Shows the rows under a shut row, and otherwise moves one cell along the row. Left does, on a right-to-left page. |
115
+ * | Left on a disclosure cell | Hides the rows under an open row, and moves to the row this one sits under from any other. |
114
116
  * | Enter or Space on a grip cell | Picks the row up, or drops it, in a `reorderable` table. Up, Down, Home, and End move a row picked up, and Escape puts it back. |
115
117
  * | Down, Up, Page Down, Page Up under `selection-behavior="replace"` | Selects the row it moves to alone. Control or Command with the key moves without selecting. |
116
118
  * | Shift with Down or Up | Selects the rows it moves across, in a `selectable` table. |
@@ -184,6 +186,7 @@ declare const HelixHeadlessTable_base: (new (...args: any[]) => import("@fusion.
184
186
  *
185
187
  * @cssstate empty - While there are no rows and none are loading.
186
188
  * @cssstate error - While `error-text` is set.
189
+ * @cssstate loading - While `loading` is set, as the rows are on their way.
187
190
  */
188
191
  export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
189
192
  /**
@@ -534,6 +537,13 @@ export declare class HelixHeadlessTable extends HelixHeadlessTable_base {
534
537
  private selectRange;
535
538
  /** One row's checkbox, ticked or not. It becomes the row a range runs from. */
536
539
  private chooseRow;
540
+ /**
541
+ * Right or Left on a disclosure cell, as on a tree's item and as Data
542
+ * Table's row headers do: on to open a shut row, back to shut an open one.
543
+ * Back on a row that is shut, or has no rows under it, moves to the row it
544
+ * sits under. Anything else moves along the row.
545
+ */
546
+ private disclose;
537
547
  /**
538
548
  * Enter, Space or F2 on a cell of a `grid` table, and whether it did
539
549
  * anything. Left alone, Enter and F2 move into the cell's controls.
@@ -4,7 +4,7 @@ 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, anchorAt, assignSlots, commaList, customElement, cycle, everyState, followLink, format, formatWords, HelixBeforeToggleEvent, HelixElement, HelixLoadMoreEvent, HelixSelectEvent, HelixSelectedEvent, HelixSortEvent, HelixSortedEvent, HelixToggleEvent, inRowOrder, MixinCollection, onAndroid, parse, ReorderController, rangeAdd, reflectElements, requestChange, scrollingAncestor, sort, splitValues, toggleEvery, whenHydrated, words, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, anchorAt, assignSlots, commaList, customElement, cycle, everyState, followLink, format, formatWords, HelixBeforeToggleEvent, HelixElement, HelixLoadMoreEvent, HelixSelectEvent, HelixSelectedEvent, HelixSortEvent, HelixSortedEvent, HelixToggleEvent, inRowOrder, MixinCollection, onAndroid, parse, ReorderController, rangeAdd, reflectElements, requestChange, scrollingAncestor, sort, splitValues, toggleEvery, treeArrow, whenHydrated, words, } from '@fusion.dev/helix-core';
8
8
  import { nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import { html } from 'lit/static-html.js';
@@ -94,6 +94,7 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
94
94
  }
95
95
  },
96
96
  columns: () => this.tableColumns.filter((column) => column.shown),
97
+ disclose: (at, forward) => this.disclose(at, forward),
97
98
  lead: () => this.lead,
98
99
  on: () => this.grid,
99
100
  part: (name) => this.partNamed(name),
@@ -471,6 +472,21 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
471
472
  }
472
473
  this.requestSelected(keys);
473
474
  }
475
+ disclose(at, forward) {
476
+ const rows = this.arrangedRows;
477
+ const row = rows[at];
478
+ const found = this.keyed.find((one) => one.row === row);
479
+ if (!(row && found)) {
480
+ return false;
481
+ }
482
+ const arrow = treeArrow({ branch: row.branch ?? false, open: this.open.has(found.key) }, forward ? 1 : -1);
483
+ if (arrow === 'open' || arrow === 'close') {
484
+ this.toggleRow(found.key);
485
+ return true;
486
+ }
487
+ const parent = arrow === 'parent' ? rows.indexOf(this.rowFor(row.parent ?? '')) : -1;
488
+ return parent >= 0 ? parent : false;
489
+ }
474
490
  activate(place, event) {
475
491
  const leads = this.lead;
476
492
  const lead = leads[place.column] === 'select';
@@ -697,6 +713,7 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
697
713
  super.updated(changed);
698
714
  this.toggleState('error', this.errorText !== '');
699
715
  this.toggleState('empty', !this.loading && !this.hasRows);
716
+ this.toggleState('loading', this.loading);
700
717
  this.assign();
701
718
  this.alignCells();
702
719
  this.markSorted();
@@ -4,7 +4,7 @@ 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 { activeCue, adoptTrack, CueController, captionsAmong, captionTracks, copiesOf, customElement, drawIcon, FullscreenController, formatWords, HelixElement, HelixNextEvent, HelixPreviousEvent, IdleController, MediaSessionController, onUnlessFalse, RemotePlaybackController, reportRuntime, SKIP_SECONDS, sameCopies, showCaption, words, writtenList, writtenNumbers, } from '@fusion.dev/helix-core';
7
+ import { activeCue, adoptTrack, auditName, CueController, captionsAmong, captionTracks, copiesOf, customElement, drawIcon, FullscreenController, formatWords, HelixElement, HelixNextEvent, HelixPreviousEvent, IdleController, MediaSessionController, onUnlessFalse, RemotePlaybackController, reportRuntime, SKIP_SECONDS, sameCopies, showCaption, words, writtenList, writtenNumbers, } from '@fusion.dev/helix-core';
8
8
  import { airplay, arrowClockwise, arrowCounterClockwise, closedCaptioning, cornersIn, cornersOut, dotsThreeVertical, download, gauge, pause, pictureInPicture, play, skipBack, skipForward, speakerHigh, speakerX, } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -464,6 +464,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
464
464
  firstUpdated(changed) {
465
465
  super.firstUpdated?.(changed);
466
466
  this.finishHydration();
467
+ auditName(this, () => null, 'Give it a `label` that says what the video shows, such as "Product tour".', () => this.label !== '');
467
468
  const video = this.video;
468
469
  if (video) {
469
470
  if (this.defaultMuted || this.silent) {
@@ -38,6 +38,9 @@ import { type PropertyValues, type TemplateResult } from 'lit';
38
38
  * @csspart thumbnail - The image of `poster`, shown when the `media` slot is empty.
39
39
  *
40
40
  * @cssstate current - The entry that is playing.
41
+ * @cssstate dragging - The entry a pointer is dragging, in a `reorderable` playlist.
42
+ * @cssstate drop-after - The entry the dragged one would land after, which the drop indicator is drawn under.
43
+ * @cssstate drop-before - The entry the dragged one would land before, which the drop indicator is drawn over.
41
44
  */
42
45
  export declare class HelixHeadlessVideoPlaylistItem extends HelixElement {
43
46
  /**
@@ -61,6 +64,11 @@ export declare class HelixHeadlessVideoPlaylistItem extends HelixElement {
61
64
  * current one, and the arrow keys reach the rest.
62
65
  */
63
66
  focusable?: boolean;
67
+ /**
68
+ * @internal Set by a `reorderable` playlist: where this entry stands in a
69
+ * move, which it says through its states.
70
+ */
71
+ move: 'dragging' | 'drop-after' | 'drop-before' | undefined;
64
72
  /**
65
73
  * The image shown before this entry plays, also used as the row's thumbnail.
66
74
  *
@@ -12,6 +12,7 @@ let HelixHeadlessVideoPlaylistItem = class HelixHeadlessVideoPlaylistItem extend
12
12
  super(...arguments);
13
13
  this.current = false;
14
14
  this.disabled = false;
15
+ this.move = undefined;
15
16
  this.poster = '';
16
17
  this.src = '';
17
18
  this.value = '';
@@ -40,6 +41,9 @@ let HelixHeadlessVideoPlaylistItem = class HelixHeadlessVideoPlaylistItem extend
40
41
  willUpdate(changed) {
41
42
  super.willUpdate(changed);
42
43
  this.toggleState('current', this.current);
44
+ this.toggleState('dragging', this.move === 'dragging');
45
+ this.toggleState('drop-after', this.move === 'drop-after');
46
+ this.toggleState('drop-before', this.move === 'drop-before');
43
47
  }
44
48
  render() {
45
49
  return html `<div part="container" role="listitem">
@@ -70,6 +74,9 @@ __decorate([
70
74
  __decorate([
71
75
  property({ attribute: false })
72
76
  ], HelixHeadlessVideoPlaylistItem.prototype, "focusable", void 0);
77
+ __decorate([
78
+ property({ attribute: false })
79
+ ], HelixHeadlessVideoPlaylistItem.prototype, "move", void 0);
73
80
  __decorate([
74
81
  property()
75
82
  ], HelixHeadlessVideoPlaylistItem.prototype, "poster", void 0);
@@ -54,6 +54,21 @@ declare const HelixHeadlessVideoPlaylist_base: (new (...args: any[]) => import("
54
54
  * the next or the previous entry. The player's previous and next controls
55
55
  * move through the entries in the same order the playlist does.
56
56
  *
57
+ * **Moving entries.** `reorderable` lets a reader change the order of the
58
+ * queue:
59
+ *
60
+ * - Alt with Up or Down moves the focused entry a place, and Alt with Home or
61
+ * End moves it to either end, as the APG's rearrangeable listbox does.
62
+ * Focus stays on it.
63
+ * - A pointer drags an entry, with a drop indicator, and the page scrolls near
64
+ * an edge. A press that does not travel still plays the entry.
65
+ * - A drop sends `helix-before-reorder`, which you can refuse, and then
66
+ * `helix-reorder`, whose `keys` hold every entry's `value` in the new order.
67
+ * **You move the entries**: the playlist moves no element of yours, and the
68
+ * next entry is the one after the current entry in the order you leave.
69
+ * - Each move is said in the live region, in words you can change:
70
+ * `moved-label`, `dropped-label`, `canceled-label`, and `picked-up-label`.
71
+ *
57
72
  * **What a screen reader hears.** A list named by `label`, with a button for
58
73
  * each entry. When the playlist moves to another entry, by a press, by the
59
74
  * player's controls, or at the end of a clip, a live region says
@@ -71,7 +86,9 @@ declare const HelixHeadlessVideoPlaylist_base: (new (...args: any[]) => import("
71
86
  *
72
87
  * @event {HelixActivateEvent} helix-activate - The current entry changed. The playlist already holds the new entry when this is sent, and `detail.previous` is the entry it left. `currentEntry` is the entry itself, with its `src`, `poster` and name.
73
88
  * @event {HelixBeforeActivateEvent} helix-before-activate - The current entry is about to change, and `detail.cause` says why: `press`, `next`, `previous`, `ended`, or `removed`. Cancelable: cancel it to keep the current entry.
89
+ * @event {HelixBeforeReorderEvent} helix-before-reorder - An entry was moved to a new place, in a `reorderable` playlist, with every entry's value in the new order in `detail.keys`. Cancelable: cancel it to keep the entry where it is.
74
90
  * @event {HelixEndEvent} helix-end - The last entry finished playing, and the playlist stopped there. Not sent while `repeat` is set.
91
+ * @event {HelixReorderEvent} helix-reorder - Fired after `helix-before-reorder` when nobody canceled it. Move your entries to the order in `keys`. Cancelable: cancel it to say the move did not happen.
75
92
  *
76
93
  * @csspart empty - The box that holds the `empty` slot, drawn while there are no entries.
77
94
  * @csspart items - The list of entries.
@@ -86,6 +103,16 @@ export declare class HelixHeadlessVideoPlaylist extends HelixHeadlessVideoPlayli
86
103
  * `registerWords('de', { 'helix-video-playlist': { label: 'Wiedergabeliste' } })`.
87
104
  */
88
105
  static words: string;
106
+ /**
107
+ * What screen readers hear when a move is put back, in a `reorderable`
108
+ * playlist. `{name}` is the entry's words.
109
+ */
110
+ canceledLabel: string;
111
+ /**
112
+ * What screen readers hear when a moved entry is let go, in a `reorderable`
113
+ * playlist. `{name}` is the entry's words.
114
+ */
115
+ droppedLabel: string;
89
116
  /**
90
117
  * What the playlist says while it has no entries. The `empty` slot replaces it.
91
118
  */
@@ -94,12 +121,23 @@ export declare class HelixHeadlessVideoPlaylist extends HelixHeadlessVideoPlayli
94
121
  * The list's name, read by screen readers.
95
122
  */
96
123
  label: string;
124
+ /**
125
+ * What screen readers hear each time a key moves an entry, in a
126
+ * `reorderable` playlist. `{name}` is the entry's words, `{position}` its
127
+ * new place, counting from 1, and `{count}` the number of entries.
128
+ */
129
+ movedLabel: string;
97
130
  /**
98
131
  * Stays on an entry when its clip ends, where the playlist would otherwise
99
132
  * play the next one. The player's controls, a press, and `next()` still
100
133
  * move. `repeat="one"` still plays the entry again.
101
134
  */
102
135
  noAdvance: boolean;
136
+ /**
137
+ * What screen readers hear when a pointer picks up an entry, in a
138
+ * `reorderable` playlist. `{name}` is the entry's words.
139
+ */
140
+ pickedUpLabel: string;
103
141
  /**
104
142
  * What the live region says when the playlist moves to another entry.
105
143
  * `{title}` is the entry's name.
@@ -109,8 +147,30 @@ export declare class HelixHeadlessVideoPlaylist extends HelixHeadlessVideoPlayli
109
147
  * `aria-current` on a button the reader is not on.
110
148
  */
111
149
  playingLabel: string;
150
+ /**
151
+ * Lets a reader move the entries, with Alt and the arrow keys or by
152
+ * dragging one. The playlist moves no entry itself: you move your entries
153
+ * on `helix-reorder`.
154
+ */
155
+ reorderable: boolean;
112
156
  /** The live region, rendered from the first move. */
113
157
  private readonly status;
158
+ /** The entry an event passed through, where it may move. */
159
+ private entryOf;
160
+ /** The entry with that value. */
161
+ private entryFor;
162
+ /**
163
+ * Moving the entries, as a listbox moves its options: Alt with the arrows
164
+ * on the focused entry, or a pointer dragging a whole entry, where a press
165
+ * that does not travel still plays it.
166
+ */
167
+ private readonly reorder;
168
+ /**
169
+ * Tells each entry where it stands in a move, which it says through its
170
+ * states for the styled tier to draw: the entry dragged, and the one the
171
+ * drop indicator is before or after.
172
+ */
173
+ private markMoves;
114
174
  /**
115
175
  * What happens when an entry ends with no next one to go to. `one` plays the
116
176
  * same entry again, and `all` goes from the last entry back to the first.