@k2b/ui 0.40.0 → 0.41.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 (88) hide show
  1. package/dist/browser/chat/ChatComposer.js +759 -570
  2. package/dist/browser/chat/ChatComposer.js.map +1 -1
  3. package/dist/browser/inputs/ChoiceGroups.js +30 -18
  4. package/dist/browser/inputs/ChoiceGroups.js.map +1 -1
  5. package/dist/browser/inputs/Select.js +32 -19
  6. package/dist/browser/inputs/Select.js.map +1 -1
  7. package/dist/browser/inputs/SpecialInputs.js +54 -25
  8. package/dist/browser/inputs/SpecialInputs.js.map +1 -1
  9. package/dist/browser/inputs/choice.js +4 -1
  10. package/dist/browser/inputs/choice.js.map +1 -1
  11. package/dist/browser/inputs/dictation/Dictation.js +546 -0
  12. package/dist/browser/inputs/dictation/Dictation.js.map +1 -0
  13. package/dist/browser/inputs/dictation/create-dictation.js +324 -0
  14. package/dist/browser/inputs/dictation/create-dictation.js.map +1 -0
  15. package/dist/browser/inputs/dictation/dictation-messages.js +115 -0
  16. package/dist/browser/inputs/dictation/dictation-messages.js.map +1 -0
  17. package/dist/browser/inputs/dictation/text-dictation.js +152 -0
  18. package/dist/browser/inputs/dictation/text-dictation.js.map +1 -0
  19. package/dist/browser/inputs/dictation/types.js +2 -0
  20. package/dist/browser/inputs/dictation/types.js.map +1 -0
  21. package/dist/browser/inputs/icon-messages.js +29 -2
  22. package/dist/browser/inputs/icon-messages.js.map +1 -1
  23. package/dist/browser/inputs/icon-texts.js +172 -0
  24. package/dist/browser/inputs/icon-texts.js.map +1 -0
  25. package/dist/browser/inputs/index.js +2 -0
  26. package/dist/browser/inputs/index.js.map +1 -1
  27. package/dist/browser/layout/AnchoredPopover.js +1 -1
  28. package/dist/browser/layout/AnchoredPopover.js.map +1 -1
  29. package/dist/browser/layout/SettingsModal.js +10 -5
  30. package/dist/browser/layout/SettingsModal.js.map +1 -1
  31. package/dist/browser/text-editor/TextEditor.js +74 -25
  32. package/dist/browser/text-editor/TextEditor.js.map +1 -1
  33. package/dist/browser/text-editor/dictation.js +166 -0
  34. package/dist/browser/text-editor/dictation.js.map +1 -0
  35. package/dist/browser/text-editor.js +1 -0
  36. package/dist/browser/text-editor.js.map +1 -1
  37. package/dist/ssr/chat/ChatComposer.js +271 -99
  38. package/dist/ssr/chat/ChatComposer.js.map +1 -1
  39. package/dist/ssr/inputs/ChoiceGroups.js +12 -3
  40. package/dist/ssr/inputs/ChoiceGroups.js.map +1 -1
  41. package/dist/ssr/inputs/Select.js +8 -2
  42. package/dist/ssr/inputs/Select.js.map +1 -1
  43. package/dist/ssr/inputs/SpecialInputs.js +54 -25
  44. package/dist/ssr/inputs/SpecialInputs.js.map +1 -1
  45. package/dist/ssr/inputs/choice.js +4 -1
  46. package/dist/ssr/inputs/choice.js.map +1 -1
  47. package/dist/ssr/inputs/dictation/Dictation.js +386 -0
  48. package/dist/ssr/inputs/dictation/Dictation.js.map +1 -0
  49. package/dist/ssr/inputs/dictation/create-dictation.js +324 -0
  50. package/dist/ssr/inputs/dictation/create-dictation.js.map +1 -0
  51. package/dist/ssr/inputs/dictation/dictation-messages.js +115 -0
  52. package/dist/ssr/inputs/dictation/dictation-messages.js.map +1 -0
  53. package/dist/ssr/inputs/dictation/text-dictation.js +152 -0
  54. package/dist/ssr/inputs/dictation/text-dictation.js.map +1 -0
  55. package/dist/ssr/inputs/dictation/types.js +2 -0
  56. package/dist/ssr/inputs/dictation/types.js.map +1 -0
  57. package/dist/ssr/inputs/icon-messages.js +29 -2
  58. package/dist/ssr/inputs/icon-messages.js.map +1 -1
  59. package/dist/ssr/inputs/icon-texts.js +172 -0
  60. package/dist/ssr/inputs/icon-texts.js.map +1 -0
  61. package/dist/ssr/inputs/index.js +2 -0
  62. package/dist/ssr/inputs/index.js.map +1 -1
  63. package/dist/ssr/layout/AnchoredPopover.js +1 -1
  64. package/dist/ssr/layout/AnchoredPopover.js.map +1 -1
  65. package/dist/ssr/layout/SettingsModal.js +10 -5
  66. package/dist/ssr/layout/SettingsModal.js.map +1 -1
  67. package/dist/ssr/text-editor/TextEditor.js +74 -25
  68. package/dist/ssr/text-editor/TextEditor.js.map +1 -1
  69. package/dist/ssr/text-editor/dictation.js +166 -0
  70. package/dist/ssr/text-editor/dictation.js.map +1 -0
  71. package/dist/ssr/text-editor.js +1 -0
  72. package/dist/ssr/text-editor.js.map +1 -1
  73. package/dist/styles.css +1 -1
  74. package/dist/types/chat/ChatComposer.d.ts +9 -0
  75. package/dist/types/inputs/SpecialInputs.d.ts +8 -0
  76. package/dist/types/inputs/dictation/Dictation.d.ts +26 -0
  77. package/dist/types/inputs/dictation/create-dictation.d.ts +77 -0
  78. package/dist/types/inputs/dictation/dictation-messages.d.ts +54 -0
  79. package/dist/types/inputs/dictation/text-dictation.d.ts +36 -0
  80. package/dist/types/inputs/dictation/types.d.ts +74 -0
  81. package/dist/types/inputs/icon-messages.d.ts +14 -0
  82. package/dist/types/inputs/icon-texts.d.ts +13 -0
  83. package/dist/types/inputs/index.d.ts +5 -0
  84. package/dist/types/layout/AnchoredPopover.d.ts +5 -0
  85. package/dist/types/text-editor/TextEditor.d.ts +10 -1
  86. package/dist/types/text-editor/dictation.d.ts +20 -0
  87. package/dist/types/text-editor.d.ts +1 -0
  88. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { type JSX } from "solid-js";
