@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
@@ -4,21 +4,13 @@ import { arrowClockwise, asterisk, broom, caretDown, caretUp, copy, cornersOut,
4
4
  import { nothing } from 'lit';
5
5
  import { property, state } from 'lit/decorators.js';
6
6
  import { keyed } from 'lit/directives/keyed.js';
7
- import { live } from 'lit/directives/live.js';
8
7
  import { html } from 'lit/static-html.js';
9
8
  import '../../button/index.js';
9
+ import '../../find/index.js';
10
10
  import '../../icon-button/index.js';
11
+ import '../../tooltip/index.js';
11
12
  import '../../toolbar/index.js';
12
13
  import { isTab, shortcutOf, spokenChord } from '../keys.js';
13
- const readable = (source) => {
14
- try {
15
- new RegExp(source);
16
- return true;
17
- }
18
- catch {
19
- return false;
20
- }
21
- };
22
14
  const ANSI = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'];
23
15
  const camel = (name) => name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
24
16
  const unset = (color) => color === '' || color === 'transparent' || /^rgba\(\s*0,\s*0,\s*0,\s*0\s*\)$/.test(color);
@@ -57,10 +49,6 @@ let HelixHeadlessTerminal = class HelixHeadlessTerminal extends HelixElement {
57
49
  this.tabMovesFocusLabel = 'Tab moves focus. Press {key} to type Tab in the terminal.';
58
50
  this.tabTypesLabel = 'Tab types in the terminal.';
59
51
  this.findOpen = false;
60
- this.query = '';
61
- this.matchIndex = -1;
62
- this.matchCount = undefined;
63
- this.invalid = false;
64
52
  this.tabMovesFocus = false;
65
53
  this.anySelected = false;
66
54
  this.hasActions = false;
@@ -117,19 +105,33 @@ let HelixHeadlessTerminal = class HelixHeadlessTerminal extends HelixElement {
117
105
  event.stopPropagation();
118
106
  }
119
107
  };
120
- this.handleFindInput = (event) => {
121
- this.query = event.target.value;
122
- this.search(false, true);
108
+ this.finder = {
109
+ end: () => {
110
+ this.searchedWith = undefined;
111
+ this.renderer?.findEnd?.();
112
+ },
113
+ find: (query, direction) => {
114
+ const options = {
115
+ backward: direction === 'previous',
116
+ caseSensitive: query.caseSensitive,
117
+ incremental: direction === 'first',
118
+ regex: query.regex,
119
+ wholeWord: query.wholeWord,
120
+ };
121
+ const searching = `${options.caseSensitive} ${options.regex} ${options.wholeWord}`;
122
+ if (this.searchedWith !== undefined && this.searchedWith !== searching) {
123
+ this.renderer?.findEnd?.();
124
+ }
125
+ this.searchedWith = searching;
126
+ this.findMatchCase = query.caseSensitive;
127
+ this.findRegex = query.regex;
128
+ this.findWholeWord = query.wholeWord;
129
+ this.renderer?.find?.(query.search, options);
130
+ return undefined;
131
+ },
123
132
  };
124
- this.handleFindKeys = (event) => {
125
- if (event.key === 'Escape') {
126
- event.preventDefault();
127
- this.closeFind();
128
- }
129
- else if (event.key === 'Enter') {
130
- event.preventDefault();
131
- this.search(event.shiftKey);
132
- }
133
+ this.handleFindToggle = (event) => {
134
+ this.findOpen = event.newState === 'open';
133
135
  };
134
136
  this.handleActions = (event) => {
135
137
  this.hasActions = event.target.assignedNodes().length > 0;
@@ -220,9 +222,7 @@ let HelixHeadlessTerminal = class HelixHeadlessTerminal extends HelixElement {
220
222
  },
221
223
  found: (index, count) => {
222
224
  if (current()) {
223
- this.matchIndex = index;
224
- this.matchCount = count;
225
- this.say(this.countText);
225
+ this.find?.sink.found({ count, index });
226
226
  }
227
227
  },
228
228
  input: (data, binary = false) => {
@@ -299,17 +299,6 @@ let HelixHeadlessTerminal = class HelixHeadlessTerminal extends HelixElement {
299
299
  ? renderer.selection !== undefined
300
300
  : renderer.find !== undefined;
301
301
  }
302
- get countText() {
303
- if (this.invalid) {
304
- return this.findInvalidLabel;
305
- }
306
- return this.matchCount === undefined
307
- ? ''
308
- : formatWords(this, 'matchesLabel', {
309
- count: this.matchCount,
310
- index: Math.max(this.matchIndex, 0) + 1,
311
- });
312
- }
313
302
  get tabKey() {
314
303
  return spokenChord('tab-focus', onApple());
315
304
  }
@@ -354,8 +343,9 @@ let HelixHeadlessTerminal = class HelixHeadlessTerminal extends HelixElement {
354
343
  }
355
344
  release() {
356
345
  this.restoreInput();
357
- this.findOpen = false;
358
- this.matchCount = undefined;
346
+ if (this.find) {
347
+ this.find.element.open = false;
348
+ }
359
349
  this.anySelected = false;
360
350
  }
361
351
  takes(shortcut) {
@@ -372,45 +362,7 @@ let HelixHeadlessTerminal = class HelixHeadlessTerminal extends HelixElement {
372
362
  return true;
373
363
  }
374
364
  async openFind() {
375
- this.findOpen = true;
376
- await this.updateComplete;
377
- const field = this.partNamed('find-field');
378
- field?.focus();
379
- field?.select();
380
- if (this.query !== '') {
381
- this.search(false, true);
382
- }
383
- }
384
- closeFind() {
385
- this.findOpen = false;
386
- this.matchCount = undefined;
387
- this.matchIndex = -1;
388
- this.renderer?.findEnd?.();
389
- this.focus();
390
- }
391
- search(backward, incremental = false) {
392
- if (this.query === '' || (this.findRegex && !readable(this.query))) {
393
- this.invalid = this.query !== '';
394
- this.matchCount = undefined;
395
- this.renderer?.findEnd?.();
396
- if (this.invalid) {
397
- this.say(this.findInvalidLabel);
398
- }
399
- return;
400
- }
401
- this.invalid = false;
402
- this.renderer?.find?.(this.query, {
403
- backward,
404
- caseSensitive: this.findMatchCase,
405
- incremental,
406
- regex: this.findRegex,
407
- wholeWord: this.findWholeWord,
408
- });
409
- }
410
- toggleOption(option) {
411
- this[option] = !this[option];
412
- this.renderer?.findEnd?.();
413
- this.search(false, true);
365
+ await this.find?.element.show();
414
366
  }
415
367
  async copySelection() {
416
368
  const text = this.renderer?.selection?.() ?? '';
@@ -441,6 +393,15 @@ let HelixHeadlessTerminal = class HelixHeadlessTerminal extends HelixElement {
441
393
  if (this.renderer && (changed.has('inputLabel') || changed.has('tabHintLabel'))) {
442
394
  this.nameInput();
443
395
  }
396
+ this.attachFind();
397
+ }
398
+ attachFind() {
399
+ const element = this.partNamed('find') ?? undefined;
400
+ if (element === this.find?.element) {
401
+ return;
402
+ }
403
+ this.find?.sink.detach();
404
+ this.find = element ? { element, sink: element.attach(this.finder) } : undefined;
444
405
  }
445
406
  connectedCallback() {
446
407
  super.connectedCallback();
@@ -457,23 +418,26 @@ let HelixHeadlessTerminal = class HelixHeadlessTerminal extends HelixElement {
457
418
  renderControls() {
458
419
  const toolbar = this.tag('toolbar');
459
420
  const button = this.tag('icon-button');
421
+ const tip = this.tag('tooltip');
460
422
  return html `<${toolbar} part="controls" exportparts="toolbar: controls-toolbar" label=${this.controlsLabel}>
461
423
  <slot name="actions" part="actions" @slotchange=${this.handleActions}></slot>
462
424
  ${this.draws('find')
463
425
  ? html `<${button}
464
- part="find"
465
- exportparts="button: find-button, live: find-live, spinner: find-spinner, text: find-text"
426
+ part="find-toggle"
427
+ id="find"
428
+ exportparts="button: find-toggle-button, live: find-toggle-live, spinner: find-toggle-spinner, text: find-toggle-text"
466
429
  .expanded=${this.findOpen}
467
430
  label=${this.findLabel}
468
- @click=${() => (this.findOpen ? this.closeFind() : void this.openFind())}
431
+ @click=${() => (this.findOpen ? this.find?.element.hide() : void this.openFind())}
469
432
  @helix-press=${stopPress}
470
433
  >
471
434
  <slot name="find-icon">${drawIcon(magnifyingGlass)}</slot>
472
- </${button}>`
435
+ </${button}><${tip} for="find" content=${this.findLabel} 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}>`
473
436
  : nothing}
474
437
  ${this.draws('copy')
475
438
  ? html `<${button}
476
439
  part="copy"
440
+ id="copy"
477
441
  exportparts="button: copy-button, live: copy-live, spinner: copy-spinner, text: copy-text"
478
442
  ?disabled=${!this.anySelected}
479
443
  label=${this.copyLabel}
@@ -481,22 +445,24 @@ let HelixHeadlessTerminal = class HelixHeadlessTerminal extends HelixElement {
481
445
  @helix-press=${stopPress}
482
446
  >
483
447
  <slot name="copy-icon">${drawIcon(copy)}</slot>
484
- </${button}>`
448
+ </${button}><${tip} for="copy" content=${this.copyLabel} 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}>`
485
449
  : nothing}
486
450
  ${this.draws('clear')
487
451
  ? html `<${button}
488
452
  part="clear"
453
+ id="clear"
489
454
  exportparts="button: clear-button, live: clear-live, spinner: clear-spinner, text: clear-text"
490
455
  label=${this.clearLabel}
491
456
  @click=${() => this.clear()}
492
457
  @helix-press=${stopPress}
493
458
  >
494
459
  <slot name="clear-icon">${drawIcon(broom)}</slot>
495
- </${button}>`
460
+ </${button}><${tip} for="clear" content=${this.clearLabel} 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}>`
496
461
  : nothing}
497
462
  ${this.draws('fullscreen')
498
463
  ? html `<${button}
499
464
  part="fullscreen"
465
+ id="fullscreen"
500
466
  exportparts="button: fullscreen-button, live: fullscreen-live, spinner: fullscreen-spinner, text: fullscreen-text"
501
467
  aria-pressed=${this.filled ? 'true' : 'false'}
502
468
  label=${this.fullscreenLabel}
@@ -504,7 +470,7 @@ let HelixHeadlessTerminal = class HelixHeadlessTerminal extends HelixElement {
504
470
  @helix-press=${stopPress}
505
471
  >
506
472
  <slot name="fullscreen-icon">${drawIcon(cornersOut)}</slot>
507
- </${button}>`
473
+ </${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}>`
508
474
  : nothing}
509
475
  </${toolbar}>`;
510
476
  }
@@ -512,84 +478,34 @@ let HelixHeadlessTerminal = class HelixHeadlessTerminal extends HelixElement {
512
478
  if (!this.draws('find')) {
513
479
  return nothing;
514
480
  }
515
- const toolbar = this.tag('toolbar');
516
- const button = this.tag('icon-button');
517
- return html `<div part="find-bar" role="search" aria-label=${this.findLabel} ?hidden=${!this.findOpen}>
518
- <input
519
- part="find-field"
520
- aria-label=${this.findLabel}
521
- autocomplete="off"
522
- placeholder=${this.findLabel}
523
- spellcheck="false"
524
- type="search"
525
- .value=${live(this.query)}
526
- @input=${this.handleFindInput}
527
- @keydown=${this.handleFindKeys}
528
- />
529
- <span part="find-count" aria-hidden="true">${this.countText}</span>
530
- <${toolbar} part="find-controls" exportparts="toolbar: find-controls-toolbar" label=${this.findControlsLabel}>
531
- <${button}
532
- part="find-match-case"
533
- exportparts="button: find-match-case-button, live: find-match-case-live, spinner: find-match-case-spinner, text: find-match-case-text"
534
- aria-pressed=${this.findMatchCase ? 'true' : 'false'}
535
- label=${this.findMatchCaseLabel}
536
- @click=${() => this.toggleOption('findMatchCase')}
537
- @helix-press=${stopPress}
538
- >
539
- <slot name="find-match-case-icon">${drawIcon(textAa)}</slot>
540
- </${button}>
541
- <${button}
542
- part="find-whole-word"
543
- exportparts="button: find-whole-word-button, live: find-whole-word-live, spinner: find-whole-word-spinner, text: find-whole-word-text"
544
- aria-pressed=${this.findWholeWord ? 'true' : 'false'}
545
- label=${this.findWholeWordLabel}
546
- @click=${() => this.toggleOption('findWholeWord')}
547
- @helix-press=${stopPress}
548
- >
549
- <slot name="find-whole-word-icon">${drawIcon(textAUnderline)}</slot>
550
- </${button}>
551
- <${button}
552
- part="find-regex"
553
- exportparts="button: find-regex-button, live: find-regex-live, spinner: find-regex-spinner, text: find-regex-text"
554
- aria-pressed=${this.findRegex ? 'true' : 'false'}
555
- label=${this.findRegexLabel}
556
- @click=${() => this.toggleOption('findRegex')}
557
- @helix-press=${stopPress}
558
- >
559
- <slot name="find-regex-icon">${drawIcon(asterisk)}</slot>
560
- </${button}>
561
- <slot name="find-actions" part="find-actions"></slot>
562
- <${button}
563
- part="find-previous"
564
- exportparts="button: find-previous-button, live: find-previous-live, spinner: find-previous-spinner, text: find-previous-text"
565
- ?disabled=${!this.matchCount}
566
- label=${this.findPreviousLabel}
567
- @click=${() => this.search(true)}
568
- @helix-press=${stopPress}
569
- >
570
- <slot name="find-previous-icon">${drawIcon(caretUp)}</slot>
571
- </${button}>
572
- <${button}
573
- part="find-next"
574
- exportparts="button: find-next-button, live: find-next-live, spinner: find-next-spinner, text: find-next-text"
575
- ?disabled=${!this.matchCount}
576
- label=${this.findNextLabel}
577
- @click=${() => this.search(false)}
578
- @helix-press=${stopPress}
579
- >
580
- <slot name="find-next-icon">${drawIcon(caretDown)}</slot>
581
- </${button}>
582
- <${button}
583
- part="find-close"
584
- exportparts="button: find-close-button, live: find-close-live, spinner: find-close-spinner, text: find-close-text"
585
- label=${this.findCloseLabel}
586
- @click=${() => this.closeFind()}
587
- @helix-press=${stopPress}
588
- >
589
- <slot name="find-close-icon">${drawIcon(x)}</slot>
590
- </${button}>
591
- </${toolbar}>
592
- </div>`;
481
+ const find = this.tag('find');
482
+ return html `<${find}
483
+ part="find"
484
+ exportparts="actions: find-actions, bar: find-bar, close: find-close, close-button: find-close-button, close-live: find-close-live, close-spinner: find-close-spinner, close-text: find-close-text, controls: find-controls, controls-toolbar: find-controls-toolbar, count: find-count, field: find-field, live: find-live, match-case: find-match-case, match-case-button: find-match-case-button, match-case-live: find-match-case-live, match-case-pressed: find-match-case-pressed, match-case-spinner: find-match-case-spinner, match-case-text: find-match-case-text, next: find-next, next-button: find-next-button, next-live: find-next-live, next-spinner: find-next-spinner, next-text: find-next-text, previous: find-previous, previous-button: find-previous-button, previous-live: find-previous-live, previous-spinner: find-previous-spinner, previous-text: find-previous-text, regex: find-regex, regex-button: find-regex-button, regex-live: find-regex-live, regex-pressed: find-regex-pressed, regex-spinner: find-regex-spinner, regex-text: find-regex-text, replace-all-button: find-replace-all-button, replace-all-live: find-replace-all-live, replace-all-spinner: find-replace-all-spinner, replace-all-text: find-replace-all-text, replace-all: find-replace-all, replace-button: find-replace-button, replace-field: find-replace-field, replace-live: find-replace-live, replace-spinner: find-replace-spinner, replace-text: find-replace-text, replace-toggle-button: find-replace-toggle-button, replace-toggle-live: find-replace-toggle-live, replace-toggle-spinner: find-replace-toggle-spinner, replace-toggle-text: find-replace-toggle-text, replace-toggle: find-replace-toggle, replace: find-replace, whole-word: find-whole-word, whole-word-button: find-whole-word-button, whole-word-live: find-whole-word-live, whole-word-pressed: find-whole-word-pressed, whole-word-spinner: find-whole-word-spinner, whole-word-text: find-whole-word-text"
485
+ close-label=${this.findCloseLabel}
486
+ controls-label=${this.findControlsLabel}
487
+ invalid-label=${this.findInvalidLabel}
488
+ label=${this.findLabel}
489
+ ?match-case=${this.findMatchCase}
490
+ match-case-label=${this.findMatchCaseLabel}
491
+ matches-label=${this.matchesLabel}
492
+ next-label=${this.findNextLabel}
493
+ previous-label=${this.findPreviousLabel}
494
+ ?regex=${this.findRegex}
495
+ regex-label=${this.findRegexLabel}
496
+ shortcut=""
497
+ ?whole-word=${this.findWholeWord}
498
+ whole-word-label=${this.findWholeWordLabel}
499
+ @toggle=${this.handleFindToggle}
500
+ >
501
+ <slot name="find-actions" slot="actions"></slot>
502
+ <slot name="find-match-case-icon" slot="match-case-icon">${drawIcon(textAa)}</slot>
503
+ <slot name="find-whole-word-icon" slot="whole-word-icon">${drawIcon(textAUnderline)}</slot>
504
+ <slot name="find-regex-icon" slot="regex-icon">${drawIcon(asterisk)}</slot>
505
+ <slot name="find-previous-icon" slot="previous-icon">${drawIcon(caretUp)}</slot>
506
+ <slot name="find-next-icon" slot="next-icon">${drawIcon(caretDown)}</slot>
507
+ <slot name="find-close-icon" slot="close-icon">${drawIcon(x)}</slot>
508
+ </${find}>`;
593
509
  }
594
510
  renderLook() {
595
511
  return html `<span data-look hidden aria-hidden="true" style="color: transparent">
@@ -765,18 +681,6 @@ __decorate([
765
681
  __decorate([
766
682
  state()
767
683
  ], HelixHeadlessTerminal.prototype, "findOpen", void 0);
768
- __decorate([
769
- state()
770
- ], HelixHeadlessTerminal.prototype, "query", void 0);
771
- __decorate([
772
- state()
773
- ], HelixHeadlessTerminal.prototype, "matchIndex", void 0);
774
- __decorate([
775
- state()
776
- ], HelixHeadlessTerminal.prototype, "matchCount", void 0);
777
- __decorate([
778
- state()
779
- ], HelixHeadlessTerminal.prototype, "invalid", void 0);
780
684
  __decorate([
781
685
  state()
782
686
  ], HelixHeadlessTerminal.prototype, "tabMovesFocus", void 0);
@@ -10,7 +10,10 @@ import { type PropertyValues, type TemplateResult } from 'lit';
10
10
  * The toolbar is one tab stop, and the arrow keys move between its controls, so
11
11
  * three toolbars take three presses of Tab to pass. Any element the browser can
12
12
  * focus counts as a control, including your own elements. A child that takes no
13
- * focus, such as a divider, is skipped. A menu is reached at its trigger.
13
+ * focus, such as a divider, is skipped. A menu is reached at its trigger. A
14
+ * disabled control is skipped too, except a Button with
15
+ * `focusable-when-disabled`, which the arrows still reach so its tooltip can
16
+ * say why it is disabled.
14
17
  *
15
18
  * A Divider in the toolbar is a separator across the toolbar's axis, as React
16
19
  * Aria's is: vertical in a horizontal toolbar and horizontal in a vertical
@@ -1,5 +1,5 @@
1
1
  import { HelixElement, OpenPhaseController, type Placement } from '@fusion.dev/helix-core';
2
- import { type PropertyValues, type TemplateResult } from 'lit';
2
+ import { type CSSResultGroup, type PropertyValues, type TemplateResult } from 'lit';
3
3
  declare const HelixHeadlessTooltip_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
4
4
  /**
5
5
  * @summary A short text that appears next to an element on hover and focus.
@@ -85,6 +85,7 @@ declare const HelixHeadlessTooltip_base: (new (...args: any[]) => import("@fusio
85
85
  * @cssstate placed-top - While the bubble sits above its trigger.
86
86
  */
87
87
  export declare class HelixHeadlessTooltip extends HelixHeadlessTooltip_base {
88
+ static styles: CSSResultGroup;
88
89
  /**
89
90
  * The text shown in the bubble.
90
91
  *
@@ -314,6 +315,12 @@ export declare class HelixHeadlessTooltip extends HelixHeadlessTooltip_base {
314
315
  * every control turns the scan into a series of pauses.
315
316
  */
316
317
  private readonly showLater;
318
+ /**
319
+ * Shows for a pointer or for focus, unless a menu or popover is open that
320
+ * the tooltip is not inside. `show()` and `open` are the consumer's own and
321
+ * are not refused.
322
+ */
323
+ private showForReader;
317
324
  /**
318
325
  * The pointer reached the bubble, in the `for` form.
319
326
  *
@@ -442,6 +449,21 @@ export declare class HelixHeadlessTooltip extends HelixHeadlessTooltip_base {
442
449
  * renders as its name, then the trigger's text.
443
450
  */
444
451
  private triggerSays;
452
+ /**
453
+ * A menu or popover opened outside the tooltip, so the tooltip yields to it.
454
+ *
455
+ * **A tooltip outside an open panel sits over the panel the reader is
456
+ * using.** A menu opened from the keyboard, or from a control beside the
457
+ * trigger while the pointer is still on it, is opened with no `click` on the
458
+ * trigger and no `pointerleave`, so neither takes the bubble down. Opening a
459
+ * menu by its keys from a trigger inside the tooltip also moves focus into
460
+ * the menu, and that `focusin` showed the bubble again. A pointer or focus
461
+ * does not show the bubble while such a panel is open, and this closes one
462
+ * they hold up as the panel opens. A bubble the consumer opened with `show()`
463
+ * or `open` holds nothing and stays.
464
+ */
465
+ private readonly yieldTo;
466
+ private stopYielding;
445
467
  connectedCallback(): void;
446
468
  disconnectedCallback(): void;
447
469
  protected firstUpdated(changed: PropertyValues): void;
@@ -1,6 +1,6 @@
1
1
  import { __decorate } from "tslib";
2
- import { anchorElement, arrowReach, customElement, elementById, focusTarget, HelixElement, hostAria, linkDescription, MixinControllerOpen, numberConverter, OpenPhaseController, onUnlessFalse, PopoverController, reportRuntime, sideOf, timeConverter, unlinkDescription, } from '@fusion.dev/helix-core';
3
- import { html } from 'lit';
2
+ import { anchorElement, arrowReach, customElement, elementById, focusTarget, HelixElement, hintInside, hintMayShow, hostAria, linkDescription, MixinControllerOpen, numberConverter, OpenPhaseController, onPanelOpen, onUnlessFalse, PopoverController, reportRuntime, sideOf, timeConverter, unlinkDescription, } from '@fusion.dev/helix-core';
3
+ import { css, html } from 'lit';
4
4
  import { property, state } from 'lit/decorators.js';
5
5
  const GENERIC = new Set(['div', 'p', 'section', 'span']);
6
6
  const WARMTH = Symbol.for('fusion.helix.tooltip.warmth');
@@ -28,6 +28,7 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
28
28
  anchor: () => this.trigger,
29
29
  arrow: () => this.partNamed('arrow'),
30
30
  boundary: () => [this, this.named],
31
+ hints: 'keep',
31
32
  offset: () => this.offset + arrowReach(this.partNamed('arrow')),
32
33
  onDismiss: () => {
33
34
  this.holds.clear();
@@ -65,10 +66,10 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
65
66
  const warm = warmth();
66
67
  const others = [...warm.showing].some((one) => one !== this);
67
68
  if (others || performance.now() - warm.hidAt < this.skipDelay) {
68
- this.show();
69
+ this.showForReader();
69
70
  return;
70
71
  }
71
- this.timer = setTimeout(() => this.show(), this.delay);
72
+ this.timer = setTimeout(() => this.showForReader(), this.delay);
72
73
  };
73
74
  this.holdOnBubble = () => {
74
75
  if (!this.hoverable) {
@@ -97,7 +98,7 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
97
98
  return;
98
99
  }
99
100
  this.holds.add('focus');
100
- this.show();
101
+ this.showForReader();
101
102
  };
102
103
  this.releaseFocus = () => {
103
104
  this.holds.delete('focus');
@@ -115,7 +116,18 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
115
116
  this.link(true, event.type === 'focusin' && first instanceof Element ? first : undefined);
116
117
  };
117
118
  this.audited = false;
119
+ this.yieldTo = (surface) => {
120
+ if (this.open && this.holds.size > 0 && !hintInside(surface, this)) {
121
+ this.holds.clear();
122
+ this.hide();
123
+ }
124
+ };
125
+ }
126
+ static { this.styles = css `
127
+ :host {
128
+ display: contents;
118
129
  }
130
+ `; }
119
131
  get trigger() {
120
132
  const named = this.named;
121
133
  if (named) {
@@ -180,6 +192,11 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
180
192
  }
181
193
  this.bubbleLayer.hide();
182
194
  }
195
+ showForReader() {
196
+ if (hintMayShow(this)) {
197
+ this.show();
198
+ }
199
+ }
183
200
  release() {
184
201
  if (this.holds.size === 0) {
185
202
  this.hide();
@@ -277,6 +294,9 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
277
294
  });
278
295
  return;
279
296
  }
297
+ if (this.triggerSays(this.describedText)) {
298
+ return;
299
+ }
280
300
  reportRuntime({
281
301
  kind: 'unlinked-trigger',
282
302
  message: `<${this.localName} for="${this.for}"> is not read out with its trigger. The link is ` +
@@ -294,12 +314,15 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
294
314
  }
295
315
  connectedCallback() {
296
316
  super.connectedCallback();
317
+ this.stopYielding ??= onPanelOpen(this.yieldTo);
297
318
  if (this.hasUpdated) {
298
319
  this.bind();
299
320
  }
300
321
  }
301
322
  disconnectedCallback() {
302
323
  super.disconnectedCallback();
324
+ this.stopYielding?.();
325
+ this.stopYielding = undefined;
303
326
  this.unbind();
304
327
  this.holds.clear();
305
328
  clearTimeout(this.timer);
@@ -306,6 +306,7 @@ export declare class HelixHeadlessTour extends HelixHeadlessTour_base {
306
306
  private light;
307
307
  /** Keeps `sizer` on the lit target, and on nothing once there is none. */
308
308
  private watch;
309
+ private readonly handleSlotChange;
309
310
  protected render(): TemplateResult;
310
311
  }
311
312
  declare global {
@@ -75,6 +75,9 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
75
75
  };
76
76
  this.followed = false;
77
77
  this.veiled = false;
78
+ this.handleSlotChange = () => {
79
+ this.requestUpdate();
80
+ };
78
81
  }
79
82
  static { this.words = 'helix-tour'; }
80
83
  get index() {
@@ -415,7 +418,7 @@ let HelixHeadlessTour = class HelixHeadlessTour extends MixinControllerOpen(Heli
415
418
  aria-hidden="true"
416
419
  style=${`left: ${spot.left}px; top: ${spot.top}px; block-size: ${spot.height}px; inline-size: ${spot.width}px; --tour-spotlight-x: ${spot.left}px; --tour-spotlight-y: ${spot.top}px; --tour-spotlight-width: ${spot.width}px; --tour-spotlight-height: ${spot.height}px`}
417
420
  ></div>`}
418
- <slot></slot>`;
421
+ <slot @slotchange=${this.handleSlotChange}></slot>`;
419
422
  }
420
423
  };
421
424
  __decorate([
@@ -31,6 +31,7 @@ let HelixHeadlessTourStop = class HelixHeadlessTourStop extends HelixElement {
31
31
  this.floating = new PopoverController(this, {
32
32
  anchor: () => this.target,
33
33
  arrow: () => this.partNamed('arrow'),
34
+ hints: 'keep',
34
35
  offset: () => {
35
36
  const tour = this.parentElement;
36
37
  return (tour?.spotlightPadding ?? 0) + arrowReach(this.partNamed('arrow'));
@@ -29,6 +29,10 @@ declare const HelixHeadlessTreeItem_base: (new (...args: any[]) => import("@fusi
29
29
  * A `disabled` item is reached by the arrow keys and announced as unavailable,
30
30
  * and a press, Enter or Space on it does nothing.
31
31
  *
32
+ * A closed branch's children are hidden until found: the browser's find and
33
+ * Find search them, and the branch opens on the one that holds the match,
34
+ * unless `helix-before-open` is canceled.
35
+ *
32
36
  * A `lazy` item is a branch whose children you load when it opens: opening it
33
37
  * sends `helix-load`, the row says it is busy, and the children you add appear
34
38
  * in it. Remove `lazy` once they are in.
@@ -264,6 +268,13 @@ export declare class HelixHeadlessTreeItem extends HelixHeadlessTreeItem_base {
264
268
  * its children.
265
269
  */
266
270
  private follow;
271
+ /**
272
+ * A search found a match among the closed branch's children, and shows it
273
+ * next: the browser's find or Find. The branch opens, and where the page
274
+ * refuses, its children are hidden again once the search has shown them, as
275
+ * Accordion's section is.
276
+ */
277
+ private readonly handleBeforeMatch;
267
278
  /** A press on the check mark checks the item and leaves a branch as it is. */
268
279
  private readonly handleCheckClick;
269
280
  private readonly handleToggleClick;
@@ -21,6 +21,19 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
21
21
  this.focusable = false;
22
22
  this.value = '';
23
23
  this.branch = false;
24
+ this.handleBeforeMatch = (event) => {
25
+ if (this.open || this.requestOpen(true)) {
26
+ return;
27
+ }
28
+ const group = event.currentTarget;
29
+ const observer = new MutationObserver(() => {
30
+ observer.disconnect();
31
+ if (!this.open && !group.hasAttribute('hidden')) {
32
+ group.setAttribute('hidden', 'until-found');
33
+ }
34
+ });
35
+ observer.observe(group, { attributeFilter: ['hidden'] });
36
+ };
24
37
  this.handleCheckClick = (event) => {
25
38
  event.preventDefault();
26
39
  event.stopPropagation();
@@ -227,7 +240,12 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
227
240
  branch reports children it has no relationship to. The pattern is
228
241
  \`treeitem\` containing \`group\`, which is also what an \`<li>\`
229
242
  holding a \`<ul>\` is. -->
230
- <div part="group" role="group" ?hidden=${!this.open}>
243
+ <div
244
+ part="group"
245
+ role=${this.open ? 'group' : nothing}
246
+ hidden=${this.open ? nothing : 'until-found'}
247
+ @beforematch=${this.handleBeforeMatch}
248
+ >
231
249
  <slot name="children"></slot>
232
250
  </div>
233
251
  </div>`;