@fusion.dev/helix-headless 0.218.0 → 0.219.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 (74) hide show
  1. package/api.json +1 -1
  2. package/custom-elements.json +1 -1
  3. package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +13 -2
  4. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +19 -9
  5. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +16 -1
  6. package/dist/breadcrumbs/root/breadcrumbs-root.js +6 -0
  7. package/dist/button/root/button-root.d.ts +33 -0
  8. package/dist/button/root/button-root.js +13 -3
  9. package/dist/canvas/root/canvas-root.d.ts +11 -0
  10. package/dist/canvas/root/canvas-root.js +1 -0
  11. package/dist/chat/composer/chat-composer.d.ts +221 -6
  12. package/dist/chat/composer/chat-composer.js +249 -19
  13. package/dist/chat/message/chat-message.d.ts +7 -0
  14. package/dist/chat/message/chat-message.js +2 -1
  15. package/dist/chat/root/chat-root.d.ts +1 -0
  16. package/dist/chat/root/chat-root.js +1 -1
  17. package/dist/copy-button/root/copy-button-root.js +3 -3
  18. package/dist/device-screen/root/device-screen-root.d.ts +11 -0
  19. package/dist/device-screen/root/device-screen-root.js +22 -10
  20. package/dist/dialog/root/dialog-root.js +6 -1
  21. package/dist/drilldown/item/drilldown-item.js +1 -1
  22. package/dist/element-events.generated.js +1 -0
  23. package/dist/filter-input/root/filter-input-root.d.ts +4 -3
  24. package/dist/filter-input/root/filter-input-root.js +1 -20
  25. package/dist/graph/root/graph-root.d.ts +11 -0
  26. package/dist/graph/root/graph-root.js +4 -1
  27. package/dist/input/root/input-root.d.ts +6 -0
  28. package/dist/input/root/input-root.js +7 -0
  29. package/dist/lightbox/root/lightbox-root.d.ts +4 -1
  30. package/dist/lightbox/root/lightbox-root.js +10 -8
  31. package/dist/listbox/root/listbox-root.d.ts +21 -0
  32. package/dist/listbox/root/listbox-root.js +37 -2
  33. package/dist/log/group/log-group.d.ts +7 -1
  34. package/dist/log/group/log-group.js +14 -1
  35. package/dist/log/root/log-root.d.ts +13 -3
  36. package/dist/log/root/log-root.js +5 -2
  37. package/dist/map/root/map-root.d.ts +11 -0
  38. package/dist/map/root/map-root.js +28 -12
  39. package/dist/menu/root/menu-root.d.ts +8 -0
  40. package/dist/menu/root/menu-root.js +22 -1
  41. package/dist/menu/section/menu-section.d.ts +10 -0
  42. package/dist/menu/section/menu-section.js +10 -0
  43. package/dist/meter/index.d.ts +1 -0
  44. package/dist/meter/index.js +1 -0
  45. package/dist/meter/root/meter-root.d.ts +16 -1
  46. package/dist/meter/root/meter-root.js +27 -11
  47. package/dist/meter/segment/meter-segment.d.ts +74 -0
  48. package/dist/meter/segment/meter-segment.js +47 -0
  49. package/dist/scroll-area/root/scroll-area-root.d.ts +20 -3
  50. package/dist/scroll-area/root/scroll-area-root.js +23 -4
  51. package/dist/spatial-bar-chart/bars.d.ts +125 -5
  52. package/dist/spatial-bar-chart/bars.js +194 -17
  53. package/dist/spatial-bar-chart/root/spatial-bar-chart-root.d.ts +148 -6
  54. package/dist/spatial-bar-chart/root/spatial-bar-chart-root.js +285 -16
  55. package/dist/splitter/root/splitter-root.js +1 -0
  56. package/dist/tabs/root/tabs-root.d.ts +4 -0
  57. package/dist/tabs/tab/tab.d.ts +1 -0
  58. package/dist/tabs/tab/tab.js +2 -2
  59. package/dist/tag/root/tag-root.d.ts +1 -1
  60. package/dist/terminal/root/terminal-root.d.ts +11 -0
  61. package/dist/terminal/root/terminal-root.js +23 -10
  62. package/dist/toolbar/root/toolbar-root.d.ts +4 -1
  63. package/dist/tooltip/root/tooltip-root.d.ts +23 -1
  64. package/dist/tooltip/root/tooltip-root.js +28 -5
  65. package/dist/tour/root/tour-root.d.ts +1 -0
  66. package/dist/tour/root/tour-root.js +4 -1
  67. package/dist/tour/stop/tour-stop.js +1 -0
  68. package/examples.json +1 -1
  69. package/lint.json +1 -1
  70. package/package.json +5 -5
  71. package/types/jsx.d.ts +1373 -52
  72. package/types/svelte.d.ts +1 -0
  73. package/types/vue.d.ts +312 -17
  74. package/types/words.d.ts +9 -1
