@fusion.dev/helix-headless 0.172.0 → 0.173.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 (41) hide show
  1. package/custom-elements.json +912 -60
  2. package/dist/button-group/root/button-group-root.d.ts +30 -0
  3. package/dist/button-group/root/button-group-root.js +50 -4
  4. package/dist/canvas/root/canvas-root.d.ts +7 -0
  5. package/dist/canvas/root/canvas-root.js +7 -0
  6. package/dist/copy-button/root/copy-button-root.d.ts +43 -3
  7. package/dist/copy-button/root/copy-button-root.js +49 -3
  8. package/dist/element-events.generated.js +2 -0
  9. package/dist/graph/root/graph-root.d.ts +22 -0
  10. package/dist/graph/root/graph-root.js +21 -5
  11. package/dist/list/root/list-root.d.ts +6 -1
  12. package/dist/list/root/list-root.js +3 -2
  13. package/dist/outline/root/outline-root.d.ts +57 -0
  14. package/dist/outline/root/outline-root.js +98 -29
  15. package/dist/speed-dial/root/speed-dial-root.d.ts +14 -0
  16. package/dist/speed-dial/root/speed-dial-root.js +34 -2
  17. package/dist/splitter/root/splitter-root.d.ts +25 -2
  18. package/dist/splitter/root/splitter-root.js +19 -4
  19. package/dist/tag/group/tag-group.d.ts +8 -1
  20. package/dist/tag/group/tag-group.js +8 -0
  21. package/dist/tag/root/tag-root.d.ts +12 -0
  22. package/dist/tag/root/tag-root.js +12 -0
  23. package/dist/time-picker/root/time-picker-root.d.ts +14 -0
  24. package/dist/time-picker/root/time-picker-root.js +8 -1
  25. package/dist/timeline/item/timeline-item.d.ts +5 -1
  26. package/dist/timeline/item/timeline-item.js +9 -0
  27. package/dist/timeline/root/timeline-root.d.ts +23 -0
  28. package/dist/timeline/root/timeline-root.js +17 -3
  29. package/dist/tooltip/root/tooltip-root.d.ts +10 -0
  30. package/dist/tooltip/root/tooltip-root.js +14 -2
  31. package/dist/virtual-list/reorder/index.d.ts +1 -0
  32. package/dist/virtual-list/reorder/index.js +4 -0
  33. package/dist/virtual-list/reorder/virtual-list-reorder.d.ts +86 -0
  34. package/dist/virtual-list/reorder/virtual-list-reorder.js +193 -0
  35. package/dist/virtual-list/root/virtual-list-root.d.ts +49 -11
  36. package/dist/virtual-list/root/virtual-list-root.js +10 -1
  37. package/lint.json +1 -1
  38. package/package.json +5 -5
  39. package/types/jsx.d.ts +568 -18
  40. package/types/vue.d.ts +115 -11
  41. package/types/words.d.ts +14 -0
@@ -57,6 +57,12 @@ import { type PropertyValues, type TemplateResult } from 'lit';
57
57
  * a named `group`, and the toolbar's own arrow keys walk its buttons, so a
58
58
  * reader meets one toolbar. Under `single` it is a `radiogroup` either way.
59
59
  *
60
+ * **Disabled.** `disabled` on the group disables every button in it, Helix
61
+ * and native alike, and says so on the group with `aria-disabled`, as React
62
+ * Aria's `isDisabled` does. A button disabled on its own stays disabled when
63
+ * the group is enabled again, and a button that leaves the group gets back
64
+ * what it said for itself.
65
+ *
60
66
  * @since 0.1.0
61
67
  * @status stable
62
68
  * @category actions
@@ -73,6 +79,11 @@ import { type PropertyValues, type TemplateResult } from 'lit';
73
79
  * @cssstate pressed - While a group with `selection-mode` has a button pressed.
74
80
  */
