@happyvertical/smrt-ui 0.55.5 → 0.55.7

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 (36) hide show
  1. package/README.md +30 -0
  2. package/dist/components/forms/CodeSelect.svelte +12 -2
  3. package/dist/components/forms/CodeSelect.svelte.d.ts.map +1 -1
  4. package/dist/components/forms/Combobox.svelte +4 -3
  5. package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
  6. package/dist/components/forms/DictationButton.svelte +110 -3
  7. package/dist/components/forms/DictationButton.svelte.d.ts.map +1 -1
  8. package/dist/components/forms/DictationStatus.svelte +33 -5
  9. package/dist/components/forms/DictationStatus.svelte.d.ts +2 -0
  10. package/dist/components/forms/DictationStatus.svelte.d.ts.map +1 -1
  11. package/dist/components/forms/FieldLabel.svelte +44 -0
  12. package/dist/components/forms/FieldLabel.svelte.d.ts +20 -0
  13. package/dist/components/forms/FieldLabel.svelte.d.ts.map +1 -0
  14. package/dist/components/forms/FormGroup.svelte +2 -17
  15. package/dist/components/forms/FormGroup.svelte.d.ts.map +1 -1
  16. package/dist/components/forms/code-select-options.d.ts +8 -1
  17. package/dist/components/forms/code-select-options.d.ts.map +1 -1
  18. package/dist/components/forms/code-select-options.js +46 -5
  19. package/dist/components/forms/code-select-types.d.ts +4 -0
  20. package/dist/components/forms/code-select-types.d.ts.map +1 -1
  21. package/dist/components/forms/dictation.svelte.d.ts +84 -2
  22. package/dist/components/forms/dictation.svelte.d.ts.map +1 -1
  23. package/dist/components/forms/dictation.svelte.js +236 -2
  24. package/dist/components/forms/hands-free-capture.d.ts +70 -0
  25. package/dist/components/forms/hands-free-capture.d.ts.map +1 -0
  26. package/dist/components/forms/hands-free-capture.js +19 -0
  27. package/dist/components/forms/hands-free-vad.d.ts +5 -0
  28. package/dist/components/forms/hands-free-vad.d.ts.map +1 -0
  29. package/dist/components/forms/hands-free-vad.js +80 -0
  30. package/dist/components/forms/index.d.ts +2 -0
  31. package/dist/components/forms/index.d.ts.map +1 -1
  32. package/dist/components/forms/index.js +2 -0
  33. package/dist/i18n/strings.ui.d.ts +7 -0
  34. package/dist/i18n/strings.ui.d.ts.map +1 -1
  35. package/dist/i18n/strings.ui.js +7 -0
  36. package/package.json +12 -2
