@fusion.dev/helix 0.221.0 → 0.223.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.
@@ -1,6 +1,7 @@
1
1
  import { HelixHeadlessChatComposer } from '@fusion.dev/helix-headless/chat';
2
2
  import { type CSSResultGroup, type PropertyValues, type TemplateResult } from 'lit';
3
3
  import '../../tooltip/index.js';
4
+ import '../../voice-level/index.js';
4
5
  /**
5
6
  * The keyframes of one lap of the light around a box `w` wide and `h` tall,
6
7
  * clockwise from its top-left corner, each side taking the share of the lap
@@ -46,7 +47,6 @@ export declare function lapOf(w: number, h: number): Keyframe[];
46
47
  * @cssproperty --chat-composer-border-width - Width of the composer's edge, so your theme can remove it.
47
48
  * @cssproperty --chat-composer-glow-color - Color of the focus glow under `glow`. Defaults to the primary color.
48
49
  * @cssproperty --chat-composer-max-block-size - Maximum height the field grows to.
49
- * @cssproperty --dictation-level - How loud the microphone is while the field listens for dictation, from 0 to 1, which the element sets; the ring around the dictation button grows with it.
50
50
  */
51
51
  export declare class HelixChatComposer extends HelixHeadlessChatComposer {
52
52
  static tier: string;
@@ -7,6 +7,7 @@ import { repeat } from 'lit/directives/repeat.js';
7
7
  import { styles as dictationStyles } from '../../shared/dictation/dictation.css.js';
8
8
  import { styles } from './chat-composer.css.js';
9
9
  import '../../tooltip/index.js';
10
+ import '../../voice-level/index.js';
10
11
  const LIGHT_X = '--_chat-composer-light-x';
11
12
  const LIGHT_Y = '--_chat-composer-light-y';
12
13
  const REACH_X = '--_chat-composer-reach-x';
@@ -1,5 +1,6 @@
1
1
  import { HelixHeadlessCodeInput } from '@fusion.dev/helix-headless/code-input';
2
2
  import type { CSSResultGroup } from 'lit';
3
+ import '../../voice-level/index.js';
3
4
  /**
4
5
  * @summary A field for a short piece of code, highlighted as it is typed.
5
6
  *
@@ -21,9 +22,10 @@ import type { CSSResultGroup } from 'lit';
21
22
  * @cssproperty --code-input-font-size - The code's size. Defaults to `text-sm`, and never under 16px on a touch screen.
22
23
  * @cssproperty --code-input-padding - Space between the edge of the field and the code.
23
24
  * @cssproperty --code-input-radius - Corner radius of the field.
24
- * @cssproperty --dictation-level - How loud the microphone is while the field listens for dictation, from 0 to 1, which the element sets; the ring around the dictation button grows with it.
25
25
  */
26
26
  export declare class HelixCodeInput extends HelixHeadlessCodeInput {
27
+ /** The tier its dictation button draws Voice Level from. */
28
+ static tier: string;
27
29
  static styles: CSSResultGroup;
28
30
  }
29
31
  declare global {
@@ -6,7 +6,9 @@ import { styles as fieldStyles } from '../../shared/field/field.css.js';
6
6
  import { styles as fieldFrameStyles } from '../../shared/field/field-frame.css.js';
7
7
  import { styles as fieldStackedStyles } from '../../shared/field/field-stacked.css.js';
8
8
  import { styles } from './code-input-root.css.js';
9
+ import '../../voice-level/index.js';
9
10
  let HelixCodeInput = class HelixCodeInput extends HelixHeadlessCodeInput {
11
+ static { this.tier = 'helix-'; }
10
12
  static { this.styles = [
11
13
  fieldStyles,
12
14
  fieldFrameStyles,
@@ -1,5 +1,6 @@
1
1
  import { HelixHeadlessFilterInput } from '@fusion.dev/helix-headless/filter-input';
2
2
  import type { CSSResultGroup } from 'lit';
3
+ import '../../voice-level/index.js';
3
4
  /**
4
5
  * @summary A text field for filters, with the active filters shown in it as tokens.
5
6
  *
@@ -26,7 +27,6 @@ import type { CSSResultGroup } from 'lit';
26
27
  * @aliases SearchField, FacetInput, TokenInput
27
28
  * @documentation https://fusion.dev/helix/components/filter-input
28
29
  *
29
- * @cssproperty --dictation-level - How loud the microphone is while the field listens for dictation, from 0 to 1, which the element sets; the ring around the dictation button grows with it.
30
30
  * @cssproperty --filter-input-action-padding-inline - The space on either side of a slotted control's label. It follows `--helix-space-xs`, the space a token uses.
31
31
  * @cssproperty --filter-input-action-size - The height of a control slotted at either end. It follows `--helix-size-tag`, so it matches the height of the tokens beside it.
32
32
  * @cssproperty --filter-input-background-color - The fill behind the field.
@@ -12,6 +12,7 @@ import { styles as glyphStyles } from '../../shared/glyph/glyph.css.js';
12
12
  import { styles as liveStyles } from '../../shared/live/live.css.js';
13
13
  import { styles as tagRowStyles } from '../../shared/tags/tags.css.js';
14
14
  import { styles } from './filter-input-root.css.js';
15
+ import '../../voice-level/index.js';
15
16
  let HelixFilterInput = class HelixFilterInput extends HelixHeadlessFilterInput {
16
17
  static { this.tier = 'helix-'; }
17
18
  static { this.styles = [
@@ -1,5 +1,6 @@
1
1
  import { HelixHeadlessInput } from '@fusion.dev/helix-headless/input';
2
2
  import type { CSSResultGroup } from 'lit';
3
+ import '../../voice-level/index.js';
3
4
  /**
4
5
  * @summary A field for a single line of text.
5
6
  *
@@ -22,7 +23,6 @@ import type { CSSResultGroup } from 'lit';
22
23
  * @aliases TextField, Field, TextInput
23
24
  * @documentation https://fusion.dev/helix/components/input
24
25
  *
25
- * @cssproperty --dictation-level - How loud the microphone is while the field listens for dictation, from 0 to 1, which the element sets; the ring around the dictation button grows with it.
26
26
  * @cssproperty --input-background-color - Fill behind the text.
27
27
  * @cssproperty --input-border-color - Edge of the field at rest. Hover, focus and an error still draw their own.
28
28
  * @cssproperty --input-padding-inline - Space between the edge of the field and what is inside it.
@@ -32,6 +32,8 @@ import type { CSSResultGroup } from 'lit';
32
32
  * @replaces input, input[type=text], input[type=email], input[type=password], input[type=search], input[type=tel], input[type=url]
33
33
  */
34
34
  export declare class HelixInput extends HelixHeadlessInput {
35
+ /** The tier its dictation button draws Voice Level from. */
36
+ static tier: string;
35
37
  static styles: CSSResultGroup;
36
38
  }
37
39
  declare global {
@@ -7,7 +7,9 @@ import { styles as fieldFrameStyles } from '../../shared/field/field-frame.css.j
7
7
  import { styles as fieldStackedStyles } from '../../shared/field/field-stacked.css.js';
8
8
  import { styles as glyphStyles } from '../../shared/glyph/glyph.css.js';
9
9
  import { styles } from './input-root.css.js';
10
+ import '../../voice-level/index.js';
10
11
  let HelixInput = class HelixInput extends HelixHeadlessInput {
12
+ static { this.tier = 'helix-'; }
11
13
  static { this.styles = [
12
14
  glyphStyles,
13
15
  fieldStyles,
@@ -1,5 +1,6 @@
1
1
  import { HelixHeadlessListbox } from '@fusion.dev/helix-headless/listbox';
2
2
  import type { CSSResultGroup } from 'lit';
3
+ import '../../voice-level/index.js';
3
4
  /**
4
5
  * @summary A list of options, from which you choose one or several.
5
6
  *
@@ -17,7 +18,6 @@ import type { CSSResultGroup } from 'lit';
17
18
  * @aliases Options, Select list
18
19
  * @documentation https://fusion.dev/helix/components/listbox
19
20
  *
20
- * @cssproperty --dictation-level - How loud the microphone is while the field listens for dictation, from 0 to 1, which the element sets; the ring around the dictation button grows with it.
21
21
  * @cssproperty --listbox-background-color - The panel's fill. Set it to `transparent` when the list is already inside a panel.
22
22
  * @cssproperty --listbox-border - The panel's border, as a shorthand. Set it to `none` when the list is already inside a panel.
23
23
  * @cssproperty --listbox-column-min-inline-size - How narrow a column may be with `layout="grid"`. As many columns as fit are drawn. Defaults to `8rem`.
@@ -7,6 +7,7 @@ import { styles as fieldStyles } from '../../shared/field/field.css.js';
7
7
  import { styles as fieldStackedStyles } from '../../shared/field/field-stacked.css.js';
8
8
  import { styles as liveStyles } from '../../shared/live/live.css.js';
9
9
  import { styles } from './listbox-root.css.js';
10
+ import '../../voice-level/index.js';
10
11
  let HelixListbox = class HelixListbox extends HelixHeadlessListbox {
11
12
  static { this.tier = 'helix-'; }
12
13
  static { this.styles = [
@@ -10,7 +10,8 @@
10
10
  *
11
11
  * What makes this a sandbox rather than a layout is here: `Sandbox.Preview`
12
12
  * hardcodes `sandbox="allow-scripts allow-forms"` on its frame and never
13
- * `allow-same-origin`, which is the pairing that would undo it.
13
+ * `allow-same-origin`, which is the pairing that would undo it. The frame is
14
+ * granted clipboard write, fullscreen, and the microphone.
14
15
  */
15
16
  export { registerCompilerWorker } from './compile.js';
16
17
  export * from './console/sandbox-console.js';
@@ -44,7 +44,23 @@ export interface PreviewSize {
44
44
  size: number;
45
45
  type: 'helix-preview-size';
46
46
  }
47
- export type PreviewMessage = PreviewConsole | PreviewFetch | PreviewPress | PreviewReady | PreviewRender | PreviewRendered | PreviewSize;
47
+ /**
48
+ * The frame asking this page to listen. Its own origin is opaque, so the
49
+ * recognizer there refuses to start.
50
+ */
51
+ export interface PreviewSpeechStart {
52
+ /** Which listening this is, so a late reply cannot land in the next one. */
53
+ id: number;
54
+ /** A BCP 47 tag, such as `en-US`. */
55
+ lang: string;
56
+ type: 'helix-speech-start';
57
+ }
58
+ /** The frame asking this page to stop the listening `id` names. */
59
+ export interface PreviewSpeechStop {
60
+ id: number;
61
+ type: 'helix-speech-stop';
62
+ }
63
+ export type PreviewMessage = PreviewConsole | PreviewFetch | PreviewPress | PreviewReady | PreviewRender | PreviewRendered | PreviewSize | PreviewSpeechStart | PreviewSpeechStop;
48
64
  /**
49
65
  * @summary A frame that renders sandbox source in a separate document.
50
66
  *
@@ -263,6 +279,10 @@ export declare class HelixSandboxPreview extends HelixElement {
263
279
  styles: string;
264
280
  /** Whether the frame has said it is listening. */
265
281
  private ready;
282
+ /** The recognizer running for the frame, on this page. */
283
+ private speech;
284
+ /** Which ask `speech` belongs to. */
285
+ private speechId;
266
286
  /** The source the frame was last sent, which it holds and does not run again. */
267
287
  private sentSource;
268
288
  /** The source the frame held when it last reported an uncaught error. */
@@ -403,17 +423,35 @@ export declare class HelixSandboxPreview extends HelixElement {
403
423
  * where the browser refused to open one.
404
424
  *
405
425
  * **The tab runs the example as this preview does**: in a frame that fills
406
- * it, with this frame's `sandbox`, loading `src` or the built-in document,
407
- * and sent the example once it loads. The example stays on an opaque origin,
408
- * and the tab's `opener` is cleared, so it cannot reach this page. The tab
409
- * shows the example as it was when it opened, and edits after that do not
410
- * reach it. A short inline script in the tab sends the example, so on a page
411
- * whose Content Security Policy refuses inline scripts the tab opens empty,
412
- * as the built-in document does there.
426
+ * it, with this frame's `sandbox` and `allow`, loading `src` or the built-in
427
+ * document, and sent the example once it loads. The example stays on an
428
+ * opaque origin, and the tab's `opener` is cleared, so it cannot reach this
429
+ * page. The tab shows the example as it was when it opened, and edits after
430
+ * that do not reach it. A short inline script in the tab sends the example,
431
+ * so on a page whose Content Security Policy refuses inline scripts the tab
432
+ * opens empty, as the built-in document does there. Dictation in the tab
433
+ * listens through the tab's own recognizer, asked from this page.
413
434
  */
414
435
  open(): Window | null;
415
436
  connectedCallback(): void;
416
437
  disconnectedCallback(): void;
438
+ /**
439
+ * Takes a frame's ask to start or stop listening, and returns whether the
440
+ * message was one. `scope` is the window that holds the frame, whose
441
+ * recognizer listens.
442
+ */
443
+ private speak;
444
+ /**
445
+ * Listens for a frame that cannot listen for itself, and sends each guess
446
+ * back to that frame.
447
+ *
448
+ * **An engine this page registered listens in its place**, so a page that
449
+ * gives its own fields another engine gives it to its examples too. The
450
+ * browser's recognizer listens in `scope`, the window that holds the frame:
451
+ * a tab `open()` makes holds its own. A registered engine runs in this page,
452
+ * so it listens only for the frame this page holds.
453
+ */
454
+ private hear;
417
455
  /**
418
456
  * Loads the frame's document again, and re-sends the example to it.
419
457
  *
@@ -1,6 +1,6 @@
1
1
  var HelixSandboxPreview_1;
2
2
  import { __decorate } from "tslib";
3
- import { customElement, DelayController, HelixConsoleEvent, HelixElement, HelixResizeEvent, words, } from '@fusion.dev/helix-core';
3
+ import { browserSpeech, customElement, DelayController, HelixConsoleEvent, HelixElement, HelixResizeEvent, pageSpeechEngine, recognizeSpeech, words, } from '@fusion.dev/helix-core';
4
4
  const SETTLE = 120;
5
5
  const PAINT_PATIENCE = 1000;
6
6
  import { html, nothing } from 'lit';
@@ -10,6 +10,7 @@ import { defaultFrame, withScript } from '../frame.js';
10
10
  import { isFetch, RELAY_OPEN, relayAddress, relayFetch, relayPrefixes, } from '../relay.js';
11
11
  import { styles as sheet } from './sandbox-preview.css.js';
12
12
  const SANDBOX = 'allow-scripts allow-forms';
13
+ const ALLOW = 'clipboard-write *; fullscreen *; microphone *';
13
14
  const RELATIVE = /(\bfrom\s*|\bimport\s*\(?\s*)(["'])(\.{1,2}\/[^"']+)\2/g;
14
15
  const moduleText = (path, source) => `data:text/javascript;charset=utf-8,${encodeURIComponent(source.replace(RELATIVE, (_, lead, quote, specifier) => `${lead}${quote}${new URL(specifier, `sandbox:/${path}`).href}${quote}`))}`;
15
16
  let HelixSandboxPreview = class HelixSandboxPreview extends HelixElement {
@@ -40,6 +41,9 @@ let HelixSandboxPreview = class HelixSandboxPreview extends HelixElement {
40
41
  return;
41
42
  }
42
43
  const data = event.data;
44
+ if (this.speak(event, window)) {
45
+ return;
46
+ }
43
47
  if (isFetch(data)) {
44
48
  void this.answer(data, event.source);
45
49
  return;
@@ -191,6 +195,7 @@ let HelixSandboxPreview = class HelixSandboxPreview extends HelixElement {
191
195
  tab.document.title = this.label;
192
196
  body.style.margin = '0';
193
197
  const frame = tab.document.createElement('iframe');
198
+ frame.setAttribute('allow', ALLOW);
194
199
  frame.setAttribute('sandbox', SANDBOX);
195
200
  frame.title = this.label;
196
201
  frame.style.cssText = 'block-size: 100vh; border: none; display: block; inline-size: 100vw';
@@ -200,15 +205,18 @@ let HelixSandboxPreview = class HelixSandboxPreview extends HelixElement {
200
205
  else {
201
206
  frame.src = this.frame?.src || this.src;
202
207
  }
203
- if (relayPrefixes(this.relay).length > 0) {
204
- tab.addEventListener('message', (event) => {
205
- if (event.source === frame.contentWindow &&
206
- event.origin === 'null' &&
207
- isFetch(event.data)) {
208
- void this.answer(event.data, event.source);
209
- }
210
- });
211
- }
208
+ const relays = relayPrefixes(this.relay).length > 0;
209
+ tab.addEventListener('message', (event) => {
210
+ if (event.source !== frame.contentWindow || event.origin !== 'null') {
211
+ return;
212
+ }
213
+ if (this.speak(event, tab)) {
214
+ return;
215
+ }
216
+ if (relays && isFetch(event.data)) {
217
+ void this.answer(event.data, event.source);
218
+ }
219
+ });
212
220
  const send = tab.document.createElement('script');
213
221
  send.textContent = `const frame = document.currentScript.previousElementSibling
214
222
  frame.onload = () => frame.contentWindow.postMessage(${JSON.stringify(this.message)}, '*')`;
@@ -222,6 +230,52 @@ frame.onload = () => frame.contentWindow.postMessage(${JSON.stringify(this.messa
222
230
  disconnectedCallback() {
223
231
  super.disconnectedCallback();
224
232
  window.removeEventListener('message', this.handleMessage);
233
+ this.speech?.stop();
234
+ this.speech = undefined;
235
+ }
236
+ speak(event, scope) {
237
+ const data = event.data;
238
+ if (data?.type === 'helix-speech-start') {
239
+ this.hear(data, event.source, scope, event.ports[0]);
240
+ return true;
241
+ }
242
+ if (data?.type === 'helix-speech-stop') {
243
+ if (this.speechId === data.id) {
244
+ this.speech?.stop();
245
+ }
246
+ return true;
247
+ }
248
+ return false;
249
+ }
250
+ hear(ask, source, scope, port) {
251
+ this.speech?.stop();
252
+ this.speechId = ask.id;
253
+ const tell = (kind, text, error, level) => {
254
+ const message = { error, id: ask.id, kind, level, text, type: 'helix-speech-heard' };
255
+ if (port) {
256
+ port.postMessage(message);
257
+ }
258
+ else {
259
+ source.postMessage(message, '*');
260
+ }
261
+ };
262
+ const listener = {
263
+ end: (reason, message) => {
264
+ tell(reason === 'error' ? 'error' : reason === 'stopped' ? 'stopped' : 'end', undefined, message);
265
+ if (this.speechId === ask.id) {
266
+ this.speech = undefined;
267
+ this.speechId = undefined;
268
+ }
269
+ },
270
+ final: (text) => tell('final', text),
271
+ level: (level) => tell('level', undefined, undefined, level),
272
+ partial: (text) => tell('partial', text),
273
+ };
274
+ const engine = pageSpeechEngine();
275
+ this.speech =
276
+ engine !== browserSpeech && scope === window
277
+ ? engine.listen({ lang: ask.lang, listener })
278
+ : recognizeSpeech(ask.lang, listener, { scope });
225
279
  }
226
280
  reload() {
227
281
  const frame = this.frame;
@@ -286,7 +340,7 @@ frame.onload = () => frame.contentWindow.postMessage(${JSON.stringify(this.messa
286
340
  srcdoc=${this.src === '' ? defaultFrame(this.map) : nothing}
287
341
  title=${this.label}
288
342
  aria-describedby=${this.failure === undefined ? nothing : 'error'}
289
- allow="clipboard-write *; fullscreen *"
343
+ allow=${ALLOW}
290
344
  sandbox=${SANDBOX}
291
345
  ></iframe>${this.failure === undefined
292
346
  ? nothing
@@ -1,2 +1,2 @@
1
1
  import { createSheet } from '@fusion.dev/helix-core';
2
- export const styles = createSheet(`[part~="voice-listening"] {box-shadow: 0 0 0 calc(var(--dictation-level, 0.4) * var(--helix-space-2xs) * 1.5) color-mix(in oklab, var(--helix-color-accent) 35%, transparent);color: var(--helix-color-accent);transition: box-shadow var(--helix-duration-fast) linear}@media (prefers-reduced-motion: reduce) {[part~="voice-listening"] {box-shadow: 0 0 0 calc(var(--helix-space-2xs) * 0.6) color-mix(in oklab, var(--helix-color-accent) 35%, transparent);transition: none}}@media print {[part~="voice"] {display: none}}`);
2
+ export const styles = createSheet(`[part="voice-level"] {--voice-level-bars: 5;--voice-level-block-size: var(--icon-size, var(--helix-size-icon));--voice-level-inline-size: var(--icon-size, var(--helix-size-icon));display: block}@media print {[part~="voice"] {display: none}}`);
@@ -1,2 +1,2 @@
1
1
  import { createSheet } from '@fusion.dev/helix-core';
2
- export const styles = createSheet(`[part="field"] {background-color: var(--textarea-background-color, var(--helix-color-well));border: var(--helix-border-thin) solid var(--textarea-border-color, var(--helix-color-border));border-radius: var(--textarea-radius, var(--helix-radii-sm));display: flex;gap: var(--helix-space-2xs);padding: var(--textarea-padding, var(--helix-space-xs));transition: border-color var(--helix-duration-fast) var(--helix-easing-standard), background-color var(--helix-duration-fast) var(--helix-easing-standard)}[part="control"] {line-height: var(--helix-leading-normal);overflow-wrap: break-word;resize: var(--textarea-resize, vertical)}slot[name="start"]::slotted(*), slot[name="end"]::slotted(*) {align-self: flex-start;color: var(--helix-color-foreground-muted);flex: 0 0 auto}[part="clear"], [part~="voice"] {--icon-size: var(--helix-size-icon-tight);all: unset;align-items: center;align-self: flex-start;border-radius: var(--helix-radii-sm);box-sizing: border-box;color: var(--helix-color-foreground-muted);cursor: pointer;display: flex;flex: 0 0 auto;justify-content: center;min-block-size: var(--helix-size-icon-loose);min-inline-size: var(--helix-size-icon-loose);transition: color var(--helix-duration-fast) var(--helix-easing-standard)}[part="clear"]:hover, [part~="voice"]:hover {color: var(--helix-color-foreground)}[part="clear"]:focus-visible, [part~="voice"]:focus-visible {outline: var(--helix-size-focus-ring) solid var(--helix-color-focus-ring);outline-offset: var(--helix-size-focus-offset)}:host([auto-grow]) [part="control"] {overflow: hidden;resize: none}:host(:disabled) [part="field"] {cursor: not-allowed;opacity: 0.5}:host([readonly]) [part="field"] {background-color: transparent}@media print {:host {print-color-adjust: exact}}`);
2
+ export const styles = createSheet(`[part="field"] {background-color: var(--textarea-background-color, var(--helix-color-well));border: var(--helix-border-thin) solid var(--textarea-border-color, var(--helix-color-border));border-radius: var(--textarea-radius, var(--helix-radii-sm));display: flex;gap: var(--helix-space-2xs);padding: var(--textarea-padding, var(--helix-space-xs));position: relative;transition: border-color var(--helix-duration-fast) var(--helix-easing-standard), background-color var(--helix-duration-fast) var(--helix-easing-standard)}[part="control"] {line-height: var(--helix-leading-normal);overflow-wrap: break-word;resize: var(--textarea-resize, vertical)}slot[name="start"]::slotted(*), slot[name="end"]::slotted(*) {align-self: flex-start;color: var(--helix-color-foreground-muted);flex: 0 0 auto}[part="clear"], [part~="voice"] {--icon-size: var(--helix-size-icon-tight);all: unset;align-items: center;align-self: flex-start;border-radius: var(--helix-radii-sm);box-sizing: border-box;color: var(--helix-color-foreground-muted);cursor: pointer;display: flex;flex: 0 0 auto;justify-content: center;min-block-size: var(--helix-size-icon-loose);min-inline-size: var(--helix-size-icon-loose);transition: color var(--helix-duration-fast) var(--helix-easing-standard)}[part="clear"]:hover, [part~="voice"]:hover {color: var(--helix-color-foreground)}[part="clear"]:focus-visible, [part~="voice"]:focus-visible {outline: var(--helix-size-focus-ring) solid var(--helix-color-focus-ring);outline-offset: var(--helix-size-focus-offset)}[part~="voice"] {background-color: transparent;block-size: var(--helix-size-icon-loose);inline-size: var(--helix-size-icon-loose);inset-block-start: calc(var(--textarea-padding, var(--helix-space-xs)) + (1em * var(--helix-leading-normal) - var(--helix-size-icon-loose)) / 2);inset-inline-end: var(--textarea-padding, var(--helix-space-xs));overflow: hidden;position: absolute}[part="field"]:has([part~="voice"]) [part="control"] {padding-inline-end: calc(var(--helix-size-icon-loose) + var(--helix-space-2xs))}[part~="voice"]:hover {background-color: color-mix(in oklab, var(--helix-color-foreground) 8%, transparent)}:host([auto-grow]) [part="control"] {overflow: hidden;resize: none}:host(:disabled) [part="field"] {cursor: not-allowed;opacity: 0.5}:host([readonly]) [part="field"] {background-color: transparent}@media print {:host {print-color-adjust: exact}}`);
@@ -1,5 +1,6 @@
1
1
  import { HelixHeadlessTextarea } from '@fusion.dev/helix-headless/textarea';
2
2
  import type { CSSResultGroup } from 'lit';
3
+ import '../../voice-level/index.js';
3
4
  /**
4
5
  * @summary A multi-line text field.
5
6
  *
@@ -14,7 +15,6 @@ import type { CSSResultGroup } from 'lit';
14
15
  * @aliases Multiline, TextArea
15
16
  * @documentation https://fusion.dev/helix/components/textarea
16
17
  *
17
- * @cssproperty --dictation-level - How loud the microphone is while the field listens for dictation, from 0 to 1, which the element sets; the ring around the dictation button grows with it.
18
18
  * @cssproperty --textarea-background-color - Fill behind the text.
19
19
  * @cssproperty --textarea-border-color - Edge of the field at rest. Hover, focus and an error still draw their own.
20
20
  * @cssproperty --textarea-padding - Space between the edge of the field and the text.
@@ -24,6 +24,8 @@ import type { CSSResultGroup } from 'lit';
24
24
  * @replaces textarea
25
25
  */
26
26
  export declare class HelixTextarea extends HelixHeadlessTextarea {
27
+ /** The tier its dictation button draws Voice Level from. */
28
+ static tier: string;
27
29
  static styles: CSSResultGroup;
28
30
  }
29
31
  declare global {
@@ -5,9 +5,13 @@ import { styles as dictationStyles } from '../../shared/dictation/dictation.css.
5
5
  import { styles as fieldStyles } from '../../shared/field/field.css.js';
6
6
  import { styles as fieldFrameStyles } from '../../shared/field/field-frame.css.js';
7
7
  import { styles as fieldStackedStyles } from '../../shared/field/field-stacked.css.js';
8
+ import { styles as glyphStyles } from '../../shared/glyph/glyph.css.js';
8
9
  import { styles } from './textarea-root.css.js';
10
+ import '../../voice-level/index.js';
9
11
  let HelixTextarea = class HelixTextarea extends HelixHeadlessTextarea {
12
+ static { this.tier = 'helix-'; }
10
13
  static { this.styles = [
14
+ glyphStyles,
11
15
  fieldStyles,
12
16
  fieldFrameStyles,
13
17
  fieldStackedStyles,