panelui-native 0.75.1 → 0.77.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 (169) hide show
  1. package/lib/module/components/ai-input/ai-input-growth.js +62 -0
  2. package/lib/module/components/ai-input/ai-input-growth.js.map +1 -0
  3. package/lib/module/components/ai-input/index.js +1584 -0
  4. package/lib/module/components/ai-input/index.js.map +1 -0
  5. package/lib/module/components/alert/index.js +1 -1
  6. package/lib/module/components/alert/index.js.map +1 -1
  7. package/lib/module/components/avatar/avatar-source.js +15 -0
  8. package/lib/module/components/avatar/avatar-source.js.map +1 -0
  9. package/lib/module/components/avatar/index.js +32 -31
  10. package/lib/module/components/avatar/index.js.map +1 -1
  11. package/lib/module/components/bottom-sheet/index.js +4 -2
  12. package/lib/module/components/bottom-sheet/index.js.map +1 -1
  13. package/lib/module/components/button/index.js +7 -1
  14. package/lib/module/components/button/index.js.map +1 -1
  15. package/lib/module/components/button-group/index.js +2 -3
  16. package/lib/module/components/button-group/index.js.map +1 -1
  17. package/lib/module/components/carousel/index.js +10 -7
  18. package/lib/module/components/carousel/index.js.map +1 -1
  19. package/lib/module/components/collapsible/index.js +1 -1
  20. package/lib/module/components/collapsible/index.js.map +1 -1
  21. package/lib/module/components/field/index.js +7 -8
  22. package/lib/module/components/field/index.js.map +1 -1
  23. package/lib/module/components/label/index.js +1 -1
  24. package/lib/module/components/label/index.js.map +1 -1
  25. package/lib/module/components/marquee/index.js +36 -11
  26. package/lib/module/components/marquee/index.js.map +1 -1
  27. package/lib/module/components/marquee/marquee-math.js +22 -6
  28. package/lib/module/components/marquee/marquee-math.js.map +1 -1
  29. package/lib/module/components/plan/index.js +2 -1
  30. package/lib/module/components/plan/index.js.map +1 -1
  31. package/lib/module/components/plot/index.js +27 -9
  32. package/lib/module/components/plot/index.js.map +1 -1
  33. package/lib/module/components/search-bar/index.js +11 -3
  34. package/lib/module/components/search-bar/index.js.map +1 -1
  35. package/lib/module/components/select/index.js +1 -0
  36. package/lib/module/components/select/index.js.map +1 -1
  37. package/lib/module/components/slider/index.js +1 -0
  38. package/lib/module/components/slider/index.js.map +1 -1
  39. package/lib/module/components/split-view/index.js +26 -27
  40. package/lib/module/components/split-view/index.js.map +1 -1
  41. package/lib/module/components/split-view/split-view-lifecycle.js +32 -0
  42. package/lib/module/components/split-view/split-view-lifecycle.js.map +1 -0
  43. package/lib/module/components/splitter/index.js +7 -13
  44. package/lib/module/components/splitter/index.js.map +1 -1
  45. package/lib/module/components/splitter/splitter-math.js +14 -0
  46. package/lib/module/components/splitter/splitter-math.js.map +1 -1
  47. package/lib/module/components/switch/index.js +1 -0
  48. package/lib/module/components/switch/index.js.map +1 -1
  49. package/lib/module/components/tabs/index.js +1 -1
  50. package/lib/module/components/tabs/index.js.map +1 -1
  51. package/lib/module/components/timeline/index.js +156 -42
  52. package/lib/module/components/timeline/index.js.map +1 -1
  53. package/lib/module/components/timeline/timeline-geometry.js +11 -0
  54. package/lib/module/components/timeline/timeline-geometry.js.map +1 -1
  55. package/lib/module/components/toast/index.js +1 -1
  56. package/lib/module/components/toast/index.js.map +1 -1
  57. package/lib/module/components/tooltip/index.js +2 -2
  58. package/lib/module/components/tooltip/index.js.map +1 -1
  59. package/lib/module/components/tree/index.js +2 -2
  60. package/lib/module/components/tree/index.js.map +1 -1
  61. package/lib/module/components/typography/index.js +2 -1
  62. package/lib/module/components/typography/index.js.map +1 -1
  63. package/lib/module/components/waterfall-chart/index.js +22 -3
  64. package/lib/module/components/waterfall-chart/index.js.map +1 -1
  65. package/lib/module/hooks/use-disclosure.js +10 -8
  66. package/lib/module/hooks/use-disclosure.js.map +1 -1
  67. package/lib/module/icons/index.js +135 -0
  68. package/lib/module/icons/index.js.map +1 -1
  69. package/lib/module/index.js +5 -1
  70. package/lib/module/index.js.map +1 -1
  71. package/lib/module/native/index.js.map +1 -1
  72. package/lib/module/primitives/controllable-state.js +61 -0
  73. package/lib/module/primitives/controllable-state.js.map +1 -0
  74. package/lib/module/primitives/finite-chart.js +15 -0
  75. package/lib/module/primitives/finite-chart.js.map +1 -0
  76. package/lib/module/primitives/glass.js +136 -0
  77. package/lib/module/primitives/glass.js.map +1 -0
  78. package/lib/module/primitives/scrim.js +8 -1
  79. package/lib/module/primitives/scrim.js.map +1 -1
  80. package/lib/typescript/src/components/ai-input/ai-input-growth.d.ts +59 -0
  81. package/lib/typescript/src/components/ai-input/ai-input-growth.d.ts.map +1 -0
  82. package/lib/typescript/src/components/ai-input/index.d.ts +442 -0
  83. package/lib/typescript/src/components/ai-input/index.d.ts.map +1 -0
  84. package/lib/typescript/src/components/avatar/avatar-source.d.ts +4 -0
  85. package/lib/typescript/src/components/avatar/avatar-source.d.ts.map +1 -0
  86. package/lib/typescript/src/components/avatar/index.d.ts.map +1 -1
  87. package/lib/typescript/src/components/bottom-sheet/index.d.ts +10 -1
  88. package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
  89. package/lib/typescript/src/components/button/index.d.ts +15 -0
  90. package/lib/typescript/src/components/button/index.d.ts.map +1 -1
  91. package/lib/typescript/src/components/carousel/index.d.ts.map +1 -1
  92. package/lib/typescript/src/components/marquee/index.d.ts +15 -6
  93. package/lib/typescript/src/components/marquee/index.d.ts.map +1 -1
  94. package/lib/typescript/src/components/marquee/marquee-math.d.ts +6 -0
  95. package/lib/typescript/src/components/marquee/marquee-math.d.ts.map +1 -1
  96. package/lib/typescript/src/components/plan/index.d.ts.map +1 -1
  97. package/lib/typescript/src/components/plot/index.d.ts +2 -1
  98. package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
  99. package/lib/typescript/src/components/search-bar/index.d.ts.map +1 -1
  100. package/lib/typescript/src/components/split-view/index.d.ts.map +1 -1
  101. package/lib/typescript/src/components/split-view/split-view-lifecycle.d.ts +14 -0
  102. package/lib/typescript/src/components/split-view/split-view-lifecycle.d.ts.map +1 -0
  103. package/lib/typescript/src/components/splitter/index.d.ts +2 -2
  104. package/lib/typescript/src/components/splitter/index.d.ts.map +1 -1
  105. package/lib/typescript/src/components/splitter/splitter-math.d.ts +2 -0
  106. package/lib/typescript/src/components/splitter/splitter-math.d.ts.map +1 -1
  107. package/lib/typescript/src/components/timeline/index.d.ts +24 -2
  108. package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
  109. package/lib/typescript/src/components/timeline/timeline-geometry.d.ts +2 -0
  110. package/lib/typescript/src/components/timeline/timeline-geometry.d.ts.map +1 -1
  111. package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
  112. package/lib/typescript/src/components/waterfall-chart/index.d.ts +2 -1
  113. package/lib/typescript/src/components/waterfall-chart/index.d.ts.map +1 -1
  114. package/lib/typescript/src/hooks/use-disclosure.d.ts.map +1 -1
  115. package/lib/typescript/src/icons/index.d.ts +18 -0
  116. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  117. package/lib/typescript/src/index.d.ts +3 -1
  118. package/lib/typescript/src/index.d.ts.map +1 -1
  119. package/lib/typescript/src/native/index.d.ts +17 -0
  120. package/lib/typescript/src/native/index.d.ts.map +1 -1
  121. package/lib/typescript/src/primitives/controllable-state.d.ts +33 -0
  122. package/lib/typescript/src/primitives/controllable-state.d.ts.map +1 -0
  123. package/lib/typescript/src/primitives/finite-chart.d.ts +5 -0
  124. package/lib/typescript/src/primitives/finite-chart.d.ts.map +1 -0
  125. package/lib/typescript/src/primitives/glass.d.ts +43 -0
  126. package/lib/typescript/src/primitives/glass.d.ts.map +1 -0
  127. package/lib/typescript/src/primitives/scrim.d.ts +7 -0
  128. package/lib/typescript/src/primitives/scrim.d.ts.map +1 -1
  129. package/package.json +7 -2
  130. package/src/components/ai-input/ai-input-growth.ts +65 -0
  131. package/src/components/ai-input/index.tsx +1971 -0
  132. package/src/components/alert/index.tsx +1 -1
  133. package/src/components/avatar/avatar-source.ts +18 -0
  134. package/src/components/avatar/index.tsx +35 -27
  135. package/src/components/bottom-sheet/index.tsx +16 -4
  136. package/src/components/button/index.tsx +20 -1
  137. package/src/components/button-group/index.tsx +1 -1
  138. package/src/components/carousel/index.tsx +10 -11
  139. package/src/components/collapsible/index.tsx +1 -1
  140. package/src/components/field/index.tsx +4 -4
  141. package/src/components/label/index.tsx +1 -1
  142. package/src/components/marquee/index.tsx +56 -12
  143. package/src/components/marquee/marquee-math.ts +26 -3
  144. package/src/components/plan/index.tsx +6 -2
  145. package/src/components/plot/index.tsx +37 -11
  146. package/src/components/search-bar/index.tsx +9 -3
  147. package/src/components/select/index.tsx +1 -1
  148. package/src/components/slider/index.tsx +1 -1
  149. package/src/components/split-view/index.tsx +24 -33
  150. package/src/components/split-view/split-view-lifecycle.ts +43 -0
  151. package/src/components/splitter/index.tsx +7 -11
  152. package/src/components/splitter/splitter-math.ts +28 -0
  153. package/src/components/switch/index.tsx +1 -1
  154. package/src/components/tabs/index.tsx +1 -1
  155. package/src/components/timeline/index.tsx +189 -34
  156. package/src/components/timeline/timeline-geometry.ts +11 -0
  157. package/src/components/toast/index.tsx +6 -1
  158. package/src/components/tooltip/index.tsx +1 -1
  159. package/src/components/tree/index.tsx +2 -2
  160. package/src/components/typography/index.tsx +2 -2
  161. package/src/components/waterfall-chart/index.tsx +37 -4
  162. package/src/hooks/use-disclosure.ts +7 -12
  163. package/src/icons/index.tsx +106 -0
  164. package/src/index.ts +27 -0
  165. package/src/native/index.ts +17 -0
  166. package/src/primitives/controllable-state.ts +112 -0
  167. package/src/primitives/finite-chart.ts +14 -0
  168. package/src/primitives/glass.tsx +171 -0
  169. package/src/primitives/scrim.tsx +7 -1
