@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.
@@ -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 };