package/README.md CHANGED
@@ -81,6 +81,36 @@ pnpm add @happyvertical/smrt-ui
81
81
  body (`Content-Type` is the recording's type, `?language=&durationMs=`)
82
82
  and answers `{ text }`; 413, 503 and 401/403 become "too long", "not set
83
83
  up" and "not allowed" messages.
84
+ - Hands-free dictation: `new Dictation({ mode: 'hands-free', handsFreeCapture:
85
+ createHandsFreeCapture, … })` keeps the microphone on and writes each
86
+ sentence down when the speaker pauses; one tap on the microphone ends it.
87
+ `createHandsFreeCapture` comes from `@happyvertical/smrt-ui/forms/hands-free`,
88
+ the one entry that imports the optional peer `@happyvertical/speech`
89
+ (>= 0.102.5: on-device voice activity detection with an adaptive noise
90
+ floor, pre-roll and a 30 s split; no audio leaves the page). It needs a
91
+ speech source with `transcribePcm` (smrt-svelte's on-device `whisper-local`
92
+ and `moonshine`); with any of the three missing it is plain press-to-talk.
93
+ Utterances are written down one at a time in the order spoken
94
+ (`dictation.queued` counts those waiting); `speaking` and `level` (0 to 1)
95
+ drive the button: a steady ring while listening, a halo that swells with the
96
+ voice while speaking, a spinner while writing down, all still under
97
+ `prefers-reduced-motion`. `vad` tunes `silenceMs` (default 800),
98
+ `minSpeechMs`, `preRollMs`, `maxUtteranceMs` and `sensitivity`.
99
+ The help text ("Just talk; I write it down when you pause...") is the
100
+ microphone's tooltip and `aria-describedby`, not a line in the form;
101
+ `DictationStatus` keeps a visually hidden polite live region ("Listening",
102
+ "Hearing you...", "Writing it down...") and shows errors. Its `sending` prop
103
+ adds the one short visible hint, "Sending...". The chat composer's
104
+ `sendOnPause` / `sendOnPauseMs` (default 1200) builds send-on-pause on it.
105
+ Half-duplex: browsers do not reliably cancel `speechSynthesis` from the
106
+ microphone, so call `dictation.suspend()` while the page reads a reply aloud
107
+ and `dictation.resume()` after (the chat composer/dock do this from their
108
+ `speaking` prop). The microphone stays open but nothing is heard (an
109
+ utterance in progress is discarded, a ~350 ms guard follows `resume()`);
110
+ `dictation.suspended` drives the dimmed, dashed-outline button and the
111
+ "Paused while the assistant speaks" tooltip/live region. A custom
112
+ `HandsFreeCapture` may implement optional `suspend()` / `resume()`;
113
+ `Dictation` ignores utterances that arrive while suspended either way.
84
114
 
85
115
  ### Link tabs
86
116
 
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { ComponentProps } from 'svelte';
3
3
  import type { CodeSelectProps } from './code-select-types.js';
4
+ import FieldLabel from './FieldLabel.svelte';
4
5
  import Input from './Input.svelte';
5
6
  import Select from './Select.svelte';
6
7
 
@@ -18,9 +19,17 @@ let {
18
19
  locale: _locale,
19
20
  name,
20
21
  form,
22
+ label,
23
+ id: idProp,
24
+ required,
21
25
  ...rest
22
26
  }: Props = $props();
23
27
  const hiddenId = $props.id();
28
+ const labelId = `${hiddenId}-label`;
29
+ const controlId = $derived(idProp ?? `${hiddenId}-control`);
30
+ const labelProps = $derived(
31
+ label ? { id: controlId, 'aria-labelledby': labelId } : { id: idProp },
32
+ );
24
33
  let select: Select | undefined = $state();
25
34
  let input: Input | undefined = $state();
26
35
  const choices = $derived(options.filter((option) => option.value !== ''));
@@ -38,10 +47,11 @@ export function getElement(): HTMLInputElement | HTMLSelectElement | null {
38
47
  return (freeText ? input : select)?.getElement() ?? null;
39
48
  }
40
49
  </script>
50
+ {#if label}<FieldLabel id={labelId} for={controlId} {label} {required} />{/if}
41
51
  {#if freeText}
42
- <Input {...inputRest} bind:this={input} bind:value type="text" {name} {form} {placeholder} disabled={disabled || readOnly} />
52
+ <Input {...inputRest} {...labelProps} {required} bind:this={input} bind:value type="text" {name} {form} {placeholder} disabled={disabled || readOnly} />
43
53
  {:else}
44
- <Select {...rest} bind:this={select} bind:value {name} {form} disabled={disabled || readOnly}>
54
+ <Select {...rest} {...labelProps} {required} bind:this={select} bind:value {name} {form} disabled={disabled || readOnly}>
45
55
  <option value="">{placeholder}</option>
46
56
  {#if retained}<option value={value}>{value}</option>{/if}
47
57
  {#each choices as option}<option value={option.value} disabled={option.disabled}>{option.label ?? option.value}</option>{/each}
@@ -1 +1 @@
1
- {"version":3,"file":"CodeSelect.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/CodeSelect.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAK9D,UAAU,KAAM,SAAQ,eAAe;IACrC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAkDD,QAAA,MAAM,UAAU;iBArBG,IAAI;sBAIC,gBAAgB,GAAG,iBAAiB,GAAG,IAAI;WAiBX,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"CodeSelect.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/CodeSelect.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAM9D,UAAU,KAAM,SAAQ,eAAe;IACrC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA4DD,QAAA,MAAM,UAAU;iBAtBG,IAAI;sBAIC,gBAAgB,GAAG,iBAAiB,GAAG,IAAI;WAkBX,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -17,6 +17,7 @@ import {
17
17
  matchingOption,
18
18
  prepareTextControlValue,
19
19
  } from './control-value-validation.js';
20
+ import FieldLabel from './FieldLabel.svelte';
20
21
  import { useControlRegistration } from './use-control-registration.svelte.js';
21
22
  export interface Props {
22
23
  /** Array of options to select from. */
@@ -334,7 +335,7 @@ useControlRegistration(() => {
334
335
  <div bind:this={rootEl} class="combobox {className}" data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
335
336
  data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
336
337
  data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}>
337
- <label for={inputId}>{label}</label>
338
+ <FieldLabel for={inputId} {label} {required} />
338
339
  <!-- The form posts the committed value (an option id), never the label the person sees. -->
339
340
  {#if name}<input type="hidden" {name} {value} {disabled} />{/if}
340
341
  <input bind:this={inputEl} id={inputId} role="combobox" autocomplete="off" {placeholder} {disabled} required={required && !value} aria-required={required ? 'true' : undefined} value={query}
@@ -346,8 +347,8 @@ useControlRegistration(() => {
346
347
  {#if status !== undefined}<div class="sr-status" role="status" aria-live="polite">{open ? status : ''}</div>{/if}
347
348
  </div>
348
349
  <style>
349
- .combobox { position: relative; display: grid; gap: var(--smrt-spacing-1); color: var(--smrt-color-on-surface); }
350
- label { font: var(--smrt-typography-label-large-font); } input { box-sizing: border-box; width: 100%; padding: var(--smrt-spacing-2) var(--smrt-spacing-3); border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-small); background: var(--smrt-color-surface); color: inherit; }
350
+ .combobox { position: relative; display: grid; gap: 0; color: var(--smrt-color-on-surface); }
351
+ input { box-sizing: border-box; width: 100%; padding: var(--smrt-spacing-2) var(--smrt-spacing-3); border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-small); background: var(--smrt-color-surface); color: inherit; }
351
352
  input:focus { outline: 2px solid var(--smrt-color-primary); outline-offset: 1px; } .options { position: absolute; z-index: var(--smrt-z-index-dropdown); top: 100%; left: 0; right: 0; display: grid; padding: var(--smrt-spacing-1); border: 1px solid var(--smrt-color-outline-variant); border-radius: var(--smrt-radius-small); background: var(--smrt-color-surface-container); box-shadow: var(--smrt-elevation-2); }
352
353
  .options button { padding: var(--smrt-spacing-2) var(--smrt-spacing-3); border: 0; border-radius: var(--smrt-radius-extra-small); background: transparent; color: var(--smrt-color-on-surface); text-align: left; } .options button.active { background: var(--smrt-color-secondary-container); }
353
354
  .options.empty { padding: var(--smrt-spacing-2) var(--smrt-spacing-3); color: var(--smrt-color-on-surface-variant); }
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Combobox.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,OAAO,EAAW,MAAM,QAAQ,CAAC;AAM/C,OAAO,KAAK,EACV,yBAAyB,EACzB,aAAa,EACd,MAAM,0BAA0B,CAAC;AAWlC,MAAM,WAAW,KAAK;IACpB,uCAAuC;IACvC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mDAAmD;IACnD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gDAAgD;IAChD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,aAAa,CAAC,EAAE,OAAO,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;IACzC,gDAAgD;IAChD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwRD,QAAA,MAAM,QAAQ,gDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"Combobox.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Combobox.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,OAAO,EAAW,MAAM,QAAQ,CAAC;AAM/C,OAAO,KAAK,EACV,yBAAyB,EACzB,aAAa,EACd,MAAM,0BAA0B,CAAC;AAYlC,MAAM,WAAW,KAAK;IACpB,uCAAuC;IACvC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mDAAmD;IACnD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gDAAgD;IAChD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gEAAgE;IAChE,aAAa,CAAC,EAAE,OAAO,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;IACzC,gDAAgD;IAChD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAyRD,QAAA,MAAM,QAAQ,gDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -7,6 +7,16 @@
7
7
  * 44px target, named by `aria-label`, `aria-pressed` while listening, and a
8
8
  * listening dot that does not rely on color alone (the name changes too).
9
9
  * Pair it with `DictationStatus` for the words ("Listening…", errors).
10
+ *
11
+ * Hands-free (`mode: 'hands-free'`): a steady ring means it is listening,
12
+ * a halo that swells with the voice means someone is speaking, and a small
13
+ * spinner means sentences are being written down. The ring is the
14
+ * non-motion cue; the halo and spinner stop moving for people who ask for
15
+ * reduced motion. While the page speaks a reply (`Dictation.suspend()`) the
16
+ * button dims with a dashed outline and its tooltip reads "Paused while the
17
+ * assistant speaks". Tapping ends it either way. The help text ("Just talk;
18
+ * I write it down when you pause...") is the tooltip and the button's
19
+ * `aria-describedby`, never a line of text in the form.
10
20
  */
11
21
  import { M } from '../../i18n/strings.ui.js';
12
22
  import { useI18n } from '../../i18n/use-i18n.js';
@@ -40,6 +50,11 @@ let {
40
50
 
41
51
  const { t } = useI18n();
42
52
  const active = $derived(dictation.active);
53
+ const handsFree = $derived(dictation.handsFree);
54
+ // Paused while the assistant speaks (half-duplex): not hearing, still open.
55
+ const paused = $derived(active && handsFree && dictation.suspended);
56
+ const speaking = $derived(active && handsFree && dictation.speaking && !paused);
57
+ const writing = $derived(handsFree && dictation.queued > 0);
43
58
  // While a recording is being written down, a tap would do nothing.
44
59
  const busy = $derived(dictation.state === 'transcribing');
45
60
  const name = $derived(
@@ -47,6 +62,22 @@ const name = $derived(
47
62
  ? (stopLabel ?? t(M['ui.dictation.stop']))
48
63
  : (label ?? t(M['ui.dictation.start'])),
49
64
  );
65
+ const uid = $props.id();
66
+ const helpId = `smrt-dictation-help-${uid}`;
67
+ const hint = $derived(
68
+ active
69
+ ? handsFree
70
+ ? paused
71
+ ? t(M['ui.dictation.paused_speaking'])
72
+ : t(M['ui.dictation.listening_hands_free'])
73
+ : name
74
+ : (title ??
75
+ t(
76
+ dictation.wantsHandsFree
77
+ ? M['ui.dictation.start_hands_free']
78
+ : M['ui.dictation.start_hint'],
79
+ )),
80
+ );
50
81
 
51
82
  function handleClick() {
52
83
  // A tap is a user gesture: wake the audio so the ready beep can play.
@@ -60,14 +91,19 @@ function handleClick() {
60
91
  type="button"
61
92
  variant="ghost"
62
93
  size="sm"
63
- class={`smrt-dictation-button${active ? ' smrt-dictation-button--listening' : ''} ${className}`.trim()}
94
+ class={`smrt-dictation-button${active ? ' smrt-dictation-button--listening' : ''}${active && handsFree ? ' smrt-dictation-button--hands-free' : ''}${speaking ? ' smrt-dictation-button--speaking' : ''}${paused ? ' smrt-dictation-button--paused' : ''} ${className}`.trim()}
64
95
  aria-label={name}
65
96
  aria-pressed={active}
66
97
  aria-busy={busy || undefined}
67
- title={active ? name : (title ?? t(M['ui.dictation.start_hint']))}
98
+ title={hint}
99
+ aria-describedby={hint !== name ? helpId : undefined}
68
100
  disabled={disabled || busy}
69
101
  onclick={handleClick}
70
102
  data-dictation-state={dictation.state}
103
+ data-dictation-mode={handsFree ? 'hands-free' : undefined}
104
+ data-dictation-speaking={speaking || undefined}
105
+ data-dictation-paused={paused || undefined}
106
+ style={speaking ? `--smrt-dictation-level: ${dictation.level}` : undefined}
71
107
  >
72
108
  <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
73
109
  <rect x="9" y="2" width="6" height="12" rx="3"></rect>
@@ -77,7 +113,13 @@ function handleClick() {
77
113
  {#if active}
78
114
  <span class="smrt-dictation-dot" aria-hidden="true"></span>
79
115
  {/if}
116
+ {#if writing || (handsFree && dictation.state === 'transcribing')}
117
+ <span class="smrt-dictation-writing" aria-hidden="true"></span>
118
+ {/if}
80
119
  </Button>
120
+ {#if hint !== name}
121
+ <span id={helpId} class="smrt-dictation-help">{hint}</span>
122
+ {/if}
81
123
 
82
124
  <style>
83
125
  :global(.smrt-dictation-button) {
@@ -99,6 +141,16 @@ function handleClick() {
99
141
  color: var(--smrt-color-on-error-container, #410002);
100
142
  }
101
143
 
144
+ /* Read out with the button's name; never shown. */
145
+ .smrt-dictation-help {
146
+ position: absolute;
147
+ inline-size: 1px;
148
+ block-size: 1px;
149
+ overflow: hidden;
150
+ clip-path: inset(50%);
151
+ white-space: nowrap;
152
+ }
153
+
102
154
  .smrt-dictation-dot {
103
155
  position: absolute;
104
156
  inset-block-start: 6px;
@@ -110,6 +162,54 @@ function handleClick() {
110
162
  animation: smrt-dictation-pulse 1.2s ease-in-out infinite;
111
163
  }
112
164
 
165
+ /* Hands-free: a steady ring while listening ... */
166
+ :global(.smrt-dictation-button--hands-free) {
167
+ box-shadow: 0 0 0 2px var(--smrt-color-error, #ba1a1a);
168
+ }
169
+
170
+ /* ... and a halo that swells with the voice while someone is speaking. */
171
+ :global(.smrt-dictation-button--speaking) {
172
+ box-shadow:
173
+ 0 0 0 3px var(--smrt-color-error, #ba1a1a),
174
+ 0 0 0
175
+ calc(4px + 10px * var(--smrt-dictation-level, 0))
176
+ color-mix(in srgb, var(--smrt-color-error, #ba1a1a) 28%, transparent);
177
+ transition: box-shadow 80ms linear;
178
+ }
179
+
180
+ /* Paused while the assistant speaks: a dashed, dimmed ring (not color alone). */
181
+ :global(.smrt-dictation-button--paused) {
182
+ opacity: 0.6;
183
+ box-shadow: none;
184
+ outline: 2px dashed var(--smrt-color-outline, #73777f);
185
+ outline-offset: -2px;
186
+ }
187
+
188
+ :global(.smrt-dictation-button--paused) .smrt-dictation-dot {
189
+ animation: none;
190
+ opacity: 0.4;
191
+ }
192
+
193
+ /* Sentences are being written down. */
194
+ .smrt-dictation-writing {
195
+ position: absolute;
196
+ inset-block-end: 6px;
197
+ inset-inline-end: 6px;
198
+ inline-size: 10px;
199
+ block-size: 10px;
200
+ border-radius: 50%;
201
+ border: 2px solid var(--smrt-color-outline-variant, #c4c6cf);
202
+ border-block-start-color: var(--smrt-color-primary, #2f5ea8);
203
+ background: var(--smrt-color-surface, #fff);
204
+ animation: smrt-dictation-spin 0.9s linear infinite;
205
+ }
206
+
207
+ @keyframes smrt-dictation-spin {
208
+ to {
209
+ transform: rotate(360deg);
210
+ }
211
+ }
212
+
113
213
  @keyframes smrt-dictation-pulse {
114
214
  0%,
115
215
  100% {
@@ -121,8 +221,15 @@ function handleClick() {
121
221
  }
122
222
 
123
223
  @media (prefers-reduced-motion: reduce) {
124
- .smrt-dictation-dot {
224
+ .smrt-dictation-dot,
225
+ .smrt-dictation-writing {
125
226
  animation: none;
126
227
  }
228
+
229
+ /* Still ringed, but the halo no longer animates with the voice. */
230
+ :global(.smrt-dictation-button--speaking) {
231
+ transition: none;
232
+ box-shadow: 0 0 0 4px var(--smrt-color-error, #ba1a1a);
233
+ }
127
234
  }
128
235
  </style>
@@ -1 +1 @@
1
- {"version":3,"file":"DictationButton.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/DictationButton.svelte.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIvD,MAAM,WAAW,KAAK;IACpB,kDAAkD;IAClD,SAAS,EAAE,SAAS,CAAC;IACrB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoDD,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"DictationButton.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/DictationButton.svelte.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIvD,MAAM,WAAW,KAAK;IACpB,kDAAkD;IAClD,SAAS,EAAE,SAAS,CAAC;IACrB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA+ED,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -2,7 +2,13 @@
2
2
  /**
3
3
  * DictationStatus — what dictation is doing, in words (see `Dictation`).
4
4
  *
5
- * "Listening…" (with the words heard so far) while the microphone is on,
5
+ * "Listening…" (with the words heard so far) while the microphone is on.
6
+ * Hands-free is announced, not shown: the line is visually hidden and says
7
+ * "Listening", "Hearing you…" while someone is speaking and "Writing it down…"
8
+ * while sentences are being written down (the long help text is the
9
+ * microphone's tooltip) and "Paused while the assistant speaks" while
10
+ * listening is suspended. "Sending…" (`sending`) is the one short hands-free
11
+ * hint that stays visible, since a message is about to leave.
6
12
  * "Writing it down…" while a recording is turned into text, and
7
13
  * a plain explanation when it cannot listen: the browser cannot turn speech
8
14
  * into text, the microphone is blocked, or nothing was heard. Nothing while
@@ -15,20 +21,28 @@ import type { Dictation, DictationErrorKind } from './dictation.svelte.js';
15
21
  export interface Props {
16
22
  /** The dictation session whose listening state and messages this shows. */
17
23
  dictation: Dictation;
24
+ /** A message is about to be sent (hands-free send on pause): shows "Sending…". */
25
+ sending?: boolean;
18
26
  /** Extra class names. */
19
27
  class?: string;
20
28
  }
21
29
 
22
- let { dictation, class: className = '' }: Props = $props();
30
+ let { dictation, sending = false, class: className = '' }: Props = $props();
23
31
 
24
32
  const { t } = useI18n();
25
33
  const showLine = $derived(
26
- dictation.state === 'starting' ||
34
+ sending ||
35
+ dictation.state === 'starting' ||
27
36
  dictation.state === 'listening' ||
28
37
  dictation.state === 'stopping' ||
29
38
  dictation.state === 'transcribing',
30
39
  );
31
40
 
41
+ // Hands-free states are announced to screen readers only.
42
+ const announceOnly = $derived(
43
+ !sending && dictation.handsFree && dictation.state === 'listening',
44
+ );
45
+
32
46
  function errorText(kind: DictationErrorKind | null): string {
33
47
  switch (kind) {
34
48
  case 'unsupported':
@@ -47,6 +61,8 @@ function errorText(kind: DictationErrorKind | null): string {
47
61
  return t(M['ui.dictation.not_transcribed']);
48
62
  case 'unavailable':
49
63
  return t(M['ui.dictation.unavailable']);
64
+ case 'model-missing':
65
+ return t(M['ui.dictation.model_missing']);
50
66
  case 'forbidden':
51
67
  return t(M['ui.dictation.forbidden']);
52
68
  default:
@@ -58,12 +74,24 @@ function errorText(kind: DictationErrorKind | null): string {
58
74
  <div class={`smrt-dictation-status ${className}`.trim()}>
59
75
  <p
60
76
  class="smrt-dictation-status-line"
61
- class:smrt-dictation-status--empty={!showLine}
77
+ class:smrt-dictation-status--empty={!showLine || announceOnly}
62
78
  role="status"
63
79
  aria-live="polite"
64
80
  >
65
- {#if dictation.state === 'starting'}
81
+ {#if sending}
82
+ {t(M['ui.dictation.sending'])}
83
+ {:else if dictation.state === 'starting'}
66
84
  {t(M['ui.dictation.starting'])}
85
+ {:else if dictation.handsFree && dictation.state === 'listening'}
86
+ {#if dictation.suspended}
87
+ {t(M['ui.dictation.paused_speaking'])}
88
+ {:else if dictation.queued > 0}
89
+ {t(M['ui.dictation.transcribing'])}
90
+ {:else if dictation.speaking}
91
+ {t(M['ui.dictation.hearing'])}
92
+ {:else}
93
+ {t(M['ui.dictation.listening_short'])}
94
+ {/if}
67
95
  {:else if dictation.state === 'listening' || dictation.state === 'stopping'}
68
96
  <span class="smrt-dictation-status-dot" aria-hidden="true"></span>
69
97
  {t(M['ui.dictation.listening'])}
@@ -2,6 +2,8 @@ import type { Dictation } from './dictation.svelte.js';
2
2
  export interface Props {
3
3
  /** The dictation session whose listening state and messages this shows. */
4
4
  dictation: Dictation;
5
+ /** A message is about to be sent (hands-free send on pause): shows "Sending…". */
6
+ sending?: boolean;
5
7
  /** Extra class names. */
6
8
  class?: string;
7
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DictationStatus.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/DictationStatus.svelte.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAsB,MAAM,uBAAuB,CAAC;AAG3E,MAAM,WAAW,KAAK;IACpB,2EAA2E;IAC3E,SAAS,EAAE,SAAS,CAAC;IACrB,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwED,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"DictationStatus.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/DictationStatus.svelte.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAsB,MAAM,uBAAuB,CAAC;AAG3E,MAAM,WAAW,KAAK;IACpB,2EAA2E;IAC3E,SAAS,EAAE,SAAS,CAAC;IACrB,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA4FD,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -0,0 +1,44 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The one label treatment for generated form fields: a plain label above its
4
+ * control. Every field component (text, money, date, select, relation, …) and
5
+ * every read-only field display renders this, so size, weight, colour and
6
+ * spacing cannot drift between field types.
7
+ */
8
+ interface Props {
9
+ /** Label text. */
10
+ label: string;
11
+ /** Id of the labelled control (`for`). Omit for read-only displays. */
12
+ for?: string;
13
+ /** Id of the label element, for `aria-labelledby` on the control. */
14
+ id?: string;
15
+ /** Shows the required marker. */
16
+ required?: boolean;
17
+ }
18
+ let { label, for: forId, id, required = false }: Props = $props();
19
+ </script>
20
+
21
+ {#if forId}
22
+ <label {id} for={forId} class="smrt-field-label">{label}{#if required}<span class="required" aria-hidden="true">*</span>{/if}</label>
23
+ {:else}
24
+ <span {id} class="smrt-field-label">{label}{#if required}<span class="required" aria-hidden="true">*</span>{/if}</span>
25
+ {/if}
26
+
27
+ <style>
28
+ .smrt-field-label {
29
+ display: block;
30
+ margin-bottom: var(--smrt-spacing-1, 4px);
31
+ font-size: var(--smrt-typography-label-large-size, 0.875rem);
32
+ font-weight: var(--smrt-typography-weight-medium, 500);
33
+ line-height: var(--smrt-typography-label-large-line-height, 1.4);
34
+ letter-spacing: normal;
35
+ text-transform: none;
36
+ font-family: inherit;
37
+ color: var(--smrt-color-on-surface, #374151);
38
+ }
39
+
40
+ .required {
41
+ color: var(--smrt-color-error, #ba1a1a);
42
+ margin-left: var(--smrt-spacing-1, 4px);
43
+ }
44
+ </style>
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The one label treatment for generated form fields: a plain label above its
3
+ * control. Every field component (text, money, date, select, relation, …) and
4
+ * every read-only field display renders this, so size, weight, colour and
5
+ * spacing cannot drift between field types.
6
+ */
7
+ interface Props {
8
+ /** Label text. */
9
+ label: string;
10
+ /** Id of the labelled control (`for`). Omit for read-only displays. */
11
+ for?: string;
12
+ /** Id of the label element, for `aria-labelledby` on the control. */
13
+ id?: string;
14
+ /** Shows the required marker. */
15
+ required?: boolean;
16
+ }
17
+ declare const FieldLabel: import("svelte").Component<Props, {}, "">;
18
+ type FieldLabel = ReturnType<typeof FieldLabel>;
19
+ export default FieldLabel;
20
+ //# sourceMappingURL=FieldLabel.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FieldLabel.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/FieldLabel.svelte.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,UAAU,KAAK;IACb,kBAAkB;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAgBD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { ControlInteractionOptions } from './control-interaction.js';
4
+ import FieldLabel from './FieldLabel.svelte';
4
5
  import {
5
6
  type FormGroupContextValue,
6
7
  nextFieldId,
@@ -62,12 +63,7 @@ setFormGroupContext(
62
63
  </script>
63
64
 
64
65
  <div class="form-group">
65
- <label for={fieldId} class="form-label">
66
- {label}
67
- {#if required}
68
- <span class="required" aria-hidden="true">*</span>
69
- {/if}
70
- </label>
66
+ <FieldLabel for={fieldId} {label} {required} />
71
67
  {@render children()}
72
68
  {#if hintId}
73
69
  <p id={hintId} class="form-hint">{hint}</p>
@@ -82,18 +78,7 @@ setFormGroupContext(
82
78
  margin-bottom: 1rem;
83
79
  }
84
80
 
85
- .form-label {
86
- display: block;
87
- font-size: var(--smrt-typography-label-large-size, 0.875rem);
88
- font-weight: var(--smrt-typography-weight-medium, 500);
89
- color: var(--smrt-color-on-surface, #374151);
90
- margin-bottom: 0.375rem;
91
- }
92
81
 
93
- .required {
94
- color: var(--smrt-color-error, #ba1a1a);
95
- margin-left: 0.125rem;
96
- }
97
82
 
98
83
  .form-hint {
99
84
  margin: 0.25rem 0 0;
@@ -1 +1 @@
1
- {"version":3,"file":"FormGroup.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/FormGroup.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAQ1E,MAAM,WAAW,KAAK;IACpB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,6DAA6D;IAC7D,QAAQ,EAAE,OAAO,CAAC;CACnB;AAgED,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"FormGroup.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/FormGroup.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAS1E,MAAM,WAAW,KAAK;IACpB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,6DAA6D;IAC7D,QAAQ,EAAE,OAAO,CAAC;CACnB;AA4DD,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -1,5 +1,12 @@
1
1
  import type { CodeSelectOption } from './code-select-types.js';
2
- /** Build localized choices from the shared ISO currency metadata. */
2
+ /**
3
+ * ISO 4217 codes that are not circulating currencies: funds codes, precious
4
+ * metals, the IMF unit and bond-market units, and the testing / "no currency"
5
+ * codes. They stay valid stored values (the select retains an unknown value)
6
+ * but are not offered as choices.
7
+ */
8
+ export declare const NON_CIRCULATING_CURRENCY_CODES: ReadonlySet<string>;
9
+ /** Build localized choices for circulating currencies from the shared ISO metadata. */
3
10
  export declare function currencyOptions(locale: string): CodeSelectOption[];
4
11
  /** Build localized ISO alpha-2 country choices. */
5
12
  export declare function countryOptions(locale: string): CodeSelectOption[];
@@ -1 +1 @@
1
- {"version":3,"file":"code-select-options.d.ts","sourceRoot":"","sources":["../../../src/components/forms/code-select-options.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAiT/D,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAMlE;AACD,mDAAmD;AACnD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAGjE;AACD,8EAA8E;AAC9E,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,GACd,SAAS,gBAAgB,EAAE,GAAG,SAAS,CAEzC"}
1
+ {"version":3,"file":"code-select-options.d.ts","sourceRoot":"","sources":["../../../src/components/forms/code-select-options.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAiT/D;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,EAAE,WAAW,CAAC,MAAM,CA4B7D,CAAC;AACH,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAWlE;AACD,mDAAmD;AACnD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAGjE;AACD,8EAA8E;AAC9E,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,GACd,SAAS,gBAAgB,EAAE,GAAG,SAAS,CAEzC"}
@@ -296,13 +296,54 @@ function displayNames(locale, type) {
296
296
  return new Intl.DisplayNames('en', { type });
297
297
  }
298
298
  }
299
- /** Build localized choices from the shared ISO currency metadata. */
299
+ /**
300
+ * ISO 4217 codes that are not circulating currencies: funds codes, precious
301
+ * metals, the IMF unit and bond-market units, and the testing / "no currency"
302
+ * codes. They stay valid stored values (the select retains an unknown value)
303
+ * but are not offered as choices.
304
+ */
305
+ export const NON_CIRCULATING_CURRENCY_CODES = new Set([
306
+ // Funds codes
307
+ 'BOV',
308
+ 'CHE',
309
+ 'CHW',
310
+ 'CLF',
311
+ 'COU',
312
+ 'MXV',
313
+ 'USN',
314
+ 'USS',
315
+ 'UYI',
316
+ 'UYW',
317
+ // Precious metals
318
+ 'XAG',
319
+ 'XAU',
320
+ 'XPD',
321
+ 'XPT',
322
+ // Bond-market units, IMF special drawing rights, other supranational units
323
+ 'XBA',
324
+ 'XBB',
325
+ 'XBC',
326
+ 'XBD',
327
+ 'XDR',
328
+ 'XSU',
329
+ 'XUA',
330
+ // Testing and "no currency"
331
+ 'XTS',
332
+ 'XXX',
333
+ ]);
334
+ /** Build localized choices for circulating currencies from the shared ISO metadata. */
300
335
  export function currencyOptions(locale) {
301
336
  const names = displayNames(locale, 'currency');
302
- return [...ISO_4217_MINOR_UNITS.keys()].sort().map((value) => ({
303
- value,
304
- label: `${value} — ${names.of(value) ?? value}`,
305
- }));
337
+ return [...ISO_4217_MINOR_UNITS.keys()]
338
+ .filter((value) => !NON_CIRCULATING_CURRENCY_CODES.has(value))
339
+ .sort()
340
+ .flatMap((value) => {
341
+ const name = names.of(value);
342
+ // A code the runtime cannot name (shown as "XYZ — XYZ") is not a currency to offer.
343
+ if (!name || name === value)
344
+ return [];
345
+ return [{ value, label: `${value} — ${name}` }];
346
+ });
306
347
  }
307
348
  /** Build localized ISO alpha-2 country choices. */
308
349
  export function countryOptions(locale) {
@@ -21,6 +21,10 @@ export interface CodeSelectProps extends Omit<HTMLSelectAttributes, 'children' |
21
21
  interaction?: ControlInteractionOptions | false;
22
22
  /** Custom choices replace defaults; an empty list is an intentional override. */
23
23
  options?: readonly CodeSelectOption[];
24
+ /** Visible label rendered above the control with the shared field label; becomes its accessible name. */
25
+ label?: string;
26
+ /** Shows the required marker on `label`. */
27
+ required?: boolean;
24
28
  /** Locale used for built-in currency/country display names; invalid locales use English. */
25
29
  locale?: string;
26
30
  /** Empty choice label. Defaults to an em dash and never auto-selects a code. */
@@ -1 +1 @@
1
- {"version":3,"file":"code-select-types.d.ts","sourceRoot":"","sources":["../../../src/components/forms/code-select-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAE1E,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AACD,uFAAuF;AACvF,MAAM,WAAW,eACf,SAAQ,IAAI,CACV,oBAAoB,EACpB,UAAU,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAC5C;IACD,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,sDAAsD;IACtD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,iFAAiF;IACjF,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACtC,4FAA4F;IAC5F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AACD,wDAAwD;AACxD,MAAM,WAAW,mBAAoB,SAAQ,eAAe;CAAG;AAC/D,wDAAwD;AACxD,MAAM,WAAW,kBAAmB,SAAQ,eAAe;CAAG;AAC9D,6FAA6F;AAC7F,MAAM,WAAW,mBACf,SAAQ,IAAI,CAAC,eAAe,EAAE,MAAM,aAAa,CAAC,iBAAiB,CAAC,CAAC,EACnE,IAAI,CAAC,aAAa,CAAC,gBAAgB,GAAG,iBAAiB,CAAC,EAAE,UAAU,CAAC;IACvE,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
1
+ {"version":3,"file":"code-select-types.d.ts","sourceRoot":"","sources":["../../../src/components/forms/code-select-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAE1E,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AACD,uFAAuF;AACvF,MAAM,WAAW,eACf,SAAQ,IAAI,CACV,oBAAoB,EACpB,UAAU,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAC5C;IACD,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,sDAAsD;IACtD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD,iFAAiF;IACjF,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACtC,yGAAyG;IACzG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4FAA4F;IAC5F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AACD,wDAAwD;AACxD,MAAM,WAAW,mBAAoB,SAAQ,eAAe;CAAG;AAC/D,wDAAwD;AACxD,MAAM,WAAW,kBAAmB,SAAQ,eAAe;CAAG;AAC9D,6FAA6F;AAC7F,MAAM,WAAW,mBACf,SAAQ,IAAI,CAAC,eAAe,EAAE,MAAM,aAAa,CAAC,iBAAiB,CAAC,CAAC,EACnE,IAAI,CAAC,aAAa,CAAC,gBAAgB,GAAG,iBAAiB,CAAC,EAAE,UAAU,CAAC;IACvE,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}