@fusion.dev/helix-headless 0.218.0 → 0.220.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 (84) 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/code-editor/root/code-editor-root.d.ts +48 -51
  18. package/dist/code-editor/root/code-editor-root.js +67 -249
  19. package/dist/copy-button/root/copy-button-root.js +3 -3
  20. package/dist/device-screen/root/device-screen-root.d.ts +11 -0
  21. package/dist/device-screen/root/device-screen-root.js +22 -10
  22. package/dist/dialog/root/dialog-root.js +6 -1
  23. package/dist/drilldown/item/drilldown-item.js +1 -1
  24. package/dist/element-events.generated.js +4 -1
  25. package/dist/filter-input/root/filter-input-root.d.ts +4 -3
  26. package/dist/filter-input/root/filter-input-root.js +1 -20
  27. package/dist/find/index.d.ts +1 -0
  28. package/dist/find/index.js +1 -0
  29. package/dist/find/root/find-root.d.ts +314 -0
  30. package/dist/find/root/find-root.js +577 -0
  31. package/dist/graph/root/graph-root.d.ts +11 -0
  32. package/dist/graph/root/graph-root.js +4 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.js +1 -0
  35. package/dist/input/root/input-root.d.ts +6 -0
  36. package/dist/input/root/input-root.js +7 -0
  37. package/dist/lightbox/root/lightbox-root.d.ts +4 -1
  38. package/dist/lightbox/root/lightbox-root.js +10 -8
  39. package/dist/listbox/root/listbox-root.d.ts +21 -0
  40. package/dist/listbox/root/listbox-root.js +37 -2
  41. package/dist/log/group/log-group.d.ts +18 -3
  42. package/dist/log/group/log-group.js +31 -4
  43. package/dist/log/root/log-root.d.ts +13 -3
  44. package/dist/log/root/log-root.js +5 -2
  45. package/dist/map/root/map-root.d.ts +11 -0
  46. package/dist/map/root/map-root.js +28 -12
  47. package/dist/menu/root/menu-root.d.ts +8 -0
  48. package/dist/menu/root/menu-root.js +22 -1
  49. package/dist/menu/section/menu-section.d.ts +10 -0
  50. package/dist/menu/section/menu-section.js +10 -0
  51. package/dist/meter/index.d.ts +1 -0
  52. package/dist/meter/index.js +1 -0
  53. package/dist/meter/root/meter-root.d.ts +16 -1
  54. package/dist/meter/root/meter-root.js +27 -11
  55. package/dist/meter/segment/meter-segment.d.ts +74 -0
  56. package/dist/meter/segment/meter-segment.js +47 -0
  57. package/dist/scroll-area/root/scroll-area-root.d.ts +20 -3
  58. package/dist/scroll-area/root/scroll-area-root.js +23 -4
  59. package/dist/spatial-bar-chart/bars.d.ts +125 -5
  60. package/dist/spatial-bar-chart/bars.js +194 -17
  61. package/dist/spatial-bar-chart/root/spatial-bar-chart-root.d.ts +148 -6
  62. package/dist/spatial-bar-chart/root/spatial-bar-chart-root.js +285 -16
  63. package/dist/splitter/root/splitter-root.js +1 -0
  64. package/dist/tabs/root/tabs-root.d.ts +4 -0
  65. package/dist/tabs/tab/tab.d.ts +1 -0
  66. package/dist/tabs/tab/tab.js +2 -2
  67. package/dist/tag/root/tag-root.d.ts +1 -1
  68. package/dist/terminal/root/terminal-root.d.ts +60 -32
  69. package/dist/terminal/root/terminal-root.js +82 -178
  70. package/dist/toolbar/root/toolbar-root.d.ts +4 -1
  71. package/dist/tooltip/root/tooltip-root.d.ts +23 -1
  72. package/dist/tooltip/root/tooltip-root.js +28 -5
  73. package/dist/tour/root/tour-root.d.ts +1 -0
  74. package/dist/tour/root/tour-root.js +4 -1
  75. package/dist/tour/stop/tour-stop.js +1 -0
  76. package/dist/tree/item/tree-item.d.ts +11 -0
  77. package/dist/tree/item/tree-item.js +19 -1
  78. package/examples.json +1 -1
  79. package/lint.json +1 -1
  80. package/package.json +5 -5
  81. package/types/jsx.d.ts +2222 -118
  82. package/types/svelte.d.ts +2 -0
  83. package/types/vue.d.ts +468 -19
  84. package/types/words.d.ts +96 -1
