@k2b/ui 0.40.0 → 0.42.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 (135) hide show
  1. package/dist/base.css +22 -0
  2. package/dist/browser/actions/Dropdown.js +220 -110
  3. package/dist/browser/actions/Dropdown.js.map +1 -1
  4. package/dist/browser/actions/index.js.map +1 -1
  5. package/dist/browser/actions/menu-search.js +166 -0
  6. package/dist/browser/actions/menu-search.js.map +1 -0
  7. package/dist/browser/actions/menu-sheet.js +107 -13
  8. package/dist/browser/actions/menu-sheet.js.map +1 -1
  9. package/dist/browser/chat/ChatComposer.js +759 -570
  10. package/dist/browser/chat/ChatComposer.js.map +1 -1
  11. package/dist/browser/content/CalendarWeekView.js +13 -1
  12. package/dist/browser/content/CalendarWeekView.js.map +1 -1
  13. package/dist/browser/inputs/ChoiceGroups.js +30 -18
  14. package/dist/browser/inputs/ChoiceGroups.js.map +1 -1
  15. package/dist/browser/inputs/IconChoices.js +121 -0
  16. package/dist/browser/inputs/IconChoices.js.map +1 -0
  17. package/dist/browser/inputs/Select.js +32 -19
  18. package/dist/browser/inputs/Select.js.map +1 -1
  19. package/dist/browser/inputs/SpecialInputs.js +54 -25
  20. package/dist/browser/inputs/SpecialInputs.js.map +1 -1
  21. package/dist/browser/inputs/choice.js +4 -1
  22. package/dist/browser/inputs/choice.js.map +1 -1
  23. package/dist/browser/inputs/dictation/Dictation.js +546 -0
  24. package/dist/browser/inputs/dictation/Dictation.js.map +1 -0
  25. package/dist/browser/inputs/dictation/create-dictation.js +324 -0
  26. package/dist/browser/inputs/dictation/create-dictation.js.map +1 -0
  27. package/dist/browser/inputs/dictation/dictation-messages.js +115 -0
  28. package/dist/browser/inputs/dictation/dictation-messages.js.map +1 -0
  29. package/dist/browser/inputs/dictation/text-dictation.js +152 -0
  30. package/dist/browser/inputs/dictation/text-dictation.js.map +1 -0
  31. package/dist/browser/inputs/dictation/types.js +2 -0
  32. package/dist/browser/inputs/dictation/types.js.map +1 -0
  33. package/dist/browser/inputs/icon-messages.js +29 -2
  34. package/dist/browser/inputs/icon-messages.js.map +1 -1
  35. package/dist/browser/inputs/icon-texts.js +172 -0
  36. package/dist/browser/inputs/icon-texts.js.map +1 -0
  37. package/dist/browser/inputs/index.js +3 -0
  38. package/dist/browser/inputs/index.js.map +1 -1
  39. package/dist/browser/intl/messages.js +6 -0
  40. package/dist/browser/intl/messages.js.map +1 -1
  41. package/dist/browser/layout/AnchoredPopover.js +1 -1
  42. package/dist/browser/layout/AnchoredPopover.js.map +1 -1
  43. package/dist/browser/layout/AppWorkspace.js +118 -93
  44. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  45. package/dist/browser/layout/SettingsModal.js +10 -5
  46. package/dist/browser/layout/SettingsModal.js.map +1 -1
  47. package/dist/browser/text-editor/TextEditor.js +74 -25
  48. package/dist/browser/text-editor/TextEditor.js.map +1 -1
  49. package/dist/browser/text-editor/dictation.js +166 -0
  50. package/dist/browser/text-editor/dictation.js.map +1 -0
  51. package/dist/browser/text-editor/format.js +11 -7
  52. package/dist/browser/text-editor/format.js.map +1 -1
  53. package/dist/browser/text-editor/table.js +39 -11
  54. package/dist/browser/text-editor/table.js.map +1 -1
  55. package/dist/browser/text-editor.js +2 -1
  56. package/dist/browser/text-editor.js.map +1 -1
  57. package/dist/ssr/actions/Dropdown.js +118 -40
  58. package/dist/ssr/actions/Dropdown.js.map +1 -1
  59. package/dist/ssr/actions/index.js.map +1 -1
  60. package/dist/ssr/actions/menu-search.js +113 -0
  61. package/dist/ssr/actions/menu-search.js.map +1 -0
  62. package/dist/ssr/actions/menu-sheet.js +82 -13
  63. package/dist/ssr/actions/menu-sheet.js.map +1 -1
  64. package/dist/ssr/chat/ChatComposer.js +271 -99
  65. package/dist/ssr/chat/ChatComposer.js.map +1 -1
  66. package/dist/ssr/content/CalendarWeekView.js +13 -1
  67. package/dist/ssr/content/CalendarWeekView.js.map +1 -1
  68. package/dist/ssr/inputs/ChoiceGroups.js +12 -3
  69. package/dist/ssr/inputs/ChoiceGroups.js.map +1 -1
  70. package/dist/ssr/inputs/IconChoices.js +59 -0
  71. package/dist/ssr/inputs/IconChoices.js.map +1 -0
  72. package/dist/ssr/inputs/Select.js +8 -2
  73. package/dist/ssr/inputs/Select.js.map +1 -1
  74. package/dist/ssr/inputs/SpecialInputs.js +54 -25
  75. package/dist/ssr/inputs/SpecialInputs.js.map +1 -1
  76. package/dist/ssr/inputs/choice.js +4 -1
  77. package/dist/ssr/inputs/choice.js.map +1 -1
  78. package/dist/ssr/inputs/dictation/Dictation.js +386 -0
  79. package/dist/ssr/inputs/dictation/Dictation.js.map +1 -0
  80. package/dist/ssr/inputs/dictation/create-dictation.js +324 -0
  81. package/dist/ssr/inputs/dictation/create-dictation.js.map +1 -0
  82. package/dist/ssr/inputs/dictation/dictation-messages.js +115 -0
  83. package/dist/ssr/inputs/dictation/dictation-messages.js.map +1 -0
  84. package/dist/ssr/inputs/dictation/text-dictation.js +152 -0
  85. package/dist/ssr/inputs/dictation/text-dictation.js.map +1 -0
  86. package/dist/ssr/inputs/dictation/types.js +2 -0
  87. package/dist/ssr/inputs/dictation/types.js.map +1 -0
  88. package/dist/ssr/inputs/icon-messages.js +29 -2
  89. package/dist/ssr/inputs/icon-messages.js.map +1 -1
  90. package/dist/ssr/inputs/icon-texts.js +172 -0
  91. package/dist/ssr/inputs/icon-texts.js.map +1 -0
  92. package/dist/ssr/inputs/index.js +3 -0
  93. package/dist/ssr/inputs/index.js.map +1 -1
  94. package/dist/ssr/intl/messages.js +6 -0
  95. package/dist/ssr/intl/messages.js.map +1 -1
  96. package/dist/ssr/layout/AnchoredPopover.js +1 -1
  97. package/dist/ssr/layout/AnchoredPopover.js.map +1 -1
  98. package/dist/ssr/layout/AppWorkspace.js +40 -20
  99. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  100. package/dist/ssr/layout/SettingsModal.js +10 -5
  101. package/dist/ssr/layout/SettingsModal.js.map +1 -1
  102. package/dist/ssr/text-editor/TextEditor.js +74 -25
  103. package/dist/ssr/text-editor/TextEditor.js.map +1 -1
  104. package/dist/ssr/text-editor/dictation.js +166 -0
  105. package/dist/ssr/text-editor/dictation.js.map +1 -0
  106. package/dist/ssr/text-editor/format.js +11 -7
  107. package/dist/ssr/text-editor/format.js.map +1 -1
  108. package/dist/ssr/text-editor/table.js +39 -11
  109. package/dist/ssr/text-editor/table.js.map +1 -1
  110. package/dist/ssr/text-editor.js +2 -1
  111. package/dist/ssr/text-editor.js.map +1 -1
  112. package/dist/styles.css +1 -1
  113. package/dist/types/actions/Dropdown.d.ts +34 -3
  114. package/dist/types/actions/index.d.ts +1 -1
  115. package/dist/types/actions/menu-search.d.ts +27 -0
  116. package/dist/types/actions/menu-sheet.d.ts +5 -1
  117. package/dist/types/chat/ChatComposer.d.ts +9 -0
  118. package/dist/types/inputs/IconChoices.d.ts +19 -0
  119. package/dist/types/inputs/SpecialInputs.d.ts +8 -0
  120. package/dist/types/inputs/dictation/Dictation.d.ts +26 -0
  121. package/dist/types/inputs/dictation/create-dictation.d.ts +77 -0
  122. package/dist/types/inputs/dictation/dictation-messages.d.ts +54 -0
  123. package/dist/types/inputs/dictation/text-dictation.d.ts +36 -0
  124. package/dist/types/inputs/dictation/types.d.ts +74 -0
  125. package/dist/types/inputs/icon-messages.d.ts +14 -0
  126. package/dist/types/inputs/icon-texts.d.ts +13 -0
  127. package/dist/types/inputs/index.d.ts +7 -0
  128. package/dist/types/intl/messages.d.ts +3 -0
  129. package/dist/types/layout/AnchoredPopover.d.ts +5 -0
  130. package/dist/types/layout/AppWorkspace.d.ts +6 -1
  131. package/dist/types/text-editor/TextEditor.d.ts +10 -1
  132. package/dist/types/text-editor/dictation.d.ts +20 -0
  133. package/dist/types/text-editor/table.d.ts +2 -0
  134. package/dist/types/text-editor.d.ts +2 -1
  135. package/package.json +3 -1
