@fusion.dev/helix-headless 0.156.0 → 0.158.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 (47) hide show
  1. package/custom-elements.json +1085 -66
  2. package/dist/command/root/command-root.d.ts +19 -5
  3. package/dist/command/root/command-root.js +14 -4
  4. package/dist/data-table/root/data-table-root.js +5 -2
  5. package/dist/drilldown/item/drilldown-item.d.ts +49 -1
  6. package/dist/drilldown/item/drilldown-item.js +77 -17
  7. package/dist/drilldown/root/drilldown-root.d.ts +45 -5
  8. package/dist/drilldown/root/drilldown-root.js +51 -5
  9. package/dist/element-events.generated.js +1 -1
  10. package/dist/filter-input/menu/filter-menu.d.ts +1 -0
  11. package/dist/filter-input/menu/filter-menu.js +4 -4
  12. package/dist/list/item/list-item.d.ts +15 -1
  13. package/dist/list/item/list-item.js +20 -1
  14. package/dist/list/root/list-root.d.ts +29 -1
  15. package/dist/list/root/list-root.js +44 -10
  16. package/dist/listbox/root/listbox-root.d.ts +19 -2
  17. package/dist/listbox/root/listbox-root.js +16 -8
  18. package/dist/map/root/map-root.d.ts +4 -0
  19. package/dist/map/root/map-root.js +1 -1
  20. package/dist/megamenu/root/megamenu-root.d.ts +50 -13
  21. package/dist/megamenu/root/megamenu-root.js +44 -6
  22. package/dist/menu/item/menu-item.d.ts +24 -1
  23. package/dist/menu/item/menu-item.js +42 -2
  24. package/dist/menu/root/menu-root.d.ts +126 -11
  25. package/dist/menu/root/menu-root.js +336 -26
  26. package/dist/notifications/root/notifications-root.d.ts +20 -0
  27. package/dist/notifications/root/notifications-root.js +32 -1
  28. package/dist/number-input/root/number-input-root.d.ts +7 -4
  29. package/dist/number-input/root/number-input-root.js +3 -3
  30. package/dist/progress/root/progress-root.js +1 -1
  31. package/dist/select/root/select-root.d.ts +8 -1
  32. package/dist/select/root/select-root.js +8 -5
  33. package/dist/skeleton/root/skeleton-root.d.ts +2 -1
  34. package/dist/skeleton/root/skeleton-root.js +1 -1
  35. package/dist/toast/item/toast-item.d.ts +26 -0
  36. package/dist/toast/item/toast-item.js +55 -3
  37. package/dist/toast/root/toast-root.d.ts +32 -0
  38. package/dist/toast/root/toast-root.js +47 -2
  39. package/dist/video/root/video-root.d.ts +1 -0
  40. package/dist/video/root/video-root.js +9 -9
  41. package/dist/video-playlist/menu/video-playlist-menu.d.ts +1 -0
  42. package/dist/video-playlist/menu/video-playlist-menu.js +1 -1
  43. package/lint.json +1 -1
  44. package/package.json +5 -5
  45. package/types/jsx.d.ts +653 -74
  46. package/types/vue.d.ts +133 -16
  47. package/types/words.d.ts +14 -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 { AriaForwardController, appleTouch, valueAsNumber as asNumber, customElement, formatNumber, formatWords, numberAsValue as fromNumber, HelixFormElement, holdCaret, MixinControllerValue, MixinField, parseNumber, restoreValue, submitImplicitly, words, wordsLocale, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, appleTouch, customElement, formatNumber, formatWords, numberAsValue as fromNumber, HelixFormElement, holdCaret, MixinControllerValue, MixinField, parseNumber, restoreValue, submitImplicitly, words, wordsLocale, } from '@fusion.dev/helix-core';
8
8
  import { minus, plus } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -410,7 +410,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
410
410
  const atMin = parsed !== null && this.min !== undefined && parsed <= this.min;
411
411
  const atMax = parsed !== null && this.max !== undefined && parsed >= this.max;