@@ -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
  /**
@@ -15,7 +15,9 @@ declare const HelixHeadlessLogGroup_base: (new (...args: any[]) => import("@fusi
15
15
  * the heading, as you write it, such as `12s`.
16
16
  *
17
17
  * A group starts shut. Write `default-open` on the step a reader should see,
18
- * such as the one that failed.
18
+ * such as the one that failed. A shut group's lines are hidden until found:
19
+ * the browser's find and Find search them, and the group opens on the one
20
+ * that holds the match, unless `helix-before-open` is canceled.
19
21
  *
20
22
  * @since 0.200.0
21
23
  * @status experimental
@@ -28,14 +30,16 @@ declare const HelixHeadlessLogGroup_base: (new (...args: any[]) => import("@fusi
28
30
  * @event {HelixBeforeOpenEvent} helix-before-open - Fired before the group opens. Cancelable.
29
31
  * @event {ToggleEvent} toggle - Fired after the group opens or closes, with `newState` and `oldState`.
30
32
  *
31
- * @csspart content - The lines, hidden while the group is shut.
33
+ * @csspart content - The lines, hidden until found while the group is shut.
32
34
  * @csspart duration - How long the step took.
33
35
  * @csspart heading - The heading.
34
36
  * @csspart mark - The caret that turns as the group opens, an `<svg>` you size.
35
37
  * @csspart status - The mark for the status, an `<svg>` you size.
36
38
  * @csspart summary - The button that opens and closes the group.
37
39
  *
40
+ * @cssstate empty - The group holds nothing to show, so opening it shows nothing.
38
41
  * @cssstate open - The group shows its lines.
42
+ * @cssstate steps - The group holds steps of its own, which a stylesheet draws as a tree.
39
43
  */