@@ -0,0 +1,1584 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * AIInput — the composer a person types a prompt into.
5
+ *
6
+ * ```tsx
7
+ * <AIInput value={value} onValueChange={setValue} onSubmit={send}>
8
+ * <AIInput.Field placeholder="Chat with the model" />
9
+ * <AIInput.Toolbar>
10
+ * <AIInput.Action label="Add" icon={<PlusIcon />} onPress={openSheet} />
11
+ * <AIInput.Pill label="Sonnet 4.6" detail="High" onPress={openModels} />
12
+ * <AIInput.Action label="Dictate" icon={<MicIcon />} onPress={record} />
13
+ * <AIInput.Submit />
14
+ * </AIInput.Toolbar>
15
+ * </AIInput>
16
+ * ```
17
+ *
18
+ * ## The field grows, and then it stops
19
+ *
20
+ * It opens one line tall and follows what is typed into it up to `maxRows`,
21
+ * after which it holds that height and scrolls its own content. Five rows is
22
+ * the default because a composer that keeps growing eventually pushes its own
23
+ * send button off the bottom of the screen, and the row that carries the
24
+ * actions stays pinned to the bottom of the box either way.
25
+ *
26
+ * ## It does not record anything
27
+ *
28
+ * There is a recording state, and it draws a live meter, but nothing here
29
+ * touches the microphone. The app owns the recorder — the permission prompt,
30
+ * the session, the platform quirks — and passes back a `level` between 0 and 1
31
+ * along with a `status`. Pass a Reanimated shared value and a recorder
32
+ * reporting every 30ms never re-renders anything above this component.
33
+ *
34
+ * That division is what keeps the composer free of an audio dependency.
35
+ *
36
+ * ## Glass is iOS 26 and above
37
+ *
38
+ * The surfaces ask for the system material and get it where it exists. Below
39
+ * iOS 26, on Android, and for anyone with Reduce Transparency switched on,
40
+ * they are drawn as solid token surfaces instead. Both are finished looks; see
41
+ * `Glass` for why nothing is faked on the platforms without it.
42
+ *
43
+ * ## Where the props come from
44
+ *
45
+ * The root takes `ViewProps`. `Field` takes `TextInputProps`, so
46
+ * `keyboardType`, `maxLength`, `autoFocus` and the rest work as they always
47
+ * do. Every part takes `className`.
48
+ */
49
+ import { Children, createContext, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
50
+ import { AccessibilityInfo, findNodeHandle, TextInput, View, useWindowDimensions } from 'react-native';
51
+ import Animated, { Easing, FadeOut, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
52
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
53
+ import { tv } from 'tailwind-variants';
54
+ import { useCSSVariable } from 'uniwind';
55
+ import { useDirection } from "../../hooks/use-direction.js";
56
+ import { AudioLinesIcon, CheckIcon, ChevronLeftIcon, ChevronRightIcon, IconColorProvider, MicIcon, SendArrowIcon, XIcon } from "../../icons/index.js";
57
+ import { hasNativeUI } from "../../native/index.js";
58
+ import { AnimatedPressable } from "../../primitives/animated-pressable.js";
59
+ import { Glass } from "../../primitives/glass.js";
60
+ import { KeyboardAvoider } from "../../primitives/keyboard-avoider.js";
61
+ import { Text } from "../../primitives/text.js";
62
+ import { cn } from "../../utils/cn.js";
63
+ import { BottomSheet } from "../bottom-sheet/index.js";
64
+ import { Button } from "../button/index.js";
65
+ import { Soundwave } from "../soundwave/index.js";
66
+ import { Switch } from "../switch/index.js";
67
+ import { AI_INPUT_METRICS, growthBounds } from "./ai-input-growth.js";
68
+
69
+ /** What the app is doing, which is what the trailing button offers to do next. */
70
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
71
+ /** Corner radius per size, in points. The material rounds itself to these. */
72
+ const RADIUS = {
73
+ sm: 22,
74
+ md: 26,
75
+ lg: 30
76
+ };
77
+
78
+ /**
79
+ * Circular control diameter per size.
80
+ *
81
+ * Deliberately under the 44pt touch minimum: these sit in a row inside a card
82
+ * with padding around it, and the card's own padding carries the target the
83
+ * finger actually gets. A 44pt circle here makes a toolbar that is taller than
84
+ * the field above it.
85
+ */
86
+ const CONTROL = {
87
+ sm: 26,
88
+ md: 30,
89
+ lg: 34
90
+ };
91
+
92
+ /**
93
+ * The row a platform-drawn control needs, and the space kept under it.
94
+ *
95
+ * `Button` frames a native icon button at 44pt — the number that ends the
96
+ * measurement chain rather than one that sets a look. A row shorter than that
97
+ * is a row its controls hang out of, over whatever is above them, and a row
98
+ * with no definite height at all is one they never lay out against: a hosted
99
+ * view only measures where something above it is fixed on both axes.
100
+ *
101
+ * The drawn controls are smaller and are laid out by the row itself, so this
102
+ * applies only when the controls have been handed over.
103
+ */
104
+ const NATIVE_ROW_HEIGHT = 48;
105
+ const NATIVE_ROW_FOOT = 4;
106
+ const EASE = Easing.out(Easing.cubic);
107
+ const HEIGHT_DURATION = 240;
108
+ const ENTER_DURATION = 220;
109
+ const EXIT_DURATION = 140;
110
+
111
+ /**
112
+ * How far a pushed screen travels in, as a fraction of the sheet's width.
113
+ *
114
+ * A third rather than the whole width: the sheet is not a screen, and a slide
115
+ * the full width of it reads as the app navigating rather than as this surface
116
+ * going one level deeper.
117
+ */
118
+ const SLIDE_FRACTION = 0.33;
119
+ const aiInputVariants = tv({
120
+ slots: {
121
+ /*
122
+ * The card carries a shadow because the material does not carry an edge.
123
+ *
124
+ * Glass lifts its own edge against what is behind it, and over a light
125
+ * background there is nothing for it to lift against — the composer reads
126
+ * as a faint rectangle on white. The shadow is what separates it, and it
127
+ * is the same one everywhere rather than a light-mode special case: on a
128
+ * dark background a black shadow costs nothing and shows nothing.
129
+ */
130
+ root: 'w-full shadow-md',
131
+ field: 'w-full bg-transparent px-4 font-normal text-foreground',
132
+ // On one line the field is what takes up the slack between the controls,
133
+ // and the row's own padding already stands it off the edge.
134
+ fieldInline: 'flex-1 bg-transparent px-2 font-normal text-foreground',
135
+ /*
136
+ * Centred, not bottom-aligned.
137
+ *
138
+ * The stacked toolbar sits under the field, so it aligns to the bottom.
139
+ * Here the field is *between* the controls, and its box is not the height
140
+ * this component computes: a multiline TextInput carries a vertical inset
141
+ * of the platform's own on top of any padding given to it. Bottom-aligning
142
+ * a box whose real height is unknown puts its text below the controls
143
+ * beside it by however much that inset is — which is what kept happening.
144
+ *
145
+ * Centring does not need to know the height. Whatever the field's box
146
+ * turns out to be, its middle lines up with theirs, and the text sits in
147
+ * the middle of it.
148
+ */
149
+ row: 'w-full flex-row items-center gap-1.5 p-2',
150
+ toolbar: 'w-full flex-row items-center gap-2 px-2 pb-2',
151
+ spacer: 'flex-1',
152
+ pill: 'flex-row items-center gap-1.5 px-3',
153
+ recording: 'w-full flex-row items-center gap-3 px-2 pb-2',
154
+ meter: 'flex-1'
155
+ },
156
+ variants: {
157
+ size: {
158
+ sm: {
159
+ toolbar: 'gap-1.5'
160
+ },
161
+ md: {},
162
+ lg: {
163
+ toolbar: 'gap-2.5'
164
+ }
165
+ },
166
+ disabled: {
167
+ true: {
168
+ root: 'opacity-60'
169
+ },
170
+ false: {}
171
+ }
172
+ },
173
+ defaultVariants: {
174
+ size: 'md',
175
+ disabled: false
176
+ }
177
+ });
178
+
179
+ /* -------------------------------------------------------------------------- */
180
+ /* Root */
181
+ /* -------------------------------------------------------------------------- */
182
+
183
+ const AIInputContext = /*#__PURE__*/createContext(null);
184
+ function useAIInput(component) {
185
+ const context = useContext(AIInputContext);
186
+ if (!context) throw new Error(`${component} must be used within an <AIInput>`);
187
+ return context;
188
+ }
189
+
190
+ /**
191
+ * The composer's context if there is one, and nothing if there is not.
192
+ *
193
+ * The controls that only want a size and an enabled state work anywhere — a
194
+ * sheet header, a voice-mode toolbar, a row of their own. Those surfaces
195
+ * render through a portal or as their own screen, so a root wrapped around the
196
+ * composer is not above them, and demanding one would make a button throw for
197
+ * being used where it was designed to be used.
198
+ */
199
+ function useAIInputOptional() {
200
+ return useContext(AIInputContext);
201
+ }
202
+ function AIInputRoot({
203
+ children,
204
+ className,
205
+ value: valueProp,
206
+ onValueChange,
207
+ defaultValue = '',
208
+ status = 'ready',
209
+ onSubmit,
210
+ onStop,
211
+ onVoice,
212
+ onRecordCancel,
213
+ onRecordConfirm,
214
+ level,
215
+ size = 'md',
216
+ disabled = false,
217
+ native = false,
218
+ minRows = 1,
219
+ maxRows = 5,
220
+ avoidKeyboard = true,
221
+ keyboardBottomInset = 0,
222
+ keyboardGap = 8,
223
+ style,
224
+ ...props
225
+ }) {
226
+ const [internalValue, setInternalValue] = useState(defaultValue);
227
+ const isControlled = valueProp !== undefined;
228
+ const value = isControlled ? valueProp : internalValue;
229
+ const [focused, setFocused] = useState(false);
230
+ const setValue = useCallback(next => {
231
+ if (!isControlled) setInternalValue(next);
232
+ onValueChange?.(next);
233
+ }, [isControlled, onValueChange]);
234
+ const submit = useCallback(() => {
235
+ const trimmed = value.trim();
236
+ if (!trimmed) return;
237
+ onSubmit?.(value);
238
+ }, [onSubmit, value]);
239
+
240
+ /*
241
+ * Whether the controls can actually be handed over.
242
+ *
243
+ * `native` is a request, not a fact: the toolkit is an optional peer, and
244
+ * without it `Button` quietly draws its own control instead — at the size
245
+ * *it* uses, which is not the size this row is built around. Asking for a
246
+ * platform control and getting a 44pt drawn one in a row shaped for a 30pt
247
+ * one is how the buttons ended up over the field.
248
+ *
249
+ * So the request is resolved against what is installed, once, and every part
250
+ * reads the answer rather than the ask.
251
+ */
252
+ const platformControls = native && hasNativeUI();
253
+ const context = useMemo(() => ({
254
+ value,
255
+ setValue,
256
+ status,
257
+ size,
258
+ level,
259
+ disabled,
260
+ native: platformControls,
261
+ minRows,
262
+ maxRows,
263
+ focused,
264
+ setFocused,
265
+ submit,
266
+ canVoice: onVoice !== undefined,
267
+ canStop: onStop !== undefined,
268
+ stop: () => onStop?.(),
269
+ recordCancel: () => onRecordCancel?.(),
270
+ recordConfirm: () => onRecordConfirm?.(),
271
+ voice: () => onVoice?.()
272
+ }), [value, setValue, status, size, level, disabled, platformControls, minRows, maxRows, focused, submit, onStop, onVoice, onRecordCancel, onRecordConfirm, onVoice]);
273
+ const slots = aiInputVariants({
274
+ size,
275
+ disabled
276
+ });
277
+
278
+ /*
279
+ * Whether the card has to keep room at its foot for a row it does not lay
280
+ * out.
281
+ *
282
+ * A handed-over control is a hosted native view, and a hosted view settles
283
+ * where the platform puts it rather than where the row it is in ends up —
284
+ * which, in a card that is also sizing itself to the field above, is over
285
+ * that field. Reserving the row's height here and pinning the row into the
286
+ * reservation takes the question away: the field's box stops where the
287
+ * reservation starts, so a control that draws itself somewhere unexpected
288
+ * has nothing above it left to cover.
289
+ *
290
+ * Read from the children rather than tracked in state, so the card and the
291
+ * row agree on the first render instead of the second.
292
+ */
293
+ const hasNativeRow = platformControls && Children.toArray(children).some(child => /*#__PURE__*/isValidElement(child) && (child.type === AIInputToolbar || child.type === AIInputRecording));
294
+ const surface = /*#__PURE__*/_jsx(Glass, {
295
+ radius: RADIUS[size],
296
+ fallbackClassName: "border border-border bg-card",
297
+ ...props,
298
+ className: slots.root({
299
+ className
300
+ }),
301
+ style: [hasNativeRow ? {
302
+ paddingBottom: NATIVE_ROW_HEIGHT
303
+ } : null, style],
304
+ children: children
305
+ });
306
+
307
+ /*
308
+ * The keyboard hook stays behind a component boundary rather than an
309
+ * `enabled` flag. Calling it at all has global consequences — without the
310
+ * keyboard controller installed it falls back to Reanimated's
311
+ * useAnimatedKeyboard, which switches Android out of adjustResize for the
312
+ * whole app. A composer that was told not to avoid the keyboard must not do
313
+ * that to every other screen in the app.
314
+ */
315
+ return /*#__PURE__*/_jsx(AIInputContext.Provider, {
316
+ value: context,
317
+ children: avoidKeyboard ? /*#__PURE__*/_jsx(KeyboardAvoider, {
318
+ active: focused,
319
+ mode: "dock"
320
+ // Docking travels the composer up by the keyboard's height less the
321
+ // inset it already sits above. Claiming to sit a little lower than it
322
+ // does is what leaves a gap once it arrives.
323
+ ,
324
+ bottomInset: keyboardBottomInset - keyboardGap,
325
+ className: "w-full",
326
+ children: surface
327
+ }) : surface
328
+ });
329
+ }
330
+
331
+ /* -------------------------------------------------------------------------- */
332
+ /* Field */
333
+ /* -------------------------------------------------------------------------- */
334
+
335
+ function AIInputField({
336
+ className,
337
+ placeholder = 'Ask anything',
338
+ onFocus,
339
+ onBlur,
340
+ style,
341
+ ...props
342
+ }) {
343
+ const context = useAIInput('AIInput.Field');
344
+ const {
345
+ value,
346
+ setValue,
347
+ size,
348
+ disabled,
349
+ minRows,
350
+ maxRows,
351
+ setFocused
352
+ } = context;
353
+ const metrics = AI_INPUT_METRICS[size];
354
+ const placeholderColor = useCSSVariable('--color-muted-foreground');
355
+ const bounds = growthBounds(metrics, minRows, maxRows);
356
+ const inline = useContext(AIInputInlineContext);
357
+ const handleFocus = useCallback(event => {
358
+ setFocused(true);
359
+ onFocus?.(event);
360
+ }, [onFocus, setFocused]);
361
+ const handleBlur = useCallback(event => {
362
+ setFocused(false);
363
+ onBlur?.(event);
364
+ }, [onBlur, setFocused]);
365
+
366
+ /*
367
+ * Type metrics go through `style` rather than through classes, because the
368
+ * height is computed from them and a line height set in two places is a line
369
+ * height that will disagree with itself.
370
+ */
371
+ /*
372
+ * On one line the field's first row has to be exactly as tall as the buttons
373
+ * beside it.
374
+ *
375
+ * The row aligns to the bottom, so that a field which has grown keeps its
376
+ * controls at the foot of the box rather than floating them beside the
377
+ * middle of the text. That alignment is only correct if an *ungrown* field
378
+ * is the same height as the controls — otherwise its extra padding pushes
379
+ * the whole row down inside the card, which is a composer whose contents sit
380
+ * low in it. So the padding is whatever centres one line in a control.
381
+ */
382
+ /*
383
+ * On one line the field states no line height at all.
384
+ *
385
+ * A line height above the font's own exists to separate lines from each
386
+ * other, and iOS spends the surplus *above* the glyphs rather than splitting
387
+ * it — so a single line set to 22 over 16pt type sits several points below
388
+ * the buttons beside it, placeholder and value alike. With none set, the
389
+ * platform lays the text out in the font's own line box, which sits where a
390
+ * line of that font is supposed to.
391
+ *
392
+ * The box is then exactly one control tall with the natural line centred in
393
+ * it. `NATURAL_LEADING` is what a line of a given size actually occupies —
394
+ * it only has to be close, since it decides a padding either side rather
395
+ * than the height itself.
396
+ */
397
+ const lineHeight = inline ? undefined : metrics.lineHeight;
398
+ const padding = inline ? 0 : metrics.padding;
399
+ const boxStyle = useMemo(() => ({
400
+ /*
401
+ * A floor and a ceiling, and no height. The field sizes itself to its
402
+ * own content between them, which the layout engine does without anyone
403
+ * measuring anything — and once it reaches the ceiling, a multiline
404
+ * field scrolls its content instead of growing.
405
+ *
406
+ * Deriving a height from `onContentSizeChange` instead is a loop whose
407
+ * answer differs by platform: one reports the height of the text, the
408
+ * other the height of the box that was just set from it, and on that one
409
+ * the field can never grow past the height it opened at.
410
+ */
411
+ minHeight: inline ? undefined : bounds.minHeight,
412
+ maxHeight: bounds.maxHeight,
413
+ fontSize: metrics.fontSize,
414
+ lineHeight,
415
+ paddingTop: padding,
416
+ paddingBottom: padding,
417
+ /*
418
+ * Android puts the caret in the middle of a multiline box unless told
419
+ * otherwise, which is wrong for a field that grows downward — and right
420
+ * for one that is a single line between two buttons.
421
+ */
422
+ textAlignVertical: inline ? 'center' : 'top'
423
+ }), [bounds.minHeight, bounds.maxHeight, inline, lineHeight, metrics.fontSize, padding]);
424
+ const slots = aiInputVariants({
425
+ size
426
+ });
427
+ return /*#__PURE__*/_jsx(TextInput, {
428
+ ...props,
429
+ multiline: true,
430
+ value: value,
431
+ onChangeText: setValue,
432
+ placeholder: placeholder,
433
+ placeholderTextColor: typeof placeholderColor === 'string' ? placeholderColor : undefined,
434
+ editable: !disabled
435
+ /*
436
+ * `scrollEnabled` is deliberately left alone.
437
+ *
438
+ * Driving it from the measurement is a loop: switching it on changes
439
+ * what the platform reports as the content size, which changes the
440
+ * answer to whether it should be on, and the field resizes for ever. A
441
+ * multiline field scrolls once its height is fixed anyway, so there is
442
+ * nothing to drive — the clamp below is the whole mechanism.
443
+ */,
444
+ onFocus: handleFocus,
445
+ onBlur: handleBlur,
446
+ className: inline ? slots.fieldInline({
447
+ className
448
+ }) : slots.field({
449
+ className
450
+ }),
451
+ style: [boxStyle, style]
452
+ });
453
+ }
454
+
455
+ /* -------------------------------------------------------------------------- */
456
+ /* Toolbar and its controls */
457
+ /* -------------------------------------------------------------------------- */
458
+
459
+ /**
460
+ * True inside `AIInput.Row` — the parts that lay out differently on one line
461
+ * read it rather than taking a prop each, so a caller rearranges the composer
462
+ * by moving one component instead of setting a flag on three.
463
+ */
464
+ const AIInputInlineContext = /*#__PURE__*/createContext(false);
465
+ /**
466
+ * The field and its controls on a single line.
467
+ *
468
+ * The composer at its smallest: one pill above the keyboard with the field
469
+ * between the controls rather than above them. It suits a bar that is always
470
+ * on screen, where the stacked composer suits one that is the focus of it.
471
+ *
472
+ * The field still grows — it just grows the row.
473
+ *
474
+ * ```tsx
475
+ * <AIInput>
476
+ * <AIInput.Row>
477
+ * <AIInput.Action label="Add" icon={<PlusIcon />} />
478
+ * <AIInput.Field placeholder="Ask anything" />
479
+ * <AIInput.Action label="Dictate" icon={<MicIcon />} />
480
+ * <AIInput.Submit />
481
+ * </AIInput.Row>
482
+ * </AIInput>
483
+ * ```
484
+ */
485
+ function AIInputRow({
486
+ className,
487
+ children,
488
+ ...props
489
+ }) {
490
+ const {
491
+ size
492
+ } = useAIInput('AIInput.Row');
493
+ const slots = aiInputVariants({
494
+ size
495
+ });
496
+ return /*#__PURE__*/_jsx(AIInputInlineContext.Provider, {
497
+ value: true,
498
+ children: /*#__PURE__*/_jsx(View, {
499
+ ...props,
500
+ className: slots.row({
501
+ className
502
+ }),
503
+ children: children
504
+ })
505
+ });
506
+ }
507
+ function AIInputToolbar({
508
+ className,
509
+ children,
510
+ style,
511
+ ...props
512
+ }) {
513
+ const {
514
+ size,
515
+ native
516
+ } = useAIInput('AIInput.Toolbar');
517
+ const slots = aiInputVariants({
518
+ size
519
+ });
520
+ return /*#__PURE__*/_jsx(View, {
521
+ ...props,
522
+ accessibilityRole: "toolbar",
523
+ className: cn(slots.toolbar({
524
+ className
525
+ }), native && 'absolute inset-x-0 bottom-0'),
526
+ style: [native ? {
527
+ height: NATIVE_ROW_HEIGHT,
528
+ paddingBottom: NATIVE_ROW_FOOT
529
+ } : null, style],
530
+ children: children
531
+ });
532
+ }
533
+
534
+ /** Pushes everything after it to the trailing end of the toolbar. */
535
+ function AIInputSpacer({
536
+ className,
537
+ ...props
538
+ }) {
539
+ return /*#__PURE__*/_jsx(View, {
540
+ ...props,
541
+ className: cn('flex-1', className)
542
+ });
543
+ }
544
+ function AIInputAction({
545
+ className,
546
+ label,
547
+ icon,
548
+ onPress,
549
+ disabled,
550
+ size: sizeProp,
551
+ native,
552
+ ...props
553
+ }) {
554
+ const context = useAIInputOptional();
555
+ const scale = sizeProp ?? context?.size ?? 'md';
556
+ const size = CONTROL[scale];
557
+ const isDisabled = disabled ?? context?.disabled ?? false;
558
+ const isNative = (native ?? context?.native ?? false) && hasNativeUI();
559
+
560
+ /*
561
+ * The platform's own button, in its own material. `Button` already knows how
562
+ * to ask for that — the circular border shape, the glass style, and the
563
+ * hosting an icon inside a native control needs — so this is a handoff
564
+ * rather than a second implementation of it.
565
+ */
566
+ if (isNative) {
567
+ return /*#__PURE__*/_jsx(View, {
568
+ ...props,
569
+ className: className,
570
+ children: /*#__PURE__*/_jsx(Button, {
571
+ native: true,
572
+ glass: true,
573
+ variant: "secondary",
574
+ size: "icon",
575
+ accessibilityLabel: label,
576
+ disabled: isDisabled,
577
+ onPress: onPress,
578
+ children: icon
579
+ })
580
+ });
581
+ }
582
+ return /*#__PURE__*/_jsx(Glass, {
583
+ variant: "clear",
584
+ radius: size / 2,
585
+ fallbackClassName: "bg-muted",
586
+ ...props,
587
+ className: className,
588
+ style: {
589
+ width: size,
590
+ height: size
591
+ },
592
+ children: /*#__PURE__*/_jsx(AnimatedPressable, {
593
+ accessibilityRole: "button",
594
+ accessibilityLabel: label,
595
+ accessibilityState: {
596
+ disabled: isDisabled
597
+ },
598
+ disabled: isDisabled,
599
+ onPress: onPress
600
+ // The dim goes on the content rather than on the material. Fading a
601
+ // system material degrades it, and at zero it stops drawing entirely.
602
+ ,
603
+ className: cn('h-full w-full items-center justify-center', isDisabled && 'opacity-40'),
604
+ children: icon
605
+ })
606
+ });
607
+ }
608
+ function AIInputPill({
609
+ className,
610
+ label,
611
+ detail,
612
+ indicator,
613
+ indicatorSymbol,
614
+ onPress,
615
+ disabled,
616
+ accessibilityLabel,
617
+ size: sizeProp,
618
+ native,
619
+ ...props
620
+ }) {
621
+ const context = useAIInputOptional();
622
+ const size = sizeProp ?? context?.size ?? 'md';
623
+ const height = CONTROL[size];
624
+ const isDisabled = disabled ?? context?.disabled ?? false;
625
+ const slots = aiInputVariants({
626
+ size
627
+ });
628
+
629
+ /*
630
+ * Only text goes to the platform. Passing elements makes it host them, and a
631
+ * hosted view inside a labelled button leaves the width unresolved — which
632
+ * is not an exception anything here could catch but a crash in native code.
633
+ *
634
+ * `detail` is text in every case that matters, so it goes over as part of the
635
+ * label rather than keeping the pill drawn; the platform sets one label in
636
+ * one weight, which is the same trade every handed-over control makes.
637
+ * `indicator` is an element and never goes, which is what `indicatorSymbol`
638
+ * is for.
639
+ */
640
+ const isNative = (native ?? context?.native ?? false) && hasNativeUI() && typeof label === 'string' && (detail === undefined || typeof detail === 'string');
641
+ if (isNative) {
642
+ return /*#__PURE__*/_jsx(View, {
643
+ ...props,
644
+ className: className,
645
+ children: /*#__PURE__*/_jsx(Button, {
646
+ native: true,
647
+ glass: true,
648
+ variant: "secondary",
649
+ size: "sm",
650
+ systemImage: indicatorSymbol,
651
+ accessibilityLabel: accessibilityLabel,
652
+ disabled: isDisabled,
653
+ onPress: onPress,
654
+ children: detail === undefined ? label : `${label} ${detail}`
655
+ })
656
+ });
657
+ }
658
+ return /*#__PURE__*/_jsx(Glass, {
659
+ variant: "clear",
660
+ radius: height / 2,
661
+ fallbackClassName: "bg-muted",
662
+ ...props,
663
+ className: className,
664
+ style: {
665
+ height
666
+ },
667
+ children: /*#__PURE__*/_jsxs(AnimatedPressable, {
668
+ accessibilityRole: "button",
669
+ accessibilityLabel: accessibilityLabel,
670
+ accessibilityState: {
671
+ disabled: isDisabled
672
+ },
673
+ disabled: isDisabled,
674
+ onPress: onPress,
675
+ className: slots.pill({
676
+ className: cn('h-full', isDisabled && 'opacity-40')
677
+ }),
678
+ children: [typeof label === 'string' ? /*#__PURE__*/_jsx(Text, {
679
+ size: "sm",
680
+ weight: "medium",
681
+ children: label
682
+ }) : label, typeof detail === 'string' ? /*#__PURE__*/_jsx(Text, {
683
+ size: "sm",
684
+ muted: true,
685
+ children: detail
686
+ }) : detail, indicator]
687
+ })
688
+ });
689
+ }
690
+ /**
691
+ * The trailing button, which is three buttons wearing one shape.
692
+ *
693
+ * Empty and ready, it opens voice mode; with something typed, it sends; while
694
+ * the model is answering, it stops. One control rather than three because only
695
+ * one of them is ever the thing to do, and a row of dimmed siblings is a row
696
+ * of things that look broken.
697
+ */
698
+ function AIInputSubmit({
699
+ className,
700
+ sendLabel = 'Send',
701
+ voiceLabel = 'Voice mode',
702
+ stopLabel = 'Stop',
703
+ native,
704
+ ...props
705
+ }) {
706
+ const context = useAIInput('AIInput.Submit');
707
+ const {
708
+ value,
709
+ status,
710
+ size,
711
+ disabled,
712
+ submit,
713
+ stop,
714
+ voice,
715
+ canVoice,
716
+ canStop
717
+ } = context;
718
+ const diameter = CONTROL[size] + 2;
719
+ const streaming = status === 'streaming' || status === 'submitted';
720
+ const hasText = value.trim().length > 0;
721
+ const onSurface = useCSSVariable('--color-primary-foreground');
722
+ const onSolid = useCSSVariable('--color-background');
723
+ const mode = streaming ? 'stop' : hasText ? 'send' : 'voice';
724
+ const label = mode === 'stop' ? stopLabel : mode === 'send' ? sendLabel : voiceLabel;
725
+ const onPress = mode === 'stop' ? stop : mode === 'send' ? submit : voice;
726
+ const isNative = (native ?? context.native) && hasNativeUI();
727
+
728
+ /*
729
+ * Nothing to send is not something to press.
730
+ *
731
+ * Empty, the button offers voice mode — but only if the app took `onVoice`;
732
+ * with nothing wired to it, it is a live-looking control that does nothing.
733
+ * Same for stop. So the button is disabled whenever the thing it is offering
734
+ * cannot happen, which for an empty composer with no voice mode means it
735
+ * stays inert until something is typed.
736
+ */
737
+ const inert = disabled || (mode === 'send' ? !hasText : mode === 'stop' ? !canStop : !canVoice);
738
+ const glyph = mode === 'send' ? /*#__PURE__*/_jsx(SendArrowIcon, {
739
+ size: 18
740
+ }) : mode === 'stop' ? /*#__PURE__*/_jsx(View, {
741
+ className: "rounded-sm bg-primary-foreground",
742
+ style: {
743
+ width: 11,
744
+ height: 11
745
+ }
746
+ }) : /*#__PURE__*/_jsx(AudioLinesIcon, {
747
+ size: 18
748
+ });
749
+
750
+ /*
751
+ * The platform's own button, tinted. `Button` maps a primary variant onto
752
+ * `glassProminent`, which is the one that keeps an accent fill — drawing the
753
+ * material by hand over a plain button throws that fill away, and this is
754
+ * the one control in the row that is supposed to be filled.
755
+ */
756
+ if (isNative) {
757
+ return /*#__PURE__*/_jsx(View, {
758
+ ...props,
759
+ className: className,
760
+ children: /*#__PURE__*/_jsx(Button, {
761
+ native: true,
762
+ glass: true,
763
+ variant: "primary",
764
+ size: "icon",
765
+ accessibilityLabel: label,
766
+ disabled: inert,
767
+ onPress: onPress,
768
+ children: glyph
769
+ })
770
+ });
771
+ }
772
+ return /*#__PURE__*/_jsx(View, {
773
+ ...props,
774
+ className: cn('overflow-hidden rounded-full', mode === 'voice' ? 'bg-foreground' : 'bg-primary', className),
775
+ style: {
776
+ width: diameter,
777
+ height: diameter
778
+ },
779
+ children: /*#__PURE__*/_jsx(AnimatedPressable, {
780
+ accessibilityRole: "button",
781
+ accessibilityLabel: label,
782
+ accessibilityState: {
783
+ disabled: inert
784
+ },
785
+ disabled: inert,
786
+ onPress: onPress,
787
+ className: cn('h-full w-full items-center justify-center', inert && 'opacity-40'),
788
+ children: /*#__PURE__*/_jsx(IconColorProvider, {
789
+ color: mode === 'voice' ? typeof onSolid === 'string' ? onSolid : undefined : typeof onSurface === 'string' ? onSurface : undefined,
790
+ children: glyph
791
+ })
792
+ })
793
+ });
794
+ }
795
+ /**
796
+ * What the toolbar becomes while a recording is running: throw it away, watch
797
+ * it, or keep it. Render it in place of `AIInput.Toolbar` when `status` is
798
+ * `recording`.
799
+ */
800
+ function AIInputRecording({
801
+ className,
802
+ cancelLabel = 'Discard recording',
803
+ confirmLabel = 'Use recording',
804
+ native,
805
+ ...props
806
+ }) {
807
+ const context = useAIInput('AIInput.Recording');
808
+ const {
809
+ size,
810
+ level,
811
+ recordCancel,
812
+ recordConfirm,
813
+ native: contextNative
814
+ } = context;
815
+ const control = CONTROL[size];
816
+ const slots = aiInputVariants({
817
+ size
818
+ });
819
+ const onPrimary = useCSSVariable('--color-primary-foreground');
820
+ const isNative = (native ?? contextNative) && hasNativeUI();
821
+ const meter = /*#__PURE__*/_jsx(View, {
822
+ className: slots.meter(),
823
+ children: /*#__PURE__*/_jsx(Soundwave, {
824
+ variant: "bars",
825
+ mode: "scrolling",
826
+ state: "listening",
827
+ level: level,
828
+ centered: true,
829
+ barWidth: 2.5,
830
+ height: control
831
+ })
832
+ });
833
+ if (isNative) {
834
+ return /*#__PURE__*/_jsxs(View, {
835
+ ...props,
836
+ className: cn(slots.recording({
837
+ className
838
+ }), 'absolute inset-x-0 bottom-0'),
839
+ style: {
840
+ height: NATIVE_ROW_HEIGHT,
841
+ paddingBottom: NATIVE_ROW_FOOT
842
+ },
843
+ children: [/*#__PURE__*/_jsx(Button, {
844
+ native: true,
845
+ glass: true,
846
+ variant: "secondary",
847
+ size: "icon",
848
+ accessibilityLabel: cancelLabel,
849
+ onPress: recordCancel,
850
+ children: /*#__PURE__*/_jsx(XIcon, {
851
+ size: 16
852
+ })
853
+ }), meter, /*#__PURE__*/_jsx(Button, {
854
+ native: true,
855
+ glass: true,
856
+ variant: "primary",
857
+ size: "icon",
858
+ accessibilityLabel: confirmLabel,
859
+ onPress: recordConfirm,
860
+ children: /*#__PURE__*/_jsx(CheckIcon, {
861
+ size: 16
862
+ })
863
+ })]
864
+ });
865
+ }
866
+ return /*#__PURE__*/_jsxs(View, {
867
+ ...props,
868
+ className: slots.recording({
869
+ className
870
+ }),
871
+ children: [/*#__PURE__*/_jsx(Glass, {
872
+ variant: "clear",
873
+ radius: control / 2,
874
+ fallbackClassName: "bg-muted",
875
+ style: {
876
+ width: control,
877
+ height: control
878
+ },
879
+ children: /*#__PURE__*/_jsx(AnimatedPressable, {
880
+ accessibilityRole: "button",
881
+ accessibilityLabel: cancelLabel,
882
+ onPress: recordCancel,
883
+ className: "h-full w-full items-center justify-center",
884
+ children: /*#__PURE__*/_jsx(XIcon, {
885
+ size: 16
886
+ })
887
+ })
888
+ }), meter, /*#__PURE__*/_jsx(View, {
889
+ className: "overflow-hidden rounded-full bg-primary",
890
+ style: {
891
+ width: control + 4,
892
+ height: control + 4
893
+ },
894
+ children: /*#__PURE__*/_jsx(AnimatedPressable, {
895
+ accessibilityRole: "button",
896
+ accessibilityLabel: confirmLabel,
897
+ onPress: recordConfirm,
898
+ className: "h-full w-full items-center justify-center",
899
+ children: /*#__PURE__*/_jsx(CheckIcon, {
900
+ size: 18,
901
+ color: typeof onPrimary === 'string' ? onPrimary : undefined
902
+ })
903
+ })
904
+ })]
905
+ });
906
+ }
907
+
908
+ /* -------------------------------------------------------------------------- */
909
+ /* Sheet */
910
+ /* -------------------------------------------------------------------------- */
911
+
912
+ const AIInputSheetContext = /*#__PURE__*/createContext(null);
913
+ function useAIInputSheet(component) {
914
+ const context = useContext(AIInputSheetContext);
915
+ if (!context) throw new Error(`${component} must be used within an <AIInput.Sheet>`);
916
+ return context;
917
+ }
918
+ function AIInputSheetScreen({
919
+ className,
920
+ id,
921
+ title,
922
+ trailing,
923
+ children,
924
+ ...props
925
+ }) {
926
+ void id;
927
+ void title;
928
+ void trailing;
929
+ return /*#__PURE__*/_jsx(View, {
930
+ ...props,
931
+ className: cn('gap-4 pb-2', className),
932
+ children: children
933
+ });
934
+ }
935
+ function screensOf(children) {
936
+ return Children.toArray(children).filter(child => /*#__PURE__*/isValidElement(child) && child.type === AIInputSheetScreen);
937
+ }
938
+ /**
939
+ * The sheet the composer's controls open, and the screens it goes on to.
940
+ *
941
+ * A row that leads somewhere pushes a screen onto this sheet rather than
942
+ * opening a second one over it. The sheet stays where it is, the header's
943
+ * close button becomes a back button, and the body slides — which is what
944
+ * makes going one level deeper feel like the same surface rather than another
945
+ * one landing on top of it.
946
+ *
947
+ * ```tsx
948
+ * <AIInput.Sheet open={open} onOpenChange={setOpen}>
949
+ * <AIInput.Sheet.Screen id="root" title="Add to chat">
950
+ * <AIInput.Sheet.Group>
951
+ * <AIInput.Sheet.Row label="Add to project" value="None" to="project" />
952
+ * <AIInput.Sheet.Toggle label="Web search" value={web} onValueChange={setWeb} />
953
+ * </AIInput.Sheet.Group>
954
+ * </AIInput.Sheet.Screen>
955
+ * <AIInput.Sheet.Screen id="project" title="Add to project">…</AIInput.Sheet.Screen>
956
+ * </AIInput.Sheet>
957
+ * ```
958
+ */
959
+ function AIInputSheet({
960
+ children,
961
+ className,
962
+ open: openProp,
963
+ onOpenChange,
964
+ defaultOpen = false,
965
+ initialScreen,
966
+ onScreenChange,
967
+ blur = false,
968
+ size = 'auto',
969
+ detached = true
970
+ }) {
971
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
972
+ const isControlled = openProp !== undefined;
973
+ const open = isControlled ? openProp : internalOpen;
974
+ const screens = screensOf(children);
975
+ const rootId = initialScreen ?? screens[0]?.props.id ?? '';
976
+ const [stack, setStack] = useState(rootId ? [rootId] : []);
977
+ const setOpen = useCallback(next => {
978
+ if (!isControlled) setInternalOpen(next);
979
+ onOpenChange?.(next);
980
+ }, [isControlled, onOpenChange]);
981
+
982
+ /*
983
+ * A closed sheet forgets where it was. Reopening onto a screen somebody
984
+ * pushed and then dismissed is a sheet that answers a question nobody asked
985
+ * again. Done on the way out rather than the way in, where the reset would
986
+ * be visible for a frame before the sheet arrived.
987
+ */
988
+ useEffect(() => {
989
+ if (!open && rootId) setStack([rootId]);
990
+ }, [open, rootId]);
991
+ const push = useCallback(id => {
992
+ setStack(current => current[current.length - 1] === id ? current : [...current, id]);
993
+ onScreenChange?.(id);
994
+ }, [onScreenChange]);
995
+ const pop = useCallback(() => {
996
+ setStack(current => {
997
+ if (current.length <= 1) return current;
998
+ const next = current.slice(0, -1);
999
+ const top = next[next.length - 1];
1000
+ if (top) onScreenChange?.(top);
1001
+ return next;
1002
+ });
1003
+ }, [onScreenChange]);
1004
+ const close = useCallback(() => setOpen(false), [setOpen]);
1005
+ const context = useMemo(() => ({
1006
+ stack,
1007
+ push,
1008
+ pop,
1009
+ close
1010
+ }), [stack, push, pop, close]);
1011
+ const activeId = stack[stack.length - 1];
1012
+ const active = screens.find(screen => screen.props.id === activeId) ?? screens[0];
1013
+ const depth = stack.length;
1014
+ const insets = useSafeAreaInsets();
1015
+
1016
+ // A sheet with no screens has nothing to show and no title to head it with.
1017
+ // Below every hook, so the count does not change with the children.
1018
+ if (!active) return null;
1019
+ return /*#__PURE__*/_jsx(BottomSheet, {
1020
+ open: open,
1021
+ onOpenChange: setOpen,
1022
+ children: /*#__PURE__*/_jsx(BottomSheet.Content, {
1023
+ blur: blur,
1024
+ size: size,
1025
+ detached: detached,
1026
+ showClose: false,
1027
+ showGrabber: false,
1028
+ className: "border-0 bg-transparent px-0 pt-0 shadow-none"
1029
+ // The sheet's own bottom padding would sit outside the material and
1030
+ // leave a band of backdrop under it. The glass takes the inset instead.
1031
+ ,
1032
+ style: {
1033
+ paddingBottom: 0
1034
+ },
1035
+ children: /*#__PURE__*/_jsx(AIInputSheetContext.Provider, {
1036
+ value: context,
1037
+ children: /*#__PURE__*/_jsxs(View, {
1038
+ className: cn('overflow-hidden border border-border bg-popover px-4 pt-2', detached ? 'rounded-3xl' : 'rounded-t-3xl border-b-0')
1039
+ // A floating sheet's own bottom margin already clears the home
1040
+ // indicator, so it takes plain padding rather than stacking the
1041
+ // inset on top of the gap.
1042
+ ,
1043
+ style: {
1044
+ paddingBottom: detached ? 16 : Math.max(insets.bottom, 16)
1045
+ },
1046
+ children: [/*#__PURE__*/_jsx(View, {
1047
+ className: "mb-2 self-center",
1048
+ children: /*#__PURE__*/_jsx(View, {
1049
+ className: "h-1 w-10 rounded-full bg-muted-foreground/40"
1050
+ })
1051
+ }), /*#__PURE__*/_jsx(AIInputSheetHeader, {
1052
+ depth: depth,
1053
+ title: active.props.title,
1054
+ trailing: active.props.trailing
1055
+ }), /*#__PURE__*/_jsx(AIInputSheetBody, {
1056
+ activeId: activeId,
1057
+ depth: depth,
1058
+ className: className,
1059
+ children: active
1060
+ })]
1061
+ })
1062
+ })
1063
+ })
1064
+ });
1065
+ }
1066
+ function AIInputSheetHeader({
1067
+ depth,
1068
+ title,
1069
+ trailing
1070
+ }) {
1071
+ const {
1072
+ pop,
1073
+ close
1074
+ } = useAIInputSheet('AIInput.Sheet');
1075
+ const rtl = useDirection() === 'rtl';
1076
+ const nested = depth > 1;
1077
+ const Back = rtl ? ChevronRightIcon : ChevronLeftIcon;
1078
+ return /*#__PURE__*/_jsxs(View, {
1079
+ className: "h-11 flex-row items-center",
1080
+ children: [/*#__PURE__*/_jsx(Glass, {
1081
+ variant: "clear",
1082
+ radius: 17,
1083
+ fallbackClassName: "bg-muted",
1084
+ style: {
1085
+ width: 34,
1086
+ height: 34
1087
+ },
1088
+ children: /*#__PURE__*/_jsx(AnimatedPressable, {
1089
+ accessibilityRole: "button",
1090
+ accessibilityLabel: nested ? 'Back' : 'Close',
1091
+ onPress: nested ? pop : close,
1092
+ className: "h-full w-full items-center justify-center",
1093
+ children: nested ? /*#__PURE__*/_jsx(Back, {
1094
+ size: 20
1095
+ }) : /*#__PURE__*/_jsx(XIcon, {
1096
+ size: 18
1097
+ })
1098
+ })
1099
+ }), /*#__PURE__*/_jsx(View, {
1100
+ pointerEvents: "none",
1101
+ className: "absolute inset-x-0 items-center",
1102
+ children: typeof title === 'string' ? /*#__PURE__*/_jsx(Text, {
1103
+ size: "xl",
1104
+ weight: "semibold",
1105
+ numberOfLines: 1,
1106
+ children: title
1107
+ }) : title
1108
+ }), /*#__PURE__*/_jsx(View, {
1109
+ className: "flex-1"
1110
+ }), trailing]
1111
+ });
1112
+ }
1113
+ function AIInputSheetBody({
1114
+ activeId,
1115
+ depth,
1116
+ className,
1117
+ children
1118
+ }) {
1119
+ const {
1120
+ width
1121
+ } = useWindowDimensions();
1122
+ const height = useSharedValue(-1);
1123
+ const paneRef = useRef(null);
1124
+
1125
+ /*
1126
+ * Whether the screen is lifted out of the flow onto its own layer.
1127
+ *
1128
+ * Not on the first layout, which is what a wizard inside a fixed frame can
1129
+ * afford. A sheet sized to its content is still sliding up while its first
1130
+ * screen measures, and switching the screen to absolute mid-flight takes the
1131
+ * height out from under the sheet and puts it back a frame later — which is
1132
+ * the sheet arriving with a stutter in it.
1133
+ *
1134
+ * So the first screen stays in the flow and the sheet opens around a real
1135
+ * height. The layer is only needed once two screens have to overlap, which
1136
+ * is the moment somebody navigates, and by then the height below is already
1137
+ * recorded to travel from.
1138
+ */
1139
+ const [layered, setLayered] = useState(false);
1140
+
1141
+ /*
1142
+ * Which way the screen slides, worked out from the move rather than from
1143
+ * whatever caused it — a row, a back button and a caller pushing directly
1144
+ * are the same move to the reader, and only the change in depth says which
1145
+ * way it went.
1146
+ *
1147
+ * Read during render, not in an effect: the arriving screen's animation is
1148
+ * fixed when it mounts, and an effect runs after that, which would leave
1149
+ * every transition playing the direction of the one before it.
1150
+ */
1151
+ const previousDepth = useRef(depth);
1152
+ const direction = depth >= previousDepth.current ? 1 : -1;
1153
+ const navigated = useRef(false);
1154
+ useEffect(() => {
1155
+ if (depth !== previousDepth.current) {
1156
+ previousDepth.current = depth;
1157
+ navigated.current = true;
1158
+ }
1159
+ }, [depth]);
1160
+
1161
+ // The first navigation is what needs two screens on top of each other.
1162
+ useEffect(() => {
1163
+ if (navigated.current) setLayered(true);
1164
+ }, [activeId]);
1165
+
1166
+ // Move the reader onto the screen that just arrived, rather than leaving
1167
+ // focus on the row that is no longer there.
1168
+ useEffect(() => {
1169
+ if (!navigated.current) return;
1170
+ const node = paneRef.current;
1171
+ if (!node) return;
1172
+ const tag = findNodeHandle(node);
1173
+ if (tag != null) AccessibilityInfo.setAccessibilityFocus(tag);
1174
+ }, [activeId]);
1175
+
1176
+ /*
1177
+ * Until the first screen has been measured the pane stays in the flow, so a
1178
+ * sheet sized to its content has a real height on the first pass. Absolute
1179
+ * from the start would measure zero — the container's only child would
1180
+ * contribute nothing — and the sheet would open around a screen nobody can
1181
+ * see.
1182
+ */
1183
+ const onPaneLayout = useCallback(event => {
1184
+ const next = event.nativeEvent.layout.height;
1185
+ if (next <= 0) return;
1186
+ // Recorded whether or not it is being used yet, so the first push has a
1187
+ // height to travel from rather than growing out of nothing.
1188
+ if (layered) {
1189
+ height.value = withTiming(next, {
1190
+ duration: HEIGHT_DURATION,
1191
+ easing: EASE
1192
+ });
1193
+ return;
1194
+ }
1195
+ height.value = next;
1196
+ }, [layered, height]);
1197
+ const heightStyle = useAnimatedStyle(() => height.value < 0 ? {} : {
1198
+ height: height.value
1199
+ });
1200
+
1201
+ /*
1202
+ * Written out rather than taken from the stock builders, which carry an
1203
+ * initial opacity and nothing else — the distance is the part worth
1204
+ * controlling here, and none of them lets it be set.
1205
+ */
1206
+ const offset = width > 0 ? width * SLIDE_FRACTION : 60;
1207
+ const entering = useCallback(() => {
1208
+ 'worklet';
1209
+
1210
+ return {
1211
+ initialValues: {
1212
+ opacity: 0,
1213
+ transform: [{
1214
+ translateX: direction * offset
1215
+ }]
1216
+ },
1217
+ animations: {
1218
+ opacity: withTiming(1, {
1219
+ duration: ENTER_DURATION,
1220
+ easing: EASE
1221
+ }),
1222
+ transform: [{
1223
+ translateX: withTiming(0, {
1224
+ duration: ENTER_DURATION,
1225
+ easing: EASE
1226
+ })
1227
+ }]
1228
+ }
1229
+ };
1230
+ }, [direction, offset]);
1231
+ return /*#__PURE__*/_jsx(Animated.View, {
1232
+ style: layered ? heightStyle : undefined,
1233
+ className: cn('w-full overflow-hidden', className),
1234
+ children: /*#__PURE__*/_jsx(Animated.View, {
1235
+ entering: layered ? entering : undefined,
1236
+ exiting: FadeOut.duration(EXIT_DURATION),
1237
+ className: layered ? 'absolute inset-x-0 top-0' : 'w-full',
1238
+ children: /*#__PURE__*/_jsx(View, {
1239
+ ref: paneRef,
1240
+ onLayout: onPaneLayout,
1241
+ className: "w-full pt-2",
1242
+ children: children
1243
+ })
1244
+ }, activeId ?? '__empty__')
1245
+ });
1246
+ }
1247
+
1248
+ /* -------------------------------------------------------------------------- */
1249
+ /* Sheet content */
1250
+ /* -------------------------------------------------------------------------- */
1251
+
1252
+ /** A card of related rows, with a hairline between each pair. */
1253
+ function AIInputSheetGroup({
1254
+ className,
1255
+ footnote,
1256
+ children,
1257
+ ...props
1258
+ }) {
1259
+ const rows = Children.toArray(children);
1260
+ return /*#__PURE__*/_jsxs(View, {
1261
+ className: "gap-2",
1262
+ children: [/*#__PURE__*/_jsx(View, {
1263
+ ...props,
1264
+ className: cn('overflow-hidden rounded-[20px] bg-muted/50', className),
1265
+ children: rows.map((row, index) => /*#__PURE__*/_jsxs(View, {
1266
+ children: [index > 0 ? /*#__PURE__*/_jsx(View, {
1267
+ className: "ms-4 h-px bg-border"
1268
+ }) : null, row]
1269
+ }, index))
1270
+ }), typeof footnote === 'string' ? /*#__PURE__*/_jsx(Text, {
1271
+ size: "sm",
1272
+ muted: true,
1273
+ className: "px-2",
1274
+ children: footnote
1275
+ }) : footnote]
1276
+ });
1277
+ }
1278
+ function AIInputSheetRow({
1279
+ className,
1280
+ label,
1281
+ description,
1282
+ icon,
1283
+ value,
1284
+ to,
1285
+ onPress,
1286
+ disabled = false,
1287
+ ...props
1288
+ }) {
1289
+ const {
1290
+ push
1291
+ } = useAIInputSheet('AIInput.Sheet.Row');
1292
+ const rtl = useDirection() === 'rtl';
1293
+ const Chevron = rtl ? ChevronLeftIcon : ChevronRightIcon;
1294
+ const handlePress = useCallback(() => {
1295
+ onPress?.();
1296
+ if (to) push(to);
1297
+ }, [onPress, push, to]);
1298
+ const body = /*#__PURE__*/_jsxs(_Fragment, {
1299
+ children: [icon, /*#__PURE__*/_jsxs(View, {
1300
+ className: "flex-1 gap-0.5",
1301
+ children: [typeof label === 'string' ? /*#__PURE__*/_jsx(Text, {
1302
+ size: "base",
1303
+ children: label
1304
+ }) : label, typeof description === 'string' ? /*#__PURE__*/_jsx(Text, {
1305
+ size: "sm",
1306
+ muted: true,
1307
+ children: description
1308
+ }) : description]
1309
+ }), typeof value === 'string' ? /*#__PURE__*/_jsx(Text, {
1310
+ size: "base",
1311
+ muted: true,
1312
+ children: value
1313
+ }) : value, to ? /*#__PURE__*/_jsx(Chevron, {
1314
+ size: 18
1315
+ }) : null]
1316
+ });
1317
+ const interactive = Boolean(to || onPress);
1318
+ if (!interactive) {
1319
+ return /*#__PURE__*/_jsx(View, {
1320
+ ...props,
1321
+ className: cn('flex-row items-center gap-3 px-4 py-4', className),
1322
+ children: body
1323
+ });
1324
+ }
1325
+ return /*#__PURE__*/_jsx(AnimatedPressable, {
1326
+ pressScale: 1,
1327
+ pressOpacity: 0.6,
1328
+ ...props,
1329
+ accessibilityRole: "button",
1330
+ accessibilityState: {
1331
+ disabled
1332
+ },
1333
+ disabled: disabled,
1334
+ onPress: handlePress,
1335
+ className: cn('flex-row items-center gap-3 px-4 py-4', disabled && 'opacity-40', className),
1336
+ children: body
1337
+ });
1338
+ }
1339
+ function AIInputSheetToggle({
1340
+ className,
1341
+ label,
1342
+ description,
1343
+ icon,
1344
+ value,
1345
+ onValueChange,
1346
+ disabled = false,
1347
+ ...props
1348
+ }) {
1349
+ return /*#__PURE__*/_jsxs(View, {
1350
+ ...props,
1351
+ className: cn('flex-row items-center gap-3 px-4 py-2.5', className),
1352
+ children: [icon, /*#__PURE__*/_jsxs(View, {
1353
+ className: "flex-1 gap-0.5",
1354
+ children: [typeof label === 'string' ? /*#__PURE__*/_jsx(Text, {
1355
+ size: "base",
1356
+ children: label
1357
+ }) : label, typeof description === 'string' ? /*#__PURE__*/_jsx(Text, {
1358
+ size: "sm",
1359
+ muted: true,
1360
+ children: description
1361
+ }) : description]
1362
+ }), /*#__PURE__*/_jsx(Switch, {
1363
+ value: value,
1364
+ onValueChange: onValueChange,
1365
+ disabled: disabled,
1366
+ label: typeof label === 'string' ? label : undefined
1367
+ })]
1368
+ });
1369
+ }
1370
+ /** One of a set, with a tick on the one in force. */
1371
+ function AIInputSheetChoice({
1372
+ className,
1373
+ label,
1374
+ description,
1375
+ badge,
1376
+ selected = false,
1377
+ onPress,
1378
+ disabled = false,
1379
+ ...props
1380
+ }) {
1381
+ const tint = useCSSVariable('--color-primary');
1382
+ return /*#__PURE__*/_jsxs(AnimatedPressable, {
1383
+ pressScale: 1,
1384
+ pressOpacity: 0.6,
1385
+ ...props,
1386
+ accessibilityRole: "radio",
1387
+ accessibilityState: {
1388
+ checked: selected,
1389
+ disabled
1390
+ },
1391
+ disabled: disabled,
1392
+ onPress: onPress,
1393
+ className: cn('flex-row items-start gap-3 px-4 py-4', disabled && 'opacity-40', className),
1394
+ children: [/*#__PURE__*/_jsxs(View, {
1395
+ className: "flex-1 gap-0.5",
1396
+ children: [/*#__PURE__*/_jsxs(View, {
1397
+ className: "flex-row items-center gap-2",
1398
+ children: [typeof label === 'string' ? /*#__PURE__*/_jsx(Text, {
1399
+ size: "lg",
1400
+ weight: "medium",
1401
+ children: label
1402
+ }) : label, badge]
1403
+ }), typeof description === 'string' ? /*#__PURE__*/_jsx(Text, {
1404
+ size: "sm",
1405
+ muted: true,
1406
+ children: description
1407
+ }) : description]
1408
+ }), selected ? /*#__PURE__*/_jsx(CheckIcon, {
1409
+ size: 20,
1410
+ color: typeof tint === 'string' ? tint : undefined
1411
+ }) : null]
1412
+ });
1413
+ }
1414
+
1415
+ /* -------------------------------------------------------------------------- */
1416
+ /* Voice mode */
1417
+ /* -------------------------------------------------------------------------- */
1418
+
1419
+ /**
1420
+ * The screen a voice conversation happens on: no field, no keyboard, one
1421
+ * microphone and a way out.
1422
+ *
1423
+ * It is a surface rather than an overlay — render it as its own route or
1424
+ * inside a modal of your own. It provides the composer's context, so
1425
+ * `AIInput.Action` and `AIInput.Pill` work in the bottom row without an
1426
+ * `AIInput` around them.
1427
+ */
1428
+ function AIInputVoiceMode({
1429
+ className,
1430
+ children,
1431
+ state = 'listening',
1432
+ level,
1433
+ title,
1434
+ onMicPress,
1435
+ micLabel = 'Mute',
1436
+ onClose,
1437
+ closeLabel = 'End voice mode',
1438
+ size = 'md',
1439
+ native = false,
1440
+ ...props
1441
+ }) {
1442
+ const insets = useSafeAreaInsets();
1443
+ const onSolid = useCSSVariable('--color-background');
1444
+ const control = CONTROL[size] + 4;
1445
+ const context = useMemo(() => ({
1446
+ value: '',
1447
+ setValue: () => {},
1448
+ status: 'ready',
1449
+ size,
1450
+ level,
1451
+ disabled: false,
1452
+ native: native && hasNativeUI(),
1453
+ minRows: 1,
1454
+ maxRows: 5,
1455
+ focused: false,
1456
+ setFocused: () => {},
1457
+ submit: () => {},
1458
+ stop: () => {},
1459
+ canVoice: false,
1460
+ canStop: false,
1461
+ recordCancel: () => {},
1462
+ recordConfirm: () => {},
1463
+ voice: () => {}
1464
+ }), [level, native, size]);
1465
+ return /*#__PURE__*/_jsx(AIInputContext.Provider, {
1466
+ value: context,
1467
+ children: /*#__PURE__*/_jsxs(View, {
1468
+ ...props,
1469
+ className: cn('flex-1 bg-background', className),
1470
+ children: [/*#__PURE__*/_jsx(Soundwave, {
1471
+ variant: "ambient",
1472
+ state: state,
1473
+ level: level,
1474
+ pointerEvents: "none",
1475
+ className: "absolute inset-0"
1476
+ }), /*#__PURE__*/_jsxs(View, {
1477
+ className: "flex-1 items-center justify-center gap-8 px-6",
1478
+ children: [typeof title === 'string' ? /*#__PURE__*/_jsx(Text, {
1479
+ size: "xl",
1480
+ className: "text-center",
1481
+ children: title
1482
+ }) : title, /*#__PURE__*/_jsxs(View, {
1483
+ className: "items-center gap-5",
1484
+ children: [/*#__PURE__*/_jsx(Soundwave, {
1485
+ variant: "pills",
1486
+ state: state,
1487
+ level: level,
1488
+ height: 28
1489
+ }), /*#__PURE__*/_jsx(Glass, {
1490
+ variant: "clear",
1491
+ radius: 44,
1492
+ fallbackClassName: "border border-border bg-muted",
1493
+ style: {
1494
+ width: 88,
1495
+ height: 88
1496
+ },
1497
+ children: /*#__PURE__*/_jsx(AnimatedPressable, {
1498
+ accessibilityRole: "button",
1499
+ accessibilityLabel: micLabel,
1500
+ onPress: onMicPress,
1501
+ className: "h-full w-full items-center justify-center",
1502
+ children: /*#__PURE__*/_jsx(MicIcon, {
1503
+ size: 32
1504
+ })
1505
+ })
1506
+ })]
1507
+ })]
1508
+ }), /*#__PURE__*/_jsxs(View, {
1509
+ className: "flex-row items-center gap-3 px-5",
1510
+ style: {
1511
+ paddingBottom: Math.max(insets.bottom, 20)
1512
+ },
1513
+ children: [children, /*#__PURE__*/_jsx(View, {
1514
+ className: "flex-1"
1515
+ }), native && hasNativeUI() ? /*#__PURE__*/_jsx(Button, {
1516
+ native: true,
1517
+ glass: true,
1518
+ variant: "primary",
1519
+ size: "icon",
1520
+ accessibilityLabel: closeLabel,
1521
+ onPress: onClose,
1522
+ children: /*#__PURE__*/_jsx(XIcon, {
1523
+ size: 16
1524
+ })
1525
+ }) : /*#__PURE__*/_jsx(View, {
1526
+ className: "overflow-hidden rounded-full bg-foreground",
1527
+ style: {
1528
+ width: control,
1529
+ height: control
1530
+ },
1531
+ children: /*#__PURE__*/_jsx(AnimatedPressable, {
1532
+ accessibilityRole: "button",
1533
+ accessibilityLabel: closeLabel,
1534
+ onPress: onClose,
1535
+ className: "h-full w-full items-center justify-center",
1536
+ children: /*#__PURE__*/_jsx(XIcon, {
1537
+ size: 18,
1538
+ color: typeof onSolid === 'string' ? onSolid : undefined
1539
+ })
1540
+ })
1541
+ })]
1542
+ })]
1543
+ })
1544
+ });
1545
+ }
1546
+
1547
+ /* -------------------------------------------------------------------------- */
1548
+
1549
+ AIInputRoot.displayName = 'AIInput';
1550
+ AIInputField.displayName = 'AIInput.Field';
1551
+ AIInputRow.displayName = 'AIInput.Row';
1552
+ AIInputToolbar.displayName = 'AIInput.Toolbar';
1553
+ AIInputSpacer.displayName = 'AIInput.Spacer';
1554
+ AIInputAction.displayName = 'AIInput.Action';
1555
+ AIInputPill.displayName = 'AIInput.Pill';
1556
+ AIInputSubmit.displayName = 'AIInput.Submit';
1557
+ AIInputRecording.displayName = 'AIInput.Recording';
1558
+ AIInputSheetScreen.displayName = 'AIInput.Sheet.Screen';
1559
+ AIInputSheetGroup.displayName = 'AIInput.Sheet.Group';
1560
+ AIInputSheetRow.displayName = 'AIInput.Sheet.Row';
1561
+ AIInputSheetToggle.displayName = 'AIInput.Sheet.Toggle';
1562
+ AIInputSheetChoice.displayName = 'AIInput.Sheet.Choice';
1563
+ AIInputSheet.displayName = 'AIInput.Sheet';
1564
+ AIInputVoiceMode.displayName = 'AIInput.VoiceMode';
1565
+ const Sheet = Object.assign(AIInputSheet, {
1566
+ Screen: AIInputSheetScreen,
1567
+ Group: AIInputSheetGroup,
1568
+ Row: AIInputSheetRow,
1569
+ Toggle: AIInputSheetToggle,
1570
+ Choice: AIInputSheetChoice
1571
+ });
1572
+ export const AIInput = Object.assign(AIInputRoot, {
1573
+ Field: AIInputField,
1574
+ Row: AIInputRow,
1575
+ Toolbar: AIInputToolbar,
1576
+ Spacer: AIInputSpacer,
1577
+ Action: AIInputAction,
1578
+ Pill: AIInputPill,
1579
+ Submit: AIInputSubmit,
1580
+ Recording: AIInputRecording,
1581
+ Sheet,
1582
+ VoiceMode: AIInputVoiceMode
1583
+ });
1584
+ //# sourceMappingURL=index.js.map