@@ -1,5 +1,5 @@
1
1
  import { __decorate } from "tslib";
2
- import { AriaForwardController, auditName, CommandController, customElement, drawDismiss, drawDismissGlyph, FocusScopeController, HelixElement, headingsIn, hostAria, lockScroll, MixinControllerOpen, OpenPhaseController, reflectElements, unlockScroll, words, } from '@fusion.dev/helix-core';
2
+ import { AriaForwardController, auditName, CommandController, customElement, drawDismiss, drawDismissGlyph, FocusScopeController, focusableElements, HelixElement, headingsIn, hostAria, lockScroll, MixinControllerOpen, OpenPhaseController, reflectElements, unlockScroll, words, } from '@fusion.dev/helix-core';
3
3
  import { html, nothing } from 'lit';
4
4
  import { property } from 'lit/decorators.js';
5
5
  import { wordsIn } from '../description.js';
@@ -161,6 +161,11 @@ let HelixHeadlessDialog = class HelixHeadlessDialog extends MixinControllerOpen(
161
161
  .filter((one) => one instanceof Promise));
162
162
  if (this.open && !dialog.open && this.isConnected) {
163
163
  dialog.showModal();
164
+ const chose = this.matches(':focus-within') &&
165
+ !(this.renderRoot instanceof ShadowRoot && this.renderRoot.activeElement === dialog);
166
+ if (!chose) {
167
+ focusableElements(this)[0]?.focus();
168
+ }
164
169
  }
165
170
  }