40
44
  export declare class HelixHeadlessLogGroup extends HelixHeadlessLogGroup_base {
41
45
  protected static markRules: string[];
@@ -63,6 +67,17 @@ export declare class HelixHeadlessLogGroup extends HelixHeadlessLogGroup_base {
63
67
  */
64
68
  statusLabel: string;
65
69
  private readonly handlePress;
70
+ /**
71
+ * A search found a match in the shut group, and shows it next: the
72
+ * browser's find or Find. The group opens, and where the page refuses, its
73
+ * lines are hidden again once the search has shown them, as Accordion's
74
+ * section is.
75
+ */
76
+ private readonly handleBeforeMatch;
77
+ /** Records whether the group holds anything, and whether a step is among it. */
78
+ private readonly handleSlotChange;
79
+ /** A slot that is never filled fires no `slotchange`, so the first render reads it once. */
80
+ protected firstUpdated(changed: PropertyValues): void;
66
81
  /**
67
82
  * The words in the button are separated by spaces in the markup, since a
68
83
  * name is read off inline boxes with nothing between them: without them a
@@ -22,9 +22,35 @@ let HelixHeadlessLogGroup = class HelixHeadlessLogGroup extends MixinControllerO
22
22
  this.handlePress = () => {
23
23
  this.toggleOpen();
24
24
  };
25
+ this.handleBeforeMatch = (event) => {
26
+ if (this.open || this.requestOpen(true)) {
27
+ return;
28
+ }
29
+ const content = event.currentTarget;
30
+ const observer = new MutationObserver(() => {
31
+ observer.disconnect();
32
+ if (!this.open && !content.hasAttribute('hidden')) {
33
+ content.setAttribute('hidden', 'until-found');
34
+ }
35
+ });
36
+ observer.observe(content, { attributeFilter: ['hidden'] });
37
+ };
38
+ this.handleSlotChange = () => {
39
+ const slot = this.renderRoot.querySelector('[part="content"] > slot');
40
+ if (!slot) {
41
+ return;
42
+ }
43
+ const held = slot.assignedElements();
44
+ this.toggleState('empty', held.length === 0);
45
+ this.toggleState('steps', held.some((element) => 'isLogGroup' in element));
46
+ };
25
47
  }
26
48
  static { this.markRules = [SPOKEN_RULE]; }
27
49
  static { this.words = 'helix-log'; }
50
+ firstUpdated(changed) {
51
+ super.firstUpdated(changed);
52
+ this.handleSlotChange();
53
+ }
28
54
  render() {
29
55
  const status = this.status;
30
56
  return html `<button
@@ -48,10 +74,11 @@ let HelixHeadlessLogGroup = class HelixHeadlessLogGroup extends MixinControllerO
48
74
  : html `<div
49
75
  part="content"
50
76
  id="content"
51
- role="group"
52
- aria-labelledby="summary"
53
- ?hidden=${!this.open}
54
- ><slot></slot></div>`}`;
77
+ role=${this.open ? 'group' : nothing}
78
+ aria-labelledby=${this.open ? 'summary' : nothing}
79
+ hidden=${this.open ? nothing : 'until-found'}
80
+ @beforematch=${this.handleBeforeMatch}
81
+ ><slot @slotchange=${this.handleSlotChange}></slot></div>`}`;
55
82
  }
56
83
  };
57
84
  __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.
@@ -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 { live } from 'lit/directives/live.js';
9
10
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
10
11
  const MAP_TYPES = ['standard', 'hybrid', 'satellite'];
@@ -412,9 +413,11 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
412
413
  }
413
414
  const entries = this.layerEntries;
414
415
  const button = this.controlTag;
416
+ const tip = this.tag('tooltip');
415
417
  const { material, raised } = this.controlAxes;
416
418
  return staticHtml `<${button}
417
419
  part="layers-toggle"
420
+ id="layers-toggle"
418
421
  exportparts="button: layers-toggle-button, live: layers-toggle-live, spinner: layers-toggle-spinner, text: layers-toggle-text"
419
422
  aria-controls="layers-menu"
420
423
  .expanded=${this.layersOpen}
@@ -436,7 +439,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
436
439
  ${unsafeSVG(stack.content)}
437
440
  </svg>
438
441
  </slot>
439
- </${button}>
442
+ </${button}><${tip} for="layers-toggle" content=${this.layersLabel} 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}>
440
443
  <div
441
444
  part="layers-menu"
442
445
  id="layers-menu"
@@ -494,12 +497,14 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
494
497
  renderCompass(pan, rotate) {
495
498
  const { faceNorthLabel } = this;
496
499
  const button = this.controlTag;
500
+ const tip = this.tag('tooltip');
497
501
  const { material, raised } = this.controlAxes;
498
502
  const ground = material ?? nothing;
499
503
  return staticHtml `<div part="compass">
500
504
  ${rotate
501
505
  ? staticHtml `<${button}
502
506
  part="rotate-left"
507
+ id="rotate-left"
503
508
  exportparts="button: rotate-left-button, live: rotate-left-live, spinner: rotate-left-spinner, text: rotate-left-text"
504
509
  label=${this.rotateLeftLabel}
505
510
  material=${ground}
@@ -507,11 +512,12 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
507
512
  @helix-press=${stopPress}
508
513
  >
509
514
  <slot name="rotate-left-icon">↺</slot>
510
- </${button}>`
515
+ </${button}><${tip} for="rotate-left" content=${this.rotateLeftLabel} 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}>`
511
516
  : nothing}
512
517
  ${pan
513
518
  ? staticHtml `<${button}
514
519
  part="pan-up"
520
+ id="pan-up"
515
521
  exportparts="button: pan-up-button, live: pan-up-live, spinner: pan-up-spinner, text: pan-up-text"
516
522
  label=${this.panLabel('up')}
517
523
  material=${ground}
@@ -519,11 +525,12 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
519
525
  @helix-press=${stopPress}
520
526
  >
521
527
  <slot name="pan-up-icon">${this.panGlyph('up')}</slot>
522
- </${button}>`
528
+ </${button}><${tip} for="pan-up" content=${this.panLabel('up')} 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}>`
523
529
  : nothing}
524
530
  ${rotate
525
531
  ? staticHtml `<${button}
526
532
  part="rotate-right"
533
+ id="rotate-right"
527
534
  exportparts="button: rotate-right-button, live: rotate-right-live, spinner: rotate-right-spinner, text: rotate-right-text"
528
535
  label=${this.rotateRightLabel}
529
536
  material=${ground}
@@ -531,11 +538,12 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
531
538
  @helix-press=${stopPress}
532
539
  >
533
540
  <slot name="rotate-right-icon">↻</slot>
534
- </${button}>`
541
+ </${button}><${tip} for="rotate-right" content=${this.rotateRightLabel} 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}>`
535
542
  : nothing}
536
543
  ${pan
537
544
  ? staticHtml `<${button}
538
545
  part="pan-start"
546
+ id="pan-start"
539
547
  exportparts="button: pan-start-button, live: pan-start-live, spinner: pan-start-spinner, text: pan-start-text"
540
548
  label=${this.panLabel('start')}
541
549
  material=${ground}
@@ -543,10 +551,11 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
543
551
  @helix-press=${stopPress}
544
552
  >
545
553
  <slot name="pan-start-icon">${this.panGlyph('start')}</slot>
546
- </${button}>`
554
+ </${button}><${tip} for="pan-start" content=${this.panLabel('start')} 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}>`
547
555
  : nothing}
548
556
  <${button}
549
557
  part="face-north"
558
+ id="face-north"
550
559
  exportparts="button: face-north-button, live: face-north-live, spinner: face-north-spinner, text: face-north-text"
551
560
  label=${faceNorthLabel}
552
561
  material=${ground}
@@ -561,10 +570,11 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
561
570
  <path data-end="south" d="M8 14.5 5.5 8h5Z" />
562
571
  </svg>
563
572
  </slot>
564
- </${button}>
573
+ </${button}><${tip} for="face-north" content=${faceNorthLabel} 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}>
565
574
  ${pan
566
575
  ? staticHtml `<${button}
567
576
  part="pan-end"
577
+ id="pan-end"
568
578
  exportparts="button: pan-end-button, live: pan-end-live, spinner: pan-end-spinner, text: pan-end-text"
569
579
  label=${this.panLabel('end')}
570
580
  material=${ground}
@@ -572,11 +582,12 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
572
582
  @helix-press=${stopPress}
573
583
  >
574
584
  <slot name="pan-end-icon">${this.panGlyph('end')}</slot>
575
- </${button}>`
585
+ </${button}><${tip} for="pan-end" content=${this.panLabel('end')} 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}>`
576
586
  : nothing}
577
587
  ${pan
578
588
  ? staticHtml `<${button}
579
589
  part="pan-down"
590
+ id="pan-down"
580
591
  exportparts="button: pan-down-button, live: pan-down-live, spinner: pan-down-spinner, text: pan-down-text"
581
592
  label=${this.panLabel('down')}
582
593
  material=${ground}
@@ -584,7 +595,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
584
595
  @helix-press=${stopPress}
585
596
  >
586
597
  <slot name="pan-down-icon">${this.panGlyph('down')}</slot>
587
- </${button}>`
598
+ </${button}><${tip} for="pan-down" content=${this.panLabel('down')} 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}>`
588
599
  : nothing}
589
600
  </div>`;
590
601
  }
@@ -599,12 +610,14 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
599
610
  return nothing;
600
611
  }
601
612
  const button = this.controlTag;
613
+ const tip = this.tag('tooltip');
602
614
  const { material, raised } = this.controlAxes;
603
615
  const ground = material ?? nothing;
604
616
  return staticHtml `<div part="controls" role="toolbar" aria-label=${this.controlsLabel}>
605
617
  ${fullscreen
606
618
  ? staticHtml `<${button}
607
619
  part="fullscreen"
620
+ id="fullscreen"
608
621
  exportparts="button: fullscreen-button, live: fullscreen-live, spinner: fullscreen-spinner, text: fullscreen-text"
609
622
  aria-pressed=${this.filled ? 'true' : 'false'}
610
623
  label=${this.fullscreenLabel}
@@ -614,12 +627,13 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
614
627
  @helix-press=${stopPress}
615
628
  >
616
629
  <slot name="fullscreen-icon">⤢</slot>
617
- </${button}>`
630
+ </${button}><${tip} for="fullscreen" content=${this.fullscreenLabel} 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}>`
618
631
  : nothing}
619
632
  ${menu}
620
633
  ${tilt
621
634
  ? staticHtml `<${button}
622
635
  part="tilt"
636
+ id="tilt"
623
637
  exportparts="button: tilt-button, live: tilt-live, spinner: tilt-spinner, text: tilt-text"
624
638
  aria-pressed=${this.pitch > 0 ? 'true' : 'false'}
625
639
  label=${this.tiltLabel}
@@ -629,11 +643,12 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
629
643
  @helix-press=${stopPress}
630
644
  >
631
645
  <slot name="tilt-icon">3D</slot>
632
- </${button}>`
646
+ </${button}><${tip} for="tilt" content=${this.tiltLabel} 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}>`
633
647
  : nothing}
634
648
  ${zoom
635
649
  ? staticHtml `<${button}
636
650
  part="zoom-in"
651
+ id="zoom-in"
637
652
  exportparts="button: zoom-in-button, live: zoom-in-live, spinner: zoom-in-spinner, text: zoom-in-text"
638
653
  label=${this.zoomInLabel}
639
654
  material=${ground}
@@ -642,9 +657,10 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
642
657
  @helix-press=${stopPress}
643
658
  >
644
659
  <slot name="zoom-in-icon">+</slot>
645
- </${button}>
660
+ </${button}><${tip} for="zoom-in" content=${this.zoomInLabel} 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}>
646
661
  <${button}
647
662
  part="zoom-out"
663
+ id="zoom-out"
648
664
  exportparts="button: zoom-out-button, live: zoom-out-live, spinner: zoom-out-spinner, text: zoom-out-text"
649
665
  label=${this.zoomOutLabel}
650
666
  material=${ground}
@@ -653,7 +669,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
653
669
  @helix-press=${stopPress}
654
670
  >
655
671
  <slot name="zoom-out-icon">−</slot>
656
- </${button}>`
672
+ </${button}><${tip} for="zoom-out" content=${this.zoomOutLabel} 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}>`
657
673
  : nothing}
658
674
  ${this.drawsCompass ? this.renderCompass(pan, rotate) : nothing}
659
675
  </div>`;
@@ -544,6 +544,14 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
544
544
  * the filters' column. `Notifications` answers the same question for its media
545
545
  * column the same way, and for the same reason.
546
546
  *
547
+ * **Per section on a page with sections**, for the same reason one level
548
+ * down: a section has its own heading, where the reader starts reading
549
+ * again, so its rows line up with each other and not with another
550
+ * section's. A section of icons over a section of plain choices indented
551
+ * the plain ones by a column nothing in them filled. The rows outside any
552
+ * section are decided together, as before, and the trailing column stays
553
+ * the page's, since ticks on one edge never set labels on two.
554
+ *
547
555
  * Read off the light DOM rather than off the rows, so it does not depend on
548
556
  * which children have upgraded by the time this runs.
549
557
  */
@@ -741,7 +741,28 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
741
741
  }
742
742
  markColumns() {
743
743
  const rows = this.flies ? this.topRows : this.drilldown.rows;
744
- this.toggleState('start', rows.some((row) => row.querySelector(':scope > [slot="start"]') !== null));
744
+ const sections = new Map();
745
+ for (const child of this.children) {
746
+ if (child.isMenuSection) {
747
+ sections.set(child, false);
748
+ }
749
+ }
750
+ let loose = false;
751
+ for (const row of rows) {
752
+ const marked = row.querySelector(':scope > [slot="start"]') !== null;
753
+ const parent = row.parentElement;
754
+ if (parent && sections.has(parent)) {
755
+ sections.set(parent, sections.get(parent) === true || marked);
756
+ }
757
+ else {
758
+ loose ||= marked;
759
+ }
760
+ }
761
+ this.toggleState('start', loose);
762
+ for (const [section, marked] of sections) {
763
+ ;
764
+ section.startColumn = marked;
765
+ }
745
766
  this.toggleState('mark', rows.some((row) => row.choice || this.drilldown.hasPage(row)));
746
767
  }
747
768
  mark(item, onPage) {