75
81
  export declare class HelixHeadlessButtonGroup extends HelixElement {
82
+ /**
83
+ * Disables every button in the group, and says so on the group with
84
+ * `aria-disabled`. Reflected, so a stylesheet can select on it.
85
+ */
86
+ disabled: boolean;
76
87
  /**
77
88
  * Keeps at least one button pressed once one is, as React Aria's
78
89
  * `disallowEmptySelection`: a press that would leave none pressed is
@@ -122,6 +133,12 @@ export declare class HelixHeadlessButtonGroup extends HelixElement {
122
133
  * not reflect, as a native control's `value` does not.
123
134
  */
124
135
  value: string;
136
+ /**
137
+ * The pressed values as a list, in a selection mode, and an empty list
138
+ * otherwise. Written, it presses exactly those buttons.
139
+ */
140
+ get values(): string[];
141
+ set values(next: string[]);
125
142
  /**
126
143
  * The Helix buttons of a group in a selection mode, in the order they are
127
144
  * written, and none otherwise. Empty on a server, which has no tree.
@@ -146,6 +163,19 @@ export declare class HelixHeadlessButtonGroup extends HelixElement {
146
163
  */
147
164
  private readonly told;
148
165
  private watcher;
166
+ /** The children that can be disabled: Helix buttons and native ones alike. Empty on a server. */
167
+ private get members();
168
+ /** The members this group has disabled, so one that leaves is given back its own answer. */
169
+ private readonly held;
170
+ /**
171
+ * Passes `disabled` down to the members, and gives a member that left
172
+ * what it said for itself.
173
+ *
174
+ * A member already disabled is not written to again: the watcher hears
175
+ * every write to a native button's `disabled`, changed or not, and would
176
+ * answer it with this.
177
+ */
178
+ private cascade;
149
179
  /**
150
180
  * Tells every button whether it is pressed and whether it holds the stop,
151
181
  * and puts back the ones that left.
@@ -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 { auditName, commaList, customElement, HelixElement, hostAria, inlineStep, pressedValue, reportRuntime, requestValue, rovingStop, toolbarAround, whenHydrated, } from '@fusion.dev/helix-core';
7
+ import { auditName, commaList, customElement, HelixElement, hostAria, inheritDisabled, inlineStep, pressedValue, reportRuntime, requestValue, rovingStop, toolbarAround, whenHydrated, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { arrowTarget } from '../arrows.js';
@@ -12,11 +12,13 @@ const isGroupButton = (child) => child instanceof HTMLElement && 'focusable' in
12
12
  let HelixHeadlessButtonGroup = class HelixHeadlessButtonGroup extends HelixElement {
13
13
  constructor() {
14
14
  super(...arguments);
15
+ this.disabled = false;
15
16
  this.disallowEmptySelection = false;
16
17
  this.label = '';
17
18
  this.value = '';
18
19
  this.lastFocused = undefined;
19
20
  this.told = new Map();
21
+ this.held = new Set();
20
22
  this.warned = false;
21
23
  this.handlePress = (event) => {
22
24
  const mode = this.selectionMode;
@@ -48,6 +50,12 @@ let HelixHeadlessButtonGroup = class HelixHeadlessButtonGroup extends HelixEleme
48
50
  }
49
51
  };
50
52
  }
53
+ get values() {
54
+ return this.selectionMode ? commaList(this.value) : [];
55
+ }
56
+ set values(next) {
57
+ this.value = next.join(',');
58
+ }
51
59
  get buttons() {
52
60
  return this.selectionMode && this.children ? [...this.children].filter(isGroupButton) : [];
53
61
  }
@@ -57,7 +65,33 @@ let HelixHeadlessButtonGroup = class HelixHeadlessButtonGroup extends HelixEleme
57
65
  pressed(button) {
58
66
  return button.value !== '' && commaList(this.value).includes(button.value);
59
67
  }
68
+ get members() {
69
+ return this.children
70
+ ? [...this.children].filter((child) => child instanceof HTMLElement && 'disabled' in child)
71
+ : [];
72
+ }
73
+ cascade() {
74
+ const members = this.members;
75
+ for (const gone of this.held) {
76
+ if (!members.includes(gone)) {
77
+ inheritDisabled(false, [gone]);
78
+ this.held.delete(gone);
79
+ }
80
+ }
81
+ if (this.disabled) {
82
+ const live = members.filter((member) => !member.disabled);
83
+ inheritDisabled(true, live);
84
+ for (const member of live) {
85
+ this.held.add(member);
86
+ }
87
+ }
88
+ else if (this.held.size > 0) {
89
+ inheritDisabled(false, [...this.held]);
90
+ this.held.clear();
91
+ }
92
+ }
60
93
  sync() {
94
+ this.cascade();
61
95
  const buttons = this.buttons;
62
96
  const roving = this.roving;
63
97
  const stop = roving
@@ -86,11 +120,16 @@ let HelixHeadlessButtonGroup = class HelixHeadlessButtonGroup extends HelixEleme
86
120
  });
87
121
  }
88
122
  }
89
- for (const child of this.selectionMode && this.children ? this.children : []) {
90
- if (child.localName.includes('-') && !isGroupButton(child)) {
123
+ const reading = Boolean(this.selectionMode) || this.disabled;
124
+ for (const child of reading && this.children ? this.children : []) {
125
+ if (child.localName.includes('-') &&
126
+ !isGroupButton(child) &&
127
+ !customElements.get(child.localName)) {
91
128
  void customElements.whenDefined(child.localName).then(() => this.sync());
92
129
  }
93
- else if (child.localName === 'button' && !child.hasAttribute('aria-pressed')) {
130
+ else if (this.selectionMode &&
131
+ child.localName === 'button' &&
132
+ !child.hasAttribute('aria-pressed')) {
94
133
  this.warn(child);
95
134
  }
96
135
  }
@@ -167,6 +206,7 @@ let HelixHeadlessButtonGroup = class HelixHeadlessButtonGroup extends HelixEleme
167
206
  role: radios ? 'radiogroup' : toolbar ? 'toolbar' : 'group',
168
207
  });
169
208
  this.internals.ariaOrientation = toolbar ? (this.orientation ?? null) : null;
209
+ this.internals.ariaDisabled = this.disabled ? 'true' : null;
170
210
  this.toggleState('pressed', Boolean(this.selectionMode) && commaList(this.value).length > 0);
171
211
  }
172
212
  firstUpdated(changed) {
@@ -181,6 +221,9 @@ let HelixHeadlessButtonGroup = class HelixHeadlessButtonGroup extends HelixEleme
181
221
  return html `<slot></slot>`;
182
222
  }
183
223
  };
224
+ __decorate([
225
+ property({ reflect: true, type: Boolean })
226
+ ], HelixHeadlessButtonGroup.prototype, "disabled", void 0);
184
227
  __decorate([
185
228
  property({ attribute: 'disallow-empty-selection', type: Boolean })
186
229
  ], HelixHeadlessButtonGroup.prototype, "disallowEmptySelection", void 0);
@@ -196,6 +239,9 @@ __decorate([
196
239
  __decorate([
197
240
  property()
198
241
  ], HelixHeadlessButtonGroup.prototype, "value", void 0);
242
+ __decorate([
243
+ property({ attribute: false })
244
+ ], HelixHeadlessButtonGroup.prototype, "values", null);
199
245
  HelixHeadlessButtonGroup = __decorate([
200
246
  customElement('helix-headless-button-group')
201
247
  ], HelixHeadlessButtonGroup);
@@ -132,6 +132,7 @@ declare const HelixHeadlessCanvas_base: (new (...args: any[]) => import("@fusion
132
132
  * @slot footer - Content below the board.
133
133
  * @slot fullscreen-icon - Replaces the glyph inside the fullscreen button.
134
134
  * @slot header - Content above the board.
135
+ * @slot loading - Content shown while `loading` is set, such as a progress bar while the document arrives.
135
136
  * @slot pan-down-icon - Replaces the glyph inside the pan down button.
136
137
  * @slot pan-end-icon - Replaces the glyph inside the button that pans toward the end of a line of text.
137
138
  * @slot pan-start-icon - Replaces the glyph inside the button that pans toward the start of a line of text.
@@ -192,6 +193,7 @@ declare const HelixHeadlessCanvas_base: (new (...args: any[]) => import("@fusion
192
193
  * @cssstate fullscreen - While the board fills the screen.
193
194
  * @cssstate holding - An item picked up with a click follows the pointer until the next press puts it down.
194
195
  * @cssstate idle - The user has been still for `idle-delay` milliseconds. The board's own controls stay; select on it to hide chrome of your own.
196
+ * @cssstate loading - While `loading` is set.
195
197
  * @cssstate pressing - While a press on the board, its cards or its connections is under way.
196
198
  * @cssstate sharing - The page keeps the plain wheel and one finger, with `gestures="cooperative"` or while a board with `gestures="activate"` waits to be pressed.
197
199
  * @cssstate using - A board with `gestures="activate"` has been pressed and has the gestures.
@@ -275,6 +277,11 @@ export declare class HelixHeadlessCanvas extends HelixHeadlessCanvas_base {
275
277
  * `leads-to-label` and `{comesFrom}` is `comes-from-label`, each filled in.
276
278
  */
277
279
  linkedLabel: string;
280
+ /**
281
+ * Marks the board as busy while its document arrives: the board is
282
+ * `aria-busy`, and the `loading` slot is shown.
283
+ */
284
+ loading: boolean;
278
285
  /**
279
286
  * What screen readers hear after a key moves a card, with `{name}` for the
280
287
  * card and `{x}` and `{y}` for where it now is, in the document's units.
@@ -57,6 +57,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
57
57
  this.labelEditorLabel = 'Label for {name}';
58
58
  this.leadsToLabel = 'leads to {cards}';
59
59
  this.linkedLabel = '{leadsTo}; {comesFrom}';
60
+ this.loading = false;
60
61
  this.movedLabel = '{name} moved to {x}, {y}';
61
62
  this.pastedLabel = '{count, plural, one {# card pasted} other {# cards pasted}}';
62
63
  this.readOnly = false;
@@ -2133,6 +2134,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
2133
2134
  this.toggleState('empty', this.nodes.length === 0);
2134
2135
  this.toggleState('fullscreen', this.filled);
2135
2136
  this.toggleState('idle', this.resting);
2137
+ this.toggleState('loading', this.loading);
2136
2138
  }
2137
2139
  place(origin) {
2138
2140
  const all = this.nodes;
@@ -2411,6 +2413,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
2411
2413
  role="group"
2412
2414
  aria-label=${this.label || this.getAttribute?.('aria-label') || this.fallbackLabel}
2413
2415
  aria-roledescription=${(this.label || this.getAttribute?.('aria-label')) && this.roleLabel ? this.roleLabel : nothing}
2416
+ aria-busy=${this.loading ? 'true' : nothing}
2414
2417
  aria-keyshortcuts="ArrowUp ArrowDown ArrowLeft ArrowRight Plus Minus"
2415
2418
  tabindex="0"
2416
2419
  @focusin=${this.idle.stir}
@@ -2461,6 +2464,7 @@ let HelixHeadlessCanvas = class HelixHeadlessCanvas extends MixinViewport(HelixE
2461
2464
  ${this.renderMarquee(origin)}
2462
2465
  </div>
2463
2466
  <slot name="empty"></slot>
2467
+ ${this.loading ? html `<slot name="loading"></slot>` : nothing}
2464
2468
  ${this.renderGestures()}
2465
2469
  </div>
2466
2470
  ${this.renderControls()} ${this.renderMinimap()}
@@ -2525,6 +2529,9 @@ __decorate([
2525
2529
  words,
2526
2530
  property({ attribute: 'linked-label' })
2527
2531
  ], HelixHeadlessCanvas.prototype, "linkedLabel", void 0);
2532
+ __decorate([
2533
+ property({ reflect: true, type: Boolean })
2534
+ ], HelixHeadlessCanvas.prototype, "loading", void 0);
2528
2535
  __decorate([
2529
2536
  words,
2530
2537
  property({ attribute: 'moved-label' })
@@ -1,4 +1,5 @@
1
- import { type PropertyValues, type TemplateResult } from 'lit';
1
+ import { type Placement } from '@fusion.dev/helix-core';
2
+ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
2
3
  import { HelixHeadlessIconButton } from '../../icon-button/root/icon-button-root.js';
3
4
  import '../../tooltip/index.js';
4
5
  /**
@@ -31,7 +32,17 @@ import '../../tooltip/index.js';
31
32
  * The button is named `label`, or the host's `aria-label`, and its name
32
33
  * changes to `copied-label` or `error-label` for the moment after a press. The
33
34
  * same words are said in a live region, so a screen reader hears the result,
34
- * and the tooltip shows them.
35
+ * and the tooltip shows them. The tooltip does not also describe the button,
36
+ * since its words are the button's name.
37
+ *
38
+ * **The tooltip.** It shows the name on hover and focus and the result after a
39
+ * press. `tooltip="copy"` shows it only for the result, and `tooltip="none"`
40
+ * draws none, as Web Awesome's copy button offers. `tooltip-placement` is the
41
+ * side it prefers, and it moves to another side where that one does not fit.
42
+ *
43
+ * **Words on the button.** `show-label` draws the name beside the icon, and
44
+ * the result in its place after a press. The button is as wide as the longest
45
+ * of the three, so a press does not resize it.
35
46
  *
36
47
  * @since 0.1.0
37
48
  * @status stable
@@ -50,6 +61,7 @@ import '../../tooltip/index.js';
50
61
  * @csspart live - The live region that says `copied-label` or `error-label` after a press, and `pending-label` when the button becomes pending under focus. It is rendered from the first time it says anything.
51
62
  * @csspart mark - The clipboard icon, and the confirmation icon that replaces it.
52
63
  * @csspart spinner - The ring a pending button turns, an empty element the styled button draws over the icon. It is rendered only while the button is pending.
64
+ * @csspart text - The words beside the icon, under `show-label`. It holds all three, `label`, `copied-label` and `error-label`, with all but the current one hidden.
53
65
  * @csspart tooltip - The tooltip bubble, forwarded from the tooltip inside the button.
54
66
  * @csspart tooltip-arrow - The tooltip's arrow, which points at the button.
55
67
  * @csspart tooltip-arrow-bottom - The arrow while the bubble is below the button, on the bubble's top edge. Set together with `tooltip-arrow`.
@@ -64,7 +76,6 @@ import '../../tooltip/index.js';
64
76
  * @cssstate copied - The moment after a successful copy, while `copied-label` names the button.
65
77
  * @cssstate failed - The moment after a press that put nothing on the clipboard, while `error-label` names the button.
66
78
  *
67
- * @withheld csspart text - Button's words, which this does not render: the label is the icon's name.
68
79
  * @withheld slot end - Button's, which this does not render.
69
80
  * @withheld slot start - Button's, which this does not render.
70
81
  */
@@ -106,6 +117,24 @@ export declare class HelixHeadlessCopyButton extends HelixHeadlessIconButton {
106
117
  * world is called Copy. A consumer with a better word still sets it.
107
118
  */
108
119
  label: string;
120
+ /**
121
+ * Draws the name as words beside the icon, and the result in their place
122
+ * after a press. The button stays as wide as the longest of them.
123
+ */
124
+ showLabel: boolean;
125
+ /**
126
+ * When the tooltip shows: `full` on hover and focus and after a press,
127
+ * `copy` only after a press, to show the result, and `none` never, for a
128
+ * button whose result shows another way, such as under `show-label`.
129
+ *
130
+ * Unset is `full`, and the attribute stays absent until you write it.
131
+ */
132
+ tooltip?: 'copy' | 'full' | 'none';
133
+ /**
134
+ * The side the tooltip prefers. It moves to another side where this one
135
+ * does not fit.
136
+ */
137
+ tooltipPlacement: Placement;
109
138
  /**
110
139
  * The text copied to the clipboard.
111
140
  *
@@ -156,6 +185,9 @@ export declare class HelixHeadlessCopyButton extends HelixHeadlessIconButton {
156
185
  */
157
186
  protected get forwardsLabel(): boolean;
158
187
  protected willUpdate(changed: PropertyValues): void;
188
+ /** Whether the last render showed a result, so `tooltip="copy"` opens and closes on the change. */
189
+ private showedResult;
190
+ protected updated(changed: PropertyValues): void;
159
191
  /** @internal A copy button never submits, so it is no form's default button. */
160
192
  protected get submitsForm(): boolean;
161
193
  /**
@@ -211,6 +243,14 @@ export declare class HelixHeadlessCopyButton extends HelixHeadlessIconButton {
211
243
  protected renderCopiedIcon(): TemplateResult;
212
244
  /** The mark for a press that put nothing on the clipboard, behind a seam of its own. */
213
245
  protected renderFailedIcon(): TemplateResult;
246
+ /**
247
+ * The words beside the icon, under `show-label`, behind a seam of its own.
248
+ *
249
+ * All three are drawn and only the current one shows, so the button is as
250
+ * wide as the longest of them and a press does not resize it. Hidden from a
251
+ * screen reader, which hears the same words as the button's name.
252
+ */
253
+ protected renderText(): TemplateResult | typeof nothing;
214
254
  /**
215
255
  * The bubble the control sits inside, behind a seam.
216
256
  *
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessCopyButton_1;
8
- import { CopyController, customElement, HelixCopyErrorEvent, words } from '@fusion.dev/helix-core';
8
+ import { CopyController, customElement, HelixCopyErrorEvent, words, } from '@fusion.dev/helix-core';
9
9
  import { check, copy, x } from '@fusion.dev/helix-icons';
10
10
  import { nothing } from 'lit';
11
11
  import { property } from 'lit/decorators.js';
@@ -22,8 +22,11 @@ let HelixHeadlessCopyButton = class HelixHeadlessCopyButton extends HelixHeadles
22
22
  this.feedbackDuration = 1600;
23
23
  this.from = '';
24
24
  this.label = 'Copy';
25
+ this.showLabel = false;
26
+ this.tooltipPlacement = 'top';
25
27
  this.value = '';
26
28
  this.namedBy = null;
29
+ this.showedResult = false;
27
30
  this.clipboard = new CopyController(this, { after: () => this.feedbackDuration });
28
31
  this.handleCopy = async (event) => {
29
32
  if (this.pending) {
@@ -62,6 +65,24 @@ let HelixHeadlessCopyButton = class HelixHeadlessCopyButton extends HelixHeadles
62
65
  this.toggleState('copied', this.copied);
63
66
  this.toggleState('failed', this.failed);
64
67
  }
68
+ updated(changed) {
69
+ super.updated(changed);
70
+ const result = this.copied || this.failed;
71
+ if (result === this.showedResult) {
72
+ return;
73
+ }
74
+ this.showedResult = result;
75
+ if (this.tooltip !== 'copy') {
76
+ return;
77
+ }
78
+ const tip = this.renderRoot.querySelector('[data-copy-tooltip]');
79
+ if (result) {
80
+ tip?.show();
81
+ }
82
+ else {
83
+ tip?.hide();
84
+ }
85
+ }
65
86
  get submitsForm() {
66
87
  return false;
67
88
  }
@@ -94,7 +115,7 @@ let HelixHeadlessCopyButton = class HelixHeadlessCopyButton extends HelixHeadles
94
115
  @keydown=${this.notePress}
95
116
  @pointerdown=${this.notePress}
96
117
  >
97
- ${this.renderCopiedIcon()} ${this.renderFailedIcon()} ${this.renderCopyIcon()}${this.renderSpinner()}
118
+ ${this.renderCopiedIcon()} ${this.renderFailedIcon()} ${this.renderCopyIcon()}${this.renderSpinner()}${this.renderText()}
98
119
  </button>`)}${this.renderLive()}`;
99
120
  }
100
121
  renderCopyIcon() {
@@ -112,8 +133,24 @@ let HelixHeadlessCopyButton = class HelixHeadlessCopyButton extends HelixHeadles
112
133
  >${HelixHeadlessCopyButton_1.icon(x)}</slot
113
134
  >`;
114
135
  }
136
+ renderText() {
137
+ if (!this.showLabel) {
138
+ return nothing;
139
+ }
140
+ const resting = !this.copied && !this.failed;
141
+ return html `<span part="text" aria-hidden="true"
142
+ ><span ?hidden=${!resting}>${this.namedBy || this.label}</span
143
+ ><span ?hidden=${!this.copied}>${this.copiedLabel}</span
144
+ ><span ?hidden=${!this.failed}>${this.errorLabel}</span
145
+ ></span>`;
146
+ }
115
147
  renderTooltip(control) {
116
- return html `<${this.tag('tooltip')} content=${this.says} persist
148
+ if (this.tooltip === 'none') {
149
+ return control;
150
+ }
151
+ const content = this.tooltip === 'copy' && !this.copied && !this.failed ? '' : this.says;
152
+ return html `<${this.tag('tooltip')} content=${content} persist
153
+ data-copy-tooltip placement=${this.tooltipPlacement}
117
154
  exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom,
118
155
  arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right,
119
156
  arrow-top: tooltip-arrow-top, tooltip, tooltip-bottom, tooltip-left, tooltip-right,
@@ -140,6 +177,15 @@ __decorate([
140
177
  words,
141
178
  property()
142
179
  ], HelixHeadlessCopyButton.prototype, "label", void 0);
180
+ __decorate([
181
+ property({ attribute: 'show-label', reflect: true, type: Boolean })
182
+ ], HelixHeadlessCopyButton.prototype, "showLabel", void 0);
183
+ __decorate([
184
+ property()
185
+ ], HelixHeadlessCopyButton.prototype, "tooltip", void 0);
186
+ __decorate([
187
+ property({ attribute: 'tooltip-placement' })
188
+ ], HelixHeadlessCopyButton.prototype, "tooltipPlacement", void 0);
143
189
  __decorate([
144
190
  property()
145
191
  ], HelixHeadlessCopyButton.prototype, "value", void 0);
@@ -363,7 +363,9 @@ registerElementEvents({
363
363
  'helix-headless-virtual-list': [
364
364
  'change',
365
365
  'helix-before-change',
366
+ 'helix-before-reorder',
366
367
  'helix-load-more',
367
368
  'helix-range-change',
369
+ 'helix-reorder',
368
370
  ],
369
371
  });
@@ -44,6 +44,7 @@ declare const HelixHeadlessGraph_base: (new (...args: any[]) => import("@fusion.
44
44
  * @slot footer - Content below the graph.
45
45
  * @slot fullscreen-icon - Replaces the glyph inside the fullscreen button.
46
46
  * @slot header - Content above the graph.
47
+ * @slot loading - What the panel shows while `loading` is set and the list holds no notes. Defaults to `loading-label`.
47
48
  * @slot pan-down-icon - Replaces the glyph inside the pan down button.
48
49
  * @slot pan-end-icon - Replaces the glyph inside the button that pans toward the end of a line of text.
49
50
  * @slot pan-start-icon - Replaces the glyph inside the button that pans toward the start of a line of text.
@@ -80,6 +81,7 @@ declare const HelixHeadlessGraph_base: (new (...args: any[]) => import("@fusion.
80
81
  * @csspart legend-swatch - The mark in a group's color, before its name.
81
82
  * @csspart list - The list of notes, which draws only the rows in view.
82
83
  * @csspart live - What screen readers hear: how many notes there are when a search or the legend changes it, and which way the drawing went after a key or a control moved it.
84
+ * @csspart loading - The box in place of the list while `loading` is set and it holds no notes, holding the `loading` slot.
83
85
  * @csspart note - One note in the list.
84
86
  * @csspart note-cursor - The note the keyboard is on in the list.
85
87
  * @csspart note-linked - A note linked to or from the selected one.
@@ -103,6 +105,7 @@ declare const HelixHeadlessGraph_base: (new (...args: any[]) => import("@fusion.
103
105
  * @cssstate empty - The list holds no notes: the graph has none, or a search or the groups hid them all.
104
106
  * @cssstate fullscreen - The graph fills the screen.
105
107
  * @cssstate idle - The user has been still long enough for the header and footer to fade.
108
+ * @cssstate loading - While `loading` is set.
106
109
  * @cssstate local - The drawing shows the selected note's local graph, with `depth`.
107
110
  * @cssstate panel-open - A graph with `no-panel` has the search and the list open over the drawing.
108
111
  * @cssstate ready - The graph has been drawn whole, after `helix-graph-ready`.
@@ -169,6 +172,13 @@ export declare class HelixHeadlessGraph extends HelixHeadlessGraph_base {
169
172
  links: readonly GraphLink[];
170
173
  /** How a note that links to the selected one is described, with `{note}`. */
171
174
  linksToLabel: string;
175
+ /**
176
+ * Marks the graph as busy while its notes arrive: the list is `aria-busy`,
177
+ * and the panel says so where it holds no notes yet.
178
+ */
179
+ loading: boolean;
180
+ /** What the panel says while `loading` is set and the list holds no notes. */
181
+ loadingLabel: string;
172
182
  /** What screen readers hear once the drawing has moved down, after `moved()`. */
173
183
  movedDownLabel: string;
174
184
  /** What screen readers hear once the drawing has moved left, after `moved()`. */
@@ -185,6 +195,18 @@ export declare class HelixHeadlessGraph extends HelixHeadlessGraph_base {
185
195
  * such as at the top of a page, where the drawing takes the whole frame.
186
196
  */
187
197
  noPanel: boolean;
198
+ /**
199
+ * What each note's row in the list shows in place of its name: a function
200
+ * given the note that returns text, an element or a Lit template.
201
+ *
202
+ * For an icon by its kind, or a date under its name, as React Aria's
203
+ * `ListBoxItem` takes children. The row is still named by the note's name,
204
+ * which typeahead and the search find it by, and described by how it stands
205
+ * to the selected note. Return the name with your content where it should
206
+ * still show. A property only, there being no way to write a function in
207
+ * markup.
208
+ */
209
+ noteContent?: ((node: GraphNode) => unknown) | undefined;
188
210
  /** The list's name, read by screen readers, and the name of the button that opens it with `no-panel`. */
189
211
  notesLabel: string;
190
212
  /**
@@ -27,6 +27,8 @@ let HelixHeadlessGraph = class HelixHeadlessGraph extends MixinViewport(MixinCon
27
27
  this.linkedFromLabel = 'Linked from {note}';
28
28
  this.links = [];
29
29
  this.linksToLabel = 'Links to {note}';
30
+ this.loading = false;
31
+ this.loadingLabel = 'Loading notes';
30
32
  this.movedDownLabel = 'Moved down';
31
33
  this.movedLeftLabel = 'Moved left';
32
34
  this.movedRightLabel = 'Moved right';
@@ -163,6 +165,7 @@ let HelixHeadlessGraph = class HelixHeadlessGraph extends MixinViewport(MixinCon
163
165
  super.willUpdate(changed);
164
166
  this.toggleState('fullscreen', this.filled);
165
167
  this.toggleState('idle', this.resting);
168
+ this.toggleState('loading', this.loading);
166
169
  this.toggleState('panel-open', this.noPanel && this.panelOpen);
167
170
  this.toggleState('searching', this.query.trim() !== '');
168
171
  this.toggleState('selected', this.active !== '');
@@ -389,14 +392,15 @@ let HelixHeadlessGraph = class HelixHeadlessGraph extends MixinViewport(MixinCon
389
392
  aria-selected=${chosen ? 'true' : 'false'}
390
393
  aria-posinset=${index + 1}
391
394
  aria-setsize=${total}
392
- aria-description=${relation || nothing}
395
+ aria-describedby=${relation ? `note-${index}-relation` : nothing}
396
+ aria-label=${this.noteContent ? nameOf(entry.node) : nothing}
393
397
  @click=${() => {
394
398
  this.cursor = index;
395
399
  this.choose(index);
396
400
  }}
397
401
  >
398
- <span part="note-name">${nameOf(entry.node)}</span>
399
- ${relation ? html `<span part="note-relation" aria-hidden="true">${relation}</span>` : nothing}
402
+ <span part="note-name">${this.noteContent ? this.noteContent(entry.node) : nameOf(entry.node)}</span>
403
+ ${relation ? html `<span part="note-relation" id=${`note-${index}-relation`} aria-hidden="true">${relation}</span>` : nothing}
400
404
  </div>`;
401
405
  })}`;
402
406
  }
@@ -458,7 +462,7 @@ let HelixHeadlessGraph = class HelixHeadlessGraph extends MixinViewport(MixinCon
458
462
  data-scroller
459
463
  aria-label=${this.notesLabel}
460
464
  aria-activedescendant=${cursor}
461
- aria-keyshortcuts="ArrowUp ArrowDown Home End PageUp PageDown Enter Space"
465
+ aria-busy=${this.loading ? 'true' : nothing}
462
466
  @keydown=${this.handleListKeydown}
463
467
  @scroll=${() => this.virtualize.measure()}
464
468
  >
@@ -467,7 +471,9 @@ let HelixHeadlessGraph = class HelixHeadlessGraph extends MixinViewport(MixinCon
467
471
  <div part="after" role="presentation" style="block-size: ${after}px"></div>
468
472
  </div>
469
473
  ${this.entries.length === 0
470
- ? html `<div part="empty"><slot name="empty">${this.emptyLabel}</slot></div>`
474
+ ? this.loading
475
+ ? html `<div part="loading"><slot name="loading">${this.loadingLabel}</slot></div>`
476
+ : html `<div part="empty"><slot name="empty">${this.emptyLabel}</slot></div>`
471
477
  : nothing}
472
478
  </div>
473
479
  </div>
@@ -529,6 +535,13 @@ __decorate([
529
535
  words,
530
536
  property({ attribute: 'links-to-label' })
531
537
  ], HelixHeadlessGraph.prototype, "linksToLabel", void 0);
538
+ __decorate([
539
+ property({ reflect: true, type: Boolean })
540
+ ], HelixHeadlessGraph.prototype, "loading", void 0);
541
+ __decorate([
542
+ words,
543
+ property({ attribute: 'loading-label' })
544
+ ], HelixHeadlessGraph.prototype, "loadingLabel", void 0);
532
545
  __decorate([
533
546
  words,
534
547
  property({ attribute: 'moved-down-label' })
@@ -551,6 +564,9 @@ __decorate([
551
564
  __decorate([
552
565
  property({ attribute: 'no-panel', reflect: true, type: Boolean })
553
566
  ], HelixHeadlessGraph.prototype, "noPanel", void 0);
567
+ __decorate([
568
+ property({ attribute: false })
569
+ ], HelixHeadlessGraph.prototype, "noteContent", void 0);
554
570
  __decorate([
555
571
  words,
556
572
  property({ attribute: 'notes-label' })
@@ -244,7 +244,8 @@ export declare class HelixHeadlessList extends HelixElement {
244
244
  /**
245
245
  * The selected rows' values, separated by commas with no spaces, such as `a,c`, in a list that selects.
246
246
  *
247
- * Set it to start with rows selected. A row's value is its `value`, or its
247
+ * A press or a key writes it in row order, as Data Table does, whatever
248
+ * order the rows were chosen in. Set it to start with rows selected. A row's value is its `value`, or its
248
249
  * words where it has none.
249
250
  */
250
251
  value: string;
@@ -328,6 +329,10 @@ export declare class HelixHeadlessList extends HelixElement {
328
329
  /**
329
330
  * Selects what a press or a key leaves, asking first. `helix-before-change`
330
331
  * carries the `value` it would leave, and `change` follows once it has moved.
332
+ *
333
+ * In row order, as Data Table and Table write theirs, whatever order the
334
+ * rows were chosen in: a press on the fourth row and then the first is
335
+ * `a,d`.
331
336
  */
332
337
  private choose;
333
338
  /** What `choiceValue` needs of this list for a row. */
@@ -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 { auditName, commaList, customElement, deepActiveElement, drawStatus, focusItemOf, formatWords, HelixActionEvent, HelixElement, HelixLoadMoreEvent, hostAria, inlineStep, KeyboardNavigationController, placeKey, pressedOwnControl, ReorderController, requestReorder, requestValue, rovingStop, StatusController, scrollingAncestor, scrollRoom, selectsAll, typeaheadByWords, UpgradeController, whenHydrated, words, } from '@fusion.dev/helix-core';
7
+ import { auditName, commaList, customElement, deepActiveElement, drawStatus, focusItemOf, formatWords, HelixActionEvent, HelixElement, HelixLoadMoreEvent, hostAria, inlineStep, inRowOrder, KeyboardNavigationController, placeKey, pressedOwnControl, ReorderController, requestReorder, requestValue, rovingStop, StatusController, scrollingAncestor, scrollRoom, selectsAll, typeaheadByWords, UpgradeController, 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 { HelixHeadlessListItem } from '../item/list-item.js';
@@ -262,7 +262,8 @@ let HelixHeadlessList = class HelixHeadlessList extends HelixElement {
262
262
  if (next === null) {
263
263
  return;
264
264
  }
265
- requestValue(this, next, (value) => {
265
+ const ordered = inRowOrder(commaList(next), this.rows.map((row) => this.valueFor(row))).join(',');
266
+ requestValue(this, ordered, (value) => {
266
267
  this.value = value;
267
268
  });
268
269
  }