@xoroh/axis 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +134 -2
- package/dist/chunk-QDT5F2VT.js +1823 -0
- package/dist/index.cjs +13872 -0
- package/dist/index.d.cts +5718 -0
- package/dist/index.d.ts +5718 -0
- package/dist/index.js +12200 -0
- package/dist/models-DM_HxYGZ.d.cts +1890 -0
- package/dist/models-DM_HxYGZ.d.ts +1890 -0
- package/dist/models.cjs +1952 -0
- package/dist/models.d.cts +2 -0
- package/dist/models.d.ts +2 -0
- package/dist/models.js +212 -0
- package/package.json +97 -4
|
@@ -0,0 +1,1890 @@
|
|
|
1
|
+
import { ReactElement, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Accessibility plumbing — visually-hidden styling, stable ids, direction.
|
|
5
|
+
*
|
|
6
|
+
* ## Why this is a primitive
|
|
7
|
+
*
|
|
8
|
+
* Every labelled surface needs three things that have nothing to do with how
|
|
9
|
+
* it looks: a way to hide content visually while keeping it announced
|
|
10
|
+
* (icon-only labels, descriptions), a stable id to link triggers to surfaces,
|
|
11
|
+
* and a text direction both renderers agree on. Both renderers hand-roll all
|
|
12
|
+
* three today with independent implementations.
|
|
13
|
+
*
|
|
14
|
+
* ## What is genuinely shared, and what is not — stated carefully
|
|
15
|
+
*
|
|
16
|
+
* The VALUES are shared: the visually-hidden declaration is fixed CSS-in-JS
|
|
17
|
+
* (not a token read — it names no colour, spacing, shape or type value), the
|
|
18
|
+
* id algorithm is a prefixed counter, and direction resolution is a pure
|
|
19
|
+
* fallback. The APPLICATION is not — web spreads the style object onto an
|
|
20
|
+
* element, native maps it onto its own hidden-text host.
|
|
21
|
+
*/
|
|
22
|
+
type A11yDir = "ltr" | "rtl" | "auto";
|
|
23
|
+
type IdScope = {
|
|
24
|
+
/** Next stable id in this scope: `prefix` + `-` + a 1-based counter. */
|
|
25
|
+
nextId(): string;
|
|
26
|
+
prefix: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Create an id scope. Counters are per-scope so two scopes never collide, and
|
|
30
|
+
* 1-based so the first id reads `prefix-1`, never `prefix-0`.
|
|
31
|
+
*/
|
|
32
|
+
declare function createIdScope(prefix: string): IdScope;
|
|
33
|
+
/**
|
|
34
|
+
* Resolve text direction: the explicit `dir` wins, else the host default,
|
|
35
|
+
* else `"ltr"`. Never returns `undefined` — a renderer branching on direction
|
|
36
|
+
* must always have an answer.
|
|
37
|
+
*/
|
|
38
|
+
declare function resolveDir(options?: {
|
|
39
|
+
dir?: A11yDir;
|
|
40
|
+
defaultDir?: A11yDir;
|
|
41
|
+
}): A11yDir;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The attachments model — picked files with upload progress and
|
|
45
|
+
* composer limits.
|
|
46
|
+
*
|
|
47
|
+
* ## Why this is a primitive
|
|
48
|
+
*
|
|
49
|
+
* The composer on both renderers tracks the same list: picked files
|
|
50
|
+
* awaiting or completing upload, each with an id, a size, and a
|
|
51
|
+
* transfer phase, bounded by the same limits (at most N files, at
|
|
52
|
+
* most M bytes each). If the web side caps at ten files while native
|
|
53
|
+
* caps at five, the same conversation refuses the same photo per
|
|
54
|
+
* renderer — a limit is behavior, and behavior belongs here once.
|
|
55
|
+
*
|
|
56
|
+
* ## What is genuinely shared, and what is not
|
|
57
|
+
*
|
|
58
|
+
* The LIST and the LIMITS are shared: add/remove/update/clear over
|
|
59
|
+
* `{id, name, size, mimeType, uri, state}`, with `maxFiles` /
|
|
60
|
+
* `maxFileSize` validation reporting a typed error the caller
|
|
61
|
+
* surfaces. The PICKING and the UPLOAD are not — image pickers,
|
|
62
|
+
* drag-drop, paste, and the async transfer are platform code the app
|
|
63
|
+
* owns; the app marks uploads finished through `update`. Rendering
|
|
64
|
+
* (chips, previews, progress bars) is styled composition in Kern.
|
|
65
|
+
*/
|
|
66
|
+
/**
|
|
67
|
+
* One file's transfer phase, mirroring the channel-SDK shape both
|
|
68
|
+
* native chat lists already use: accepted and moving, moved, or moved
|
|
69
|
+
* and acknowledged by the transport.
|
|
70
|
+
*/
|
|
71
|
+
type AttachmentState = "uploading" | "finished" | "uploaded";
|
|
72
|
+
/** One picked file in the composer. `size` is bytes; `uri` is local. */
|
|
73
|
+
type ChatAttachment = {
|
|
74
|
+
/** Stable identity, used as the list key. */
|
|
75
|
+
id: string;
|
|
76
|
+
/** Display name with extension. */
|
|
77
|
+
name: string;
|
|
78
|
+
/** Byte size, when the picker reported one. */
|
|
79
|
+
size?: number;
|
|
80
|
+
/** MIME type, when the picker reported one. */
|
|
81
|
+
mimeType?: string;
|
|
82
|
+
/** Local URI for previews; remote URL only after upload. */
|
|
83
|
+
uri?: string;
|
|
84
|
+
state: AttachmentState;
|
|
85
|
+
};
|
|
86
|
+
/** Composer limits. Both optional; absent means unbounded. */
|
|
87
|
+
type AttachmentLimits = {
|
|
88
|
+
/** Maximum attachments held at once. */
|
|
89
|
+
maxFiles?: number;
|
|
90
|
+
/** Maximum bytes per attachment. */
|
|
91
|
+
maxFileSize?: number;
|
|
92
|
+
};
|
|
93
|
+
/** Why a candidate was refused. The caller surfaces this, never a throw. */
|
|
94
|
+
type AttachmentError = "too-many" | "too-large" | "duplicate";
|
|
95
|
+
/**
|
|
96
|
+
* Whether `candidate` may join `current` under `limits`: duplicate
|
|
97
|
+
* ids, the per-file byte cap, and the list cap, in that order — a
|
|
98
|
+
* file that is both a duplicate and oversized reports `duplicate`,
|
|
99
|
+
* the identity problem first.
|
|
100
|
+
*/
|
|
101
|
+
declare function validateAttachment(current: readonly ChatAttachment[], candidate: Pick<ChatAttachment, "id" | "size">, limits?: AttachmentLimits): AttachmentError | null;
|
|
102
|
+
/** Apply a patch to one attachment by id; unknown ids leave the list alone. */
|
|
103
|
+
declare function updateAttachment(current: readonly ChatAttachment[], id: string, patch: Partial<Pick<ChatAttachment, "state" | "uri" | "name">>): ChatAttachment[];
|
|
104
|
+
/**
|
|
105
|
+
* The composer attachment list, controlled or uncontrolled. `add`
|
|
106
|
+
* validates first: refused candidates report through `onError` and
|
|
107
|
+
* never enter the list. Accepted candidates enter as `uploading`
|
|
108
|
+
* unless the caller states otherwise (a synchronously-available file
|
|
109
|
+
* reports `finished` at add time); the app moves them on through
|
|
110
|
+
* `update` as its transfer progresses. `remove` by id is a no-op for
|
|
111
|
+
* unknown ids.
|
|
112
|
+
*/
|
|
113
|
+
declare function useAttachments(options?: {
|
|
114
|
+
value?: readonly ChatAttachment[];
|
|
115
|
+
defaultValue?: readonly ChatAttachment[];
|
|
116
|
+
onChange?: (next: readonly ChatAttachment[]) => void;
|
|
117
|
+
maxFiles?: number;
|
|
118
|
+
maxFileSize?: number;
|
|
119
|
+
onError?: (error: AttachmentError, candidate: ChatAttachment) => void;
|
|
120
|
+
}): {
|
|
121
|
+
attachments: readonly ChatAttachment[];
|
|
122
|
+
add: (candidate: ChatAttachment) => void;
|
|
123
|
+
addMany: (candidates: readonly ChatAttachment[]) => void;
|
|
124
|
+
update: (id: string, patch: Partial<Pick<ChatAttachment, "state" | "uri" | "name">>) => void;
|
|
125
|
+
remove: (id: string) => void;
|
|
126
|
+
clear: () => void;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Calendar behavior — month-grid date logic as pure functions.
|
|
131
|
+
*
|
|
132
|
+
* No React, no React Native: month-grid generation, roving-focus key
|
|
133
|
+
* mapping, min/max + `isDateDisabled`, single + range selection,
|
|
134
|
+
* locale weekday names, `firstDayOfWeek`, and `dir` live here. Moved
|
|
135
|
+
* from the removed `Calendar` component (ADR-012) without rewriting
|
|
136
|
+
* the date math — state ownership (controlled/uncontrolled value and
|
|
137
|
+
* month) stays with consumers via `useControllableState`.
|
|
138
|
+
*
|
|
139
|
+
* Date math uses plain `Date` UTC construction only
|
|
140
|
+
* (`new Date(Date.UTC(y, m, d))`; no date library). Nothing here
|
|
141
|
+
* throws on caller input: invalid strings parse to `null`, invalid
|
|
142
|
+
* month keys build an empty grid.
|
|
143
|
+
*/
|
|
144
|
+
type CalendarDateParts = {
|
|
145
|
+
year: number;
|
|
146
|
+
monthIndex: number;
|
|
147
|
+
day: number;
|
|
148
|
+
};
|
|
149
|
+
type CalendarMonthParts = {
|
|
150
|
+
year: number;
|
|
151
|
+
monthIndex: number;
|
|
152
|
+
};
|
|
153
|
+
/** 0 = Sunday … 6 = Saturday (matches `getUTCDay`). */
|
|
154
|
+
type FirstDayOfWeek = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
155
|
+
/** Month-name length for `resolveCalendarMonthNames`. */
|
|
156
|
+
type CalendarMonthNameStyle = "long" | "short" | "narrow";
|
|
157
|
+
type CalendarDirection = "ltr" | "rtl";
|
|
158
|
+
type CalendarSelectionMode = "single" | "range";
|
|
159
|
+
/** Inclusive range, ordered (`start <= end`). `null` for none. */
|
|
160
|
+
type CalendarRange = {
|
|
161
|
+
start: string;
|
|
162
|
+
end: string;
|
|
163
|
+
} | null;
|
|
164
|
+
type CalendarRangePosition = "start" | "end" | "inside" | null;
|
|
165
|
+
type CalendarCell = {
|
|
166
|
+
/** ISO `yyyy-mm-dd`. */
|
|
167
|
+
iso: string;
|
|
168
|
+
/** Day of month. */
|
|
169
|
+
day: number;
|
|
170
|
+
disabled: boolean;
|
|
171
|
+
selected: boolean;
|
|
172
|
+
range: CalendarRangePosition;
|
|
173
|
+
};
|
|
174
|
+
type CalendarGrid = {
|
|
175
|
+
/** Week rows of seven: day cells with `null` leading/trailing blanks. */
|
|
176
|
+
weeks: Array<Array<CalendarCell | null>>;
|
|
177
|
+
/** Seven weekday names, rotated to `firstDayOfWeek`. */
|
|
178
|
+
weekdayNames: string[];
|
|
179
|
+
};
|
|
180
|
+
type CalendarKey = "ArrowLeft" | "ArrowRight" | "ArrowUp" | "ArrowDown" | "Home" | "End" | "PageUp" | "PageDown";
|
|
181
|
+
type CalendarGridOptions = {
|
|
182
|
+
/** 0 = Sunday … 6 = Saturday. Default `0` (the removed component's grid). */
|
|
183
|
+
firstDayOfWeek?: FirstDayOfWeek;
|
|
184
|
+
/** BCP 47 locale for weekday names. Default `"en-US"`. */
|
|
185
|
+
locale?: string;
|
|
186
|
+
/** Earliest selectable date (ISO). Earlier days are disabled. */
|
|
187
|
+
minDate?: string;
|
|
188
|
+
/** Latest selectable date (ISO). Later days are disabled. */
|
|
189
|
+
maxDate?: string;
|
|
190
|
+
/** Extra per-day disable predicate. */
|
|
191
|
+
isDateDisabled?: (iso: string) => boolean;
|
|
192
|
+
/** Whole-grid terminal flag. Default `false`. */
|
|
193
|
+
disabled?: boolean;
|
|
194
|
+
/** Selected date (ISO) for single mode. */
|
|
195
|
+
selected?: string | null;
|
|
196
|
+
/** Active range for range mode. */
|
|
197
|
+
range?: CalendarRange;
|
|
198
|
+
};
|
|
199
|
+
/**
|
|
200
|
+
* Strict ISO date parse with a UTC round-trip guard (`2026-02-30`
|
|
201
|
+
* constructs to March, so the mismatch rejects it). Returns `null`
|
|
202
|
+
* for anything invalid — never throws.
|
|
203
|
+
*/
|
|
204
|
+
declare function parseCalendarDate(value: string | null | undefined): CalendarDateParts | null;
|
|
205
|
+
/** Strict month-key parse. Returns `null` for anything invalid — never throws. */
|
|
206
|
+
declare function parseCalendarMonth(value: string | null | undefined): CalendarMonthParts | null;
|
|
207
|
+
declare function toCalendarDate(year: number, monthIndex: number, day: number): string;
|
|
208
|
+
declare function toCalendarMonth(year: number, monthIndex: number): string;
|
|
209
|
+
/** Month length via UTC overflow: day 0 of the next month is the last day of this one. */
|
|
210
|
+
declare function daysInCalendarMonth(year: number, monthIndex: number): number;
|
|
211
|
+
/** Month arithmetic without date construction — plain year/month rollover. */
|
|
212
|
+
declare function shiftCalendarMonth(year: number, monthIndex: number, delta: 1 | -1): CalendarMonthParts;
|
|
213
|
+
/** Leading blanks before the 1st, rotated to `firstDayOfWeek`. */
|
|
214
|
+
declare function leadingBlankCount(year: number, monthIndex: number, firstDayOfWeek?: FirstDayOfWeek): number;
|
|
215
|
+
/**
|
|
216
|
+
* Seven short weekday names starting at `firstDayOfWeek`, via `Intl`
|
|
217
|
+
* (`short` weekday over a known Sunday-start week). Falls back to the
|
|
218
|
+
* Sunday-first `Su…Sa` labels when `Intl` is unavailable — never throws.
|
|
219
|
+
*/
|
|
220
|
+
declare function resolveCalendarWeekdayNames(locale?: string, firstDayOfWeek?: FirstDayOfWeek): string[];
|
|
221
|
+
/**
|
|
222
|
+
* Twelve month names for a locale, in calendar order.
|
|
223
|
+
* Formatted at UTC noon mid-month so neither DST edges nor month ends
|
|
224
|
+
* can shift the read. Falls back to English on missing `Intl` —
|
|
225
|
+
* never throws.
|
|
226
|
+
*/
|
|
227
|
+
declare function resolveCalendarMonthNames(locale?: string, style?: CalendarMonthNameStyle): string[];
|
|
228
|
+
/**
|
|
229
|
+
* Locale-aware week start (0 = Sunday … 6 = Saturday).
|
|
230
|
+
*
|
|
231
|
+
* An explicit Unicode `-u-fw-` extension wins over everything else.
|
|
232
|
+
* Otherwise prefers live `Intl.Locale` week info — `getWeekInfo()` or
|
|
233
|
+
* the `weekInfo` property when either exists (`firstDay` numbers days
|
|
234
|
+
* 1 = Monday … 7 = Sunday). Otherwise falls back to an embedded
|
|
235
|
+
* region table (CLDR supplemental weekData, firstDay only):
|
|
236
|
+
* Saturday-start and Friday-start regions are listed; the listed
|
|
237
|
+
* Sunday-start regions return 0; every other or unknown region
|
|
238
|
+
* returns 1 (Monday, the CLDR `001` root default). The region resolves
|
|
239
|
+
* via `Intl.Locale.maximize()` when available, then a bare-language
|
|
240
|
+
* likely-region map (CLDR likelySubtags), then a plain BCP-47 subtag
|
|
241
|
+
* parse as the last resort. Never throws.
|
|
242
|
+
*/
|
|
243
|
+
declare function resolveLocaleFirstDay(locale?: string): FirstDayOfWeek;
|
|
244
|
+
/**
|
|
245
|
+
* Whether an ISO date is unselectable: whole-grid `disabled`, outside
|
|
246
|
+
* `[minDate, maxDate]`, or rejected by `isDateDisabled`. Invalid bounds
|
|
247
|
+
* are ignored (never throw on caller input).
|
|
248
|
+
*/
|
|
249
|
+
declare function isCalendarDateDisabled(iso: string, options?: Pick<CalendarGridOptions, "minDate" | "maxDate" | "isDateDisabled" | "disabled">): boolean;
|
|
250
|
+
/**
|
|
251
|
+
* Month grid for a month key: week rows of seven with `null` leading
|
|
252
|
+
* blanks, day cells carrying selection, range, and disabled state.
|
|
253
|
+
* An invalid month key builds an empty grid — never throws.
|
|
254
|
+
*/
|
|
255
|
+
declare function buildCalendarGrid(monthKey: string, options?: CalendarGridOptions): CalendarGrid;
|
|
256
|
+
/**
|
|
257
|
+
* Roving-focus key mapping: the next focused ISO date for a grid key.
|
|
258
|
+
* Arrows move within the week (`±1`) and across rows (`±7`), crossing
|
|
259
|
+
* month (and year) boundaries through DST-safe UTC arithmetic — no
|
|
260
|
+
* clamping at month ends, so grids that paint outside days stay
|
|
261
|
+
* reachable. `dir` swaps Left/Right. Home/End jump to the visual row
|
|
262
|
+
* edges and stay grid-local (clamped to the visible month).
|
|
263
|
+
* PageUp/PageDown move the same day across months (clamped to the
|
|
264
|
+
* month length). Returns `null` for invalid input — never throws.
|
|
265
|
+
*/
|
|
266
|
+
declare function resolveCalendarFocus(currentISO: string | null | undefined, key: CalendarKey, options?: {
|
|
267
|
+
firstDayOfWeek?: FirstDayOfWeek;
|
|
268
|
+
dir?: CalendarDirection;
|
|
269
|
+
}): string | null;
|
|
270
|
+
type CalendarSelection = {
|
|
271
|
+
/** Single-mode value (and the latest tapped date in range mode). */
|
|
272
|
+
value: string | null;
|
|
273
|
+
/** Range-mode range (`null` until two taps complete it). */
|
|
274
|
+
range: CalendarRange;
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* Selection resolution for a tapped ISO date. Single mode selects it.
|
|
278
|
+
* Range mode: the first tap anchors `start`, the second tap completes
|
|
279
|
+
* the ordered range, a tap with a complete range re-anchors. A tap on a
|
|
280
|
+
* disabled date is a no-op (`null`) — never throws.
|
|
281
|
+
*/
|
|
282
|
+
declare function resolveCalendarSelection(iso: string, options?: {
|
|
283
|
+
mode?: CalendarSelectionMode;
|
|
284
|
+
value?: string | null;
|
|
285
|
+
range?: CalendarRange;
|
|
286
|
+
} & Pick<CalendarGridOptions, "minDate" | "maxDate" | "isDateDisabled" | "disabled">): CalendarSelection | null;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* The branch model — the edit-and-retry pager over answer variants.
|
|
290
|
+
*
|
|
291
|
+
* ## Why this is a primitive
|
|
292
|
+
*
|
|
293
|
+
* Regenerating or editing resubmits a turn and leaves several answers
|
|
294
|
+
* where one was; the pager (`< 1/2 >`) on both renderers walks the
|
|
295
|
+
* same index over the same count. If the web side wraps around while
|
|
296
|
+
* native clamps, the same Next press lands on different answers per
|
|
297
|
+
* renderer — pager motion is behavior, and behavior belongs here once.
|
|
298
|
+
*
|
|
299
|
+
* ## What is genuinely shared, and what is not
|
|
300
|
+
*
|
|
301
|
+
* The INDEX is shared: normalise, step, stop-at-the-ends, and the
|
|
302
|
+
* hide-when-single rule, following the `selection`-model precedent
|
|
303
|
+
* (pure transitions plus a controlled/uncontrolled hook). The TREE is
|
|
304
|
+
* not — persisted message trees stay transport-owned; this models the
|
|
305
|
+
* local pager over `count` variants, the cheap v1 the research
|
|
306
|
+
* recommends until a consumer proves it needs more.
|
|
307
|
+
*/
|
|
308
|
+
/** Clamp `index` into `0..count-1`; an empty branch has index `0`. */
|
|
309
|
+
declare function normalizeBranchIndex(index: number, count: number): number;
|
|
310
|
+
/** Step forward, stopping at the last variant: Next on the last is a no-op. */
|
|
311
|
+
declare function nextBranchIndex(index: number, count: number): number;
|
|
312
|
+
/** Step back, stopping at the first variant: Previous on the first is a no-op. */
|
|
313
|
+
declare function previousBranchIndex(index: number, count: number): number;
|
|
314
|
+
/**
|
|
315
|
+
* Whether the pager hides: a single variant has nowhere to step, and
|
|
316
|
+
* callers hide it by default (opt out only for a pager that reserves
|
|
317
|
+
* layout while variants load).
|
|
318
|
+
*/
|
|
319
|
+
declare function shouldHideBranchPicker(count: number, hideWhenSingleBranch?: boolean): boolean;
|
|
320
|
+
/**
|
|
321
|
+
* The pager index, controlled or uncontrolled. `count` changes
|
|
322
|
+
* normalise immediately rather than waiting for a press: a variant
|
|
323
|
+
* list that shrinks below the shown index falls back to the last
|
|
324
|
+
* remaining variant, so the pager never points past its own list
|
|
325
|
+
* (the `useSelection` narrowing precedent).
|
|
326
|
+
*/
|
|
327
|
+
declare function useBranch(options: {
|
|
328
|
+
count: number;
|
|
329
|
+
value?: number;
|
|
330
|
+
defaultValue?: number;
|
|
331
|
+
onChange?: (index: number) => void;
|
|
332
|
+
}): {
|
|
333
|
+
index: number;
|
|
334
|
+
count: number;
|
|
335
|
+
previous: () => void;
|
|
336
|
+
next: () => void;
|
|
337
|
+
goTo: (index: number) => void;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* The message model — identity, role, and the accessible name.
|
|
342
|
+
*
|
|
343
|
+
* ## Why this is a primitive
|
|
344
|
+
*
|
|
345
|
+
* Every renderer draws the same distinction: the reader's own turns
|
|
346
|
+
* (end-aligned bubble) versus everyone else's (full-width row), and a
|
|
347
|
+
* screen reader must announce whose turn each one is. If the web side
|
|
348
|
+
* labels "You" while native labels the raw user id, the same
|
|
349
|
+
* conversation reads differently per renderer for the users who can
|
|
350
|
+
* least afford the ambiguity.
|
|
351
|
+
*
|
|
352
|
+
* ## What is genuinely shared, and what is not
|
|
353
|
+
*
|
|
354
|
+
* The SHAPE and the NAME are shared: the role vocabulary, the message
|
|
355
|
+
* identity, and the speaker label both renderers announce. The LAYOUT
|
|
356
|
+
* is not — bubbles, rows, grouping, and markdown rendering are styled
|
|
357
|
+
* composition in Kern (there is no markdown renderer here by design:
|
|
358
|
+
* renderer/highlighter dependencies would break the token-free,
|
|
359
|
+
* platform-neutral boundary). Message PARTS (text/image/reasoning/
|
|
360
|
+
* tool-call part trees) are transport-owned and stay out: this model
|
|
361
|
+
* names the turn, never its payload.
|
|
362
|
+
*/
|
|
363
|
+
/** Whose turn a message is. `tool` is a tool result rendered as a turn. */
|
|
364
|
+
type ChatRole = "user" | "assistant" | "system" | "tool";
|
|
365
|
+
/** The identity of one conversation turn. Payload stays transport-owned. */
|
|
366
|
+
type ChatMessage = {
|
|
367
|
+
/** Stable turn identity, used as the list key. */
|
|
368
|
+
id: string;
|
|
369
|
+
role: ChatRole;
|
|
370
|
+
/** Milliseconds since epoch; ordering aid only, never rendered here. */
|
|
371
|
+
createdAt?: number;
|
|
372
|
+
};
|
|
373
|
+
/**
|
|
374
|
+
* The announced speaker for a role: the explicit author name wins
|
|
375
|
+
* (a named agent, a display name), else the role's plain-language
|
|
376
|
+
* fallback. Never returns `""` — a renderer branching on the speaker
|
|
377
|
+
* must always have an answer.
|
|
378
|
+
*/
|
|
379
|
+
declare function resolveChatMessageSpeaker(options: {
|
|
380
|
+
role: ChatRole;
|
|
381
|
+
authorName?: string;
|
|
382
|
+
}): string;
|
|
383
|
+
/**
|
|
384
|
+
* The accessible name for a turn: `Speaker: text`, or the bare
|
|
385
|
+
* speaker when there is no plain-text preview (a turn carrying only
|
|
386
|
+
* tool calls or attachments has nothing quotable — the speaker alone
|
|
387
|
+
* is the honest label, never an empty string or a placeholder).
|
|
388
|
+
*/
|
|
389
|
+
declare function resolveChatMessageLabel(options: {
|
|
390
|
+
role: ChatRole;
|
|
391
|
+
authorName?: string;
|
|
392
|
+
text?: string;
|
|
393
|
+
}): string;
|
|
394
|
+
/** Whether the turn is the reader's own (end-aligned bubble side). */
|
|
395
|
+
declare function isUserMessage(message: Pick<ChatMessage, "role">): boolean;
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* The source model — cited references behind a citation chip.
|
|
399
|
+
*
|
|
400
|
+
* ## Why this is a primitive
|
|
401
|
+
*
|
|
402
|
+
* Tool and web-search rows on both renderers cite the same
|
|
403
|
+
* references: an `href` with an optional human title. The shared part
|
|
404
|
+
* is deliberately thin — a shape plus list hygiene — because
|
|
405
|
+
* everything visual (chips, cards, footnotes, inline citations) is
|
|
406
|
+
* styled composition in Kern. Thin is not nothing: without the shared
|
|
407
|
+
* dedupe rule, the same URL cited twice renders two chips on one
|
|
408
|
+
* renderer and one on the other.
|
|
409
|
+
*
|
|
410
|
+
* ## What is genuinely shared, and what is not
|
|
411
|
+
*
|
|
412
|
+
* The SHAPE and the HYGIENE are shared: `{href, title?}`,
|
|
413
|
+
* whitespace trimming, empty-href dropping, and first-wins dedupe by
|
|
414
|
+
* `href`. The FETCHING is not — resolving, opening, and previewing
|
|
415
|
+
* sources are app and platform code.
|
|
416
|
+
*/
|
|
417
|
+
/** One cited reference: where it points, plus what to call it. */
|
|
418
|
+
type ChatSource = {
|
|
419
|
+
/** Cited URL. The dedupe key; surrounding whitespace is trimmed. */
|
|
420
|
+
href: string;
|
|
421
|
+
/** Human title; falls back to the href when absent. */
|
|
422
|
+
title?: string;
|
|
423
|
+
};
|
|
424
|
+
/**
|
|
425
|
+
* Clean a source list: trim hrefs, drop empties, dedupe by href with
|
|
426
|
+
* first-wins (the earliest citation keeps its position and title).
|
|
427
|
+
* Never throws on `undefined` — a turn with no sources normalises to
|
|
428
|
+
* `[]`, not a crash.
|
|
429
|
+
*/
|
|
430
|
+
declare function normalizeChatSources(sources: readonly ChatSource[] | undefined): ChatSource[];
|
|
431
|
+
/**
|
|
432
|
+
* The announced label for a citation: the title when it says
|
|
433
|
+
* something, else the href. Never returns `""` — a chip the screen
|
|
434
|
+
* reader cannot name is a silent control.
|
|
435
|
+
*/
|
|
436
|
+
declare function resolveChatSourceLabel(source: ChatSource): string;
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* The shared generation-status vocabulary for every chat model.
|
|
440
|
+
*
|
|
441
|
+
* ## Why this is a primitive
|
|
442
|
+
*
|
|
443
|
+
* The composer (send/stop swap), the message list (streaming cursor),
|
|
444
|
+
* reasoning disclosure (auto-open while streaming), and tool cards
|
|
445
|
+
* (running vs settled) all branch on the same question: what is the
|
|
446
|
+
* run doing right now. Four independent spellings of that answer is
|
|
447
|
+
* how a composer offers Send mid-generation on one renderer and Stop
|
|
448
|
+
* on the other.
|
|
449
|
+
*
|
|
450
|
+
* ## What is genuinely shared, and what is not
|
|
451
|
+
*
|
|
452
|
+
* The VOCABULARY is shared: one `ChatStatus` every model reads, so
|
|
453
|
+
* `usePromptInput`'s send gate and `resolveReasoningOpen`'s auto-open
|
|
454
|
+
* can never disagree about what "streaming" means. The TRANSPORT is
|
|
455
|
+
* not — the app brings its own AI SDK / AG-UI / channel client and
|
|
456
|
+
* maps its status onto this type at the edge. Attachment upload
|
|
457
|
+
* states (`uploading | finished | uploaded`) stay separate on purpose:
|
|
458
|
+
* they describe file transfer, not generation, and conflating the two
|
|
459
|
+
* would gate sending on uploads the transport may handle inline.
|
|
460
|
+
*/
|
|
461
|
+
/**
|
|
462
|
+
* Where the current run stands. `idle` is the rest state (composer
|
|
463
|
+
* accepts input); `submitted` is waiting on the transport before the
|
|
464
|
+
* first token; `streaming` is tokens flowing; `error` is a failed run
|
|
465
|
+
* the caller may retry from.
|
|
466
|
+
*/
|
|
467
|
+
type ChatStatus = "idle" | "submitted" | "streaming" | "error";
|
|
468
|
+
/** A run is in flight: the composer must offer Stop, never Send. */
|
|
469
|
+
declare function isChatBusy(status: ChatStatus): boolean;
|
|
470
|
+
/** Tokens are flowing now: stream cursors show, reasoning auto-opens. */
|
|
471
|
+
declare function isChatStreaming(status: ChatStatus): boolean;
|
|
472
|
+
/** The last run failed: the composer may offer retry. */
|
|
473
|
+
declare function isChatError(status: ChatStatus): boolean;
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* The collection model — ordered item identity for filtering and lookup.
|
|
477
|
+
*
|
|
478
|
+
* ## Why this is a primitive
|
|
479
|
+
*
|
|
480
|
+
* Combobox filtering, menu filtering, and select value-label lookup
|
|
481
|
+
* all reduce to the same two questions: which registered items match
|
|
482
|
+
* a query, and what text belongs to a key. Three independent
|
|
483
|
+
* implementations drift on exactly the edge users feel — whether
|
|
484
|
+
* `"mango"` matches `" Mango"` — so the same query filters
|
|
485
|
+
* differently per component.
|
|
486
|
+
*
|
|
487
|
+
* ## What is genuinely shared, and what is not
|
|
488
|
+
*
|
|
489
|
+
* The REGISTRY and the MATCH are shared: keyed item identity in
|
|
490
|
+
* registration order, the default case-insensitive substring match,
|
|
491
|
+
* and the filter/lookup helpers every consumer reads. The PARTS are
|
|
492
|
+
* not — which components register (select items publish labels,
|
|
493
|
+
* menu items publish filter text, combobox options are data and
|
|
494
|
+
* never register) is per-family composition. Keyboard traversal
|
|
495
|
+
* over the registry (active index, typeahead focus) is NOT here:
|
|
496
|
+
* the contract's conditional-keyboard rule admits it only where a
|
|
497
|
+
* section adopts it, and no section does yet.
|
|
498
|
+
*/
|
|
499
|
+
/** One registered item: stable identity plus its matchable text. */
|
|
500
|
+
type CollectionItem = {
|
|
501
|
+
/** Stable identity, unique within its registry. */
|
|
502
|
+
key: string;
|
|
503
|
+
/** Matchable text (label, filter text, option text). */
|
|
504
|
+
text: string;
|
|
505
|
+
};
|
|
506
|
+
/** Whether `text` matches `query`. The default is substring; callers pass ranking. */
|
|
507
|
+
type CollectionMatch = (query: string, text: string) => boolean;
|
|
508
|
+
/**
|
|
509
|
+
* The shared default match: trimmed, case-insensitive substring. An
|
|
510
|
+
* empty query matches everything (an unfiltered list shows all
|
|
511
|
+
* items, never none).
|
|
512
|
+
*/
|
|
513
|
+
declare function defaultCollectionMatch(query: string, text: string): boolean;
|
|
514
|
+
/**
|
|
515
|
+
* Keys of `items` matching `query`, in registration order. Pure —
|
|
516
|
+
* data-driven consumers (combobox options) filter without
|
|
517
|
+
* registering anything.
|
|
518
|
+
*/
|
|
519
|
+
declare function filterCollectionKeys(items: readonly CollectionItem[], query: string, match?: CollectionMatch): string[];
|
|
520
|
+
/** The registered text for `key`, or `undefined` when unregistered. */
|
|
521
|
+
declare function findCollectionText(items: readonly CollectionItem[], key: string): string | undefined;
|
|
522
|
+
/**
|
|
523
|
+
* The item registry for children-driven consumers (select items
|
|
524
|
+
* publish labels, menu items publish filter text). Re-registering a
|
|
525
|
+
* key replaces its text in place (a label edit never reorders the
|
|
526
|
+
* list); unregistering drops it. Registration order is the list
|
|
527
|
+
* order — filtering never re-sorts.
|
|
528
|
+
*/
|
|
529
|
+
declare function useCollectionRegistry(): {
|
|
530
|
+
items: readonly CollectionItem[];
|
|
531
|
+
register: (item: CollectionItem) => void;
|
|
532
|
+
unregister: (key: string) => void;
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* The Slot composition model — prop merging for element substitution.
|
|
537
|
+
*
|
|
538
|
+
* ## Why this is a primitive
|
|
539
|
+
*
|
|
540
|
+
* Every overlay part (`Dialog.Trigger`, `Popover.Trigger`, `Menu.Trigger`, …)
|
|
541
|
+
* lets the consumer substitute the rendered element while the part keeps its
|
|
542
|
+
* own behaviour props (event handlers, `aria-*`, refs). Both renderers do that
|
|
543
|
+
* merge today with independent inline spreads, so a fix to handler ordering on
|
|
544
|
+
* one side silently does not reach the other — the `useControllableState`
|
|
545
|
+
* failure mode repeating.
|
|
546
|
+
*
|
|
547
|
+
* ## What is genuinely shared, and what is not
|
|
548
|
+
*
|
|
549
|
+
* The MERGE is shared and belongs here: event handlers chain (the part's own
|
|
550
|
+
* handler runs, then the consumer's), `className` concatenates, `style` merges
|
|
551
|
+
* shallow with the consumer winning, and every other prop takes the consumer's
|
|
552
|
+
* value unless it is `undefined`. The ELEMENT is not — cloning a React element
|
|
553
|
+
* is the web renderer's job, and the native renderer maps the merged props onto
|
|
554
|
+
* its own host. So this returns merged props; each renderer applies them.
|
|
555
|
+
*
|
|
556
|
+
* That split is what keeps it a primitive rather than a component: pure data
|
|
557
|
+
* over plain objects, with no element type in sight.
|
|
558
|
+
*/
|
|
559
|
+
type SlotProps = Record<string, unknown>;
|
|
560
|
+
/**
|
|
561
|
+
* Chain two optional event handlers into one.
|
|
562
|
+
*
|
|
563
|
+
* The part's own handler runs first so its behaviour (opening, selecting)
|
|
564
|
+
* cannot be swallowed by a consumer handler that stops propagation; the
|
|
565
|
+
* consumer's runs second so it still observes the event. Either side may be
|
|
566
|
+
* absent. Neither return value is used — an event handler that needs to veto
|
|
567
|
+
* must do so through state, not through merge order.
|
|
568
|
+
*/
|
|
569
|
+
declare function composeEventHandlers<E>(partHandler: ((event: E) => void) | undefined, consumerHandler: ((event: E) => void) | undefined): ((event: E) => void) | undefined;
|
|
570
|
+
/**
|
|
571
|
+
* Merge part props with consumer (slot child) props.
|
|
572
|
+
*
|
|
573
|
+
* - `on*` function props chain via {@link composeEventHandlers}
|
|
574
|
+
* - `className` concatenates with a space (consumer last, so equal-specificity
|
|
575
|
+
* utilities resolve toward the consumer)
|
|
576
|
+
* - `style` flattens RN arrays (later entries win) then merges shallow
|
|
577
|
+
* with the consumer winning per key
|
|
578
|
+
* - every other prop takes the consumer's value unless it is `undefined`,
|
|
579
|
+
* in which case the part's stands
|
|
580
|
+
*
|
|
581
|
+
* Later consumer sets win over earlier ones, left to right.
|
|
582
|
+
*/
|
|
583
|
+
declare function mergeSlotProps<T extends SlotProps>(partProps: T, ...consumerSets: readonly (Partial<T> | undefined | null)[]): T;
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* The `render`-prop composition model — Base UI parity for element
|
|
587
|
+
* substitution on React Native.
|
|
588
|
+
*
|
|
589
|
+
* Base UI parts accept `render` (an element or a function of props
|
|
590
|
+
* and state) plus `className`/`style` as values or functions of
|
|
591
|
+
* state. The merge itself is the shared `slot` model
|
|
592
|
+
* (`mergeSlotProps`: part handler first, consumer style winning);
|
|
593
|
+
* what lives here is the native-renderer application: resolving the
|
|
594
|
+
* prop into a node.
|
|
595
|
+
*
|
|
596
|
+
* Rules, mirroring Base UI semantics where the platform allows:
|
|
597
|
+
* - `render` absent → `null`, and the part renders its default host.
|
|
598
|
+
* - element form → cloned with the part props merged UNDER the
|
|
599
|
+
* element's own props (consumer wins, except composed handlers).
|
|
600
|
+
* - function form → called with `(props, state)`, where `props` are
|
|
601
|
+
* the part props to spread and `state` is the part's state object.
|
|
602
|
+
* - `className` never reaches a host with a value: React Native warns
|
|
603
|
+
* on unknown props, and there is no stylesheet engine to consume
|
|
604
|
+
* it. It is overridden to `undefined` on the clone (documented
|
|
605
|
+
* deviation — state styling flows through function `style`/`render`
|
|
606
|
+
* props instead). `undefined` props are skipped by the renderer,
|
|
607
|
+
* so no warning fires.
|
|
608
|
+
* - `style` accepts a value or a function of state (Base UI parity);
|
|
609
|
+
* `resolveStyle` applies it. Arrays merge caller-last, matching
|
|
610
|
+
* the slot rule.
|
|
611
|
+
*/
|
|
612
|
+
type RenderProp<State, Props extends SlotProps> = ReactElement | ((props: Props, state: State) => ReactNode);
|
|
613
|
+
type StateStyle<State, Style> = Style | ((state: State) => Style);
|
|
614
|
+
/**
|
|
615
|
+
* Apply a value-or-function style prop against part state.
|
|
616
|
+
*/
|
|
617
|
+
declare function resolveStyle<State, Style>(style: StateStyle<State, Style> | undefined, state: State): Style | undefined;
|
|
618
|
+
/**
|
|
619
|
+
* Resolve a part's `render` prop into a node, or `null` when absent
|
|
620
|
+
* (the part renders its default host). Never spreads `className`
|
|
621
|
+
* onto the clone — see the module note.
|
|
622
|
+
*/
|
|
623
|
+
declare function resolveRender<State, Props extends SlotProps>(render: RenderProp<State, Props> | undefined | null, partProps: Props, state: State): ReactNode;
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Dismiss wiring — which platform signal closes which surface.
|
|
627
|
+
*
|
|
628
|
+
* ## Why this is a primitive
|
|
629
|
+
*
|
|
630
|
+
* `dismissPolicy.ts` owns the POLICY (may this surface dismiss; must a close
|
|
631
|
+
* affordance exist). This owns the WIRING: which concrete signal — pointer
|
|
632
|
+
* outside the surface, focus leaving it, the Escape key, the system back
|
|
633
|
+
* gesture — is bound on a given surface. Both renderers bind the same SET of
|
|
634
|
+
* signals for the same surface kind today with independent conditionals, so a
|
|
635
|
+
* surface that gains a binding on web silently keeps the old set natively.
|
|
636
|
+
*
|
|
637
|
+
* ## What is genuinely shared, and what is not
|
|
638
|
+
*
|
|
639
|
+
* The DECLARATION is shared: a modal surface wires outside-pointer, focus-out
|
|
640
|
+
* and escape; a non-modal one wires none of those and only its visible close
|
|
641
|
+
* control. The BINDING is not — a DOM pointerdown listener and an RN hardware
|
|
642
|
+
* back handler are platform code. So this returns the branch set, and each
|
|
643
|
+
* renderer binds its own listeners to it.
|
|
644
|
+
*
|
|
645
|
+
* Branch/surface language: a BRANCH is one dismiss signal; a SURFACE declares
|
|
646
|
+
* which branches it wires. A branch fires only when declared AND the surface is
|
|
647
|
+
* open AND the policy allows dismissal — the declaration is consulted, never
|
|
648
|
+
* decorative (the `dismissTriggersFor` lesson).
|
|
649
|
+
*/
|
|
650
|
+
/** One concrete dismiss signal a renderer can bind. */
|
|
651
|
+
type DismissSource =
|
|
652
|
+
/** Pointer contact outside the surface (scrim press, backdrop tap). */
|
|
653
|
+
"outside-pointer"
|
|
654
|
+
/** Focus moved outside the surface while it is open. */
|
|
655
|
+
| "focus-out"
|
|
656
|
+
/** The Escape key (web) or dismiss key equivalent. */
|
|
657
|
+
| "escape"
|
|
658
|
+
/** The system back affordance (Android hardware back, swipe-back). */
|
|
659
|
+
| "system-back"
|
|
660
|
+
/** A visible close affordance inside the surface was activated. */
|
|
661
|
+
| "close";
|
|
662
|
+
type DismissBranches = Record<DismissSource, boolean>;
|
|
663
|
+
/**
|
|
664
|
+
* Why an overlay open state changed. Base UI
|
|
665
|
+
* `Dialog.Root.ChangeEventReason` parity subset: `trigger-press`
|
|
666
|
+
* (trigger toggle, either direction), `outside-press` (scrim/backdrop
|
|
667
|
+
* press), `escape-key` (Android hardware back and iOS VoiceOver
|
|
668
|
+
* scrub — the native escape equivalents; RN has no Escape key),
|
|
669
|
+
* `close-press` (close affordance). `focus-out` is declared but never
|
|
670
|
+
* bound on native (no global key listener); `imperative-action` and
|
|
671
|
+
* `none` cover programmatic closes without a user gesture — notably
|
|
672
|
+
* drag-release dismissal reports `none`, because Base UI has no
|
|
673
|
+
* gesture-dismiss reason and a drag is neither an outside press nor a
|
|
674
|
+
* close press. DOM-only details (`event`, `cancel()`, the trigger
|
|
675
|
+
* element) are omitted: there is no DOM event to carry on native.
|
|
676
|
+
*/
|
|
677
|
+
type OverlayOpenChangeReason = "trigger-press" | "outside-press" | "escape-key" | "close-press" | "focus-out" | "imperative-action" | "none";
|
|
678
|
+
/** Open-change details: the reason, and nothing else (see above). */
|
|
679
|
+
type OverlayOpenChangeDetails = {
|
|
680
|
+
reason: OverlayOpenChangeReason;
|
|
681
|
+
};
|
|
682
|
+
type DismissWiringOptions = {
|
|
683
|
+
/** A modal surface locks interaction outside itself, so outside signals dismiss. */
|
|
684
|
+
modal: boolean;
|
|
685
|
+
/** Whether the surface may dismiss at all (the policy's `canDismiss`). */
|
|
686
|
+
dismissible: boolean;
|
|
687
|
+
/**
|
|
688
|
+
* Whether a visible close affordance is rendered. A surface with no control
|
|
689
|
+
* cannot wire the `close` branch: the branch would be declared and never
|
|
690
|
+
* fire, which is worse than not declaring it.
|
|
691
|
+
*/
|
|
692
|
+
hasVisibleClose: boolean;
|
|
693
|
+
};
|
|
694
|
+
/**
|
|
695
|
+
* The branches a surface wires, given what it is.
|
|
696
|
+
*
|
|
697
|
+
* Modal + dismissible wires every branch; anything else wires only what it can
|
|
698
|
+
* honestly offer. A non-modal surface never wires outside-pointer or focus-out
|
|
699
|
+
* — the content behind it stays interactive by definition, so treating outside
|
|
700
|
+
* contact as dismissal would close a surface the user never left.
|
|
701
|
+
*/
|
|
702
|
+
declare function dismissBranchesFor(options: DismissWiringOptions): DismissBranches;
|
|
703
|
+
/**
|
|
704
|
+
* Whether `source` dismisses a surface that is currently `enabled` (open), per
|
|
705
|
+
* the branches the surface declared. An undeclared branch never fires,
|
|
706
|
+
* whatever the open state.
|
|
707
|
+
*/
|
|
708
|
+
declare function shouldDismissOn(source: DismissSource, enabled: boolean, branches: DismissBranches): boolean;
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Anchor positioning math — where a floating surface goes relative to its trigger.
|
|
712
|
+
*
|
|
713
|
+
* ## Why this is a primitive
|
|
714
|
+
*
|
|
715
|
+
* Popover, menu, select, tooltip, combobox and preview-card all place a surface
|
|
716
|
+
* next to an anchor with a gap, then clamp it into the viewport. Both renderers
|
|
717
|
+
* do that arithmetic today with independent implementations, so a clamping fix
|
|
718
|
+
* on one side silently does not reach the other.
|
|
719
|
+
*
|
|
720
|
+
* ## What is genuinely shared, and what is not
|
|
721
|
+
*
|
|
722
|
+
* The MATH is shared and belongs here: given an anchor rect, a floating size,
|
|
723
|
+
* a placement and a gap, which origin the surface takes, and how it clamps into
|
|
724
|
+
* a viewport. The MEASUREMENT is not — reading DOM rects or RN `measure` is
|
|
725
|
+
* platform code. So the renderer measures, this computes.
|
|
726
|
+
*
|
|
727
|
+
* All values are plain numbers in the same unit space the caller measured in
|
|
728
|
+
* (CSS px on web, dp on native). Nothing here names a token: the gap is a
|
|
729
|
+
* caller-supplied number, not a spacing value.
|
|
730
|
+
*/
|
|
731
|
+
type Rect = {
|
|
732
|
+
x: number;
|
|
733
|
+
y: number;
|
|
734
|
+
width: number;
|
|
735
|
+
height: number;
|
|
736
|
+
};
|
|
737
|
+
type Placement = "top" | "bottom" | "left" | "right";
|
|
738
|
+
/** Cross-axis alignment of the surface against the anchor (Base UI parity). */
|
|
739
|
+
type Align = "start" | "center" | "end";
|
|
740
|
+
type PositioningOptions = {
|
|
741
|
+
/** Which side of the anchor the surface prefers. Defaults to `"bottom"`. */
|
|
742
|
+
placement?: Placement;
|
|
743
|
+
/** Gap between anchor and surface edges. Defaults to `0`. */
|
|
744
|
+
offset?: number;
|
|
745
|
+
/**
|
|
746
|
+
* Cross-axis alignment. Defaults to `"center"` (the historical
|
|
747
|
+
* behavior — every existing caller centers). `"start"`/`"end"`
|
|
748
|
+
* pin the surface's leading/trailing edge to the anchor's.
|
|
749
|
+
*/
|
|
750
|
+
align?: Align;
|
|
751
|
+
/** Shift along the cross axis after alignment. Defaults to `0`. */
|
|
752
|
+
alignOffset?: number;
|
|
753
|
+
};
|
|
754
|
+
/**
|
|
755
|
+
* One call for the common path: preferred origin, then clamped. `viewport`
|
|
756
|
+
* defaults to unbounded when the renderer has not measured one yet — the
|
|
757
|
+
* preferred origin stands rather than clamping against a zero rect.
|
|
758
|
+
*/
|
|
759
|
+
/**
|
|
760
|
+
* Determine the effective placement side for `anchor` and `size`,
|
|
761
|
+
* flipping to the opposite side if the preferred placement overflows
|
|
762
|
+
* `viewport` and the opposite side fits.
|
|
763
|
+
*/
|
|
764
|
+
declare function resolveFloatingSide(anchor: Rect, size: {
|
|
765
|
+
width: number;
|
|
766
|
+
height: number;
|
|
767
|
+
}, options?: PositioningOptions & {
|
|
768
|
+
viewport?: {
|
|
769
|
+
width: number;
|
|
770
|
+
height: number;
|
|
771
|
+
};
|
|
772
|
+
viewportPadding?: number;
|
|
773
|
+
}): Placement;
|
|
774
|
+
declare function resolveFloatingRect(anchor: Rect, size: {
|
|
775
|
+
width: number;
|
|
776
|
+
height: number;
|
|
777
|
+
}, options?: PositioningOptions & {
|
|
778
|
+
viewport?: {
|
|
779
|
+
width: number;
|
|
780
|
+
height: number;
|
|
781
|
+
};
|
|
782
|
+
viewportPadding?: number;
|
|
783
|
+
flip?: boolean;
|
|
784
|
+
}): {
|
|
785
|
+
x: number;
|
|
786
|
+
y: number;
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Form validation plumbing — error maps and emptiness checks as pure
|
|
791
|
+
* functions. No React, no React Native.
|
|
792
|
+
*
|
|
793
|
+
* Errors travel as plain strings keyed by field name; a field is
|
|
794
|
+
* invalid while its list is non-empty. Server errors take precedence
|
|
795
|
+
* over locally computed ones while present (the caller explicitly set
|
|
796
|
+
* them); editing a field drops its server entry so a fix is never
|
|
797
|
+
* shadowed by a stale server message. Nothing here throws on caller
|
|
798
|
+
* input.
|
|
799
|
+
*/
|
|
800
|
+
/** Per-field error messages, keyed by field `name`. */
|
|
801
|
+
type FieldErrors = Record<string, string | string[]>;
|
|
802
|
+
/** Normalize one field's errors to a list. Missing means none. */
|
|
803
|
+
declare function normalizeFieldErrors(error: string | string[] | undefined): string[];
|
|
804
|
+
/**
|
|
805
|
+
* Merge locally computed errors under server errors: a field shows
|
|
806
|
+
* its server entry while one exists, else its local entries.
|
|
807
|
+
*/
|
|
808
|
+
declare function mergeFormErrors(internal: FieldErrors, server: FieldErrors): FieldErrors;
|
|
809
|
+
/** Whether a value counts as empty: `undefined`, `null`, or `""`. */
|
|
810
|
+
declare function isEmptyValue(value: unknown): boolean;
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Wave 1 of the P2c-3 extraction (`docs/conventions/primitives.md` §3): one
|
|
814
|
+
* owner for behaviour that exists in BOTH renderers today with independent
|
|
815
|
+
* implementations, so a fix to one cannot silently fail to reach the other.
|
|
816
|
+
*
|
|
817
|
+
* Overlay modality is the "is this overlay the active one" fact: everything
|
|
818
|
+
* behind an open sheet/dialog/menu must become inert, and only the topmost
|
|
819
|
+
* overlay may respond. Each renderer has to implement it — DOM via `inert` and
|
|
820
|
+
* the `aria-hidden`/focus-trap dance, RN via `AccessibilityInfo` and
|
|
821
|
+
* `pointerEvents` — and the LOGIC of which overlay is on top, and what becomes
|
|
822
|
+
* inert when it opens and closes, is identical.
|
|
823
|
+
*
|
|
824
|
+
* What stays in each renderer is the ACT of applying inertness (it needs the
|
|
825
|
+
* platform), which is exactly the split the spec asks for: the decision is
|
|
826
|
+
* shared, the surface is not.
|
|
827
|
+
*/
|
|
828
|
+
/** An overlay's identity within the stack. Any stable key will do. */
|
|
829
|
+
type OverlayId = string;
|
|
830
|
+
type OverlayModalityState = {
|
|
831
|
+
/** The topmost registered overlay, or `null` when the stack is empty. */
|
|
832
|
+
readonly top: OverlayId | null;
|
|
833
|
+
/** Every registered overlay, bottom-first. */
|
|
834
|
+
readonly stack: readonly OverlayId[];
|
|
835
|
+
/** The topmost registered MODAL overlay, or `null` when none is open. */
|
|
836
|
+
readonly modalTop: OverlayId | null;
|
|
837
|
+
/** True when at least one modal overlay is registered (i.e. open). */
|
|
838
|
+
readonly isModalActive: boolean;
|
|
839
|
+
/** True when `id` is the topmost overlay — i.e. it is the interactive one. */
|
|
840
|
+
isTop(id: OverlayId): boolean;
|
|
841
|
+
/**
|
|
842
|
+
* True when content OUTSIDE `id` must be inert. False for the top overlay and
|
|
843
|
+
* false for an empty stack, so a consumer can wire this straight to `inert`
|
|
844
|
+
* without a special case for "nothing is open".
|
|
845
|
+
*/
|
|
846
|
+
isInertOutside(id: OverlayId): boolean;
|
|
847
|
+
};
|
|
848
|
+
/**
|
|
849
|
+
* Registry of open overlays, in one place, shared by every renderer.
|
|
850
|
+
*
|
|
851
|
+
* Registration is a stack rather than a counter because the topmost overlay is
|
|
852
|
+
* the only interactive one, and a counter cannot express that. Registering the
|
|
853
|
+
* same id twice is a no-op rather than a second stack entry: a re-render that
|
|
854
|
+
* re-registers must not make one overlay look two-deep.
|
|
855
|
+
*
|
|
856
|
+
* Modality travels with the registration (`{ modal: true }` for modal
|
|
857
|
+
* surfaces): the Android app-root hook (`useModalActive`) needs to know
|
|
858
|
+
* whether ANY modal is open, and the registry is the one owner that sees
|
|
859
|
+
* every overlay. Non-modal overlays (popovers, tooltips, non-modal drawers)
|
|
860
|
+
* register without it and never count as modal.
|
|
861
|
+
*/
|
|
862
|
+
declare function createOverlayModality(): {
|
|
863
|
+
register(id: OverlayId, options?: {
|
|
864
|
+
modal?: boolean;
|
|
865
|
+
}): () => void;
|
|
866
|
+
subscribe(listener: () => void): () => void;
|
|
867
|
+
getState(): OverlayModalityState;
|
|
868
|
+
};
|
|
869
|
+
/**
|
|
870
|
+
* Register an overlay for as long as this component is mounted.
|
|
871
|
+
*
|
|
872
|
+
* `active: false` unregisters without unmounting, which is what a sheet that is
|
|
873
|
+
* mounted-but-closed needs — an overlay that is present in the tree but not
|
|
874
|
+
* open must not make content outside it inert. `modal` marks modal surfaces
|
|
875
|
+
* so the shared registry can answer "is any modal open" (the Android
|
|
876
|
+
* app-root hook); non-modal overlays register without it.
|
|
877
|
+
*/
|
|
878
|
+
declare function useOverlayRegistration(registry: ReturnType<typeof createOverlayModality>, id: OverlayId, active?: boolean, modal?: boolean): void;
|
|
879
|
+
/** Subscribe to the overlay stack. Returns the current state, re-rendering on change. */
|
|
880
|
+
declare function useOverlayModality(registry: ReturnType<typeof createOverlayModality>): OverlayModalityState;
|
|
881
|
+
type OverlayModalityRegistry = ReturnType<typeof createOverlayModality>;
|
|
882
|
+
/**
|
|
883
|
+
* Shared overlay modality registry for global stacking, top-detection,
|
|
884
|
+
* and dismissal order across the application.
|
|
885
|
+
*/
|
|
886
|
+
declare function getSharedOverlayModality(): OverlayModalityRegistry;
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* The Presence model — exit-animation suspension for unmounting surfaces.
|
|
890
|
+
*
|
|
891
|
+
* ## Why this is a primitive
|
|
892
|
+
*
|
|
893
|
+
* A surface that disappears the instant `open` flips to `false` cannot play an
|
|
894
|
+
* exit animation; a surface that stays mounted forever leaks. Both renderers
|
|
895
|
+
* need the same rule — stay mounted while exiting, unmount when the exit
|
|
896
|
+
* finishes — and each wires a different finish signal (web: CSS
|
|
897
|
+
* `animationend`; native: the `Modal` close / Animated callback). One owner for
|
|
898
|
+
* the state machine, per-renderer finish signals.
|
|
899
|
+
*
|
|
900
|
+
* ## What is genuinely shared, and what is not
|
|
901
|
+
*
|
|
902
|
+
* The MACHINE is shared: `mounted → exiting → unmounted`, where closing moves
|
|
903
|
+
* to `exiting` (still rendered) and only the renderer's `finishExit()` moves to
|
|
904
|
+
* `unmounted`. Reopening from `exiting` returns to `mounted` without ever
|
|
905
|
+
* unmounting. The SIGNAL is not — no DOM event or Animated value appears here.
|
|
906
|
+
*/
|
|
907
|
+
/** Where a surface is in its mount lifecycle. */
|
|
908
|
+
type PresenceState = "mounted" | "exiting" | "unmounted";
|
|
909
|
+
/**
|
|
910
|
+
* Pure transition: which lifecycle state follows `open` from `current`.
|
|
911
|
+
*
|
|
912
|
+
* Closing from `mounted` suspends in `exiting` (the renderer keeps painting
|
|
913
|
+
* until its exit animation reports done); reopening from `exiting` restores
|
|
914
|
+
* `mounted` directly. Nothing here touches a timer or an event.
|
|
915
|
+
*/
|
|
916
|
+
declare function nextPresenceState(current: PresenceState, open: boolean): PresenceState;
|
|
917
|
+
type PresenceModel = {
|
|
918
|
+
subscribe(listener: () => void): () => void;
|
|
919
|
+
getState(): PresenceState;
|
|
920
|
+
/** The renderer reports its controlled `open` value. */
|
|
921
|
+
setOpen(open: boolean): void;
|
|
922
|
+
/** The renderer reports its exit animation finished. No-op unless exiting. */
|
|
923
|
+
finishExit(): void;
|
|
924
|
+
/** True while the surface must stay in the tree (mounted or exiting). */
|
|
925
|
+
isPresent(): boolean;
|
|
926
|
+
};
|
|
927
|
+
/** Create a presence model starting from `initialOpen`. */
|
|
928
|
+
declare function createPresenceModel(initialOpen: boolean): PresenceModel;
|
|
929
|
+
/**
|
|
930
|
+
* React binding for the presence model. `open` is the controlled value; the
|
|
931
|
+
* renderer calls the returned `finishExit` from its own animation-done signal.
|
|
932
|
+
* The surface renders while `present` is true.
|
|
933
|
+
*/
|
|
934
|
+
declare function usePresence(open: boolean): {
|
|
935
|
+
present: boolean;
|
|
936
|
+
state: PresenceState;
|
|
937
|
+
finishExit: () => void;
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Slider behavior — range math as pure functions.
|
|
942
|
+
*
|
|
943
|
+
* No React, no React Native: clamping, step snapping, percent
|
|
944
|
+
* mapping, single/range normalization, grant-target resolution, and
|
|
945
|
+
* commit transforms (including `minStepsBetweenValues` gap
|
|
946
|
+
* enforcement for range sliders). The compound `Slider` parts own
|
|
947
|
+
* state via `useControllableState` and gestures via `PanResponder`;
|
|
948
|
+
* every number they commit flows through here first. Nothing here
|
|
949
|
+
* throws on caller input: degenerate ranges collapse, garbage
|
|
950
|
+
* resolves to the nearest sane value.
|
|
951
|
+
*/
|
|
952
|
+
type SliderOrientation = "horizontal" | "vertical";
|
|
953
|
+
/** Options shared by the commit path. */
|
|
954
|
+
type SliderCommitOptions = {
|
|
955
|
+
min: number;
|
|
956
|
+
max: number;
|
|
957
|
+
/** Snap increment. Values `<= 0` (or non-finite) mean continuous. */
|
|
958
|
+
step: number;
|
|
959
|
+
/**
|
|
960
|
+
* Minimum gap between values in a range slider, in `step` units.
|
|
961
|
+
* When `step <= 0` the gap applies in raw value units. Default `0`.
|
|
962
|
+
*/
|
|
963
|
+
minStepsBetweenValues: number;
|
|
964
|
+
};
|
|
965
|
+
/** Clamp `value` into `[min, max]`. Degenerate `min > max` collapses to the single point `min`. */
|
|
966
|
+
declare function clampSliderValue(value: number, min: number, max: number): number;
|
|
967
|
+
/**
|
|
968
|
+
* Snap `value` (assumed clamped) to the nearest `min + k * step`.
|
|
969
|
+
* Values `<= 0` (or non-finite `step`) mean continuous — returned unchanged.
|
|
970
|
+
*/
|
|
971
|
+
declare function snapSliderValue(value: number, min: number, step: number): number;
|
|
972
|
+
/** Percentage (0–100) of `value` along `[min, max]`. Degenerate ranges report `0`. */
|
|
973
|
+
declare function sliderValuePercent(value: number, min: number, max: number): number;
|
|
974
|
+
/**
|
|
975
|
+
* Normalize a caller `value` (single or array) into a clamped
|
|
976
|
+
* array of at least one entry. Non-finite entries fall back to
|
|
977
|
+
* `min`. Snapping is deliberately NOT applied here: the displayed
|
|
978
|
+
* value stays clamped-but-raw until a commit snaps it (a slider
|
|
979
|
+
* that jumps on mount is a worse experience than one that snaps
|
|
980
|
+
* on first commit). Never throws; empty arrays resolve to
|
|
981
|
+
* `[min]`.
|
|
982
|
+
*/
|
|
983
|
+
declare function normalizeSliderValues(value: number | readonly number[] | undefined, min: number, max: number): number[];
|
|
984
|
+
/**
|
|
985
|
+
* Ratio (0–1) of a touch location along the track extent. Vertical
|
|
986
|
+
* sliders invert (track top is `max`).
|
|
987
|
+
*/
|
|
988
|
+
declare function sliderRatioFromLocation(location: number, extent: number, orientation: SliderOrientation): number;
|
|
989
|
+
/** Value at a track ratio. */
|
|
990
|
+
declare function sliderValueFromRatio(ratio: number, min: number, max: number): number;
|
|
991
|
+
/** Index of the entry nearest to `target` (ties go to the lower index). */
|
|
992
|
+
declare function nearestSliderThumbIndex(values: readonly number[], target: number): number;
|
|
993
|
+
/**
|
|
994
|
+
* Commit transform: replace `values[index]` with `next` (clamped +
|
|
995
|
+
* snapped), then enforce the `minStepsBetweenValues` gap against
|
|
996
|
+
* neighbours (range sliders only — single values pass through).
|
|
997
|
+
* Returns a new array; inputs are never mutated.
|
|
998
|
+
*/
|
|
999
|
+
declare function resolveSliderCommit(values: readonly number[], index: number, next: number, options: SliderCommitOptions): number[];
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* The dismissal POLICY for a kern surface — what closes it, and whether a
|
|
1003
|
+
* visible close affordance must be rendered.
|
|
1004
|
+
*
|
|
1005
|
+
* ## Why this is a primitive
|
|
1006
|
+
*
|
|
1007
|
+
* P2c-2 §3 Wave 1: give behaviour that exists on BOTH renderers with
|
|
1008
|
+
* independent implementations one owner. Dismissal is that behaviour.
|
|
1009
|
+
*
|
|
1010
|
+
* The native `SheetSurface` already encodes the rule in one line, with a
|
|
1011
|
+
* comment recording the defect it fixed:
|
|
1012
|
+
*
|
|
1013
|
+
* // a sheet that declares `dismissible` but renders no close control for it
|
|
1014
|
+
* const showClose = dismissible ?? Boolean(onDismiss);
|
|
1015
|
+
*
|
|
1016
|
+
* The web `SheetSurface` encodes the same decision differently — the close
|
|
1017
|
+
* button is rendered when `onClose` is passed. Both are one rule wearing two
|
|
1018
|
+
* renderers, which is the `useControllableState` failure mode repeating: a fix
|
|
1019
|
+
* to one silently does not reach the other.
|
|
1020
|
+
*
|
|
1021
|
+
* ## What is genuinely shared, and what is not
|
|
1022
|
+
*
|
|
1023
|
+
* The POLICY is shared and belongs here: which triggers dismiss, and whether a
|
|
1024
|
+
* close affordance must exist. The TRIGGERS are not — a scrim `Pressable` and a
|
|
1025
|
+
* `Portal`/`Backdrop` are DOM, `onRequestClose` and hardware back are RN. So
|
|
1026
|
+
* this returns the decisions, and each renderer wires its own triggers to them.
|
|
1027
|
+
*
|
|
1028
|
+
* That split is what keeps it a primitive rather than contract data: a rule both
|
|
1029
|
+
* renderers must apply identically, with no platform type in sight.
|
|
1030
|
+
*/
|
|
1031
|
+
/** How a surface is dismissed. Each renderer binds its own triggers to these. */
|
|
1032
|
+
type DismissTriggers = {
|
|
1033
|
+
/** The scrim/backdrop outside the surface was pressed. */
|
|
1034
|
+
scrim?: boolean;
|
|
1035
|
+
/** Escape (web) or the hardware/gesture back (native). */
|
|
1036
|
+
escape?: boolean;
|
|
1037
|
+
/** A visible close affordance was activated. */
|
|
1038
|
+
closeButton?: boolean;
|
|
1039
|
+
};
|
|
1040
|
+
type DismissPolicyOptions = {
|
|
1041
|
+
/**
|
|
1042
|
+
* Whether the surface can be dismissed at all. `undefined` means "follow
|
|
1043
|
+
* whether a dismiss handler was supplied" — which is how a mandatory surface
|
|
1044
|
+
* (a dialog the user must answer) is expressed without a separate API.
|
|
1045
|
+
*/
|
|
1046
|
+
dismissible?: boolean;
|
|
1047
|
+
/**
|
|
1048
|
+
* Whether the surface declares a dismissal path at all. A surface with no
|
|
1049
|
+
* handler cannot show a close control: the control would render and do nothing,
|
|
1050
|
+
* which is worse than not rendering it.
|
|
1051
|
+
*/
|
|
1052
|
+
hasDismissHandler: boolean;
|
|
1053
|
+
};
|
|
1054
|
+
type DismissPolicy = {
|
|
1055
|
+
/** True when at least one trigger may dismiss. */
|
|
1056
|
+
canDismiss: boolean;
|
|
1057
|
+
/**
|
|
1058
|
+
* A visible close affordance MUST be rendered.
|
|
1059
|
+
*
|
|
1060
|
+
* This is the part the scrim-only shell got wrong: it could render a surface
|
|
1061
|
+
* that declared a dismissal path and showed no control for it, so the declared
|
|
1062
|
+
* path was unreachable. Deriving `showClose` from the same value the triggers
|
|
1063
|
+
* use means a sheet cannot declare dismissal and render nothing.
|
|
1064
|
+
*/
|
|
1065
|
+
showClose: boolean;
|
|
1066
|
+
/**
|
|
1067
|
+
* Bind to a platform trigger. `enabled` is the caller's current OPEN state:
|
|
1068
|
+
* a trigger on a closed surface must do nothing, whatever the policy says.
|
|
1069
|
+
*
|
|
1070
|
+
* `triggers` is what `dismissTriggersFor()` returned. It is REQUIRED rather
|
|
1071
|
+
* than optional: while it was absent this parameter was accepted and ignored,
|
|
1072
|
+
* which made `dismissTriggersFor` decorative — a docked surface could declare
|
|
1073
|
+
* no scrim and still be dismissed by one, because the declaration was never
|
|
1074
|
+
* consulted. The lint rule that caught it (`noUnusedFunctionParameters`) was
|
|
1075
|
+
* right and this is the fix it was pointing at.
|
|
1076
|
+
*/
|
|
1077
|
+
shouldDismiss: (trigger: keyof DismissTriggers, enabled: boolean, triggers: DismissTriggers) => boolean;
|
|
1078
|
+
};
|
|
1079
|
+
/**
|
|
1080
|
+
* Resolve the dismissal policy. Pure — the caller supplies the current open
|
|
1081
|
+
* state as `enabled`.
|
|
1082
|
+
*/
|
|
1083
|
+
declare function createDismissPolicy(options: DismissPolicyOptions): DismissPolicy;
|
|
1084
|
+
/**
|
|
1085
|
+
* The triggers a renderer should wire, given what it can actually offer.
|
|
1086
|
+
*
|
|
1087
|
+
* A docked surface has no scrim and no Escape — it is persistent, not modal — so
|
|
1088
|
+
* it declares those `false` rather than binding handlers that can never fire. That
|
|
1089
|
+
* is the `DockSheet` reasoning (role="region", no elevation token) expressed as a
|
|
1090
|
+
* data decision instead of scattered conditionals.
|
|
1091
|
+
*/
|
|
1092
|
+
declare function dismissTriggersFor(surface: {
|
|
1093
|
+
modal: boolean;
|
|
1094
|
+
hasVisibleClose: boolean;
|
|
1095
|
+
}): DismissTriggers;
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* The FocusTrap model — tab looping and autofocus events for modal surfaces.
|
|
1099
|
+
*
|
|
1100
|
+
* ## Why this is a primitive
|
|
1101
|
+
*
|
|
1102
|
+
* A modal surface must keep keyboard focus inside itself while open and move
|
|
1103
|
+
* focus into itself when it opens. Both renderers implement that today — web
|
|
1104
|
+
* through the dialog primitive's trap, native through explicit first-focusable
|
|
1105
|
+
* management — with independent loop arithmetic, so an off-by-one on one side
|
|
1106
|
+
* silently does not reach the other.
|
|
1107
|
+
*
|
|
1108
|
+
* ## What is genuinely shared, and what is not
|
|
1109
|
+
*
|
|
1110
|
+
* The LOOP is shared: given an ordered list of `count` focusable stops, which
|
|
1111
|
+
* stop follows a Tab / Shift+Tab, and where focus lands on open. The STOPS are
|
|
1112
|
+
* not — querying tabbable DOM nodes and RN focus targets is platform code. So
|
|
1113
|
+
* this works over indices; each renderer maps indices to its own stops.
|
|
1114
|
+
*
|
|
1115
|
+
* Autofocus is an EVENT descriptor, not an imperative call: the model returns
|
|
1116
|
+
* which index to focus (`{ type: "focus-stop", index }` or `{ type: "none" }`)
|
|
1117
|
+
* and the renderer performs it. Returning a description keeps DOM/RN focus
|
|
1118
|
+
* calls out of the kernel.
|
|
1119
|
+
*/
|
|
1120
|
+
type TrapMove = "next" | "previous" | "first" | "last";
|
|
1121
|
+
/** Pure index arithmetic for one trap move. */
|
|
1122
|
+
declare function nextTrapIndex(current: number, count: number, move: TrapMove, loop: boolean): number;
|
|
1123
|
+
/** Where focus goes when the trap opens. A description, not a focus() call. */
|
|
1124
|
+
type AutofocusEvent = {
|
|
1125
|
+
type: "focus-stop";
|
|
1126
|
+
index: number;
|
|
1127
|
+
} | {
|
|
1128
|
+
type: "none";
|
|
1129
|
+
};
|
|
1130
|
+
/**
|
|
1131
|
+
* Which stop to focus on open: the caller's explicit `autoFocusIndex` when it
|
|
1132
|
+
* names a real stop, else the first stop, else no event (an empty trap has
|
|
1133
|
+
* nothing to focus and must not invent a target).
|
|
1134
|
+
*/
|
|
1135
|
+
declare function resolveAutofocus(count: number, autoFocusIndex?: number): AutofocusEvent;
|
|
1136
|
+
type FocusTrapModel = {
|
|
1137
|
+
/** Currently focused stop, or -1 when the trap holds no stops. */
|
|
1138
|
+
getCurrent(): number;
|
|
1139
|
+
getCount(): number;
|
|
1140
|
+
move(move: TrapMove): number;
|
|
1141
|
+
/** Tab from the last stop wraps exactly when `loop` is true. */
|
|
1142
|
+
loops(): boolean;
|
|
1143
|
+
autofocus(autoFocusIndex?: number): AutofocusEvent;
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Number field behavior — locale-aware parse/format and step math as
|
|
1148
|
+
* pure functions. No React, no React Native.
|
|
1149
|
+
*
|
|
1150
|
+
* The value is always a plain `number` (or `null` for empty) whatever
|
|
1151
|
+
* the input renders. Formatting is display-only: typing parses back
|
|
1152
|
+
* through the same locale, so a formatted value never drifts from the
|
|
1153
|
+
* state it represents. Nothing here throws on caller input.
|
|
1154
|
+
*/
|
|
1155
|
+
type NumberFormatSpec = {
|
|
1156
|
+
/** BCP 47 locale for grouping/decimal separators. Defaults to runtime locale. */
|
|
1157
|
+
locale?: string;
|
|
1158
|
+
/** `Intl.NumberFormat` options for display. */
|
|
1159
|
+
format?: Intl.NumberFormatOptions;
|
|
1160
|
+
};
|
|
1161
|
+
/**
|
|
1162
|
+
* Format a value for display. `null` renders empty. Falls back to
|
|
1163
|
+
* `String(value)` when `Intl` is unavailable — never throws.
|
|
1164
|
+
*/
|
|
1165
|
+
declare function formatNumberValue(value: number | null, spec?: NumberFormatSpec): string;
|
|
1166
|
+
/**
|
|
1167
|
+
* Parse display text back to a number through the same locale:
|
|
1168
|
+
* grouping separators are stripped, the locale decimal separator is
|
|
1169
|
+
* mapped to `.`. Returns `null` for empty or unparseable text —
|
|
1170
|
+
* never throws.
|
|
1171
|
+
*/
|
|
1172
|
+
declare function parseNumberValue(text: string | null | undefined, spec?: NumberFormatSpec): number | null;
|
|
1173
|
+
/** Clamp into `[min, max]`; undefined bounds are open. */
|
|
1174
|
+
declare function clampNumberValue(value: number, min?: number, max?: number): number;
|
|
1175
|
+
/**
|
|
1176
|
+
* Step from a value by `direction`, snapping to the step grid when
|
|
1177
|
+
* `snapOnStep` is set, then clamping to `[min, max]`.
|
|
1178
|
+
*/
|
|
1179
|
+
declare function stepNumberValue(value: number, direction: 1 | -1, options?: {
|
|
1180
|
+
step?: number;
|
|
1181
|
+
snapOnStep?: boolean;
|
|
1182
|
+
min?: number;
|
|
1183
|
+
max?: number;
|
|
1184
|
+
}): number;
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* The owned Portal registry — kern's own overlay host, alongside Base UI.
|
|
1188
|
+
*
|
|
1189
|
+
* ## Why this is a primitive, and why it does NOT replace Base UI on web
|
|
1190
|
+
*
|
|
1191
|
+
* Every overlay renders its surface somewhere other than its trigger's DOM
|
|
1192
|
+
* parent: that "somewhere else" needs an owner (ordering, mount/unmount,
|
|
1193
|
+
* cleanup on unmount). Today the web renderer borrows that owner from Base UI's
|
|
1194
|
+
* `Portal` and native borrows it from `Modal`. D12 rules the extraction as
|
|
1195
|
+
* DUAL-PATH: kern gains its OWN portal registry that both renderers can host
|
|
1196
|
+
* through, while web KEEPS its Base UI usage. Removing the borrowed owner
|
|
1197
|
+
* before the owned one is proven would strand every overlay on a host nobody
|
|
1198
|
+
* exercises; adding alongside lets the owned path prove itself per surface.
|
|
1199
|
+
*
|
|
1200
|
+
* ## What is genuinely shared, and what is not
|
|
1201
|
+
*
|
|
1202
|
+
* The REGISTRY is shared: which overlay ids are mounted, in what order, and
|
|
1203
|
+
* the rule that unmounting removes exactly one registration. The HOST is not —
|
|
1204
|
+
* a DOM container element and an RN `Modal` stack are platform code. So this
|
|
1205
|
+
* returns mount state, and each renderer paints it.
|
|
1206
|
+
*/
|
|
1207
|
+
type PortalEntry = {
|
|
1208
|
+
/** Stable overlay identity, bottom-first in `order()`. */
|
|
1209
|
+
id: string;
|
|
1210
|
+
/** Monotonic sequence so two mounts in one frame still order deterministically. */
|
|
1211
|
+
sequence: number;
|
|
1212
|
+
};
|
|
1213
|
+
type PortalRegistry = {
|
|
1214
|
+
subscribe(listener: () => void): () => void;
|
|
1215
|
+
/** Mount `id`. Re-mounting an open id refreshes nothing — it is a no-op. */
|
|
1216
|
+
mount(id: string): void;
|
|
1217
|
+
/** Unmount `id`. Unknown ids are a no-op, never an error. */
|
|
1218
|
+
unmount(id: string): void;
|
|
1219
|
+
/** Mounted ids, bottom-first. */
|
|
1220
|
+
order(): readonly string[];
|
|
1221
|
+
isMounted(id: string): boolean;
|
|
1222
|
+
};
|
|
1223
|
+
/** Create an empty portal registry. Pure — no DOM, no RN. */
|
|
1224
|
+
declare function createPortalRegistry(): PortalRegistry;
|
|
1225
|
+
/**
|
|
1226
|
+
* Resolve where a surface renders: the caller's explicit host wins, else the
|
|
1227
|
+
* shared default. A description, not a container lookup — finding the DOM node
|
|
1228
|
+
* or RN host is the renderer's job.
|
|
1229
|
+
*/
|
|
1230
|
+
declare function resolvePortalTarget(options: {
|
|
1231
|
+
ownerProvided?: string;
|
|
1232
|
+
defaultTarget?: string;
|
|
1233
|
+
}): string;
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* The Press model — press/activation behaviour shared by every pressable.
|
|
1237
|
+
*
|
|
1238
|
+
* ## Why this is a primitive
|
|
1239
|
+
*
|
|
1240
|
+
* Pressed-state tracking (pressed visual while held, cancelled when the pointer
|
|
1241
|
+
* leaves) and keyboard activation (Enter and Space activate a pressable) exist
|
|
1242
|
+
* on BOTH renderers with independent implementations: web spreads `onClick` /
|
|
1243
|
+
* `onKeyDown`, native spreads `onPress`. A fix to which keys activate on one
|
|
1244
|
+
* side silently does not reach the other.
|
|
1245
|
+
*
|
|
1246
|
+
* ## What is genuinely shared, and what is not
|
|
1247
|
+
*
|
|
1248
|
+
* The MODEL is shared: pressed-or-not, and which keys count as activation.
|
|
1249
|
+
* The EVENTS are not — DOM keyboard/pointer events and RN press events are
|
|
1250
|
+
* platform types. So this exposes `begin`/`end`/`cancel` plus the key
|
|
1251
|
+
* predicate, and each renderer binds its own events to them.
|
|
1252
|
+
*/
|
|
1253
|
+
/** Pressed-or-not. Cancelled returns to idle without ever reporting a press. */
|
|
1254
|
+
type PressPhase = "idle" | "pressed";
|
|
1255
|
+
type PressModel = {
|
|
1256
|
+
getPhase(): PressPhase;
|
|
1257
|
+
isPressed(): boolean;
|
|
1258
|
+
/** Pointer/key went down on the target. No-op when disabled. */
|
|
1259
|
+
begin(): void;
|
|
1260
|
+
/** Release over the target: reports a press via the caller's `onPress`. */
|
|
1261
|
+
end(): void;
|
|
1262
|
+
/** Release off-target or interrupted: back to idle, no press reported. */
|
|
1263
|
+
cancel(): void;
|
|
1264
|
+
/** Whether the model accepts input at all. */
|
|
1265
|
+
isDisabled(): boolean;
|
|
1266
|
+
};
|
|
1267
|
+
/**
|
|
1268
|
+
* React binding for the press model. Returns the pressed bit plus
|
|
1269
|
+
* platform-free `begin`/`end`/`cancel` the renderer binds to its own events —
|
|
1270
|
+
* web to pointer/key handlers, native to `onPressIn`/`onPressOut`.
|
|
1271
|
+
*/
|
|
1272
|
+
declare function usePress(options?: {
|
|
1273
|
+
disabled?: boolean;
|
|
1274
|
+
onPress?: () => void;
|
|
1275
|
+
}): {
|
|
1276
|
+
pressed: boolean;
|
|
1277
|
+
begin: () => void;
|
|
1278
|
+
end: () => void;
|
|
1279
|
+
cancel: () => void;
|
|
1280
|
+
};
|
|
1281
|
+
|
|
1282
|
+
/**
|
|
1283
|
+
* The prompt-input model — draft value, the send gate, submit keys,
|
|
1284
|
+
* and composer sizing.
|
|
1285
|
+
*
|
|
1286
|
+
* ## Why this is a primitive
|
|
1287
|
+
*
|
|
1288
|
+
* The composer on both renderers answers the same questions on every
|
|
1289
|
+
* keystroke: is there anything sendable, does this key submit or
|
|
1290
|
+
* newline, and how tall is the multiline field. If the web side sends
|
|
1291
|
+
* on Enter while native needs Ctrl+Enter, the same keyboard habit
|
|
1292
|
+
* submits on one renderer and litters newlines on the other — a
|
|
1293
|
+
* submit mode is behavior, and behavior belongs here once.
|
|
1294
|
+
*
|
|
1295
|
+
* ## What is genuinely shared, and what is not
|
|
1296
|
+
*
|
|
1297
|
+
* The DRAFT PROTOCOL is shared: value state, the `canSend` gate over
|
|
1298
|
+
* the shared `ChatStatus` vocabulary, the submit-key rule per
|
|
1299
|
+
* `submitMode`, Escape cancellation, and the clamped composer height.
|
|
1300
|
+
* The FIELD is not — the text host, autosize measurement, focus, and
|
|
1301
|
+
* the model-picker / tools-menu / mic chrome are styled composition
|
|
1302
|
+
* in Kern. Sending itself stays app-owned: `submit` reports the draft
|
|
1303
|
+
* through `onSubmit` and leaves clearing to the caller, so controlled
|
|
1304
|
+
* and uncontrolled drafts behave identically.
|
|
1305
|
+
*/
|
|
1306
|
+
|
|
1307
|
+
/** Which key commits the draft. `none` submits from the Send affordance only. */
|
|
1308
|
+
type PromptInputSubmitMode = "enter" | "ctrlEnter" | "none";
|
|
1309
|
+
/**
|
|
1310
|
+
* Whether the draft may send now: non-blank text, no run in flight
|
|
1311
|
+
* (a busy composer offers Stop instead — the `isChatBusy` half of
|
|
1312
|
+
* the shared vocabulary), and within `maxLength` when one is set.
|
|
1313
|
+
* An `error` status sends: retry reuses the draft.
|
|
1314
|
+
*/
|
|
1315
|
+
declare function resolveCanSend(options: {
|
|
1316
|
+
text: string;
|
|
1317
|
+
status: ChatStatus;
|
|
1318
|
+
maxLength?: number;
|
|
1319
|
+
}): boolean;
|
|
1320
|
+
/**
|
|
1321
|
+
* Whether a key event commits the draft under `submitMode`: plain
|
|
1322
|
+
* Enter submits in `enter` mode (Shift+Enter holds the newline
|
|
1323
|
+
* convention both renderers share); `ctrlEnter` needs Ctrl or Cmd;
|
|
1324
|
+
* `none` never submits by key. Key names are plain strings — each
|
|
1325
|
+
* renderer maps its own key events onto them.
|
|
1326
|
+
*/
|
|
1327
|
+
declare function shouldSubmitOnKey(options: {
|
|
1328
|
+
key: string;
|
|
1329
|
+
shiftKey?: boolean;
|
|
1330
|
+
ctrlKey?: boolean;
|
|
1331
|
+
metaKey?: boolean;
|
|
1332
|
+
submitMode: PromptInputSubmitMode;
|
|
1333
|
+
}): boolean;
|
|
1334
|
+
/**
|
|
1335
|
+
* Whether an Escape key cancels the draft when `cancelOnEscape` is
|
|
1336
|
+
* set. The renderer owns blur; this only answers the key question so
|
|
1337
|
+
* both renderers clear-or-keep identically.
|
|
1338
|
+
*/
|
|
1339
|
+
declare function shouldCancelOnEscapeKey(key: string, cancelOnEscape: boolean): boolean;
|
|
1340
|
+
/**
|
|
1341
|
+
* The composer height for `lineCount` lines at `lineHeight` per line,
|
|
1342
|
+
* clamped to `minLines`..`maxLines` (defaults `1`..`5`, the range
|
|
1343
|
+
* every flagship composer grows through before scrolling). The
|
|
1344
|
+
* renderer counts lines from its own measurement; this clamps.
|
|
1345
|
+
*/
|
|
1346
|
+
declare function resolveComposerHeight(options: {
|
|
1347
|
+
lineCount: number;
|
|
1348
|
+
lineHeight: number;
|
|
1349
|
+
minLines?: number;
|
|
1350
|
+
maxLines?: number;
|
|
1351
|
+
}): number;
|
|
1352
|
+
/**
|
|
1353
|
+
* The composer draft, controlled or uncontrolled, gated on the run
|
|
1354
|
+
* status. `submit` reports the draft through `onSubmit` when
|
|
1355
|
+
* `canSend` holds and is otherwise a terminal no-op (no callbacks
|
|
1356
|
+
* from an empty or busy composer). `submit` never clears, by
|
|
1357
|
+
* decision: the caller clears after handing the draft to its
|
|
1358
|
+
* transport (`onSubmit={(text) => { send(text); clear(); }}`), so
|
|
1359
|
+
* the clear visibly follows the send on every renderer and
|
|
1360
|
+
* controlled drafts behave exactly like uncontrolled ones. A hook
|
|
1361
|
+
* that cleared internally could not clear controlled drafts, and
|
|
1362
|
+
* two behaviors under one name is the bug farm.
|
|
1363
|
+
*/
|
|
1364
|
+
declare function usePromptInput(options?: {
|
|
1365
|
+
value?: string;
|
|
1366
|
+
defaultValue?: string;
|
|
1367
|
+
onChange?: (next: string) => void;
|
|
1368
|
+
status?: ChatStatus;
|
|
1369
|
+
submitMode?: PromptInputSubmitMode;
|
|
1370
|
+
cancelOnEscape?: boolean;
|
|
1371
|
+
maxLength?: number;
|
|
1372
|
+
onSubmit?: (text: string) => void;
|
|
1373
|
+
}): {
|
|
1374
|
+
value: string;
|
|
1375
|
+
setValue: (next: string) => void;
|
|
1376
|
+
canSend: boolean;
|
|
1377
|
+
submit: () => void;
|
|
1378
|
+
clear: () => void;
|
|
1379
|
+
handleKey: (event: {
|
|
1380
|
+
key: string;
|
|
1381
|
+
shiftKey?: boolean;
|
|
1382
|
+
ctrlKey?: boolean;
|
|
1383
|
+
metaKey?: boolean;
|
|
1384
|
+
}) => boolean;
|
|
1385
|
+
};
|
|
1386
|
+
|
|
1387
|
+
/**
|
|
1388
|
+
* The reasoning helper — streaming auto-open over the existing
|
|
1389
|
+
* `Collapsible`.
|
|
1390
|
+
*
|
|
1391
|
+
* ## Why this reuses Collapsible instead of adding a primitive
|
|
1392
|
+
*
|
|
1393
|
+
* A reasoning block is a disclosure unit: a trigger ("Thought for
|
|
1394
|
+
* 4s") with a panel (the thinking stream) that mounts while open.
|
|
1395
|
+
* That is exactly what `Collapsible` already is — same boolean
|
|
1396
|
+
* domain, same mount-on-open presence, same button-with-expanded
|
|
1397
|
+
* trigger. The only chat-specific behavior is WHEN it opens: live
|
|
1398
|
+
* thinking auto-opens while streaming and returns to caller control
|
|
1399
|
+
* once settled. A one-line resolver expresses that; a new primitive
|
|
1400
|
+
* would duplicate `Collapsible` to own a single conditional.
|
|
1401
|
+
*
|
|
1402
|
+
* ## How Kern wires it
|
|
1403
|
+
*
|
|
1404
|
+
* `open={resolveReasoningOpen(callerOpen, status)}` on the
|
|
1405
|
+
* `Collapsible`, with the caller's toggle feeding back through
|
|
1406
|
+
* `onOpenChange`. No hook, no component, no state here — the open
|
|
1407
|
+
* bit stays where `Collapsible` already keeps it.
|
|
1408
|
+
*/
|
|
1409
|
+
|
|
1410
|
+
/**
|
|
1411
|
+
* Whether the reasoning disclosure reads open: streaming thinking
|
|
1412
|
+
* auto-opens (the reader watches the thought form); settled thinking
|
|
1413
|
+
* follows the caller's `open` (`undefined` reads closed — an
|
|
1414
|
+
* uncontrolled disclosure starts collapsed, the `Collapsible`
|
|
1415
|
+
* default).
|
|
1416
|
+
*/
|
|
1417
|
+
declare function resolveReasoningOpen(open: boolean | undefined, status: ChatStatus): boolean;
|
|
1418
|
+
|
|
1419
|
+
/**
|
|
1420
|
+
* Roving index — one tab stop per group, arrow traversal within it.
|
|
1421
|
+
*
|
|
1422
|
+
* ## Why this is a primitive and not a component
|
|
1423
|
+
*
|
|
1424
|
+
* A composite control (a list of segments, a carousel's item track, a
|
|
1425
|
+
* time-picker's three fields) is ONE tab stop, not N. Inside it, arrow keys move
|
|
1426
|
+
* between items. That is "roving focus", and it is a model — an active index plus
|
|
1427
|
+
* the invariant that only the active item is in the tab order — not a widget.
|
|
1428
|
+
*
|
|
1429
|
+
* The mechanism is platform-specific and deliberately NOT here: web presses
|
|
1430
|
+
* ArrowLeft/ArrowRight; React Native has no arrow keys at all, because a touch
|
|
1431
|
+
* device has no keyboard. So the two renderers supply their own input handling
|
|
1432
|
+
* and this model supplies only the state and the derived per-item facts. Folding
|
|
1433
|
+
* key handling in would make the "platform-free" claim false, which is the same
|
|
1434
|
+
* mistake the SheetSurface extraction would have made.
|
|
1435
|
+
*
|
|
1436
|
+
* ## What callers get
|
|
1437
|
+
*
|
|
1438
|
+
* `describe(index)` returns `isActive` and `isTabbable`. Those are deliberately
|
|
1439
|
+
* NOT named `tabIndex` or `focused`: `tabIndex` is web vocabulary, and a native
|
|
1440
|
+
* renderer maps `isTabbable` onto `accessibilityState.selected` instead. The
|
|
1441
|
+
* model must not encode which platform is consuming it, or a future third
|
|
1442
|
+
* renderer has to work around the web one.
|
|
1443
|
+
*
|
|
1444
|
+
* ## Wrap and disabled
|
|
1445
|
+
*
|
|
1446
|
+
* `loop: true` wraps from last to first. `loop: false` stops, and the
|
|
1447
|
+
* corresponding `next()`/`previous()` at the end is a no-op that returns the
|
|
1448
|
+
* same index — callers that need to know can compare. Disabled items are
|
|
1449
|
+
* skipped by traversal but keep their own position, so `count` and the index
|
|
1450
|
+
* stable: skipping by deletion would renumber everything after it.
|
|
1451
|
+
*/
|
|
1452
|
+
type RovingOrientation = "horizontal" | "vertical" | "both";
|
|
1453
|
+
type RovingOptions = {
|
|
1454
|
+
/** Number of items in the group. */
|
|
1455
|
+
count: number;
|
|
1456
|
+
/** Which arrow keys move. `both` accepts either axis. */
|
|
1457
|
+
orientation?: RovingOrientation;
|
|
1458
|
+
/** Wrap from last to first. Default: no wrap. */
|
|
1459
|
+
loop?: boolean;
|
|
1460
|
+
/** Controlled active index. */
|
|
1461
|
+
activeIndex?: number;
|
|
1462
|
+
defaultActiveIndex?: number;
|
|
1463
|
+
onActiveIndexChange?: (index: number) => void;
|
|
1464
|
+
/** Items to skip during traversal. Position is preserved in the index space. */
|
|
1465
|
+
isDisabled?: (index: number) => boolean;
|
|
1466
|
+
};
|
|
1467
|
+
type RovingItem = {
|
|
1468
|
+
index: number;
|
|
1469
|
+
/** This is the item that holds the group's single tab stop. */
|
|
1470
|
+
isActive: boolean;
|
|
1471
|
+
/** True only for the active item — the "one tab stop" invariant. */
|
|
1472
|
+
isTabbable: boolean;
|
|
1473
|
+
disabled: boolean;
|
|
1474
|
+
};
|
|
1475
|
+
type RovingModel = {
|
|
1476
|
+
activeIndex: number;
|
|
1477
|
+
setActive: (index: number) => void;
|
|
1478
|
+
next: () => number;
|
|
1479
|
+
previous: () => number;
|
|
1480
|
+
first: () => number;
|
|
1481
|
+
last: () => number;
|
|
1482
|
+
describe: (index: number) => RovingItem;
|
|
1483
|
+
orientation: RovingOrientation;
|
|
1484
|
+
loop: boolean;
|
|
1485
|
+
};
|
|
1486
|
+
/**
|
|
1487
|
+
* React binding.
|
|
1488
|
+
*
|
|
1489
|
+
* The model is plain and synchronous, but a React caller needs the re-render
|
|
1490
|
+
* that follows a change of active index, so state is held here rather than
|
|
1491
|
+
* inside `createRovingModel`. Uncontrolled changes bump a counter to force the
|
|
1492
|
+
* re-render and read the value back out of the model; controlled callers are
|
|
1493
|
+
* driven by their own prop and are not re-rendered from here.
|
|
1494
|
+
*/
|
|
1495
|
+
declare function useRovingModel(options: RovingOptions): RovingModel;
|
|
1496
|
+
|
|
1497
|
+
/**
|
|
1498
|
+
* The collection/selection model shared by every Kern control that selects.
|
|
1499
|
+
*
|
|
1500
|
+
* ## Why this is a primitive
|
|
1501
|
+
*
|
|
1502
|
+
* SegmentedButton, ToggleGroup, Accordion and Select all reduce to the same two
|
|
1503
|
+
* questions: is this key selected, and what happens when it is activated. The
|
|
1504
|
+
* answer differs in exactly one way — whether selection is exclusive or additive
|
|
1505
|
+
* — and that difference is a Kern decision (M3's segmented button is multi-select;
|
|
1506
|
+
* its radio group is not), not a platform detail.
|
|
1507
|
+
*
|
|
1508
|
+
* So the model is shared and the rendering is not. On web the selection axis is
|
|
1509
|
+
* Base UI's `multiple`; on native it is `accessibilityRole` switching between
|
|
1510
|
+
* `radio` and `checkbox`. Neither belongs in this file, which is why it can be a
|
|
1511
|
+
* real primitive rather than contract data.
|
|
1512
|
+
*
|
|
1513
|
+
* ## Why the key is generic
|
|
1514
|
+
*
|
|
1515
|
+
* SegmentedButton and ToggleGroup select by string value; Accordion selects by
|
|
1516
|
+
* INDEX, and `expanded: number[]` is public API. Pinning this to `string` would
|
|
1517
|
+
* have left Accordion out and made the "shared model" claim half true. The
|
|
1518
|
+
* selection rules never inspect the key, so there is nothing to gain from
|
|
1519
|
+
* narrowing it.
|
|
1520
|
+
*
|
|
1521
|
+
* ## Representation
|
|
1522
|
+
*
|
|
1523
|
+
* Selection is ALWAYS an array, including in single mode where it holds at most
|
|
1524
|
+
* one entry. Normalising to one shape removes the branch every consumer otherwise
|
|
1525
|
+
* writes (`isMultiple ? values : [value]`).
|
|
1526
|
+
*/
|
|
1527
|
+
/** The identity of a selectable item: string for value-keyed, number for index-keyed. */
|
|
1528
|
+
type SelectionKey = string | number;
|
|
1529
|
+
type SelectionMode =
|
|
1530
|
+
/**
|
|
1531
|
+
* Radio semantics: activating replaces, and re-activating the selected item
|
|
1532
|
+
* does NOTHING — it does not deselect. A radio group with nothing selected is
|
|
1533
|
+
* not a state M3 describes, and a control that empties itself on a second tap
|
|
1534
|
+
* surprises pointer users.
|
|
1535
|
+
*/
|
|
1536
|
+
"single"
|
|
1537
|
+
/**
|
|
1538
|
+
* Accordion / disclosure semantics: activating replaces, and re-activating the
|
|
1539
|
+
* CURRENT item COLLAPSES it. Distinct from `single` because "the one open
|
|
1540
|
+
* section" closing is the entire point of a disclosure.
|
|
1541
|
+
*
|
|
1542
|
+
* Found by a test: the first version of this primitive had only `single` and
|
|
1543
|
+
* applying it to Accordion broke the collapse that component exists to provide.
|
|
1544
|
+
*/
|
|
1545
|
+
| "single-toggle"
|
|
1546
|
+
/** Multi-select: activating adds or removes. */
|
|
1547
|
+
| "multiple";
|
|
1548
|
+
/** At most one entry in `single` mode; any number in `multiple`. */
|
|
1549
|
+
type Selection<K extends SelectionKey = string> = readonly K[];
|
|
1550
|
+
/** Coerce a caller's single-or-array value into the array representation. */
|
|
1551
|
+
declare function normalizeSelection<K extends SelectionKey>(value: K | readonly K[] | undefined, mode: SelectionMode): Selection<K>;
|
|
1552
|
+
/** Whether `key` is currently selected. */
|
|
1553
|
+
declare function isSelected<K extends SelectionKey>(selected: Selection<K>, key: K): boolean;
|
|
1554
|
+
/**
|
|
1555
|
+
* Apply an activation to the selection.
|
|
1556
|
+
*
|
|
1557
|
+
* In `single` mode an activation always REPLACES, even re-activating the current
|
|
1558
|
+
* key — that is what a radio group does. Deselecting would leave the group with
|
|
1559
|
+
* nothing selected, which M3 does not describe. (A hand-rolled implementation
|
|
1560
|
+
* this replaced set `""` on re-activation, producing a phantom empty-string
|
|
1561
|
+
* selection instead.)
|
|
1562
|
+
*/
|
|
1563
|
+
declare function toggleSelection<K extends SelectionKey>(selected: Selection<K>, key: K, mode: SelectionMode): Selection<K>;
|
|
1564
|
+
/**
|
|
1565
|
+
* The selection state, controlled or uncontrolled, with a stable `select`
|
|
1566
|
+
* callback.
|
|
1567
|
+
*
|
|
1568
|
+
* `mode` changes normalise immediately rather than waiting for an interaction:
|
|
1569
|
+
* widening single -> multiple keeps the selection, narrowing multiple -> single
|
|
1570
|
+
* keeps the FIRST entry, so a control never reports more selected items than its
|
|
1571
|
+
* mode permits.
|
|
1572
|
+
*/
|
|
1573
|
+
declare function useSelection<K extends SelectionKey>({ value, defaultValue, onChange, mode, }: {
|
|
1574
|
+
value?: K | readonly K[];
|
|
1575
|
+
defaultValue?: K | readonly K[];
|
|
1576
|
+
onChange?: (next: Selection<K>) => void;
|
|
1577
|
+
mode: SelectionMode;
|
|
1578
|
+
}): [Selection<K>, (key: K) => void];
|
|
1579
|
+
|
|
1580
|
+
/**
|
|
1581
|
+
* The stick-to-bottom model — auto-scroll that pauses when the reader
|
|
1582
|
+
* scrolls up and resumes at the bottom.
|
|
1583
|
+
*
|
|
1584
|
+
* ## Why this is a primitive
|
|
1585
|
+
*
|
|
1586
|
+
* Message lists on both renderers answer the same two questions on
|
|
1587
|
+
* every scroll event and every content growth: is the viewport pinned
|
|
1588
|
+
* to the newest edge, and should new content pull it there. Two
|
|
1589
|
+
* independent implementations drift on exactly the edge that matters —
|
|
1590
|
+
* the threshold that decides "at the bottom" — so streaming text
|
|
1591
|
+
* yanks one renderer away from history the reader is re-reading.
|
|
1592
|
+
*
|
|
1593
|
+
* ## What is genuinely shared, and what is not
|
|
1594
|
+
*
|
|
1595
|
+
* The MATH and the STUCK BIT are shared: given plain rect numbers,
|
|
1596
|
+
* whether the viewport counts as pinned, and the `stuck` state with
|
|
1597
|
+
* its pause-on-scroll-up / resume-at-bottom transitions. The
|
|
1598
|
+
* MEASUREMENT and the SCROLLING are not — reading content/viewport
|
|
1599
|
+
* sizes and calling `scrollToEnd` are platform code. So the renderer
|
|
1600
|
+
* measures and reports, this decides; the renderer performs the
|
|
1601
|
+
* scroll this returns true for.
|
|
1602
|
+
*
|
|
1603
|
+
* All values are plain numbers in the same unit space the caller
|
|
1604
|
+
* measured in (CSS px on web, dp on native), following the
|
|
1605
|
+
* `positioning` precedent. `scrollOffset` is the distance scrolled
|
|
1606
|
+
* from the start edge (top); inverted RN lists report the same
|
|
1607
|
+
* quantity from their own metrics before calling in.
|
|
1608
|
+
*/
|
|
1609
|
+
/** Plain-rect snapshot of the scroll container. All values `>= 0`. */
|
|
1610
|
+
type StickToBottomMetrics = {
|
|
1611
|
+
/** Total scrollable content extent. */
|
|
1612
|
+
contentHeight: number;
|
|
1613
|
+
/** Visible viewport extent. */
|
|
1614
|
+
viewportHeight: number;
|
|
1615
|
+
/** Distance scrolled from the start (top) edge. */
|
|
1616
|
+
scrollOffset: number;
|
|
1617
|
+
};
|
|
1618
|
+
/**
|
|
1619
|
+
* Whether the viewport counts as pinned to the newest edge: the
|
|
1620
|
+
* remaining distance is within `threshold` (default `24`, about one
|
|
1621
|
+
* text line — sub-line drift from fractional layout must not unpin).
|
|
1622
|
+
* Content shorter than the viewport is trivially at the bottom.
|
|
1623
|
+
*/
|
|
1624
|
+
declare function isAtBottom(metrics: StickToBottomMetrics, threshold?: number): boolean;
|
|
1625
|
+
/**
|
|
1626
|
+
* Whether the scroll-to-bottom affordance shows: the reader has left
|
|
1627
|
+
* the bottom AND there is somewhere to go (content overflows the
|
|
1628
|
+
* viewport). A short list never shows the button, stuck or not.
|
|
1629
|
+
*/
|
|
1630
|
+
declare function resolveScrollButtonVisible(options: {
|
|
1631
|
+
stuck: boolean;
|
|
1632
|
+
contentHeight: number;
|
|
1633
|
+
viewportHeight: number;
|
|
1634
|
+
}): boolean;
|
|
1635
|
+
/**
|
|
1636
|
+
* The stuck bit, controlled or uncontrolled, with the two transitions
|
|
1637
|
+
* every renderer wires:
|
|
1638
|
+
*
|
|
1639
|
+
* - `handleScroll`: the reader moved — stuck follows `isAtBottom`,
|
|
1640
|
+
* so scrolling up pauses and reaching the bottom resumes.
|
|
1641
|
+
* - `handleContentChange`: new content arrived (a streamed token, a
|
|
1642
|
+
* new message) — returns whether the caller scrolls to the newest
|
|
1643
|
+
* edge now (true only while stuck; a paused reader is never yanked).
|
|
1644
|
+
* - `scrollToBottom`: the scroll-button press — re-pins (the caller
|
|
1645
|
+
* performs the scroll, then feeds the resulting metrics back
|
|
1646
|
+
* through `handleScroll`).
|
|
1647
|
+
*/
|
|
1648
|
+
declare function useStickToBottom(options?: {
|
|
1649
|
+
stuck?: boolean;
|
|
1650
|
+
defaultStuck?: boolean;
|
|
1651
|
+
onStuckChange?: (stuck: boolean) => void;
|
|
1652
|
+
/** "At the bottom" tolerance. Default `24`. */
|
|
1653
|
+
threshold?: number;
|
|
1654
|
+
}): {
|
|
1655
|
+
stuck: boolean;
|
|
1656
|
+
showScrollButton: (metrics: StickToBottomMetrics) => boolean;
|
|
1657
|
+
handleScroll: (metrics: StickToBottomMetrics) => void;
|
|
1658
|
+
handleContentChange: (metrics: StickToBottomMetrics) => boolean;
|
|
1659
|
+
scrollToBottom: () => void;
|
|
1660
|
+
};
|
|
1661
|
+
|
|
1662
|
+
/**
|
|
1663
|
+
* Time picker value normalisation — the substantive contract, as pure logic.
|
|
1664
|
+
*
|
|
1665
|
+
* The value is ALWAYS 24-hour internally, whatever the picker renders. Choosing
|
|
1666
|
+
* "3 PM" reports `hours: 15`; re-opening in 24-hour mode shows `15`. A picker
|
|
1667
|
+
* that kept 12-hour state would drift the moment a consumer persisted it, and
|
|
1668
|
+
* the drift would be invisible until someone compared two records.
|
|
1669
|
+
*
|
|
1670
|
+
* `step` is the minute granularity and must divide 60. A step that does not
|
|
1671
|
+
* falls back to 1 rather than silently producing minutes no control could have
|
|
1672
|
+
* produced — and the reported value is then minute-accurate, so the fallback is
|
|
1673
|
+
* visible in the data rather than hidden in the rendering.
|
|
1674
|
+
*/
|
|
1675
|
+
type TimePickerValue = {
|
|
1676
|
+
/** 0–23. Always 24-hour internally, whatever is rendered. */
|
|
1677
|
+
hours: number;
|
|
1678
|
+
/** 0–59. */
|
|
1679
|
+
minutes: number;
|
|
1680
|
+
};
|
|
1681
|
+
type TimePickerFormat = "12h" | "24h";
|
|
1682
|
+
/** Display-ready parts: localized digits with a localized day period. */
|
|
1683
|
+
type TimeParts = {
|
|
1684
|
+
/** Hours as displayed (no leading zero in 12h mode, two digits in 24h). */
|
|
1685
|
+
hours: string;
|
|
1686
|
+
/** Two-digit minutes. */
|
|
1687
|
+
minutes: string;
|
|
1688
|
+
/** Localized day period in 12h mode (`AM`/`PM` for en-US), else `null`. */
|
|
1689
|
+
period: string | null;
|
|
1690
|
+
};
|
|
1691
|
+
/**
|
|
1692
|
+
* Minute options for a step, as 0-based offsets from the hour.
|
|
1693
|
+
*
|
|
1694
|
+
* A valid step is a POSITIVE INTEGER that divides 60. The integer requirement
|
|
1695
|
+
* matters on its own: 60/1.5 is exactly 40, so a divisibility test alone
|
|
1696
|
+
* accepts 1.5 and yields fractional minutes (0, 1.5, 3.0…) that no picker can
|
|
1697
|
+
* display. Falls back to 1 rather than shipping 18:75.
|
|
1698
|
+
*/
|
|
1699
|
+
declare function minutesForStep(step: number): readonly number[];
|
|
1700
|
+
/** Clamp to a whole hour: hours into 0–23, minutes onto the step grid. */
|
|
1701
|
+
declare function normalizeTimeValue(value: Partial<TimePickerValue> | undefined, step?: number): TimePickerValue;
|
|
1702
|
+
/** The value `format` renders, derived from the 24-hour state. Never stored. */
|
|
1703
|
+
declare function formatTimeValue(value: TimePickerValue, format: TimePickerFormat): {
|
|
1704
|
+
hours: number;
|
|
1705
|
+
period: "AM" | "PM";
|
|
1706
|
+
};
|
|
1707
|
+
/** Convert a 12-hour choice back into the 24-hour state it represents. */
|
|
1708
|
+
declare function toTwentyFourHour(twelveHour: number, period: "AM" | "PM"): number;
|
|
1709
|
+
/**
|
|
1710
|
+
* Display-ready parts for a value: localized digits plus, in 12h mode,
|
|
1711
|
+
* the locale's day-period name (`AM`/`PM` for en-US). The state stays
|
|
1712
|
+
* 24-hour; only the presentation localizes. Never throws.
|
|
1713
|
+
*/
|
|
1714
|
+
declare function formatTimeParts(value: TimePickerValue, format?: TimePickerFormat, locale?: string): TimeParts;
|
|
1715
|
+
/**
|
|
1716
|
+
* Step a value by minutes on the clock: wraps past midnight in both
|
|
1717
|
+
* directions (clock arithmetic, not clamping — stepping is cyclic),
|
|
1718
|
+
* then snaps down onto the step grid (the `normalizeTimeValue`
|
|
1719
|
+
* contract). Negative steps move backward. Never throws.
|
|
1720
|
+
*/
|
|
1721
|
+
declare function addTimeMinutes(value: TimePickerValue | undefined, deltaMinutes: number, step?: number): TimePickerValue;
|
|
1722
|
+
|
|
1723
|
+
/**
|
|
1724
|
+
* The toast queue — ordered open-toast identity with subscribe/notify.
|
|
1725
|
+
*
|
|
1726
|
+
* ## Why this is a primitive, and what it does NOT own
|
|
1727
|
+
*
|
|
1728
|
+
* A single-shot `ToastRoot` (open/duration/action) covers one notification;
|
|
1729
|
+
* an app needs a QUEUE: several toasts alive at once, added from
|
|
1730
|
+
* anywhere, dismissed individually or all at once, painted in mount
|
|
1731
|
+
* order. The QUEUE is shared and belongs here: which toast ids are
|
|
1732
|
+
* open, in what order, and the rule that dismissing removes exactly
|
|
1733
|
+
* one registration. The PAYLOAD is not — message content, action
|
|
1734
|
+
* labels, and durations are caller data held by the rendered
|
|
1735
|
+
* `ToastProvider` (components zone), which owns this queue and maps
|
|
1736
|
+
* each id to its content. So this returns mount state, and the
|
|
1737
|
+
* renderer paints it.
|
|
1738
|
+
*
|
|
1739
|
+
* The `version` counter exists for `useSyncExternalStore`: the
|
|
1740
|
+
* snapshot must be a stable primitive, so subscribers read the
|
|
1741
|
+
* counter (bumped on every mutation) rather than a fresh array.
|
|
1742
|
+
*/
|
|
1743
|
+
type ToastQueue = {
|
|
1744
|
+
subscribe(listener: () => void): () => void;
|
|
1745
|
+
/** Queue a toast id. Re-adding an open id is a no-op — it keeps its original order. */
|
|
1746
|
+
add(id: string): void;
|
|
1747
|
+
/** Dismiss `id`. Unknown ids are a no-op, never an error. */
|
|
1748
|
+
dismiss(id: string): void;
|
|
1749
|
+
/** Dismiss every queued toast. */
|
|
1750
|
+
clear(): void;
|
|
1751
|
+
/** Open ids, oldest-first. */
|
|
1752
|
+
order(): readonly string[];
|
|
1753
|
+
/** Whether `id` is currently queued. */
|
|
1754
|
+
isOpen(id: string): boolean;
|
|
1755
|
+
/** Queued toast count. */
|
|
1756
|
+
count(): number;
|
|
1757
|
+
/** Mutation counter for store snapshots. */
|
|
1758
|
+
version(): number;
|
|
1759
|
+
};
|
|
1760
|
+
/** Create an empty toast queue. Pure — no DOM, no RN. */
|
|
1761
|
+
declare function createToastQueue(): ToastQueue;
|
|
1762
|
+
|
|
1763
|
+
/**
|
|
1764
|
+
* The tool-call model — run state plus the human-approval handshake.
|
|
1765
|
+
*
|
|
1766
|
+
* ## Why this is a primitive
|
|
1767
|
+
*
|
|
1768
|
+
* Tool progress rows on both renderers branch on the same question:
|
|
1769
|
+
* is this call still running, did it settle, did it fail, or is it
|
|
1770
|
+
* waiting on the human. Two spellings of "waiting on the human" is
|
|
1771
|
+
* how one renderer shows an Approve/Deny row while the other shows a
|
|
1772
|
+
* spinner — the approval a safety review demanded, silently missing
|
|
1773
|
+
* on one platform.
|
|
1774
|
+
*
|
|
1775
|
+
* ## What is genuinely shared, and what is not
|
|
1776
|
+
*
|
|
1777
|
+
* The STATE MACHINE is shared: the AI SDK v5 `ToolUIPart` vocabulary
|
|
1778
|
+
* (`input-streaming`, `input-available`, `output-available`,
|
|
1779
|
+
* `output-error`, `output-denied`, `approval-requested`), the
|
|
1780
|
+
* running/error/approval predicates every card reads, and the
|
|
1781
|
+
* approve/deny transitions (approve resumes the run, deny settles it
|
|
1782
|
+
* as denied). The REGISTRY is not — which tools exist and how they
|
|
1783
|
+
* execute is app state; this model tracks one call the transport
|
|
1784
|
+
* owns, never the catalogue. Rendering (header/content/input/output
|
|
1785
|
+
* cards) is styled composition in Kern.
|
|
1786
|
+
*/
|
|
1787
|
+
/**
|
|
1788
|
+
* One tool call's lifecycle, mirroring the AI SDK v5 `ToolUIPart`
|
|
1789
|
+
* states: arguments still streaming in, arguments complete and queued,
|
|
1790
|
+
* output produced, output failed, denied by the human, or paused for
|
|
1791
|
+
* an approval decision.
|
|
1792
|
+
*/
|
|
1793
|
+
type ToolCallState = "input-streaming" | "input-available" | "output-available" | "output-error" | "output-denied" | "approval-requested";
|
|
1794
|
+
/** Arguments flowing or queued: progress UI shows, no result yet. */
|
|
1795
|
+
declare function isToolCallRunning(state: ToolCallState): boolean;
|
|
1796
|
+
/** The call settled with a result the card can render. */
|
|
1797
|
+
declare function isToolCallComplete(state: ToolCallState): boolean;
|
|
1798
|
+
/** The call failed: the card shows `errorText`, never output. */
|
|
1799
|
+
declare function isToolCallError(state: ToolCallState): boolean;
|
|
1800
|
+
/** The run is paused on the human: the card must offer approve/deny. */
|
|
1801
|
+
declare function isToolCallApproval(state: ToolCallState): boolean;
|
|
1802
|
+
/**
|
|
1803
|
+
* The announced label for a call: tool name plus its plain-language
|
|
1804
|
+
* phase, with the error text appended on failure so the announcement
|
|
1805
|
+
* never reports "error" without saying which one.
|
|
1806
|
+
*/
|
|
1807
|
+
declare function resolveToolCallLabel(options: {
|
|
1808
|
+
name: string;
|
|
1809
|
+
state: ToolCallState;
|
|
1810
|
+
errorText?: string;
|
|
1811
|
+
}): string;
|
|
1812
|
+
/**
|
|
1813
|
+
* One call's state, controlled or uncontrolled, with the approval
|
|
1814
|
+
* handshake. `approve` resumes the run (`approval-requested` →
|
|
1815
|
+
* `input-available`) and `deny` settles it (`approval-requested` →
|
|
1816
|
+
* `output-denied`); from any other state both are terminal no-ops —
|
|
1817
|
+
* no transitions, no callbacks — following the `disabled` precedent
|
|
1818
|
+
* that a control which cannot act reports nothing.
|
|
1819
|
+
*/
|
|
1820
|
+
declare function useToolCall(options?: {
|
|
1821
|
+
state?: ToolCallState;
|
|
1822
|
+
defaultState?: ToolCallState;
|
|
1823
|
+
onStateChange?: (state: ToolCallState) => void;
|
|
1824
|
+
onApprove?: () => void;
|
|
1825
|
+
onDeny?: () => void;
|
|
1826
|
+
}): {
|
|
1827
|
+
state: ToolCallState;
|
|
1828
|
+
approve: () => void;
|
|
1829
|
+
deny: () => void;
|
|
1830
|
+
};
|
|
1831
|
+
|
|
1832
|
+
/**
|
|
1833
|
+
* The typeahead model — character-buffer matching over collection items.
|
|
1834
|
+
*
|
|
1835
|
+
* ## Why this is a primitive
|
|
1836
|
+
*
|
|
1837
|
+
* Select, menu, and combobox lists on both renderers answer the same
|
|
1838
|
+
* question when character keys arrive: which item does the typed
|
|
1839
|
+
* buffer point at. The rules that make typeahead feel right —
|
|
1840
|
+
* prefix matching, same-character cycling, buffer expiry — are
|
|
1841
|
+
* identical per renderer, so two spellings drift on exactly the
|
|
1842
|
+
* behavior touch-typists memorize.
|
|
1843
|
+
*
|
|
1844
|
+
* ## What is genuinely shared, and what is not
|
|
1845
|
+
*
|
|
1846
|
+
* The BUFFER and the MATCH are shared: timed buffer accumulation
|
|
1847
|
+
* over `CollectionItem`s, prefix matching, and same-character
|
|
1848
|
+
* cycling with wrap-around. The KEY PLUMBING is not — no Axis list
|
|
1849
|
+
* part receives character key events on touch hardware (there is no
|
|
1850
|
+
* text field to type into), so no rendered part consumes this yet:
|
|
1851
|
+
* the first consumer is hardware-keyboard forwarding (Kern P1),
|
|
1852
|
+
* which reads this model without pulling any renderer. Filtering
|
|
1853
|
+
* while typing (combobox entry, menu filter input) already flows
|
|
1854
|
+
* through the `collection` match; this covers highlight-to-match,
|
|
1855
|
+
* not filter-to-match.
|
|
1856
|
+
*/
|
|
1857
|
+
|
|
1858
|
+
/** The typed buffer: accumulated characters plus the last-key time (ms). */
|
|
1859
|
+
type TypeaheadBuffer = {
|
|
1860
|
+
text: string;
|
|
1861
|
+
/** `Date.now()`-space timestamp of the last appended character. */
|
|
1862
|
+
at: number;
|
|
1863
|
+
};
|
|
1864
|
+
/**
|
|
1865
|
+
* Append `char` to the buffer, restarting it when `now` arrives
|
|
1866
|
+
* later than `timeout` (default `500`) after the previous key.
|
|
1867
|
+
* Non-single inputs are ignored — keys arrive one character at a
|
|
1868
|
+
* time, and pasting a word is not typing.
|
|
1869
|
+
*/
|
|
1870
|
+
declare function appendTypeaheadQuery(previous: TypeaheadBuffer | null, char: string, now: number, timeout?: number): TypeaheadBuffer;
|
|
1871
|
+
/** Whether the buffer has gone stale with no new key by `now`. */
|
|
1872
|
+
declare function isTypeaheadExpired(buffer: TypeaheadBuffer | null, now: number, timeout?: number): boolean;
|
|
1873
|
+
/**
|
|
1874
|
+
* The key the buffer points at: the first item whose text starts
|
|
1875
|
+
* with the buffer (case-insensitive). A buffer of one repeated
|
|
1876
|
+
* character (`"aaa"`) cycles instead — it matches that single
|
|
1877
|
+
* initial, searching forward from `fromKey` (exclusive) with
|
|
1878
|
+
* wrap-around, so repeated presses walk the same-initial items.
|
|
1879
|
+
* An unknown `fromKey` searches from the start.
|
|
1880
|
+
*/
|
|
1881
|
+
declare function matchTypeaheadKey(items: readonly CollectionItem[], query: string, fromKey?: string | null): string | undefined;
|
|
1882
|
+
|
|
1883
|
+
type StateAction<T> = T | ((previous: T | undefined) => T);
|
|
1884
|
+
/** Controlled/uncontrolled state with updater support and mode-change warning. */
|
|
1885
|
+
declare function useControllableState<T>(value: T | undefined, defaultValue: T | undefined, onChange?: (next: T, details?: OverlayOpenChangeDetails) => void): [
|
|
1886
|
+
T | undefined,
|
|
1887
|
+
(next: StateAction<T>, details?: OverlayOpenChangeDetails) => void
|
|
1888
|
+
];
|
|
1889
|
+
|
|
1890
|
+
export { type PromptInputSubmitMode as $, type Align as A, type ChatStatus as B, type CalendarCell as C, type DismissBranches as D, type CollectionItem as E, type FieldErrors as F, type CollectionMatch as G, type DismissPolicy as H, type DismissPolicyOptions as I, type DismissSource as J, type DismissTriggers as K, type DismissWiringOptions as L, type FirstDayOfWeek as M, type FocusTrapModel as N, type OverlayOpenChangeDetails as O, type Placement as P, type IdScope as Q, type RenderProp as R, type SlotProps as S, type NumberFormatSpec as T, type OverlayModalityState as U, type PortalEntry as V, type PortalRegistry as W, type PositioningOptions as X, type PresenceModel as Y, type PressModel as Z, type PressPhase as _, type StateStyle as a, normalizeChatSources as a$, type Rect as a0, type RovingItem as a1, type RovingModel as a2, type RovingOptions as a3, type RovingOrientation as a4, type Selection as a5, type SelectionKey as a6, type SelectionMode as a7, type SliderCommitOptions as a8, type StateAction as a9, formatNumberValue as aA, formatTimeParts as aB, formatTimeValue as aC, getSharedOverlayModality as aD, isAtBottom as aE, isCalendarDateDisabled as aF, isChatBusy as aG, isChatError as aH, isChatStreaming as aI, isEmptyValue as aJ, isSelected as aK, isToolCallApproval as aL, isToolCallComplete as aM, isToolCallError as aN, isToolCallRunning as aO, isTypeaheadExpired as aP, isUserMessage as aQ, leadingBlankCount as aR, matchTypeaheadKey as aS, mergeFormErrors as aT, mergeSlotProps as aU, minutesForStep as aV, nearestSliderThumbIndex as aW, nextBranchIndex as aX, nextPresenceState as aY, nextTrapIndex as aZ, normalizeBranchIndex as a_, type StickToBottomMetrics as aa, type TimeParts as ab, type TimePickerFormat as ac, type TimePickerValue as ad, type ToastQueue as ae, type ToolCallState as af, type TrapMove as ag, type TypeaheadBuffer as ah, addTimeMinutes as ai, appendTypeaheadQuery as aj, buildCalendarGrid as ak, clampNumberValue as al, clampSliderValue as am, composeEventHandlers as an, createDismissPolicy as ao, createIdScope as ap, createOverlayModality as aq, createPortalRegistry as ar, createPresenceModel as as, createToastQueue as at, daysInCalendarMonth as au, defaultCollectionMatch as av, dismissBranchesFor as aw, dismissTriggersFor as ax, filterCollectionKeys as ay, findCollectionText as az, type OverlayId as b, normalizeFieldErrors as b0, normalizeSelection as b1, normalizeSliderValues as b2, normalizeTimeValue as b3, parseCalendarDate as b4, parseCalendarMonth as b5, parseNumberValue as b6, previousBranchIndex as b7, resolveAutofocus as b8, resolveCalendarFocus as b9, sliderValuePercent as bA, snapSliderValue as bB, stepNumberValue as bC, toCalendarDate as bD, toCalendarMonth as bE, toTwentyFourHour as bF, toggleSelection as bG, updateAttachment as bH, useAttachments as bI, useBranch as bJ, useCollectionRegistry as bK, useControllableState as bL, useOverlayModality as bM, useOverlayRegistration as bN, usePresence as bO, usePress as bP, usePromptInput as bQ, useRovingModel as bR, useSelection as bS, useStickToBottom as bT, useToolCall as bU, validateAttachment as bV, resolveCalendarMonthNames as ba, resolveCalendarSelection as bb, resolveCalendarWeekdayNames as bc, resolveCanSend as bd, resolveChatMessageLabel as be, resolveChatMessageSpeaker as bf, resolveChatSourceLabel as bg, resolveComposerHeight as bh, resolveDir as bi, resolveFloatingRect as bj, resolveFloatingSide as bk, resolveLocaleFirstDay as bl, resolvePortalTarget as bm, resolveReasoningOpen as bn, resolveRender as bo, resolveScrollButtonVisible as bp, resolveSliderCommit as bq, resolveStyle as br, resolveToolCallLabel as bs, shiftCalendarMonth as bt, shouldCancelOnEscapeKey as bu, shouldDismissOn as bv, shouldHideBranchPicker as bw, shouldSubmitOnKey as bx, sliderRatioFromLocation as by, sliderValueFromRatio as bz, type OverlayOpenChangeReason as c, type PresenceState as d, type OverlayModalityRegistry as e, type SliderOrientation as f, type A11yDir as g, type AttachmentError as h, type AttachmentLimits as i, type AttachmentState as j, type AutofocusEvent as k, type CalendarDateParts as l, type CalendarDirection as m, type CalendarGrid as n, type CalendarGridOptions as o, type CalendarKey as p, type CalendarMonthNameStyle as q, type CalendarMonthParts as r, type CalendarRange as s, type CalendarRangePosition as t, type CalendarSelection as u, type CalendarSelectionMode as v, type ChatAttachment as w, type ChatMessage as x, type ChatRole as y, type ChatSource as z };
|