166
171
  refuse() {
@@ -58,7 +58,7 @@ let HelixHeadlessDrilldownItem = class HelixHeadlessDrilldownItem extends HelixE
58
58
  updated(changed) {
59
59
  super.updated(changed);
60
60
  if (this.partNamed('item')) {
61
- auditName(this, () => this.partNamed('item'), 'row');
61
+ auditName(this, () => this.partNamed('item') ?? undefined, 'row');
62
62
  }
63
63
  }
64
64
  firstUpdated(changed) {
@@ -38,6 +38,7 @@ registerElementEvents({
38
38
  'helix-before-listen',
39
39
  'helix-listen',
40
40
  'helix-send',
41
+ 'helix-stop',
41
42
  'helix-typing',
42
43
  'input',
43
44
  ],
@@ -638,9 +638,10 @@ export declare class HelixHeadlessFilterInput extends HelixHeadlessFilterInput_b
638
638
  * Says so when a picker is written in the field and never registered.
639
639
  *
640
640
  * The picker has an entry of its own, so a page that imports only the field
641
- * gets an inert element there: a trigger that opens nothing. Asked once the
642
- * page has loaded and painted, so an entry a later module imports is not
643
- * taken for a missing one.
641
+ * gets an inert element there: a trigger that opens nothing. Core's
642
+ * `auditChildren` asks once the page has loaded and painted, and again once
643
+ * it has registered nothing for `QUIET` milliseconds, so an entry a later
644
+ * module or a later chunk imports is not taken for a missing one.
644
645
  */
645
646
  private auditPicker;
646
647
  protected updated(changed: PropertyValues): void;
@@ -586,26 +586,7 @@ let HelixHeadlessFilterInput = class HelixHeadlessFilterInput extends MixinVoice
586
586
  this.auditPicker();
587
587
  }
588
588
  auditPicker() {
589
- const { tier } = this.constructor;
590
- const tag = `${tier}filter-menu`;
591
- const check = () => {
592
- if (this.isConnected && this.querySelector(tag) && !customElements.get(tag)) {
593
- reportRuntime({
594
- kind: 'unregistered-part',
595
- message: `<${tag}> in <${this.localName}> is not registered, so its trigger opens nothing. ${howToRegister({ entry: 'filter-input/menu', host: this.localName, tag, tier })}`,
596
- tag: this.localName,
597
- });
598
- }
599
- };
600
- const frame = () => {
601
- requestAnimationFrame(check);
602
- };
603
- if (document.readyState === 'complete') {
604
- frame();
605
- }
606
- else {
607
- window.addEventListener('load', frame, { once: true });
608
- }
589
+ auditChildren(this, 'filter-menu', 'filter-input/menu', 'its trigger opens nothing');
609
590
  }
610
591
  updated(changed) {
611
592
  super.updated(changed);
@@ -2,6 +2,7 @@ import { type GraphSignalDetail, HelixElement, ToolbarController, type ViewportP
2
2
  import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  import { type StaticValue } from 'lit/static-html.js';
4
4
  import '../../icon-button/index.js';
5
+ import '../../tooltip/index.js';
5
6
  import { type GraphLink, type GraphNode } from './graph-model.js';
6
7
  declare const HelixHeadlessGraph_base: (new (...args: any[]) => import("@fusion.dev/helix-core").ViewportElement) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerActiveInterface) & typeof HelixElement;
7
8
  /**
@@ -149,6 +150,16 @@ declare const HelixHeadlessGraph_base: (new (...args: any[]) => import("@fusion.
149
150
  * @csspart settling - What is said while a layout runs unseen, where the reader asked for less motion or the page paused its motion, over the hidden drawing.
150
151
  * @csspart stage - The drawing and its controls.
151
152
  * @csspart stop-layout - The button that stops a layout that has run past five seconds, where the notes stand.
153
+ * @csspart tooltip-arrow - The arrow of the tooltip that names each button, forwarded from `Tooltip`.
154
+ * @csspart tooltip-arrow-bottom - The arrow while the bubble is below its button, on the bubble's top edge. Set together with `tooltip-arrow`.
155
+ * @csspart tooltip-arrow-left - The arrow while the bubble is left of its button, on the bubble's right edge. Set together with `tooltip-arrow`.
156
+ * @csspart tooltip-arrow-right - The arrow while the bubble is right of its button, on the bubble's left edge. Set together with `tooltip-arrow`.
157
+ * @csspart tooltip-arrow-top - The arrow while the bubble is above its button, on the bubble's bottom edge. Set together with `tooltip-arrow`.
158
+ * @csspart tooltip-surface - The bubble of the tooltip that names each button, forwarded from `Tooltip`.
159
+ * @csspart tooltip-surface-bottom - The bubble while it is below its button. Set together with `tooltip-surface`.
160
+ * @csspart tooltip-surface-left - The bubble while it is left of its button. Set together with `tooltip-surface`.
161
+ * @csspart tooltip-surface-right - The bubble while it is right of its button. Set together with `tooltip-surface`.
162
+ * @csspart tooltip-surface-top - The bubble while it is above its button. Set together with `tooltip-surface`.
152
163
  * @csspart viewport - The box the renderer draws in.
153
164
  * @csspart zoom-in - The zoom in button.
154
165
  * @csspart zoom-in-button - `zoom-in`'s inner button.
@@ -5,6 +5,7 @@ import { html, nothing } from 'lit';
5
5
  import { property, state } from 'lit/decorators.js';
6
6
  import { html as staticHtml } from 'lit/static-html.js';
7
7
  import '../../icon-button/index.js';
8
+ import '../../tooltip/index.js';
8
9
  import { repeat } from 'lit/directives/repeat.js';
9
10
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
10
11
  import { adjacencyOf, cursorAfter, entriesOf, groupsOf, nameOf, sortedOf, within, withoutGroups, } from './graph-model.js';
@@ -559,9 +560,11 @@ let HelixHeadlessGraph = class HelixHeadlessGraph extends MixinViewport(MixinCon
559
560
  return nothing;
560
561
  }
561
562
  const button = this.controlTag;
563
+ const tip = this.tag('tooltip');
562
564
  const { material, raised } = this.controlAxes;
563
565
  return staticHtml `<${button}
564
566
  part="notes-toggle"
567
+ id="notes-toggle"
565
568
  exportparts="button: notes-toggle-button, live: notes-toggle-live, spinner: notes-toggle-spinner, text: notes-toggle-text"
566
569
  aria-controls="panel"
567
570
  .expanded=${this.panelOpen}
@@ -574,7 +577,7 @@ let HelixHeadlessGraph = class HelixHeadlessGraph extends MixinViewport(MixinCon
574
577
  <svg aria-hidden="true" fill="currentColor" viewBox=${list.viewBox}>
575
578
  ${unsafeSVG(list.content)}
576
579
  </svg>
577
- </${button}>`;
580
+ </${button}><${tip} for="notes-toggle" content=${this.notesLabel} exportparts="arrow: tooltip-arrow, arrow-bottom: tooltip-arrow-bottom, arrow-left: tooltip-arrow-left, arrow-right: tooltip-arrow-right, arrow-top: tooltip-arrow-top, surface: tooltip-surface, surface-bottom: tooltip-surface-bottom, surface-left: tooltip-surface-left, surface-right: tooltip-surface-right, surface-top: tooltip-surface-top"></${tip}>`;
578
581
  }
579
582
  renderFilterIcon() {
580
583
  return html `<slot name="filter-icon">
@@ -99,6 +99,12 @@ export declare class HelixHeadlessInput extends HelixHeadlessInput_base {
99
99
  * a token like `email` or `bday` to ask for autofill, or to `off` to refuse.
100
100
  */
101
101
  autocomplete: string;
102
+ /**
103
+ * Whether the browser corrects words as the reader types, as the platform's
104
+ * `autocorrect`. Turn it off, with `autocorrect="off"`, for text that is not
105
+ * prose, such as an address or a code, which corrections would change.
106
+ */
107
+ autocorrect: boolean;
102
108
  /**
103
109
  * Whether the field shows a button that empties it.
104
110
  *
@@ -13,6 +13,7 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinVoice(MixinContro
13
13
  this.announcesInput = true;
14
14
  this.autocapitalize = '';
15
15
  this.autocomplete = '';
16
+ this.autocorrect = true;
16
17
  this.clearable = false;
17
18
  this.clearLabel = 'Clear';
18
19
  this.enterKeyHint = '';
@@ -198,6 +199,7 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinVoice(MixinContro
198
199
  inputmode=${this.inputMode || nothing}
199
200
  enterkeyhint=${this.enterKeyHint || nothing}
200
201
  autocapitalize=${this.autocapitalize || nothing}
202
+ autocorrect=${this.autocorrect ? nothing : 'off'}
201
203
  aria-describedby=${this.describedBy ?? nothing}
202
204
  aria-invalid=${this.invalidity ?? nothing}
203
205
  ?disabled=${this.isDisabled}
@@ -243,6 +245,11 @@ __decorate([
243
245
  __decorate([
244
246
  property()
245
247
  ], HelixHeadlessInput.prototype, "autocomplete", void 0);
248
+ __decorate([
249
+ property({
250
+ converter: { fromAttribute: (value) => value?.toLowerCase() !== 'off' },
251
+ })
252
+ ], HelixHeadlessInput.prototype, "autocorrect", void 0);
246
253
  __decorate([
247
254
  property({ type: Boolean })
248
255
  ], HelixHeadlessInput.prototype, "clearable", void 0);
@@ -22,7 +22,8 @@ declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusi
22
22
  *
23
23
  * Set `controls` for the viewer's own previous, next and close controls, named
24
24
  * by `previous-label`, `next-label` and `dismiss-label`, whose glyphs you can
25
- * replace in the `previous-icon`, `next-icon` and `dismiss-icon` slots. Without
25
+ * replace in the `previous-icon`, `next-icon` and `dismiss-icon` slots. A set
26
+ * of one item has nothing to step to, so it draws no previous, next or counter. Without
26
27
  * it, compose the chrome yourself in the `header` and `footer` slots, with
27
28
  * buttons that send `--previous`, `--next` and `--close`.
28
29
  *
@@ -427,6 +428,8 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
427
428
  * control that has just shown the last picture keeps focus.
428
429
  */
429
430
  protected renderControls(): TemplateResult;
431
+ /** Previous and next, for a set of more than one. */
432
+ protected renderStepControls(): TemplateResult;
430
433
  /**
431
434
  * Zoom out and zoom in, for a pointer with no wheel and no second finger.
432
435
  *
@@ -438,11 +438,19 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
438
438
  </div>
439
439
  <slot name="footer" part="footer"></slot>
440
440
  ${this.controls ? this.renderControls() : nothing}
441
- ${this.counter ? html `<span part="counter" aria-hidden="true">${this.place()}</span>` : nothing}
441
+ ${this.counter && this.items.length > 1 ? html `<span part="counter" aria-hidden="true">${this.place()}</span>` : nothing}
442
442
  <span part="live" role="status" aria-live="polite">${this.announcement || nothing}</span>
443
443
  </dialog>`;
444
444
  }
445
445
  renderControls() {
446
+ const stepping = this.items.length > 1;
447
+ return html `${stepping ? this.renderStepControls() : nothing}${this.maxZoom > 1 ? this.renderZoomControls() : nothing}${drawDismiss({
448
+ label: this.dismissLabel,
449
+ mark: html `<slot name="dismiss-icon">${drawDismissGlyph()}</slot>`,
450
+ onDismiss: () => this.close(),
451
+ })}`;
452
+ }
453
+ renderStepControls() {
446
454
  return html `<button
447
455
  part="previous"
448
456
  type="button"
@@ -468,13 +476,7 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
468
476
  ${unsafeSVG(caretRight.content)}
469
477
  </svg>
470
478
  </slot>
471
- </button>
472
- ${this.maxZoom > 1 ? this.renderZoomControls() : nothing}
473
- ${drawDismiss({
474
- label: this.dismissLabel,
475
- mark: html `<slot name="dismiss-icon">${drawDismissGlyph()}</slot>`,
476
- onDismiss: () => this.close(),
477
- })}`;
479
+ </button>`;
478
480
  }
479
481
  renderZoomControls() {
480
482
  return html `<button
@@ -335,6 +335,17 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
335
335
  * it back on the first match.
336
336
  */
337
337
  private parked;
338
+ /**
339
+ * Whether the reader moved the highlight since the filter last changed, by
340
+ * an arrow key, Home or End, a press, or the control that owns this list.
341
+ * Until they do, the highlight follows the first option a filter leaves.
342
+ */
343
+ private steered;
344
+ /**
345
+ * Watches the options' containers while a filter is set, so options a
346
+ * framework reorders or replaces after a keystroke are counted again.
347
+ */
348
+ private sources;
338
349
  /**
339
350
  * The option last chosen, which Shift with Space or with a press extends
340
351
  * from.
@@ -596,6 +607,16 @@ export declare class HelixHeadlessListbox extends HelixHeadlessListbox_base {
596
607
  highlightValue(value: string): void;
597
608
  /** Hides what the filter excludes, and writes the rest onto the options. */
598
609
  private place;
610
+ /**
611
+ * Counts the options again when an element that holds them gains or loses
612
+ * a child, while `on`: the list itself, and each element a slot in it
613
+ * brings in, such as a group of Command's commands.
614
+ *
615
+ * **A slot does not say when its elements' own children move**, so a
616
+ * framework sorting the options inside a group, after the filter that
617
+ * found them, left the highlight on the option that had been first.
618
+ */
619
+ private watchSources;
599
620
  /**
600
621
  * Whether another Helix component renders this list and binds its value
601
622
  * down, as Select, Combobox, Command and Time Picker do. That component seeds
@@ -36,6 +36,8 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinVoice(MixinCo
36
36
  this.valueMissingMessage = 'Choose one to continue.';
37
37
  this.activeValue = '';
38
38
  this.parked = false;
39
+ this.steered = false;
40
+ this.sources = undefined;
39
41
  this.anchor = '';
40
42
  this.shifted = false;
41
43
  this.typeahead = new Typeahead();
@@ -57,6 +59,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinVoice(MixinCo
57
59
  return;
58
60
  }
59
61
  const { value } = event.detail;
62
+ this.steered = true;
60
63
  this.activeValue = value;
61
64
  if (this.multiple && this.shifted) {
62
65
  this.extend(value);
@@ -243,6 +246,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinVoice(MixinCo
243
246
  const shown = this.shown.filter((option) => !option.disabled);
244
247
  this.typeahead.reset();
245
248
  this.parked = where === 'none';
249
+ this.steered = true;
246
250
  const values = where === 'selected' ? new Set(this.values) : undefined;
247
251
  const chosen = values && shown.find((option) => values.has(option.value));
248
252
  this.activeValue =
@@ -365,6 +369,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinVoice(MixinCo
365
369
  }
366
370
  arrive(value) {
367
371
  this.parked = false;
372
+ this.steered = true;
368
373
  this.activeValue = value;
369
374
  const link = this.entries.some((option) => option.value === value && Boolean(option.href));
370
375
  if (!this.multiple && !this.manual && !this.forwarded && !link) {
@@ -404,6 +409,7 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinVoice(MixinCo
404
409
  }
405
410
  this.typeahead.reset();
406
411
  this.parked = false;
412
+ this.steered = true;
407
413
  this.activeValue = value;
408
414
  if (this.childrenKnown) {
409
415
  this.place();
@@ -426,15 +432,37 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinVoice(MixinCo
426
432
  rule.hidden = needle !== '';
427
433
  }
428
434
  const shown = this.shown;
435
+ const first = shown.find((option) => !option.disabled)?.value ?? '';
429
436
  const rescue = !this.parked && (needle !== '' || this.activeValue !== '');
430
- if (rescue && !shown.some((option) => option.value === this.activeValue)) {
431
- this.activeValue = shown.find((option) => !option.disabled)?.value ?? '';
437
+ if (needle !== '' && !this.parked && !this.steered) {
438
+ this.activeValue = first;
439
+ }
440
+ else if (rescue && !shown.some((option) => option.value === this.activeValue)) {
441
+ this.activeValue = first;
432
442
  }
433
443
  for (const option of this.entries) {
434
444
  option.active = option.value === this.activeValue;
435
445
  }
436
446
  this.hasMatches = shown.length > 0;
437
447
  this.counted = true;
448
+ this.watchSources(needle !== '');
449
+ }
450
+ watchSources(on) {
451
+ if (typeof MutationObserver === 'undefined') {
452
+ return;
453
+ }
454
+ this.sources?.disconnect();
455
+ if (!on) {
456
+ return;
457
+ }
458
+ this.sources ??= new MutationObserver(() => this.requestUpdate());
459
+ const watching = { childList: true, subtree: true };
460
+ this.sources.observe(this, watching);
461
+ for (const slot of this.among((node) => node instanceof HTMLSlotElement, this.children)) {
462
+ for (const element of slot.assignedElements({ flatten: true })) {
463
+ this.sources.observe(element, watching);
464
+ }
465
+ }
438
466
  }
439
467
  get owned() {
440
468
  const root = this.getRootNode();
@@ -478,12 +506,16 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinVoice(MixinCo
478
506
  this.addEventListener('keydown', this.handleKeydown);
479
507
  this.addEventListener('click', this.handleClick, true);
480
508
  auditChildren(this, 'option', 'option', 'the list offers no choices');
509
+ if (this.hasUpdated) {
510
+ this.watchSources(this.filter.trim() !== '');
511
+ }
481
512
  }
482
513
  disconnectedCallback() {
483
514
  super.disconnectedCallback();
484
515
  this.removeEventListener('helix-select', this.handleSelection);
485
516
  this.removeEventListener('keydown', this.handleKeydown);
486
517
  this.removeEventListener('click', this.handleClick, true);
518
+ this.sources?.disconnect();
487
519
  markMatches(this, [], '');
488
520
  }
489
521
  willUpdate(changed) {
@@ -502,6 +534,9 @@ let HelixHeadlessListbox = class HelixHeadlessListbox extends MixinVoice(MixinCo
502
534
  if (changed.has('items') && this.items) {
503
535
  auditChildren(this, 'option', 'option', 'the items draw no choices', true);
504
536
  }
537
+ if (changed.has('filter')) {
538
+ this.steered = false;
539
+ }
505
540
  if (this.childrenKnown) {
506
541
  this.place();
507
542
  this.tell(changed);
@@ -1,5 +1,5 @@
1
1
  import { HelixElement } from '@fusion.dev/helix-core';
2
- import { type TemplateResult } from 'lit';
2
+ import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  import type { LogStatus } from '../buffer.js';
4
4
  declare const HelixHeadlessLogGroup_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
5
5
  /**
@@ -35,7 +35,9 @@ declare const HelixHeadlessLogGroup_base: (new (...args: any[]) => import("@fusi
35
35
  * @csspart status - The mark for the status, an `<svg>` you size.
36
36
  * @csspart summary - The button that opens and closes the group.
37
37
  *
38
+ * @cssstate empty - The group holds nothing to show, so opening it shows nothing.
38
39
  * @cssstate open - The group shows its lines.
40
+ * @cssstate steps - The group holds steps of its own, which a stylesheet draws as a tree.
39
41
  */
40
42
  export declare class HelixHeadlessLogGroup extends HelixHeadlessLogGroup_base {
41
43
  protected static markRules: string[];
@@ -63,6 +65,10 @@ export declare class HelixHeadlessLogGroup extends HelixHeadlessLogGroup_base {
63
65
  */
64
66
  statusLabel: string;
65
67
  private readonly handlePress;
68
+ /** Records whether the group holds anything, and whether a step is among it. */
69
+ private readonly handleSlotChange;
70
+ /** A slot that is never filled fires no `slotchange`, so the first render reads it once. */
71
+ protected firstUpdated(changed: PropertyValues): void;
66
72
  /**
67
73
  * The words in the button are separated by spaces in the markup, since a
68
74
  * name is read off inline boxes with nothing between them: without them a
@@ -22,9 +22,22 @@ let HelixHeadlessLogGroup = class HelixHeadlessLogGroup extends MixinControllerO
22
22
  this.handlePress = () => {
23
23
  this.toggleOpen();
24
24
  };
25
+ this.handleSlotChange = () => {
26
+ const slot = this.renderRoot.querySelector('[part="content"] > slot');
27
+ if (!slot) {
28
+ return;
29
+ }
30
+ const held = slot.assignedElements();
31
+ this.toggleState('empty', held.length === 0);
32
+ this.toggleState('steps', held.some((element) => 'isLogGroup' in element));
33
+ };
25
34
  }
26
35
  static { this.markRules = [SPOKEN_RULE]; }
27
36
  static { this.words = 'helix-log'; }
37
+ firstUpdated(changed) {
38
+ super.firstUpdated(changed);
39
+ this.handleSlotChange();
40
+ }
28
41
  render() {
29
42
  const status = this.status;
30
43
  return html `<button
@@ -51,7 +64,7 @@ let HelixHeadlessLogGroup = class HelixHeadlessLogGroup extends MixinControllerO
51
64
  role="group"
52
65
  aria-labelledby="summary"
53
66
  ?hidden=${!this.open}
54
- ><slot></slot></div>`}`;
67
+ ><slot @slotchange=${this.handleSlotChange}></slot></div>`}`;
55
68
  }
56
69
  };
57
70
  __decorate([
@@ -104,7 +104,7 @@ declare const HelixHeadlessLog_base: (new (...args: any[]) => import("@fusion.de
104
104
  * @csspart after - The space the rows below the drawn ones would take. Only for lines drawn from data.
105
105
  * @csspart before - The space the rows above the drawn ones would take. Only for lines drawn from data.
106
106
  * @csspart dropped - The line above the log that says how many earlier lines `limit` let go.
107
- * @csspart empty - Shown when the log has no lines.
107
+ * @csspart empty - Shown when the log has no lines and no steps.
108
108
  * @csspart group-content - Not drawn: a group drawn from data stands for its heading, and its lines are rows beside it.
109
109
  * @csspart group-duration - How long a step drawn from `groups` took.
110
110
  * @csspart group-heading - The heading of a step drawn from `groups`.
@@ -166,7 +166,7 @@ declare const HelixHeadlessLog_base: (new (...args: any[]) => import("@fusion.de
166
166
  * @cssproperty --log-digits - Written on the `log`: how many digits the largest line number has, while `line-numbers` is set, so a stylesheet can size the column of numbers.
167
167
  *
168
168
  * @cssstate done - The run is over: `done` is set, or `source` ended.
169
- * @cssstate empty - The log has no lines.
169
+ * @cssstate empty - The log has no lines and no steps.
170
170
  * @cssstate filtered - `query` or `level` is set, so only the lines they keep are shown.
171
171
  * @cssstate pinned - The log follows its newest line.
172
172
  */
@@ -203,7 +203,11 @@ export declare class HelixHeadlessLog extends HelixHeadlessLog_base {
203
203
  doneLabel: string;
204
204
  /** What the line above the log says once `limit` has let lines go. `{count}` is how many. */
205
205
  droppedLabel: string;
206
- /** The text shown and announced when the log has no lines. */
206
+ /**
207
+ * The text shown and announced when the log has no lines and no steps. A
208
+ * step with nothing in it yet, such as one still `pending`, is its heading
209
+ * alone, so a log holding one says nothing.
210
+ */
207
211
  emptyText: string;
208
212
  /**
209
213
  * What a screen reader hears when lines at `error` arrive. `{count}` is how
@@ -377,6 +381,12 @@ export declare class HelixHeadlessLog extends HelixHeadlessLog_base {
377
381
  private readonly virtualize;
378
382
  /** Whether the lines are data rather than children. */
379
383
  private get fed();
384
+ /**
385
+ * Whether lines or steps were written as children. A step counts with no
386
+ * line in it, since its heading is drawn: a step still waiting, or one
387
+ * holding a note in place of output.
388
+ */
389
+ private get holdsChildren();
380
390
  /** The parser `format` names. */
381
391
  private get formatParser();
382
392
  /** The steps from `groups`, then the steps a parser opened. */
@@ -252,6 +252,9 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
252
252
  this.groups.length > 0 ||
253
253
  this.sourceValue !== null);
254
254
  }
255
+ get holdsChildren() {
256
+ return this.childLines.length > 0 || this.childGroups.length > 0;
257
+ }
255
258
  get formatParser() {
256
259
  return this.formatValue === undefined ? undefined : LOG_FORMATS[this.formatValue];
257
260
  }
@@ -762,7 +765,7 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
762
765
  this.toggleState('pinned', this.pinned);
763
766
  this.toggleState('done', this.done);
764
767
  this.toggleState('filtered', this.query.trim() !== '' || this.level !== undefined);
765
- this.toggleState('empty', this.childrenKnown && !this.fed && this.childLines.length === 0);
768
+ this.toggleState('empty', this.childrenKnown && !this.fed && !this.holdsChildren);
766
769
  }
767
770
  updated(changed) {
768
771
  super.updated(changed);
@@ -870,7 +873,7 @@ let HelixHeadlessLog = class HelixHeadlessLog extends MixinCollection(HelixEleme
870
873
  const fed = this.fed;
871
874
  const dropped = this.buffer.dropped;
872
875
  const content = this.query.trim() !== '' ||
873
- (fed ? this.view.rows.length > 0 : this.childLines.length > 0 || !this.childrenKnown);
876
+ (fed ? this.view.rows.length > 0 : this.holdsChildren || !this.childrenKnown);
874
877
  return html `<slot name="toolbar" part="toolbar"></slot>
875
878
  <p part="dropped" ?hidden=${dropped === 0}>${dropped === 0 ? nothing : formatWords(this, 'droppedLabel', { count: dropped })}</p>
876
879
  <div
@@ -2,6 +2,7 @@ import { HelixElement, KeyboardNavigationController, type ViewportControlsPlacem
2
2
  import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  import { type StaticValue } from 'lit/static-html.js';
4
4
  import '../../icon-button/index.js';
5
+ import '../../tooltip/index.js';
5
6
  /**
6
7
  * What `ViewportControlsPlacement` is called for a map.
7
8
  *
@@ -171,6 +172,16 @@ declare const HelixHeadlessMap_base: (new (...args: any[]) => import("@fusion.de
171
172
  * @csspart tilt-live - `tilt`'s live region, which speaks while it is pending.
172
173
  * @csspart tilt-spinner - The ring `tilt` turns while it is pending.
173
174
  * @csspart tilt-text - The box `tilt`'s glyph sits in.
175
+ * @csspart tooltip-arrow - The arrow of the tooltip that names each button, forwarded from `Tooltip`.
176
+ * @csspart tooltip-arrow-bottom - The arrow while the bubble is below its button, on the bubble's top edge. Set together with `tooltip-arrow`.
177
+ * @csspart tooltip-arrow-left - The arrow while the bubble is left of its button, on the bubble's right edge. Set together with `tooltip-arrow`.
178
+ * @csspart tooltip-arrow-right - The arrow while the bubble is right of its button, on the bubble's left edge. Set together with `tooltip-arrow`.
179
+ * @csspart tooltip-arrow-top - The arrow while the bubble is above its button, on the bubble's bottom edge. Set together with `tooltip-arrow`.
180
+ * @csspart tooltip-surface - The bubble of the tooltip that names each button, forwarded from `Tooltip`.
181
+ * @csspart tooltip-surface-bottom - The bubble while it is below its button. Set together with `tooltip-surface`.
182
+ * @csspart tooltip-surface-left - The bubble while it is left of its button. Set together with `tooltip-surface`.
183
+ * @csspart tooltip-surface-right - The bubble while it is right of its button. Set together with `tooltip-surface`.
184
+ * @csspart tooltip-surface-top - The bubble while it is above its button. Set together with `tooltip-surface`.
174
185
  * @csspart viewport - The box the map is drawn in.
175
186
  * @csspart zoom-in - The button that zooms in.
176
187
  * @csspart zoom-in-button - `zoom-in`'s inner button.