@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
@@ -37,7 +37,7 @@ import { type CSSResultGroup, nothing, type PropertyValues, type TemplateResult
37
37
  * @slot end - Content after the label and outside the link, such as a switcher's
38
38
  * menu trigger, a count, or a mark. It can hold a control, and it is not read as
39
39
  * part of the link's accessible name.
40
- * @slot separator - Replaces the chevron shown before this item.
40
+ * @slot separator - Replaces the mark shown before this item, a chevron or the trail's `separator`.
41
41
  * @slot start - Content before the label, such as an icon.
42
42
  *
43
43
  * @event {HelixSelectEvent} helix-select - A plain press on the crumb's link, with its `href` as the value, before the browser follows it. Cancelable: cancel it to keep the browser from following, as a client-side router does.
@@ -46,7 +46,7 @@ import { type CSSResultGroup, nothing, type PropertyValues, type TemplateResult
46
46
  * @csspart fold-separator - The chevron before that button.
47
47
  * @csspart link - The crumb itself, which is an `<a>` where `href` is set.
48
48
  * @csspart separator - The separator preceding the item.
49
- * @csspart separator-mark - The chevron between crumbs, shown when no separator is slotted.
49
+ * @csspart separator-mark - The chevron between crumbs, or the slash under the trail's `separator="slash"`, shown when no separator is slotted.
50
50
  *
51
51
  * @cssstate current - While the crumb is the current page, marked `current` or the last of a trail with none marked.
52
52
  * @cssstate end - While the `end` slot holds something, so a stylesheet can join
@@ -96,6 +96,11 @@ export declare class HelixHeadlessBreadcrumb extends HelixElement {
96
96
  * `current`, so this one is said to be the current page.
97
97
  */
98
98
  implied: boolean;
99
+ /**
100
+ * @internal Set by the trail from its `separator`: the mark this crumb draws
101
+ * before itself and before its fold button.
102
+ */
103
+ mark: 'chevron' | 'slash';
99
104
  /** @internal Set by the trail: what a press on the fold's button does. */
100
105
  onFold: (() => void) | undefined;
101
106
  /**
@@ -140,6 +145,12 @@ export declare class HelixHeadlessBreadcrumb extends HelixElement {
140
145
  * without reimplementing the crumb, its link and its current-page branch.
141
146
  */
142
147
  protected renderSeparator(): TemplateResult;
148
+ /**
149
+ * The trail's mark: a chevron, mirrored on a right-to-left page because it
150
+ * points the way the trail runs, or a slash, which is a character and is
151
+ * written the same way in either direction.
152
+ */
153
+ private renderMark;
143
154
  protected render(): TemplateResult;
144
155
  }
145
156
  declare global {
@@ -12,6 +12,7 @@ let HelixHeadlessBreadcrumb = class HelixHeadlessBreadcrumb extends HelixElement
12
12
  this.folded = false;
13
13
  this.foldLabel = '';
14
14
  this.implied = false;
15
+ this.mark = 'chevron';
15
16
  this.onFold = undefined;
16
17
  this.rel = '';
17
18
  this.target = '';
@@ -48,10 +49,7 @@ let HelixHeadlessBreadcrumb = class HelixHeadlessBreadcrumb extends HelixElement
48
49
  if (this.fold === 0) {
49
50
  return nothing;
50
51
  }
51
- return html `<span part="fold-separator" aria-hidden="true"
52
- ><svg data-mirror fill="currentColor" viewBox=${caretRight.viewBox}>
53
- ${unsafeSVG(caretRight.content)}
54
- </svg></span
52
+ return html `<span part="fold-separator" aria-hidden="true">${this.renderMark()}</span
55
53
  ><button part="expand" type="button" aria-label=${this.foldLabel} @click=${() => this.onFold?.()}>
56
54
  …
57
55
  </button>`;
@@ -64,11 +62,20 @@ let HelixHeadlessBreadcrumb = class HelixHeadlessBreadcrumb extends HelixElement
64
62
  this.toggleState('end', slotFilled(slotNamed(this.renderRoot, 'end')));
65
63
  }
66
64
  renderSeparator() {
67
- return html `<slot name="separator"
68
- ><svg part="separator-mark" data-mirror fill="currentColor" viewBox=${caretRight.viewBox}>
69
- ${unsafeSVG(caretRight.content)}
70
- </svg></slot
71
- >`;
65
+ return html `<slot name="separator">${this.renderMark(true)}</slot>`;
66
+ }
67
+ renderMark(named = false) {
68
+ if (this.mark === 'slash') {
69
+ return named
70
+ ? html `<span part="separator-mark" data-mark="slash">/</span>`
71
+ : html `<span data-mark="slash">/</span>`;
72
+ }
73
+ const chevron = unsafeSVG(caretRight.content);
74
+ return named
75
+ ? html `<svg part="separator-mark" data-mirror fill="currentColor" viewBox=${caretRight.viewBox}>
76
+ ${chevron}
77
+ </svg>`
78
+ : html `<svg data-mirror fill="currentColor" viewBox=${caretRight.viewBox}>${chevron}</svg>`;
72
79
  }
73
80
  render() {
74
81
  const label = html `<slot name="start"></slot><slot></slot>`;
@@ -105,6 +112,9 @@ __decorate([
105
112
  __decorate([
106
113
  property({ attribute: false })
107
114
  ], HelixHeadlessBreadcrumb.prototype, "implied", void 0);
115
+ __decorate([
116
+ property({ attribute: false })
117
+ ], HelixHeadlessBreadcrumb.prototype, "mark", void 0);
108
118
  __decorate([
109
119
  property()
110
120
  ], HelixHeadlessBreadcrumb.prototype, "rel", void 0);
@@ -64,7 +64,7 @@ export interface BreadcrumbItem {
64
64
  * @csspart crumb-fold-separator - The chevron before that button.
65
65
  * @csspart crumb-link - The link, or the current page's words, of a crumb drawn from `items`.
66
66
  * @csspart crumb-separator - The separator before a crumb drawn from `items`.
67
- * @csspart crumb-separator-mark - The chevron before a crumb drawn from `items`.
67
+ * @csspart crumb-separator-mark - The chevron, or the slash under `separator="slash"`, before a crumb drawn from `items`.
68
68
  * @csspart expand - The button in the folded crumb that shows the whole trail.
69
69
  * @csspart list - The ordered list.
70
70
  */
@@ -100,6 +100,21 @@ export declare class HelixHeadlessBreadcrumbs extends HelixElement {
100
100
  * 2, so the first crumb and the current page always show.
101
101
  */
102
102
  maxItems?: number;
103
+ /**
104
+ * The mark between one crumb and the next, for the whole trail. Unset is a
105
+ * chevron; `slash` is a slash, as a path or a repository and its branch is
106
+ * written.
107
+ *
108
+ * **On the trail rather than on each crumb**, because the marks are one
109
+ * decision: a trail whose separators differ from crumb to crumb is a
110
+ * mistake, and a trail drawn from `items` has no crumb of yours to set it
111
+ * on. A crumb's own `separator` slot still replaces its mark, for the rare
112
+ * crumb that needs something else.
113
+ *
114
+ * Optional and reflected, per the rule that a reflected property takes no
115
+ * default, so a stylesheet can select on it.
116
+ */
117
+ separator?: 'slash';
103
118
  /** Whether a reader has asked to see the whole of a folded trail. */
104
119
  private expanded;
105
120
  /** The crumbs slotted into the trail, in order. */
@@ -43,6 +43,7 @@ let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixEleme
43
43
  crumb.fold = index === 0 ? fold : 0;
44
44
  crumb.foldLabel = index === 0 ? label : '';
45
45
  crumb.onFold = index === 0 && fold > 0 ? this.unfold : undefined;
46
+ crumb.mark = this.separator ?? 'chevron';
46
47
  });
47
48
  });
48
49
  };
@@ -87,6 +88,7 @@ let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixEleme
87
88
  : html `<${crumb}
88
89
  part="crumb crumb-collapsed"
89
90
  exportparts="expand: crumb-expand, fold-separator: crumb-fold-separator, link: crumb-link, separator: crumb-separator, separator-mark: crumb-separator-mark"
91
+ .mark=${this.separator ?? 'chevron'}
90
92
  ><button
91
93
  part="expand"
92
94
  type="button"
@@ -105,6 +107,7 @@ let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixEleme
105
107
  rel=${item.rel ?? nothing}
106
108
  target=${item.target ?? nothing}
107
109
  ?current=${index === last}
110
+ .mark=${this.separator ?? 'chevron'}
108
111
  >${item.label}</${crumb}
109
112
  >`;
110
113
  return html `${repeat(first, ({ index }) => index, draw)}${fold}${repeat(rest, ({ index }) => index, draw)}`;
@@ -129,6 +132,9 @@ __decorate([
129
132
  __decorate([
130
133
  property({ attribute: 'max-items', converter: numberConverter('max-items', undefined) })
131
134
  ], HelixHeadlessBreadcrumbs.prototype, "maxItems", void 0);
135
+ __decorate([
136
+ property({ reflect: true })
137
+ ], HelixHeadlessBreadcrumbs.prototype, "separator", void 0);
132
138
  __decorate([
133
139
  state()
134
140
  ], HelixHeadlessBreadcrumbs.prototype, "expanded", void 0);
@@ -35,6 +35,10 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
35
35
  * Set `pending` while the action a press started is still running. The button
36
36
  * keeps focus and refuses presses, and a reader on it hears `pending-label`.
37
37
  *
38
+ * Set `focusable-when-disabled` on a disabled button whose reason a Tooltip
39
+ * gives. It stays in the tab order and takes hover, so the tooltip opens, and
40
+ * it refuses every press.
41
+ *
38
42
  * Every press that acts sends `helix-press` with what pressed it in
39
43
  * `detail.pointerType`: `mouse`, `pen`, `touch`, `keyboard`, or `virtual` for
40
44
  * a screen reader's press or a script's `click()`, as React Aria's `onPress`
@@ -146,6 +150,35 @@ export declare class HelixHeadlessButton extends HelixFormElementBase {
146
150
  * anything a consumer sets.
147
151
  */
148
152
  focusable: boolean;
153
+ /**
154
+ * Whether a disabled button stays in the tab order, so a tooltip around it
155
+ * can say why it is disabled.
156
+ *
157
+ * Set it on a button whose reason a Tooltip gives, such as a Save with no
158
+ * changes to save. While the button is disabled, it keeps focus and takes
159
+ * hover, and it refuses every press: a click, Enter and Space, and Enter in
160
+ * a field of its form. The click does not reach your listeners, and a submit
161
+ * button submits nothing. The inner control carries `aria-disabled` in place
162
+ * of the native `disabled`, so a screen reader still says it is unavailable.
163
+ * The styled button keeps the disabled look.
164
+ *
165
+ * Leave it unset on a button with no reason to give. A plain disabled button
166
+ * leaves the tab order, as a native one does, and a keyboard reader skips a
167
+ * stop that does nothing. Set `pending` for the wait after a press.
168
+ */
169
+ focusableWhenDisabled: boolean;
170
+ /**
171
+ * @internal Whether the button is disabled and kept in the tab order by
172
+ * `focusableWhenDisabled`, so the inner control takes `aria-disabled` and
173
+ * refuses presses itself.
174
+ */
175
+ protected get heldDisabled(): boolean;
176
+ /**
177
+ * @internal Whether the inner control refuses a press it still receives:
178
+ * the button is pending, or disabled and kept in the tab order. A subclass
179
+ * that draws its own control, as `CopyButton` does, binds to it.
180
+ */
181
+ protected get refusesPress(): boolean;
149
182
  /**
150
183
  * Where this button's submission goes, overriding the form's own.
151
184
  *
@@ -30,6 +30,7 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElementBase
30
30
  this.command = '';
31
31
  this.commandFor = '';
32
32
  this.focusable = true;
33
+ this.focusableWhenDisabled = false;
33
34
  this.formAction = '';
34
35
  this.formEnctype = '';
35
36
  this.formMethod = '';
@@ -48,7 +49,7 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElementBase
48
49
  }
49
50
  };
50
51
  this.handleClick = (event) => {
51
- if (this.pending) {
52
+ if (this.refusesPress) {
52
53
  event.preventDefault();
53
54
  event.stopImmediatePropagation();
54
55
  return;
@@ -83,6 +84,12 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElementBase
83
84
  get forwardsLabel() {
84
85
  return true;
85
86
  }
87
+ get heldDisabled() {
88
+ return this.isDisabled && this.focusableWhenDisabled;
89
+ }
90
+ get refusesPress() {
91
+ return this.pending || this.heldDisabled;
92
+ }
86
93
  sayPending() {
87
94
  const focused = this.renderRoot?.activeElement != null;
88
95
  if (!focused && this.status.said === '') {
@@ -207,11 +214,11 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElementBase
207
214
  aria-expanded=${this.expanded === undefined ? nothing : String(this.expanded)}
208
215
  aria-haspopup=${this.popup ?? nothing}
209
216
  aria-busy=${this.pending ? 'true' : nothing}
210
- aria-disabled=${this.pending ? 'true' : nothing}
217
+ aria-disabled=${this.refusesPress ? 'true' : nothing}
211
218
  aria-label=${this.label || nothing}
212
219
  command=${this.command || nothing}
213
220
  tabindex=${stop}
214
- ?disabled=${this.isDisabled}
221
+ ?disabled=${this.isDisabled && !this.heldDisabled}
215
222
  @click=${this.handleClick}
216
223
  @keydown=${this.notePress}
217
224
  @pointerdown=${this.notePress}
@@ -235,6 +242,9 @@ __decorate([
235
242
  __decorate([
236
243
  property({ attribute: false })
237
244
  ], HelixHeadlessButton.prototype, "focusable", void 0);
245
+ __decorate([
246
+ property({ attribute: 'focusable-when-disabled', type: Boolean })
247
+ ], HelixHeadlessButton.prototype, "focusableWhenDisabled", void 0);
238
248
  __decorate([
239
249
  property({ attribute: 'formaction' })
240
250
  ], HelixHeadlessButton.prototype, "formAction", void 0);
@@ -2,6 +2,7 @@ import { type CanvasView, HelixElement, type ViewportPanDetail, type ViewportWay
2
2
  import { 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 CanvasArrangeOptions } from '../arrange.js';
6
7
  import { type CanvasDocument, type CanvasRoute, type CanvasTone } from './canvas-document.js';
7
8
  export type { CanvasArrangeOptions } from '../arrange.js';
@@ -210,6 +211,16 @@ declare const HelixHeadlessCanvas_base: (new (...args: any[]) => import("@fusion
210
211
  * @csspart pan-up-spinner - The ring `pan-up` turns while it is pending.
211
212
  * @csspart pan-up-text - The box `pan-up`'s glyph sits in.
212
213
  * @csspart region - The named region the board is drawn in.
214
+ * @csspart tooltip-arrow - The arrow of the tooltip that names each button, forwarded from `Tooltip`.
215
+ * @csspart tooltip-arrow-bottom - The arrow while the bubble is below its button, on the bubble's top edge. Set together with `tooltip-arrow`.
216
+ * @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`.
217
+ * @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`.
218
+ * @csspart tooltip-arrow-top - The arrow while the bubble is above its button, on the bubble's bottom edge. Set together with `tooltip-arrow`.
219
+ * @csspart tooltip-surface - The bubble of the tooltip that names each button, forwarded from `Tooltip`.
220
+ * @csspart tooltip-surface-bottom - The bubble while it is below its button. Set together with `tooltip-surface`.
221
+ * @csspart tooltip-surface-left - The bubble while it is left of its button. Set together with `tooltip-surface`.
222
+ * @csspart tooltip-surface-right - The bubble while it is right of its button. Set together with `tooltip-surface`.
223
+ * @csspart tooltip-surface-top - The bubble while it is above its button. Set together with `tooltip-surface`.
213
224
  * @csspart viewport - The box the board is drawn inside.
214
225
  * @csspart zoom-in - The button that zooms in.
215
226
  * @csspart zoom-in-button - `zoom-in`'s inner button.
@@ -3,6 +3,7 @@ import { assignSlots, auditName, capturePointer, customElement, formatPercent, f
3
3
  import { html, nothing, svg } from 'lit';
4
4
  import { property, state } from 'lit/decorators.js';
5
5
  import '../../icon-button/index.js';
6
+ import '../../tooltip/index.js';
6
7
  import { arrangement } from '../arrange.js';
7
8
  import { descriptionOf } from '../describe.js';
8
9
  import { HelixHeadlessCanvasEdge } from '../edge/canvas-edge.js';
@@ -1,13 +1,21 @@
1
1
  import { HelixFormElement } from '@fusion.dev/helix-core';
2
- import { type PropertyValues, type TemplateResult } from 'lit';
2
+ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
+ import '../../tooltip/index.js';
4
+ /**
5
+ * How long `example` shows before the next one replaces it, in milliseconds:
6
+ * `EXAMPLE_SHOWN`, and `EXAMPLE_PER_CHARACTER` more for each character past
7
+ * `EXAMPLE_SHORT`, up to `EXAMPLE_LONGEST`. A longer example takes longer to
8
+ * read.
9
+ */
10
+ export declare function exampleShown(example: string): number;
3
11
  declare const HelixHeadlessChatComposer_base: (new (...args: any[]) => import("@fusion.dev/helix-core").VoiceElement) & typeof HelixFormElement;
4
12
  /**
5
13
  * @summary A field for writing a turn in a conversation.
6
14
  *
7
15
  * @description
8
16
  * The composer is a separate element so you can replace it. If you need
9
- * attachments, slash commands, or a model picker, replace the composer and keep
10
- * the transcript.
17
+ * slash commands or a layout of your own, replace the composer and keep the
18
+ * transcript.
11
19
  *
12
20
  * By default, Enter sends and Shift+Enter starts a new line, the convention in
13
21
  * messaging apps. Document editors mostly use the opposite, so `submit-on`
@@ -47,33 +55,71 @@ declare const HelixHeadlessChatComposer_base: (new (...args: any[]) => import("@
47
55
  * `typing-timeout` milliseconds. Pass it on to show "is typing" to the other
48
56
  * side.
49
57
  *
58
+ * **`busy` says a reply is on its way**, and turns the send button into a
59
+ * stop button, named by `stop-label`, that sends `helix-stop`. The field
60
+ * stays open so the reader can write the next turn, and Enter does not send
61
+ * it until `busy` is cleared. With `queue`, Enter sends while `busy` too, for
62
+ * a page that holds a turn until the reply ends or hands it to the reply
63
+ * under way; the button stays the stop button.
64
+ *
65
+ * **The `idle` slot takes the send button's place while there is nothing to
66
+ * send**: the field is empty, `attached` is not set, and no reply is on its
67
+ * way. Put the action an empty draft offers there, such as a button that
68
+ * starts a voice conversation. The send button comes back as soon as there
69
+ * is a draft. Where focus was on the one that goes, it moves to the one that
70
+ * comes, so a keyboard reader stays in the same place.
71
+ *
72
+ * **What the draft carries goes in `attachments`**, inside the box and above
73
+ * the field, such as thumbnails of attached images and cards for attached
74
+ * files. The region takes no room while nothing is in it. A press on its
75
+ * empty space keeps the reader in the field, and a press on a control in it,
76
+ * such as a button that removes an attachment, goes to that control. Set
77
+ * `attached` as well, so the send button shows for a draft with no text.
78
+ *
50
79
  * @since 0.1.0
51
80
  * @status stable
52
81
  * @category forms
53
82
  * @aliases Message box, Prompt input
54
83
  * @documentation https://fusion.dev/helix/components/chat
55
84
  *
85
+ * @slot attachments - What the draft carries, such as images or files, inside the box and above the field.
56
86
  * @slot end - Content under the field, beside the send button.
87
+ * @slot idle - A control shown in place of the send button while the draft is empty and no reply is on its way, such as a button that starts a voice conversation.
57
88
  * @slot start - Content under the field at the leading edge, such as an attachment button or a model picker.
58
89
  *
59
90
  * @event {HelixBeforeListenEvent} helix-before-listen - The dictation button was pressed. Cancelable: cancel it to keep the composer from listening.
60
91
  * @event {HelixListenEvent} helix-listen - Dictation settled on a phrase and put it in the draft, after the draft's own `input`.
61
92
  * @event {HelixSendEvent} helix-send - Fired when the user sends their turn, and cancelable.
93
+ * @event {HelixStopEvent} helix-stop - Fired when the user presses the stop button, shown in place of the send button while `busy` is set.
62
94
  * @event {HelixTypingEvent} helix-typing - The user started or stopped writing, with `typing` saying which, as the composer's `typing` does.
63
95
  * @event {Event} input - The user changed the text. Read it from `value`, for a typing indicator or a saved draft. Bubbles and is composed, as a native field's is.
64
96
  *
97
+ * @csspart attachments - The box around the `attachments` slot, above the field. It is hidden while the slot is empty.
65
98
  * @csspart composer - The whole composer.
66
99
  * @csspart end - The box around the `end` slot.
67
100
  * @csspart feedback - The `feedback` message, under the field while it is set.
68
101
  * @csspart field - The text field.
69
- * @csspart mark - The icon inside the send button.
70
- * @csspart send - The send button.
102
+ * @csspart idle - The box around the `idle` slot, shown in the send button's place while the draft is empty.
103
+ * @csspart mark - The icon inside the send button, a return key, or a square while `busy`.
104
+ * @csspart reason-arrow - The arrow of the bubble that says `send-reason`, forwarded from `Tooltip`.
105
+ * @csspart reason-arrow-bottom - The arrow while the bubble is below the send button, on the bubble's top edge. Set together with `reason-arrow`.
106
+ * @csspart reason-arrow-left - The arrow while the bubble is left of the send button, on the bubble's right edge. Set together with `reason-arrow`.
107
+ * @csspart reason-arrow-right - The arrow while the bubble is right of the send button, on the bubble's left edge. Set together with `reason-arrow`.
108
+ * @csspart reason-arrow-top - The arrow while the bubble is above the send button, on the bubble's bottom edge. Set together with `reason-arrow`.
109
+ * @csspart reason-surface - The bubble that says `send-reason` over the send button, forwarded from `Tooltip`.
110
+ * @csspart reason-surface-bottom - The bubble while it is below the send button. Set together with `reason-surface`.
111
+ * @csspart reason-surface-left - The bubble while it is left of the send button. Set together with `reason-surface`.
112
+ * @csspart reason-surface-right - The bubble while it is right of the send button. Set together with `reason-surface`.
113
+ * @csspart reason-surface-top - The bubble while it is above the send button. Set together with `reason-surface`.
114
+ * @csspart send - The send button, which is the stop button while `busy`.
71
115
  * @csspart start - The box around the `start` slot.
72
116
  * @csspart tools - The row under the field.
73
117
  * @csspart voice - The dictation button, under `voice`, before the send button.
74
118
  * @csspart voice-listening - The dictation button while it listens. Set alongside `voice`.
75
119
  * @csspart voice-status - What a screen reader hears as dictation starts and stops, drawn for no one else.
76
120
  *
121
+ * @cssstate example-entering - The example in the empty field has just replaced the one before it, for an entrance. It holds for 350 milliseconds. See `placeholders`.
122
+ * @cssstate example-leaving - The example in the empty field is giving way to the next one, for an exit. It holds for 250 milliseconds, and the next example becomes the placeholder 100 milliseconds in, so the two can cross. See `placeholders`.
77
123
  * @cssstate invalid - The draft fails `required`, `minlength`, `maxlength`, or a custom error, and the reader has tried to send or submit it, or edited the field and left it. Or `feedback` is set.
78
124
  * @cssstate listening - While the composer listens for dictation. The element sets `--dictation-level` on itself meanwhile, from 0 to 1, for a meter.
79
125
  */
@@ -84,6 +130,22 @@ export declare class HelixHeadlessChatComposer extends HelixHeadlessChatComposer
84
130
  * `registerWords('de', { 'helix-chat-composer': { sendLabel: 'Senden' } })`.
85
131
  */
86
132
  static words: string;
133
+ /**
134
+ * Whether the draft holds something besides its text, such as an attached
135
+ * file. The draft then counts as one though the field is empty, so the
136
+ * send button shows in place of the `idle` slot.
137
+ */
138
+ attached: boolean;
139
+ /**
140
+ * Whether a reply is on its way. While set, the send button is a stop
141
+ * button that sends `helix-stop`, and the draft does not send.
142
+ *
143
+ * **The same button rather than one beside it**, because sending and
144
+ * stopping are never both possible: while a reply is on its way there is
145
+ * nothing to send to, and the place the reader's eye goes to act is the
146
+ * place that can act. The field stays open for the next turn.
147
+ */
148
+ busy: boolean;
87
149
  /**
88
150
  * A message about the draft, shown under the field. While set, the composer
89
151
  * is invalid, does not send, and does not submit, as a field's `feedback`
@@ -107,6 +169,35 @@ export declare class HelixHeadlessChatComposer extends HelixHeadlessChatComposer
107
169
  * Placeholder text shown in an empty field.
108
170
  */
109
171
  placeholder: string;
172
+ /**
173
+ * Examples the empty field shows in turn, in place of `placeholder`, such
174
+ * as tasks a reader might ask for. Each shows for 6.5 seconds, and a longer
175
+ * one shows longer, up to 10 seconds.
176
+ *
177
+ * **Each is the field's placeholder in its turn**, so a screen reader that
178
+ * reaches the field hears the one showing. The swap runs through
179
+ * `:state(example-leaving)`, as one example leaves, and
180
+ * `:state(example-entering)`, as the next arrives.
181
+ *
182
+ * **They move only while nobody is about to write, and while the reader
183
+ * can see them.** An empty field without focus moves on. Focus or text
184
+ * holds the example showing, and an example already leaving finishes its
185
+ * turn. A composer scrolled out of view holds its example, and the example
186
+ * gets its full time again when the composer comes back. Where the reader
187
+ * asked for less motion, or the page paused its motion, the first example
188
+ * stays.
189
+ */
190
+ placeholders: readonly string[];
191
+ /**
192
+ * Whether Enter sends while `busy`, for a page that queues the turn or
193
+ * hands it to the reply under way.
194
+ *
195
+ * Off by default, because a page that does neither would drop the turn: it
196
+ * has nowhere to put a message while a reply is coming. The button stays
197
+ * the stop button either way, since stopping is still the one thing it
198
+ * should do while a reply is on its way.
199
+ */
200
+ queue: boolean;
110
201
  /**
111
202
  * Whether the form needs a draft to submit. An empty draft never sends,
112
203
  * required or not.
@@ -118,10 +209,28 @@ export declare class HelixHeadlessChatComposer extends HelixHeadlessChatComposer
118
209
  * It grows from there.
119
210
  */
120
211
  rows: number;
212
+ /**
213
+ * Whether the draft waits to be sent, such as while an attachment is still
214
+ * read. The send button is unavailable and Enter does not send, while the
215
+ * field stays open. The stop button shown while `busy` still stops.
216
+ */
217
+ sendDisabled: boolean;
121
218
  /**
122
219
  * Label for the send button.
123
220
  */
124
221
  sendLabel: string;
222
+ /**
223
+ * Why the draft waits, such as "Reading photo.jpg", shown in a tooltip on
224
+ * the send button while `send-disabled` is set. The button then stays in
225
+ * the tab order and takes the pointer, with `aria-disabled` in place of
226
+ * `disabled`, so the reason can be reached by keyboard and by pointer, as
227
+ * Button's `focusable-when-disabled` does. A press still sends nothing.
228
+ */
229
+ sendReason: string;
230
+ /**
231
+ * Label for the stop button shown while `busy`.
232
+ */
233
+ stopLabel: string;
125
234
  /**
126
235
  * What sends the turn: `enter` for the Enter key, or `button` for the send
127
236
  * button only.
@@ -164,6 +273,29 @@ export declare class HelixHeadlessChatComposer extends HelixHeadlessChatComposer
164
273
  private edited;
165
274
  /** Whether the composer shows that its draft is wrong. */
166
275
  private shownInvalid;
276
+ /** Which of `placeholders` the empty field shows. */
277
+ private example;
278
+ /**
279
+ * Which of `placeholders` is on its way out while the next comes in, so a
280
+ * tier can draw the two at once.
281
+ */
282
+ private leavingExample;
283
+ /** Whether the field holds focus, which holds the examples still. */
284
+ private focused;
285
+ /** Whether any of the composer is in view, which the examples need to move. */
286
+ private onScreen;
287
+ /** Watches the composer come into view and go out of it. */
288
+ private sight;
289
+ /** The wait before the example showing leaves, while the examples move. */
290
+ private turning;
291
+ /** The wait before the next example appears, part way through the exit. */
292
+ private crossing;
293
+ /** The exit of the example leaving, while it runs. */
294
+ private leaving;
295
+ /** The entrance of the example arriving, while it runs. */
296
+ private entering;
297
+ /** Stops listening to the page's motion switch. */
298
+ private unmoved;
167
299
  /** The pause after which the user has stopped writing. */
168
300
  private readonly pause;
169
301
  /**
@@ -204,8 +336,90 @@ export declare class HelixHeadlessChatComposer extends HelixHeadlessChatComposer
204
336
  * and the send control drifts inward by one for every slot a consumer left
205
337
  * alone. No selector can ask this: a slot's assigned nodes are not its DOM
206
338
  * children, so `:empty` describes the fallback rather than the fill.
339
+ * Re-marked on `slotchange` as well as after a render, so a control a
340
+ * framework renders after the first render is shown.
207
341
  */
208
342
  private markEmpty;
343
+ /**
344
+ * Whether the `idle` slot shows in the send button's place: a child is in
345
+ * it, the draft is empty, and no reply is on its way. Read off the children
346
+ * where they are known, and drawn again as the slot's contents change. A
347
+ * server cannot know them, so it serves the send button, which a hydrating
348
+ * render keeps until `finishHydration` reads them.
349
+ */
350
+ private get idling();
351
+ /**
352
+ * Whether anything is slotted in `attachments`, where the children are
353
+ * known. A server cannot know them, so it serves the region hidden and
354
+ * takes no gap above the field for an empty draft, the common case.
355
+ * `markEmpty` keeps it right after every render and on `slotchange`.
356
+ */
357
+ private get carrying();
358
+ /**
359
+ * A press on the attachments' empty space, such as the gap between two
360
+ * thumbnails, keeps the reader in the field, so the next key they press is
361
+ * typed. A press on a control of their own, such as a button that removes
362
+ * an attachment, is left to that control.
363
+ */
364
+ private readonly handleAttachmentsDown;
365
+ /** Where focus was as an update began: on the send button, in the idle control, or elsewhere. */
366
+ private focusedOn;
367
+ /** Reads which control holds focus, before an update swaps them. */
368
+ private noteFocus;
369
+ /** Moves focus to the control that came, where it was on the one that went. */
370
+ private keepFocus;
371
+ connectedCallback(): void;
372
+ disconnectedCallback(): void;
373
+ /** Notes focus coming and going, which holds the examples or frees them. */
374
+ private readonly handleFocus;
375
+ /** The examples drawn over the field: the one showing, and the one leaving while they cross. */
376
+ private renderExamples;
377
+ /** The example the empty field shows, or `placeholder` where there are none. */
378
+ private get shownPlaceholder();
379
+ /**
380
+ * Starts the examples moving, or stops them, as the list, the field, and
381
+ * the page allow.
382
+ *
383
+ * **Each turn is timed by the example showing**, so a long one stays up
384
+ * longer. **Focus holds the examples and leaves a turn under way to
385
+ * finish**, so the field never shows half an exit. Text, reduced motion, a
386
+ * paused page, scrolling out of view, and leaving the document stop them at
387
+ * once.
388
+ */
389
+ private turnExamples;
390
+ /**
391
+ * Starts the example showing on its way out. The next appears part way
392
+ * through the exit, and the one leaving goes once its exit ends.
393
+ */
394
+ private leaveExample;
395
+ /** Shows the next example, on its way in, and times the turn after it. */
396
+ private arriveExample;
397
+ private stopExamples;
398
+ /**
399
+ * Called as the example showing has landed: its entrance ended, or the
400
+ * examples stopped. A tier that holds anything for the swap lets it go
401
+ * here. The headless tier holds nothing.
402
+ */
403
+ protected exampleLanded(): void;
404
+ /**
405
+ * What the tier above draws over the empty field while `placeholders` holds
406
+ * two examples or more, behind a seam so a tier can draw the swap the
407
+ * field's own placeholder cannot. `example` is the field's placeholder, and
408
+ * `leaving` the one on its way out, each with its place in `placeholders`.
409
+ * For the first 100 milliseconds of an exit the two are the same example,
410
+ * and after that they cross. The headless tier draws nothing, and its field
411
+ * shows each example as its placeholder.
412
+ *
413
+ * **The field keeps its placeholder either way**, so whatever is drawn
414
+ * here is decoration, and the field is what a screen reader reaches.
415
+ */
416
+ protected renderExample(_example: {
417
+ index: number;
418
+ text: string;
419
+ }, _leaving?: {
420
+ index: number;
421
+ text: string;
422
+ }): TemplateResult | typeof nothing;
209
423
  protected updated(changed: PropertyValues): void;
210
424
  protected firstUpdated(changed: PropertyValues): void;
211
425
  protected willUpdate(changed: PropertyValues): void;
@@ -226,7 +440,7 @@ export declare class HelixHeadlessChatComposer extends HelixHeadlessChatComposer
226
440
  */
227
441
  protected renderSend(): TemplateResult;
228
442
  /**
229
- * The field, and a row of tools under it.
443
+ * What the draft carries, the field, and a row of tools under it.
230
444
  *
231
445
  * **The tools take a line of their own rather than sharing the field's.** On
232
446
  * one line every tool a consumer adds takes width away from the thing being
@@ -254,6 +468,7 @@ export declare class HelixHeadlessChatComposer extends HelixHeadlessChatComposer
254
468
  */
255
469
  private readonly handleInput;
256
470
  private readonly handleSend;
471
+ private readonly handleStop;
257
472
  /**
258
473
  * Enter sends where that is what `submit-on` says, and never mid-composition.
259
474
  *