@@ -22,7 +22,8 @@ export type DropdownActionBase = {
22
22
  description?: string;
23
23
  /** Quiet trailing information, such as a count. */
24
24
  trailing?: JSX.Element;
25
- variant?: "danger";
25
+ /** `danger` for destructive entries; `accent` for the entry most people want, such as creating a blank item. */
26
+ variant?: "danger" | "accent";
26
27
  };
27
28
  export type DropdownAction = (DropdownActionBase & {
28
29
  action: () => void;
@@ -83,9 +84,23 @@ export type DropdownSection = {
83
84
  items: readonly (DropdownAction | DropdownChoice)[];
84
85
  };
85
86
  export type DropdownItem = DropdownAction | DropdownChoice | DropdownSection;
87
+ /** A search field above the entries, for a menu long enough that finding an entry takes reading. */
88
+ export type DropdownSearch = {
89
+ /** Names the field and is its placeholder unless `placeholder` is set, such as "Find template". */
90
+ label: string;
91
+ placeholder?: string;
92
+ /**
93
+ * The entries for a query that is not blank. Defaults to the actions and choices whose label or description contains
94
+ * the query, kept in their sections.
95
+ */
96
+ filter?: (query: string) => readonly DropdownItem[];
97
+ /** What a screen reader hears after the query changes; defaults to the number of matching entries. */
98
+ status?: (query: string, matches: number) => string;
99
+ };
86
100
  export type DropdownProps = {
87
101
  items: readonly DropdownItem[];
88
- children: JSX.Element;
102
+ /** The `Dropdown.Trigger`; leave it out for a menu opened through `attach` or `openAt`. */
103
+ children?: JSX.Element;
89
104
  position?: DropdownPosition | (() => DropdownPosition);
90
105
  /** Larger menu rows and typography for touch. Defaults to `default`. */
91
106
  variant?: "default" | "touch";
@@ -123,6 +138,21 @@ export type DropdownProps = {
123
138
  openAt?: (open: (anchor: DropdownAnchor, returnFocus?: DropdownReturnFocus) => void) => void;
124
139
  /** Receives `close`, which closes the menu or sheet and returns focus. */
125
140
  closeWith?: (close: () => void) => void;
141
+ /**
142
+ * Puts a search field above the entries. Focus starts in the field; Up and Down move through the matching entries,
143
+ * Enter chooses one, and the first stays marked while the query changes.
144
+ */
145
+ search?: DropdownSearch;
146
+ /**
147
+ * Adopts an element rendered elsewhere as the trigger, such as a server-rendered link: a plain click, Enter, Space,
148
+ * Up, or Down opens the menu, while a modified or middle click keeps the link. The element gets `aria-expanded` and
149
+ * `aria-controls`; render `aria-haspopup` with it, so the server HTML already says it opens a menu. Read once on mount.
150
+ */
151
+ attach?: () => HTMLElement | null | undefined;
152
+ /** `tile` sets every entry's icon on a quiet square, for menus whose icons tell entries apart, such as templates. */
153
+ icons?: "plain" | "tile";
154
+ /** One quiet sentence below the entries that explains them all, such as what choosing one keeps; it describes the menu. */
155
+ note?: string;
126
156
  };
127
157
  export type DropdownTriggerProps = Omit<ButtonProps, "aria-controls" | "aria-expanded" | "aria-haspopup" | "children" | "disabled" | "onClick" | "onKeyDown"> & {
128
158
  children: JSX.Element;
@@ -148,7 +178,7 @@ export type DropdownItemProps = {
148
178
  trailing?: JSX.Element;
149
179
  disabled?: boolean;
150
180
  danger?: boolean;
151
- variant?: "danger";
181
+ variant?: "danger" | "accent";
152
182
  href?: string;
153
183
  external?: boolean;
154
184
  /** Runs when the item is chosen. With `href`, a plain click runs it instead of following the link. */
@@ -166,6 +196,7 @@ export declare function DropdownItem(props: DropdownItemProps): JSX.Element;
166
196
  export declare function DropdownItems(props: {
167
197
  items: readonly DropdownItem[];
168
198
  close: (restoreFocus?: boolean) => void;
199
+ role?: "menuitem" | "option";
169
200
  }): JSX.Element;
170
201
  type DropdownComponent = ((props: DropdownProps) => JSX.Element) & {
171
202
  Root: (props: DropdownProps) => JSX.Element;
@@ -8,7 +8,7 @@ export type { DisclosureProps } from "./Disclosure";
8
8
  export { Disclosure } from "./Disclosure";
9
9
  export type { DragPayload, DragSourceProps, DropTargetProps } from "./DragAndDrop";
10
10
  export { activeDrag, DragSource, DropTarget, useKeyboardDrag } from "./DragAndDrop";
11
- export type { DropdownAction, DropdownActionBase, DropdownAnchor, DropdownChoice, DropdownItemProps, DropdownPosition, DropdownProps, DropdownReturnFocus, DropdownSection, DropdownTriggerProps, } from "./Dropdown";
11
+ export type { DropdownAction, DropdownActionBase, DropdownAnchor, DropdownChoice, DropdownItemProps, DropdownPosition, DropdownProps, DropdownReturnFocus, DropdownSearch, DropdownSection, DropdownTriggerProps, } from "./Dropdown";
12
12
  export { Dropdown, DropdownItem, dropdownPosition } from "./Dropdown";
13
13
  export type { FilterChipOption, FilterChipProps, FilterChipSection, FilterToggleProps } from "./FilterChip";
14
14
  export { FilterChip, FilterToggle } from "./FilterChip";
@@ -0,0 +1,27 @@
1
+ import { type Accessor, type JSX } from "solid-js";
2
+ import type { DropdownItem, DropdownSearch } from "./Dropdown";
3
+ /** The actions and choices whose label or description contains the query, kept in their sections. */
4
+ export declare const filterDropdownItems: (items: readonly DropdownItem[], query: string, locale?: string) => DropdownItem[];
5
+ export type MenuSearchApi = {
6
+ /** Moves the focus into the field and marks the first or last entry. */
7
+ focus: (mark: "first" | "last") => void;
8
+ };
9
+ /**
10
+ * A search field over a list box of the menu's entries: the combobox pattern. The field keeps the focus, the marked
11
+ * entry is its active descendant, and a polite status says how many entries match.
12
+ */
13
+ export declare function MenuSearch(props: {
14
+ Items: (props: {
15
+ items: readonly DropdownItem[];
16
+ close: (restoreFocus?: boolean) => void;
17
+ role?: "menuitem" | "option";
18
+ }) => JSX.Element;
19
+ items: readonly DropdownItem[];
20
+ search: DropdownSearch;
21
+ /** Names the list box. */
22
+ label: string;
23
+ query: Accessor<string>;
24
+ setQuery: (query: string) => void;
25
+ close: (restoreFocus?: boolean) => void;
26
+ api?: (api: MenuSearchApi) => void;
27
+ }): JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { type JSX } from "solid-js";
2
- import type { DropdownItem } from "./Dropdown";
2
+ import type { DropdownItem, DropdownSearch } from "./Dropdown";
3
3
  /** Phones, where a menu opens as a bottom sheet instead of beside its trigger. */
4
4
  export declare const PHONE_SHEET_QUERY = "(max-width: 47.999rem)";
5
5
  export declare const prefersMenuSheet: () => boolean;
@@ -16,6 +16,10 @@ export type MenuSheetOptions = {
16
16
  locale: string;
17
17
  /** Shown above the menu, for example a row of quick reactions. `close` closes the sheet. */
18
18
  top?: (close: () => void) => JSX.Element;
19
+ search?: DropdownSearch;
20
+ icons?: "plain" | "tile";
21
+ /** A quiet sentence below the entries. */
22
+ note?: string;
19
23
  signal?: AbortSignal;
20
24
  };
21
25
  /** Opens menu items as a bottom sheet; resolves after the sheet closed and the chosen action ran. */
@@ -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
  /**
@@ -0,0 +1,19 @@
1
+ import { type JSX } from "solid-js";
2
+ import type { ValueFieldProps } from "./field-contract";
3
+ export type IconChoiceOption<T extends string = string> = {
4
+ value: T;
5
+ /** What the icon shows; screen readers and the tooltip use it, so the picture is never the only signal. */
6
+ label: string;
7
+ /** An icon class, such as `ti ti-report`. */
8
+ icon: string;
9
+ };
10
+ export type IconChoicesProps<T extends string = string> = Omit<ValueFieldProps<T | null>, "onValueChange" | "onValueCommit"> & {
11
+ options: readonly IconChoiceOption<T>[];
12
+ onValueChange?: (value: T) => void;
13
+ onValueCommit?: (value: T) => void;
14
+ };
15
+ /**
16
+ * A labeled single choice from a few named icons, shown as a row of icon buttons. Use `IconInput` when any icon of the
17
+ * catalogue is allowed.
18
+ */
19
+ export declare function IconChoices<T extends string = string>(props: IconChoicesProps<T>): JSX.Element;
@@ -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";
@@ -34,6 +39,8 @@ export { FileDropTarget, fileDropTarget } from "./FileDropTarget";
34
39
  export type { FileDropzoneProps, ImageCropperProps, ImageInputProps } from "./FileInputs";
35
40
  export { FileDropzone, ImageCropper, ImageInput } from "./FileInputs";
36
41
  export type { DateContext, FieldProps, MaybeAccessor, ValueFieldProps } from "./field-contract";
42
+ export type { IconChoiceOption, IconChoicesProps } from "./IconChoices";
43
+ export { IconChoices } from "./IconChoices";
37
44
  export { DEFAULT_ICON_GROUPS, DEFAULT_ICON_OPTIONS } from "./icon-options";
38
45
  export type { ImageCropAspect, ImageCropOutput, ImageCropRect, ImageCropRotation, ImageCropSize, ImageCropSource, ImageCropState, } from "./image-crop";
39
46
  export { clampImageCropRect, createCroppedImageCanvas, createCroppedImageDataUrl, getInitialImageCropRect, imageCropRectToPixels, normalizeImageCropRotation, resizeImageCropAroundCenter, rotateImageCropRight, } from "./image-crop";
@@ -42,6 +42,9 @@ declare const uiMessages: import("@k2b/stdlib").Catalog<{
42
42
  search: string;
43
43
  searchLabel: string;
44
44
  searchResults: string;
45
+ searchMatches: ({ count }: {
46
+ count: number;
47
+ }) => string;
45
48
  save: string;
46
49
  select: string;
47
50
  time: string;
@@ -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;
@@ -21,12 +21,17 @@ export type AppWorkspaceHeadProps = {
21
21
  count?: JSX.Element;
22
22
  /** Secondary actions at the end of the row. Never a filled button. */
23
23
  actions?: JSX.Element;
24
- /** The one filled primary action. Below 48rem it becomes a round icon button beside the host's bottom bar. */
24
+ /**
25
+ * The one filled primary action. Below 48rem it becomes a round icon button beside the host's bottom bar. `popup`
26
+ * says it opens a menu, such as one a `Dropdown` with `attach` shows: server HTML then carries `aria-haspopup`, and a
27
+ * chevron follows the label.
28
+ */
25
29
  primary?: {
26
30
  label: string;
27
31
  icon: string;
28
32
  href?: string;
29
33
  onClick?: (event: MouseEvent) => void;
34
+ popup?: "menu" | "dialog";
30
35
  };
31
36
  };
32
37
  export type AppWorkspaceProps = {
@@ -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 {};
@@ -61,6 +61,8 @@ export type TableHost = {
61
61
  export type MenuAction = "completeColumns" | "rowAbove" | "rowBelow" | "colLeft" | "colRight" | "deleteRow" | "deleteCol" | "moveRowUp" | "moveRowDown" | "moveColLeft" | "moveColRight" | "toggleHeader" | "deleteTable" | "alignLeft" | "alignCenter" | "alignRight";
62
62
  export declare const tableHost: Facet<TableHost, TableHost | null>;
63
63
  export declare const tablesIn: (state: EditorState) => T.Table[];
64
+ /** Render the same Markdown tokens as prose, keeping code and escaped markers literal. */
65
+ export declare const renderInline: (raw: string) => string;
64
66
  /** A local source draft for one table. Invalid drafts never replace shared text.
65
67
  * Collaborative structure changes require the host's confirmed anchored action. */
66
68
  export declare const editTableSource: (view: EditorView, wrap: HTMLElement) => void;
@@ -1,4 +1,5 @@
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";
4
- export { editTableSource, type MenuAction, runMenuAction, runTableAction, type TableFocusTarget, type TableHost, type TableTarget, tableGrid, tableHost, } from "./text-editor/table";
5
+ export { editTableSource, type MenuAction, renderInline, runMenuAction, runTableAction, type TableFocusTarget, type TableHost, type TableTarget, tableGrid, tableHost, } from "./text-editor/table";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k2b/ui",
3
- "version": "0.40.0",
3
+ "version": "0.42.0",
4
4
  "description": "Production-ready components for SolidJS applications.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -61,6 +61,8 @@
61
61
  "fixture:build": "bun run build && NODE_ENV=production bun run fixture/scripts/build.ts",
62
62
  "fixture:typecheck": "tsc -p fixture/tsconfig.json --noEmit --pretty false",
63
63
  "test": "bun run build && bun test --path-ignore-patterns '**/*.behavior.test.*' src scripts test/browser.browser.test.ts && bun run scripts/check-packed-consumer.ts",
64
+ "test:part:layout-content": "bun run build && bun test --path-ignore-patterns '**/*.behavior.test.*' src/layout src/content",
65
+ "test:part:rest": "bun run build && bun test --path-ignore-patterns '**/*.behavior.test.*' --path-ignore-patterns 'src/layout/**' --path-ignore-patterns 'src/content/**' src scripts test/browser.browser.test.ts && bun run scripts/check-packed-consumer.ts",
64
66
  "typecheck": "tsc --checkers 2 -p tsconfig.json --noEmit --pretty false && bun run check:declarations && bun run typecheck:dom",
65
67
  "typecheck:dom": "tsc -p test/tsconfig.json --noEmit --pretty false",
66
68
  "prepack": "bun run build"