2
+ import type { DictationAlternative, DictationSource } from "../inputs/dictation/types";
2
3
  import type { ChatAction, ChatAttachment, ChatComposerEmoji, ChatComposerMicrophone, ChatComposerState, ChatContextUsageData, ChatMention, ChatModelOption, ChatSubmitInput } from "./types";
3
4
  export type ChatCommandContext = {
4
5
  setValue: (value: string) => void;
@@ -237,6 +238,14 @@ export type ChatComposerProps = {
237
238
  emoji?: ChatComposerEmoji;
238
239
  /** A microphone at a fixed place: a tap dictates, holding it or its menu asks for a voice message. */
239
240
  microphone?: ChatComposerMicrophone;
241
+ /**
242
+ * Live dictation at the microphone's place, before Send. A press starts it; while it runs, the dictation bar stands in
243
+ * for the footer and the words land at the caret, also when the person types elsewhere. Sending waits until it ended.
244
+ */
245
+ dictation?: {
246
+ source: DictationSource;
247
+ alternative?: DictationAlternative;
248
+ };
240
249
  /** One line above the field. The conversation variant always keeps its height; the default shows it only with content. */
241
250
  hint?: JSX.Element;
242
251
  /**
@@ -18,4 +18,12 @@ export type IconInputProps = ValueFieldProps<string | null> & {
18
18
  gridSize?: SelectGridSize;
19
19
  name?: string;
20
20
  };
21
+ /**
22
+ * Every query word has to appear in the icon's names or keywords: as the start
23
+ * of a word, or anywhere in one once it has three letters, so German compounds
24
+ * such as "Kreditkarte" match "karte". A loose subsequence match let "gro" find
25
+ * "warninG eRrOr", so it is not used. Ranked by where the query hits: the label
26
+ * start, a label word, a keyword, then anywhere.
27
+ */
28
+ export declare const searchIconOptions: <T extends IconOption>(source: readonly T[], query: string, limit: number) => T[];
21
29
  export declare function IconInput(props: IconInputProps): JSX.Element;
@@ -0,0 +1,26 @@
1
+ import { type JSX } from "solid-js";
2
+ import { type DictationController } from "./create-dictation";
3
+ /** Whether the bar stands in for the host's controls: while dictating, finishing, or showing a bar notice. */
4
+ export declare const dictationBarShown: (dictation: DictationController) => boolean;
5
+ export type DictationButtonProps = {
6
+ dictation: DictationController;
7
+ size?: "xs" | "sm";
8
+ class?: string;
9
+ /** The box the notice cards line up with, such as the whole composer; defaults to the button. */
10
+ cardAnchor?: () => HTMLElement | undefined;
11
+ };
12
+ /**
13
+ * The microphone at its fixed place. A press starts live dictation; offline it stays focusable and says why it waits.
14
+ * Notices that need a decision, such as a blocked microphone or a used-up allowance, open as cards above it.
15
+ */
16
+ export declare function DictationButton(props: DictationButtonProps): JSX.Element;
17
+ export type DictationBarProps = {
18
+ dictation: DictationController;
19
+ class?: string;
20
+ };
21
+ /**
22
+ * The row that stands in for the host's controls while dictating, at the same place and height: Discard, level, time,
23
+ * microphone and Stop; then "Finishing …" until the last words arrived; and a notice with "Continue" when the session
24
+ * ended early. Render it where `dictationBarShown()` is true. Escape stops, after closing an open menu.
25
+ */
26
+ export declare function DictationBar(props: DictationBarProps): JSX.Element;
@@ -0,0 +1,77 @@
1
+ import { type Accessor } from "solid-js";
2
+ import type { DictationAlternative, DictationDevice, DictationSource, DictationTarget } from "./types";
3
+ export type DictationStatus = "idle" | "starting" | "live" | "finishing";
4
+ /**
5
+ * What stays visible after a session could not start or ended on its own. `interrupted` and `ended` replace the bar,
6
+ * so "Continue" writes on at the same mark; the others are cards at the microphone.
7
+ */
8
+ export type DictationNotice = {
9
+ kind: "blocked";
10
+ } | {
11
+ kind: "unavailable";
12
+ } | {
13
+ kind: "not_available";
14
+ } | {
15
+ kind: "quota";
16
+ resetsAt?: string;
17
+ } | {
18
+ kind: "overloaded";
19
+ } | {
20
+ kind: "elsewhere";
21
+ } | {
22
+ kind: "interrupted";
23
+ } | {
24
+ kind: "ended";
25
+ reason: "max_duration" | "silence";
26
+ maxMs: number;
27
+ };
28
+ export type DictationOptions = {
29
+ source: DictationSource;
30
+ target: DictationTarget;
31
+ /** Offered beside "Try again" when live dictation is busy. */
32
+ alternative?: DictationAlternative;
33
+ /** While true, dictation cannot start, such as in a read-only field; the host calls `end()` when it turns true. */
34
+ disabled?: () => boolean;
35
+ };
36
+ export type DictationController = {
37
+ status: Accessor<DictationStatus>;
38
+ /** Dictating, starting, or finishing: the bar stands in for the host's controls. */
39
+ active: Accessor<boolean>;
40
+ notice: Accessor<DictationNotice | null>;
41
+ /** Recent levels from 0 to 1, newest last. */
42
+ levels: Accessor<readonly number[]>;
43
+ elapsedMs: Accessor<number>;
44
+ maxMs: Accessor<number>;
45
+ offline: Accessor<boolean>;
46
+ device: Accessor<DictationDevice | undefined>;
47
+ deviceId: Accessor<string | undefined>;
48
+ devices: Accessor<readonly DictationDevice[]>;
49
+ loadDevices(): Promise<void>;
50
+ chooseDevice(id: string): void;
51
+ alternative?: DictationAlternative;
52
+ start(): void;
53
+ stop(): Promise<void>;
54
+ /** Ends the session at once and removes only the text it inserted. */
55
+ discard(): void;
56
+ /** Closes a notice; the text stays. */
57
+ dismiss(): void;
58
+ /** Ends any session at once and closes the dictation; the text stays. For a field that changes or turns read-only. */
59
+ end(): void;
60
+ /** Whether dictation may start now. */
61
+ disabled: Accessor<boolean>;
62
+ /** Elements that receive focus: the microphone after the end, the stop button after the start. */
63
+ bind(role: "microphone" | "stop" | "continue", element: HTMLElement | undefined): void;
64
+ };
65
+ /** Bar notices stand where the bar was; card notices open at the microphone. */
66
+ export declare const isBarNotice: (notice: DictationNotice | null) => notice is {
67
+ kind: "interrupted";
68
+ } | {
69
+ kind: "ended";
70
+ reason: "max_duration" | "silence";
71
+ maxMs: number;
72
+ };
73
+ /**
74
+ * The state of one dictation place: which session runs, what its bar shows, and the notice it leaves. Render it with
75
+ * `DictationButton` and `DictationBar`; the target decides where text lands. One controller runs one session at a time.
76
+ */
77
+ export declare function createDictation(options: DictationOptions): DictationController;
@@ -0,0 +1,54 @@
1
+ export declare const dictationMessages: import("@k2b/stdlib").Catalog<{
2
+ dictate: string;
3
+ dictateOffline: string;
4
+ offline: string;
5
+ discard: string;
6
+ stop: string;
7
+ running: string;
8
+ finishingGroup: string;
9
+ finishing: string;
10
+ starting: string;
11
+ ended: string;
12
+ discarded: string;
13
+ microphone: string;
14
+ microphoneNamed: ({ name }: {
15
+ name: string;
16
+ }) => string;
17
+ systemDefault: string;
18
+ interrupted: string;
19
+ interruptedShort: string;
20
+ maxDone: ({ minutes }: {
21
+ minutes: number;
22
+ }) => string;
23
+ maxDoneShort: ({ minutes }: {
24
+ minutes: number;
25
+ }) => string;
26
+ silenceDone: string;
27
+ silenceDoneShort: string;
28
+ continue: string;
29
+ dismiss: string;
30
+ blockedTitle: string;
31
+ blockedBody: string;
32
+ blockedStep1: string;
33
+ blockedStep2: string;
34
+ retry: string;
35
+ quotaTitle: string;
36
+ quotaBody: ({ time }: {
37
+ time: string;
38
+ }) => string;
39
+ quotaBodyTomorrow: ({ time }: {
40
+ time: string;
41
+ }) => string;
42
+ overloadedTitle: string;
43
+ overloadedBody: string;
44
+ elsewhereTitle: string;
45
+ elsewhereBody: string;
46
+ unavailableTitle: string;
47
+ unavailableBody: string;
48
+ notAvailableTitle: string;
49
+ notAvailableBody: string;
50
+ ok: string;
51
+ timeLeft: ({ time }: {
52
+ time: string;
53
+ }) => string;
54
+ }>;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Dictation into plain text, such as a textarea: a mark where the next words land and the ranges dictation inserted.
3
+ * Both follow edits the person makes anywhere else, so typing elsewhere never moves the dictated text.
4
+ */
5
+ export type TextDictationState = {
6
+ mark: number;
7
+ /** Inserted ranges, as UTF-16 offsets into the current text, in order. */
8
+ ranges: readonly (readonly [number, number])[];
9
+ /** Whether the next insert is the first of a session, which adjusts the space before it. */
10
+ fresh: boolean;
11
+ };
12
+ export declare const beginTextDictation: (mark: number) => TextDictationState;
13
+ /** Where `offset` lands after `previous` became `next`; `after` keeps it behind text inserted exactly at it. */
14
+ export declare const mapTextOffset: (previous: string, next: string, offset: number, after: boolean) => number;
15
+ /** Follow an edit that dictation did not make. Ranges that the edit removed entirely disappear. */
16
+ export declare const mapTextDictation: (state: TextDictationState, previous: string, next: string) => TextDictationState;
17
+ /** Append `text` at the mark. The first words of a session get one space from earlier text, never two. */
18
+ export declare const insertTextDictation: (state: TextDictationState, value: string, text: string) => {
19
+ state: TextDictationState;
20
+ value: string;
21
+ };
22
+ /** Remove every dictated range; text that was there before stays. */
23
+ export declare const discardTextDictation: (state: TextDictationState, value: string) => {
24
+ value: string;
25
+ caret: number;
26
+ };
27
+ /**
28
+ * Split highlight segments of a field's text at the newest dictated words and the mark, for an overlay that tints the
29
+ * fresh words and shows where the next ones land. A mark arrives as an empty segment.
30
+ */
31
+ export declare const splitDictationSegments: <T extends {
32
+ text: string;
33
+ }>(segments: readonly T[], fresh: readonly (readonly [number, number])[], mark: number | undefined) => (T & {
34
+ fresh?: boolean;
35
+ mark?: boolean;
36
+ })[];
@@ -0,0 +1,74 @@
1
+ /**
2
+ * A speech-to-text stream as dictation consumes it. The library knows no server: an application passes a source, such
3
+ * as one that relays the microphone to a speech model, and a target that places the text.
4
+ */
5
+ export type DictationErrorCode = "not_configured" | "no_access" | "daily_limit" | "overloaded" | "busy_elsewhere" | "provider_failed" | "connection_lost" | "microphone_blocked" | "microphone_unavailable";
6
+ export type DictationDevice = {
7
+ id: string;
8
+ label: string;
9
+ };
10
+ /** Why a session ended on its own; the text so far stays. */
11
+ export type DictationEndReason = "max_duration" | "silence" | "daily_limit";
12
+ export type DictationEvent = {
13
+ type: "state";
14
+ state: "connecting";
15
+ } | {
16
+ type: "state";
17
+ state: "live";
18
+ maxMs: number;
19
+ device?: DictationDevice;
20
+ } | {
21
+ type: "state";
22
+ state: "finishing";
23
+ } | {
24
+ type: "state";
25
+ state: "finished";
26
+ reason?: DictationEndReason;
27
+ }
28
+ /** 0 to 1, about every 100 ms while live. */
29
+ | {
30
+ type: "level";
31
+ level: number;
32
+ }
33
+ /** New text to append at the dictation mark; earlier text is never revised. */
34
+ | {
35
+ type: "text";
36
+ text: string;
37
+ } | {
38
+ type: "error";
39
+ code: DictationErrorCode;
40
+ retryable: boolean;
41
+ resetsAt?: string;
42
+ };
43
+ export type DictationSession = {
44
+ /** Ends after `finished` or `error`, or without further events once the start signal aborted. */
45
+ events: AsyncIterable<DictationEvent>;
46
+ /** Regular stop: resolves after `finished` or an error, so the last words arrive before it. */
47
+ stop(): Promise<void>;
48
+ };
49
+ export type DictationSource = {
50
+ /** Aborting `signal` closes the microphone and the connection at once, for discarding or leaving the page. */
51
+ start(options: {
52
+ deviceId?: string;
53
+ signal: AbortSignal;
54
+ }): DictationSession;
55
+ /** Microphones with their names; names may be empty before the first permission. */
56
+ devices(): Promise<DictationDevice[]>;
57
+ };
58
+ /**
59
+ * Where dictated text lands. `begin` starts each session: the first one remembers the insertion point, later ones of
60
+ * the same dictation write on at it. `insert` appends at the mark, `discard` removes exactly the inserted text, and
61
+ * `end` closes the dictation; the text stays.
62
+ */
63
+ export type DictationTarget = {
64
+ begin(): void;
65
+ insert(text: string): void;
66
+ discard(): void;
67
+ end(): void;
68
+ };
69
+ /** A second way out when live dictation is busy, such as recording instead, with what it changes. */
70
+ export type DictationAlternative = {
71
+ label: string;
72
+ note: string;
73
+ run: () => void;
74
+ };
@@ -1,9 +1,23 @@
1
1
  declare const iconMessages: import("@k2b/stdlib").Catalog<{
2
2
  filterIcons: string;
3
3
  pickIcon: string;
4
+ recommended: string;
5
+ people: string;
6
+ work: string;
7
+ communication: string;
8
+ files: string;
9
+ data: string;
10
+ commerce: string;
11
+ places: string;
12
+ food: string;
13
+ media: string;
14
+ devices: string;
4
15
  }>;
5
16
  export type IconMessages = ReturnType<(typeof iconMessages)["resolve"]>["t"];
17
+ export type IconGroupMessage = keyof Omit<IconMessages, "filterIcons" | "pickIcon">;
6
18
  /** Resolved from the inherited render locale. */
19
+ /** The catalog locale the render locale resolves to, such as "de" for "de-AT". */
20
+ export declare const useIconLocale: () => (() => string);
7
21
  export declare const useIconMessages: () => (() => IconMessages);
8
22
  export declare const checkIconMessages: () => import("@k2b/stdlib").CatalogIssue[];
9
23
  export {};
@@ -0,0 +1,13 @@
1
+ import type { IconOption } from "./SpecialInputs";
2
+ type IconText = readonly [label: string, description: string, keywords: string];
3
+ /**
4
+ * German names for the default catalogue, keyed by the Tabler glyph name.
5
+ * Keywords are extra German search words; the English label and keywords stay
6
+ * searchable in every locale. This is catalogue data, not UI messages: the
7
+ * locale itself resolves through the icon message catalog, and
8
+ * `icon-options.test.ts` keeps the table complete.
9
+ */
10
+ export declare const GERMAN_ICON_TEXTS: Readonly<Record<string, IconText>>;
11
+ /** The default catalogue in a resolved locale; English is the catalogue itself. */
12
+ export declare const localizedIconOptions: (locale: string) => readonly IconOption[];
13
+ export {};
@@ -25,6 +25,11 @@ export type { Completion, QueryContext, SuggestContext, Suggestion, } from "./co
25
25
  export { abbreviations } from "./completion";
26
26
  export type { DatePickerBaseProps, DatePickerProps, DatePreset, DateRangePickerProps, DateRangeValue, DateTimePickerProps, DurationPreset, } from "./DatePicker";
27
27
  export { DatePicker, DateRangePicker, DateTimePicker } from "./DatePicker";
28
+ export type { DictationController, DictationNotice, DictationOptions, DictationStatus } from "./dictation/create-dictation";
29
+ export { createDictation } from "./dictation/create-dictation";
30
+ export type { DictationBarProps, DictationButtonProps } from "./dictation/Dictation";
31
+ export { DictationBar, DictationButton, dictationBarShown } from "./dictation/Dictation";
32
+ export type { DictationAlternative, DictationDevice, DictationEndReason, DictationErrorCode, DictationEvent, DictationSession, DictationSource, DictationTarget, } from "./dictation/types";
28
33
  export type { EmojiPickerPopoverProps, EmojiPickerProps } from "./emoji/EmojiPicker";
29
34
  export { EmojiPicker } from "./emoji/EmojiPicker";
30
35
  export type { EmojiSkinTone } from "./emoji/emoji-index";
@@ -18,6 +18,11 @@ export type AnchoredPopoverProps = {
18
18
  plannedHeight?: number;
19
19
  /** Width of the panel on wide screens, such as `21rem`. On a phone it is a bottom sheet across the screen. */
20
20
  width?: string;
21
+ /**
22
+ * `false` keeps a small notice beside its control on a phone too, inside the screen with a margin, instead of a
23
+ * bottom sheet that would cover the control. Defaults to `true`.
24
+ */
25
+ sheet?: boolean;
21
26
  /** Moves the focus into the panel when it opens; defaults to the panel itself. */
22
27
  initialFocus?: () => HTMLElement | null | undefined;
23
28
  class?: string;
@@ -1,6 +1,7 @@
1
1
  import type { Extension } from "@codemirror/state";
2
2
  import { type JSX } from "solid-js";
3
3
  import { type DropdownPosition } from "../actions/Dropdown";
4
+ import type { DictationAlternative, DictationSource } from "../inputs/dictation/types";
4
5
  import { type TextEditorController, type TextEditorMode, type TextEditorOptions } from "./engine";
5
6
  import { type InlineFormat } from "./format";
6
7
  import { type TableHost } from "./table";
@@ -28,8 +29,16 @@ export type TextEditorProps = {
28
29
  /** Opens the host's attachment picker when clipboard images cannot be inserted. */
29
30
  onAttachImages?: () => void;
30
31
  onTableCheck?: (needsCheck: boolean) => void;
31
- /** Host-owned sync status; its place is reserved across transitions. */
32
+ /** Host-owned sync status. Its row appears only while there is a status or a table check; keep it set across transitions. */
32
33
  status?: JSX.Element;
34
+ /**
35
+ * Live dictation as the last control of the toolbar, so it never scrolls away. While it runs, the dictation bar
36
+ * stands in for the formatting controls in the same row. Words land at the caret as typed text does.
37
+ */
38
+ dictation?: {
39
+ source: DictationSource;
40
+ alternative?: DictationAlternative;
41
+ };
33
42
  class?: string;
34
43
  };
35
44
  export type TableSizePickerProps = {
@@ -0,0 +1,20 @@
1
+ import { type RangeSet, StateField } from "@codemirror/state";
2
+ import { Decoration, type DecorationSet, EditorView } from "@codemirror/view";
3
+ import type { DictationTarget } from "../inputs/dictation/types";
4
+ type DictationState = {
5
+ /** Where the next words land; `null` while no dictation is open. */
6
+ mark: number | null;
7
+ /** Every range dictation inserted, mapped through all later edits. */
8
+ dictated: RangeSet<Decoration>;
9
+ /** The newest words, tinted for a moment. */
10
+ fresh: DecorationSet;
11
+ };
12
+ /** The editor state dictation needs; add it to the view that `codeMirrorDictationTarget` writes into. */
13
+ export declare const dictationExtension: () => StateField<DictationState>;
14
+ /**
15
+ * A dictation target for a CodeMirror view. Words go into the document at once, as typed text does, so a shared
16
+ * document shows them to everyone and they last as long as the host saves typed text. The mark follows local and
17
+ * remote edits; Discard removes exactly the dictated ranges in one step, including what the person typed inside them.
18
+ */
19
+ export declare function codeMirrorDictationTarget(editorView: () => EditorView | undefined): DictationTarget;
20
+ export {};
@@ -1,3 +1,4 @@
1
+ export { codeMirrorDictationTarget, dictationExtension } from "./text-editor/dictation";
1
2
  export { createTextEditor, type TextEditorController, type TextEditorMode, type TextEditorOptions } from "./text-editor/engine";
2
3
  export { commands as textEditorCommands, formatLabels as textEditorFormatLabels, type InlineFormat } from "./text-editor/format";
3
4
  export { TableSizePicker, type TableSizePickerProps, TextEditor, type TextEditorControls, type TextEditorProps, } from "./text-editor/TextEditor";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k2b/ui",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "description": "Production-ready components for SolidJS applications.",
5
5
  "repository": {
6
6
  "type": "git",