412
412
  return html `${this.renderLabel()}
413
- <div part="field" role="group">
413
+ <div part="field" role="group" aria-labelledby=${this.labeled ? this.labelId : nothing}>
414
414
  <button
415
415
  part="decrement"
416
416
  type="button"
@@ -484,7 +484,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
484
484
  ${this.renderHint()}${this.renderFeedback()}`;
485
485
  }
486
486
  get valueAsNumber() {
487
- return asNumber(this.value);
487
+ return this.numeric ?? Number.NaN;
488
488
  }
489
489
  set valueAsNumber(count) {
490
490
  this.value = fromNumber(count);
@@ -51,7 +51,7 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
51
51
  this.internals.ariaValueMax = String(max);
52
52
  this.internals.ariaValueNow = this.indeterminate
53
53
  ? null
54
- : String(Math.min(Math.max(this.value || min, min), max));
54
+ : String(Math.min(Math.max(Number.isNaN(this.value) ? min : this.value, min), max));
55
55
  const shown = this.valueText || slottedWords(this, ['']);
56
56
  this.internals.ariaValueText = this.indeterminate ? shown || null : shown || this.text;
57
57
  this.toggleState('complete', this.complete);
@@ -19,7 +19,9 @@ declare const HelixHeadlessSelect_base: (new (...args: any[]) => import("@fusion
19
19
  * whole set.
20
20
  *
21
21
  * `Select` owns the value, passes it down to the listbox, and takes changes
22
- * back, so the value lives in one place.
22
+ * back, so the value lives in one place. With `multiple`, the value is the
23
+ * chosen values joined with commas, and the form receives one entry for each.
24
+ * An option's value may hold a comma, and it is read as one value.
23
25
  *
24
26
  * Set `valueContent` to draw the trigger's content from the chosen options,
25
27
  * and several chosen options are written as a list in the reader's language.
@@ -291,6 +293,11 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
291
293
  private get display();
292
294
  /** The chosen options, in the order they appear. None on a server or while hydrating, and none for `items`. */
293
295
  private get chosenOptions();
296
+ /**
297
+ * The chosen values, read against the options' values, so an option whose
298
+ * value holds a comma is one value. See `chosenValues`.
299
+ */
300
+ private get chosen();
294
301
  /** `items` as the list reads them, built once per array. */
295
302
  private data;
296
303
  /**
@@ -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, afterLinkFollowed, customElement, dataOptions, drawAutofill, drawStatus, HelixFormElement, hostAria, hostWords, isRtl, MixinControllerOpen, MixinControllerValue, MixinField, onApple, PopoverController, reportValues, StatusController, sideOf, typesCharacter, words, wordsLocale, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, afterLinkFollowed, chosenValues, customElement, dataOptions, drawAutofill, drawStatus, HelixFormElement, hostAria, hostWords, isRtl, MixinControllerOpen, MixinControllerValue, MixinField, onApple, PopoverController, reportValues, StatusController, sideOf, typesCharacter, words, wordsLocale, } from '@fusion.dev/helix-core';
8
8
  import { caretDown } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property } from 'lit/decorators.js';
@@ -173,7 +173,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
173
173
  return this.partNamed('listbox');
174
174
  }
175
175
  get display() {
176
- const chosen = new Set(this.value === '' ? [] : this.value.split(','));
176
+ const chosen = new Set(this.chosen);
177
177
  const words = this.entries.filter((option) => chosen.has(option.value)).map((one) => one.text);
178
178
  if (words.length > 0) {
179
179
  return new Intl.ListFormat(wordsLocale(this), { style: 'short', type: 'unit' }).format(words);
@@ -184,9 +184,12 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
184
184
  if (this.items) {
185
185
  return [];
186
186
  }
187
- const chosen = new Set(this.value === '' ? [] : this.value.split(','));
187
+ const chosen = new Set(this.chosen);
188
188
  return optionsAmong(this.knownChildren).filter((option) => chosen.has(option.value));
189
189
  }
190
+ get chosen() {
191
+ return chosenValues(this.value, new Set(this.entries.map((option) => option.value)), this.multiple);
192
+ }
190
193
  get entries() {
191
194
  if (!this.items) {
192
195
  return optionsAmong(this.knownChildren);
@@ -257,7 +260,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
257
260
  }
258
261
  }
259
262
  const present = new Set(options.map((option) => option.value));
260
- const held = this.value === '' ? [] : this.value.split(',');
263
+ const held = chosenValues(this.value, new Set([...present, ...this.seen]), this.multiple);
261
264
  const gone = held.filter((value) => this.seen.has(value) && !present.has(value));
262
265
  this.seen = present;
263
266
  if (gone.length > 0) {
@@ -266,7 +269,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
266
269
  }
267
270
  sync() {
268
271
  if (this.multiple) {
269
- reportValues(this.internals, this.name, this.value === '' ? [] : this.value.split(','));
272
+ reportValues(this.internals, this.name, this.chosen);
270
273
  }
271
274
  else {
272
275
  this.internals.setFormValue(this.value === '' ? null : this.value);
@@ -41,7 +41,8 @@ export declare class HelixHeadlessSkeleton extends HelixElement {
41
41
  * What is loading, read out as the placeholder's name, such as "Loading
42
42
  * profile".
43
43
  *
44
- * Absent hides the placeholder from screen readers.
44
+ * Absent hides the placeholder from screen readers. Around content, the name
45
+ * is read while `loading` is set, and the loaded content is read as it is.
45
46
  */
46
47
  label: string;
47
48
  /**
@@ -22,7 +22,7 @@ let HelixHeadlessSkeleton = class HelixHeadlessSkeleton extends HelixElement {
22
22
  const wraps = this.wraps;
23
23
  this.toggleState('wrapping', wraps);
24
24
  this.internals.ariaBusy = this.loading ? 'true' : null;
25
- if (this.label) {
25
+ if (this.label && (this.loading || !wraps)) {
26
26
  hostAria(this.internals, { label: this.label, role: 'progressbar' });
27
27
  this.internals.ariaHidden = null;
28
28
  }
@@ -25,6 +25,9 @@ declare const HelixHeadlessToastItem_base: (new (...args: any[]) => import("@fus
25
25
  * control. While it moves, the distance is on the `item` part as
26
26
  * `--toast-swipe-x`.
27
27
  *
28
+ * **Escape dismisses the message holding focus**, through the same
29
+ * `helix-dismiss`, and focus moves to the next message.
30
+ *
28
31
  * **Messages are inverted by default.** The item flips `color-scheme`, so a new
29
32
  * message stands out from the page without using a color that carries meaning.
30
33
  *
@@ -57,6 +60,10 @@ declare const HelixHeadlessToastItem_base: (new (...args: any[]) => import("@fus
57
60
  *
58
61
  * @cssproperty --toast-duration - How long this message stays, in milliseconds, for a progress hint.
59
62
  * @cssproperty --toast-swipe-x - How far a swipe has moved the message across, in pixels, toward the right.
63
+ *
64
+ * @cssstate closed - The message has been dismissed and is hidden, or leaving.
65
+ * @cssstate paused - The dismiss timer is paused, while a pointer or focus is on the message, or while the page is hidden.
66
+ * @cssstate waiting - The region holds the message back, past its `max-visible`, until a message before it goes.
60
67
  */
61
68
  export declare class HelixHeadlessToastItem extends HelixHeadlessToastItem_base {
62
69
  /**
@@ -110,6 +117,18 @@ export declare class HelixHeadlessToastItem extends HelixHeadlessToastItem_base
110
117
  * A subset of the tone axis: a toast reports a status and never a brand role.
111
118
  */
112
119
  tone?: 'danger' | 'info' | 'success' | 'warning';
120
+ /**
121
+ * Whether the region holds the message back, because more messages are
122
+ * showing than its `max-visible`. The region sets it. A waiting message is
123
+ * hidden, its timer has not started, and it is said once it shows.
124
+ */
125
+ waiting: boolean;
126
+ /** Whether the dismiss timer has started, which it does once the message first shows. */
127
+ private started;
128
+ /** Whether the timer is held for `waiting`, so the hold is let go once. */
129
+ private holding;
130
+ /** Starts the timer when the message first shows, and holds it while it waits again. */
131
+ private wait;
113
132
  /** Whether it is showing, whoever said so, and `undefined` until somebody has. */
114
133
  private openState;
115
134
  /**
@@ -128,6 +147,13 @@ export declare class HelixHeadlessToastItem extends HelixHeadlessToastItem_base
128
147
  private readonly handlePause;
129
148
  private readonly handleResume;
130
149
  connectedCallback(): void;
150
+ disconnectedCallback(): void;
151
+ /**
152
+ * Escape dismisses the message holding focus, as Radix's toast does. Taken
153
+ * only when the dismissal goes ahead, so a refused one leaves the key to
154
+ * the page.
155
+ */
156
+ private readonly handleKeydown;
131
157
  protected willUpdate(changed: PropertyValues): void;
132
158
  protected updated(changed: PropertyValues): void;
133
159
  /** The elements assigned to one of the message's named slots. */
@@ -38,8 +38,12 @@ let HelixHeadlessToastItem = class HelixHeadlessToastItem extends MixinDismissib
38
38
  holdWhileHidden: true,
39
39
  paused: (paused) => {
40
40
  this.toggleAttribute('data-paused', paused);
41
+ this.toggleState('paused', paused);
41
42
  },
42
43
  });
44
+ this.waiting = false;
45
+ this.started = false;
46
+ this.holding = false;
43
47
  this.openState = undefined;
44
48
  this.handlePause = () => {
45
49
  this.countdown.hold();
@@ -47,6 +51,15 @@ let HelixHeadlessToastItem = class HelixHeadlessToastItem extends MixinDismissib
47
51
  this.handleResume = () => {
48
52
  this.countdown.release();
49
53
  };
54
+ this.handleKeydown = (event) => {
55
+ if (event.key !== 'Escape' || event.defaultPrevented || !this.open) {
56
+ return;
57
+ }
58
+ if (this.dismiss()) {
59
+ event.preventDefault();
60
+ event.stopPropagation();
61
+ }
62
+ };
50
63
  this.name = () => {
51
64
  const words = this.partNamed('content');
52
65
  if (!words) {
@@ -72,6 +85,23 @@ let HelixHeadlessToastItem = class HelixHeadlessToastItem extends MixinDismissib
72
85
  const physical = isRtl(this) ? -travel : travel;
73
86
  this.partNamed('item')?.style.setProperty('--toast-swipe-x', travel === 0 ? '' : `${Math.round(physical)}px`);
74
87
  }
88
+ wait(waiting) {
89
+ if (waiting) {
90
+ if (this.started && !this.holding) {
91
+ this.holding = true;
92
+ this.countdown.hold();
93
+ }
94
+ return;
95
+ }
96
+ if (this.holding) {
97
+ this.holding = false;
98
+ this.countdown.release();
99
+ }
100
+ else if (!this.started && this.hasUpdated) {
101
+ this.started = true;
102
+ this.countdown.start(this.duration);
103
+ }
104
+ }
75
105
  dismiss() {
76
106
  if (!this.open) {
77
107
  return true;
@@ -97,19 +127,35 @@ let HelixHeadlessToastItem = class HelixHeadlessToastItem extends MixinDismissib
97
127
  connectedCallback() {
98
128
  super.connectedCallback();
99
129
  if (this.hasUpdated && this.open) {
100
- this.resume();
130
+ if (this.waiting) {
131
+ this.wait(true);
132
+ }
133
+ else {
134
+ this.resume();
135
+ }
101
136
  }
137
+ this.addEventListener('keydown', this.handleKeydown);
138
+ }
139
+ disconnectedCallback() {
140
+ super.disconnectedCallback();
141
+ this.holding = false;
142
+ this.removeEventListener('keydown', this.handleKeydown);
102
143
  }
103
144
  willUpdate(changed) {
104
145
  super.willUpdate(changed);
105
146
  this.toggleAttribute('data-closed', !this.open);
106
- this.toggleAttribute('hidden', !this.open);
147
+ this.toggleAttribute('hidden', !this.open || this.waiting);
148
+ this.toggleState('closed', !this.open);
149
+ this.toggleState('waiting', this.waiting);
107
150
  hostAria(this.internals, { role: 'alertdialog' });
108
151
  this.internals.ariaModal = 'false';
109
152
  }
110
153
  updated(changed) {
111
154
  super.updated(changed);
112
155
  this.name();
156
+ if (changed.has('waiting')) {
157
+ this.wait(this.waiting);
158
+ }
113
159
  }
114
160
  assigned(name) {
115
161
  return (this.renderRoot
@@ -118,7 +164,10 @@ let HelixHeadlessToastItem = class HelixHeadlessToastItem extends MixinDismissib
118
164
  }
119
165
  firstUpdated(changed) {
120
166
  super.firstUpdated(changed);
121
- this.countdown.start(this.duration);
167
+ if (!this.waiting) {
168
+ this.started = true;
169
+ this.countdown.start(this.duration);
170
+ }
122
171
  }
123
172
  renderIcon(mark) {
124
173
  return html `<slot name="icon">
@@ -183,6 +232,9 @@ __decorate([
183
232
  __decorate([
184
233
  property({ reflect: true })
185
234
  ], HelixHeadlessToastItem.prototype, "tone", void 0);
235
+ __decorate([
236
+ property({ attribute: false, type: Boolean })
237
+ ], HelixHeadlessToastItem.prototype, "waiting", void 0);
186
238
  HelixHeadlessToastItem = __decorate([
187
239
  customElement('helix-headless-toast-item')
188
240
  ], HelixHeadlessToastItem);
@@ -49,6 +49,19 @@ import { type PropertyValues, type TemplateResult } from 'lit';
49
49
  * message, then the one before it, then back to where it was before it entered
50
50
  * the stack.
51
51
  *
52
+ * **`max-visible` limits how many messages show at once**, as Zag's `max`
53
+ * does. The messages after that many, in document order, wait: each is hidden,
54
+ * its timer has not started, and the region says it once it shows, when a
55
+ * message before it goes. A waiting message has `:state(waiting)`.
56
+ *
57
+ * **Keyboard.**
58
+ *
59
+ * | Key | What it does |
60
+ * |---|---|
61
+ * | F8, or your `hotkey` | Moves focus to the first control in the stack, and back to where it was when pressed in the stack. |
62
+ * | Tab, Shift+Tab | Moves between the controls of the messages. |
63
+ * | Escape | Dismisses the message holding focus. Focus moves to the next message, then the one before it, then back to where it was. |
64
+ *
52
65
  * @since 0.1.0
53
66
  * @status stable
54
67
  * @category overlays
@@ -81,6 +94,12 @@ export declare class HelixHeadlessToast extends HelixElement {
81
94
  * the rest of your interface.
82
95
  */
83
96
  label: string;
97
+ /**
98
+ * How many messages show at once. The messages after that many wait, hidden
99
+ * and with their timers stopped, until a message before them goes. Unset,
100
+ * every message shows.
101
+ */
102
+ maxVisible?: number;
84
103
  /**
85
104
  * Which corner or edge the stack sits at.
86
105
  *
@@ -171,6 +190,18 @@ export declare class HelixHeadlessToast extends HelixElement {
171
190
  private watcher;
172
191
  /** The messages that are showing, in order. */
173
192
  private get showing();
193
+ /** The messages `max-visible` holds back. */
194
+ private readonly held;
195
+ /**
196
+ * Holds back the messages past `max-visible`, and returns the ones it has
197
+ * just let go. A message that has been dismissed takes no place in the line.
198
+ */
199
+ private queue;
200
+ /**
201
+ * Lets go of the messages past `max-visible` that now have room, raises the
202
+ * stack and says them, once each has shown.
203
+ */
204
+ private restack;
174
205
  private get region();
175
206
  /**
176
207
  * Puts the stack in the top layer while a message shows, and on top of it.
@@ -215,6 +246,7 @@ export declare class HelixHeadlessToast extends HelixElement {
215
246
  connectedCallback(): void;
216
247
  disconnectedCallback(): void;
217
248
  protected firstUpdated(changed: PropertyValues): void;
249
+ protected updated(changed: PropertyValues): void;
218
250
  protected render(): TemplateResult;
219
251
  }
220
252
  declare global {
@@ -40,6 +40,7 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
40
40
  }
41
41
  };
42
42
  this.made = new WeakSet();
43
+ this.held = new WeakSet();
43
44
  this.handleFocusIn = (event) => {
44
45
  const from = event.relatedTarget;
45
46
  if (from instanceof HTMLElement && !deepContains(this, from)) {
@@ -165,7 +166,8 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
165
166
  }
166
167
  sweep() {
167
168
  for (const one of [...this.children]) {
168
- if (!(one instanceof HTMLElement) || !one.hidden || !this.made.has(one)) {
169
+ const closed = one.open === false;
170
+ if (!(one instanceof HTMLElement) || !closed || !one.hidden || !this.made.has(one)) {
169
171
  continue;
170
172
  }
171
173
  this.made.delete(one);
@@ -173,7 +175,38 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
173
175
  }
174
176
  }
175
177
  get showing() {
176
- return [...this.children].filter((one) => one instanceof HTMLElement && !one.hidden);
178
+ return [...this.children].filter((one) => one instanceof HTMLElement && !one.hidden && !this.held.has(one));
179
+ }
180
+ queue() {
181
+ const max = this.maxVisible;
182
+ const line = [...this.children].filter((one) => one instanceof HTMLElement && one.open !== false);
183
+ const released = [];
184
+ line.forEach((one, at) => {
185
+ const wait = max !== undefined && max >= 0 && at >= max;
186
+ if (this.held.has(one) && !wait) {
187
+ released.push(one);
188
+ }
189
+ if (wait) {
190
+ this.held.add(one);
191
+ }
192
+ else {
193
+ this.held.delete(one);
194
+ }
195
+ if ((one.waiting ?? false) !== wait) {
196
+ one.waiting = wait;
197
+ }
198
+ });
199
+ return released;
200
+ }
201
+ restack() {
202
+ const released = this.queue();
203
+ if (released.length === 0) {
204
+ return;
205
+ }
206
+ void Promise.all(released.map((one) => one.updateComplete)).then(() => {
207
+ this.place(released);
208
+ this.tell(released);
209
+ });
177
210
  }
178
211
  get region() {
179
212
  return this.partNamed('region');
@@ -238,6 +271,7 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
238
271
  document.addEventListener('keydown', this.handleHotkey);
239
272
  this.watcher = new MutationObserver((records) => {
240
273
  const added = records.flatMap((record) => [...record.addedNodes]);
274
+ this.restack();
241
275
  this.place(added);
242
276
  this.tell(added);
243
277
  this.sweep();
@@ -262,8 +296,16 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
262
296
  }
263
297
  firstUpdated(changed) {
264
298
  super.firstUpdated(changed);
299
+ this.queue();
265
300
  this.place();
266
301
  }
302
+ updated(changed) {
303
+ super.updated(changed);
304
+ if (changed.has('maxVisible')) {
305
+ this.restack();
306
+ this.place();
307
+ }
308
+ }
267
309
  render() {
268
310
  return html `<div part="region" popover="manual" tabindex="-1"><slot></slot><span part="live" role="status">${this.said}</span><span part="live" role="alert">${this.urgent}</span></div>`;
269
311
  }
@@ -275,6 +317,9 @@ __decorate([
275
317
  words,
276
318
  property()
277
319
  ], HelixHeadlessToast.prototype, "label", void 0);
320
+ __decorate([
321
+ property({ attribute: 'max-visible', type: Number })
322
+ ], HelixHeadlessToast.prototype, "maxVisible", void 0);
278
323
  __decorate([
279
324
  property({ reflect: true })
280
325
  ], HelixHeadlessToast.prototype, "placement", void 0);
@@ -129,6 +129,7 @@ import '../../menu/index.js';
129
129
  * @csspart entry-shortcut-key - One key of a row's shortcut chord, inside `entry-shortcut`. The player's rows set none.
130
130
  * @csspart entry-shortcut-separator - The `+` between two keys of a row's shortcut, where the platform writes one.
131
131
  * @csspart entry-start - The leading column that holds the `start` slot. It is always rendered.
132
+ * @csspart entry-surface - The panel a row's submenu opens in beside it. The player's rows have none.
132
133
  * @csspart failure - The message shown when the source cannot be played.
133
134
  * @csspart forward - The button that skips forward ten seconds.
134
135
  * @csspart fullscreen - The button that enters and leaves fullscreen.
@@ -610,7 +610,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
610
610
  part="entry"
611
611
  exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
612
612
  shortcut-kbd: entry-shortcut-kbd, shortcut-key: entry-shortcut-key,
613
- shortcut-separator: entry-shortcut-separator, start: entry-start"
613
+ shortcut-separator: entry-shortcut-separator, start: entry-start, surface: entry-surface"
614
614
  value="off"
615
615
  checkable
616
616
  exclusive
@@ -623,7 +623,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
623
623
  ${tracks.map((track, at) => html `<${this.tag('menu-item')} part="entry"
624
624
  exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
625
625
  shortcut-kbd: entry-shortcut-kbd, shortcut-key: entry-shortcut-key,
626
- shortcut-separator: entry-shortcut-separator, start: entry-start"
626
+ shortcut-separator: entry-shortcut-separator, start: entry-start, surface: entry-surface"
627
627
  value=${track.language || String(at)}
628
628
  checkable
629
629
  exclusive
@@ -652,7 +652,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
652
652
  return html `<${this.tag('menu-item')} part="entry"
653
653
  exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
654
654
  shortcut-kbd: entry-shortcut-kbd, shortcut-key: entry-shortcut-key,
655
- shortcut-separator: entry-shortcut-separator, start: entry-start"
655
+ shortcut-separator: entry-shortcut-separator, start: entry-start, surface: entry-surface"
656
656
  value="remote"
657
657
  @helix-select=${() => void this.remote.prompt()}
658
658
  >
@@ -664,7 +664,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
664
664
  return html `<${this.tag('menu-item')} part="entry"
665
665
  exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
666
666
  shortcut-kbd: entry-shortcut-kbd, shortcut-key: entry-shortcut-key,
667
- shortcut-separator: entry-shortcut-separator, start: entry-start" value="speed" @helix-select=${() => this.cycleSpeed()}>
667
+ shortcut-separator: entry-shortcut-separator, start: entry-start, surface: entry-surface" value="speed" @helix-select=${() => this.cycleSpeed()}>
668
668
  <slot name="speed-icon" slot="start">${drawIcon(gauge)}</slot>
669
669
  ${this.speedLabel}: ${this.rate}x
670
670
  </${this.tag('menu-item')}>`;
@@ -674,7 +674,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
674
674
  return html `<${this.tag('menu-item')} part="entry"
675
675
  exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
676
676
  shortcut-kbd: entry-shortcut-kbd, shortcut-key: entry-shortcut-key,
677
- shortcut-separator: entry-shortcut-separator, start: entry-start"
677
+ shortcut-separator: entry-shortcut-separator, start: entry-start, surface: entry-surface"
678
678
  value=${one}
679
679
  @helix-select=${() => this.skipBy(back ? -SKIP_SECONDS : SKIP_SECONDS)}
680
680
  >
@@ -688,7 +688,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
688
688
  return html `<${this.tag('menu-item')} part="entry"
689
689
  exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
690
690
  shortcut-kbd: entry-shortcut-kbd, shortcut-key: entry-shortcut-key,
691
- shortcut-separator: entry-shortcut-separator, start: entry-start" value="previous" @helix-select=${() => this.skip(true)}>
691
+ shortcut-separator: entry-shortcut-separator, start: entry-start, surface: entry-surface" value="previous" @helix-select=${() => this.skip(true)}>
692
692
  <slot name="previous-icon" slot="start">${drawIcon(skipBack)}</slot>
693
693
  ${this.previousLabel}
694
694
  </${this.tag('menu-item')}>`;
@@ -697,7 +697,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
697
697
  return html `<${this.tag('menu-item')} part="entry"
698
698
  exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
699
699
  shortcut-kbd: entry-shortcut-kbd, shortcut-key: entry-shortcut-key,
700
- shortcut-separator: entry-shortcut-separator, start: entry-start" value="next" @helix-select=${() => this.skip(false)}>
700
+ shortcut-separator: entry-shortcut-separator, start: entry-start, surface: entry-surface" value="next" @helix-select=${() => this.skip(false)}>
701
701
  <slot name="next-icon" slot="start">${drawIcon(skipForward)}</slot>
702
702
  ${this.nextLabel}
703
703
  </${this.tag('menu-item')}>`;
@@ -707,7 +707,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
707
707
  return html `<${this.tag('menu-item')} part="entry"
708
708
  exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
709
709
  shortcut-kbd: entry-shortcut-kbd, shortcut-key: entry-shortcut-key,
710
- shortcut-separator: entry-shortcut-separator, start: entry-start"
710
+ shortcut-separator: entry-shortcut-separator, start: entry-start, surface: entry-surface"
711
711
  ?download=${local}
712
712
  href=${this.src}
713
713
  rel=${local ? nothing : 'noreferrer'}
@@ -721,7 +721,7 @@ let HelixHeadlessVideo = class HelixHeadlessVideo extends HelixElement {
721
721
  return html `<${this.tag('menu-item')} part="entry"
722
722
  exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
723
723
  shortcut-kbd: entry-shortcut-kbd, shortcut-key: entry-shortcut-key,
724
- shortcut-separator: entry-shortcut-separator, start: entry-start" value="pip" @helix-select=${() => void this.togglePip()}>
724
+ shortcut-separator: entry-shortcut-separator, start: entry-start, surface: entry-surface" value="pip" @helix-select=${() => void this.togglePip()}>
725
725
  <slot name="pip-icon" slot="start">${drawIcon(pictureInPicture)}</slot>
726
726
  ${this.pipLabel}
727
727
  </${this.tag('menu-item')}>`;
@@ -30,6 +30,7 @@ import '../../menu/index.js';
30
30
  * @csspart entry-shortcut-key - One key of an entry's shortcut chord, inside `entry-shortcut`. The playlist's entries set none.
31
31
  * @csspart entry-shortcut-separator - The `+` between two keys of an entry's shortcut, where the platform writes one.
32
32
  * @csspart entry-start - The column at the start of an entry. It is a slot and is always present.
33
+ * @csspart entry-surface - The panel an entry's submenu opens in beside it. The playlist's entries have none.
33
34
  * @csspart menu - The menu that holds the entries.
34
35
  * @csspart menu-back - The button that goes back from a submenu.
35
36
  * @csspart menu-back-mark - The icon on a submenu's back button.
@@ -88,7 +88,7 @@ let HelixHeadlessVideoPlaylistMenu = class HelixHeadlessVideoPlaylistMenu extend
88
88
  part="entry"
89
89
  exportparts="end: entry-end, item: entry-item, mark: entry-mark, shortcut: entry-shortcut,
90
90
  shortcut-kbd: entry-shortcut-kbd, shortcut-key: entry-shortcut-key,
91
- shortcut-separator: entry-shortcut-separator, start: entry-start"
91
+ shortcut-separator: entry-shortcut-separator, start: entry-start, surface: entry-surface"
92
92
  value=${entry.value}
93
93
  checkable
94
94
  ?checked=${entry.current}