vireuikit 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,388 @@
1
- export { A as A11Y_NONE, a as A11yMode, b as A11yOverride, P as Presentation, S as Surface, p as presentationFor, r as resolveA11y, s as sameA11y } from './a11y-D3SS74I3.js';
2
- import { VireGlassMaterial, VireGlassGeometry, VireGlassMorph } from 'vireglass';
1
+ import { S as Size, R as Rect, A as AdaptiveContext, I as InputLayers } from './context-DQaAcRq-.js';
2
+ export { a as A11Y_NONE, b as A11yMode, c as A11yOverride, F as Fold, H as HalfOpenOptions, d as INPUT_MIXED, e as INPUT_POINTER, f as INPUT_TABLET_KEYBOARD, g as INPUT_TOUCH, P as POSTURE_FLAT, h as Posture, i as Presentation, j as Surface, k as adaptiveContext, l as halfOpen, m as intersectRect, p as postureFromSegments, n as presentationFor, r as rectContains, o as resolveA11y, s as sameA11y, q as segmentOf, t as surfaceRegion, w as windowSegments } from './context-DQaAcRq-.js';
3
+ import { VireGlassGeometry, VireGlassMorph, VireGlassMaterial } from 'vireglass';
4
+
5
+ type WidthClass = 'compact' | 'medium' | 'expanded' | 'large' | 'xlarge';
6
+ type HeightClass = 'compact' | 'medium' | 'expanded';
7
+ declare const WIDTH_CLASSES: readonly WidthClass[];
8
+ declare const HEIGHT_CLASSES: readonly HeightClass[];
9
+ /** The lower bound of each class in dp; compact starts at 0. */
10
+ declare const WIDTH_BREAKPOINTS: {
11
+ readonly medium: 600;
12
+ readonly expanded: 840;
13
+ readonly large: 1200;
14
+ readonly xlarge: 1600;
15
+ };
16
+ declare const HEIGHT_BREAKPOINTS: {
17
+ readonly medium: 480;
18
+ readonly expanded: 900;
19
+ };
20
+ type WindowClass = {
21
+ width: WidthClass;
22
+ height: HeightClass;
23
+ };
24
+ declare function widthClass(width: number): WidthClass;
25
+ declare function heightClass(height: number): HeightClass;
26
+ declare function classifyWindow(size: Size): WindowClass;
27
+ declare function widthAtLeast(value: WidthClass, minimum: WidthClass): boolean;
28
+
29
+ /** A spring with `response`-second settle time; `damping` < 1 overshoots. `instant` skips
30
+ * integration entirely, for reduced motion. */
31
+ type Spring = {
32
+ x: number;
33
+ v: number;
34
+ target: number;
35
+ };
36
+ declare function stepSpring(s: Spring, dt: number, response: number, damping: number, instant?: boolean): boolean;
37
+ declare const smoothstep: (e0: number, e1: number, x: number) => number;
38
+ type GrowthPoint = {
39
+ x: number;
40
+ y: number;
41
+ };
42
+ /** A shape and its position — it has no role of its own; `growthFrame` assigns the role (body, or
43
+ * what the body is merging into). */
44
+ type GrowthShape = {
45
+ geometry: VireGlassGeometry;
46
+ center: GrowthPoint;
47
+ };
48
+ /** `shrink` — capsule → drop, no overshoot. `grow` — drop → target; may exceed 1 for the
49
+ * overshoot. Both are read as-is; `stepGrowth` owns the bounds and the handoff between them. */
50
+ type GrowthPhase = {
51
+ shrink: number;
52
+ grow: number;
53
+ };
54
+ type GrowthInk = {
55
+ /** 0…1: how much of the source's ink is still visible (1) vs. already defocused into the drop (0). */
56
+ outgoing: number;
57
+ /** 0…1: how much of the target's ink has come into focus (0 = not yet, 1 = fully). */
58
+ incoming: number;
59
+ };
60
+ type GrowthFrame = {
61
+ /** The shape worth drawing as glass right now: the larger of the two, which supplies the
62
+ * thickness and the bevel. */
63
+ body: GrowthShape;
64
+ /** The target shape as-is, regardless of role — it drives the response to finger/light while
65
+ * the glass hasn't become it yet (while `body` is still the drop). */
66
+ grown: GrowthShape;
67
+ /** The second merge shape, ready for `toLensProps`/`toSurfaceUniforms`; `undefined` while the
68
+ * neck hasn't grown yet or has already retracted. */
69
+ merge: VireGlassMorph | undefined;
70
+ ink: GrowthInk;
71
+ };
72
+ declare function growthInk(phase: GrowthPhase): GrowthInk;
73
+ /** `anchor` is the corner shared by source and target: the body pins to it, otherwise the target
74
+ * would grow out of the drop's center instead of the button's corner. */
75
+ declare function growthFrame(source: GrowthShape, target: GrowthShape, anchor: GrowthPoint, phase: GrowthPhase): GrowthFrame;
76
+ type GrowthState = {
77
+ shrink: Spring;
78
+ grow: Spring;
79
+ };
80
+ declare const createGrowthState: () => GrowthState;
81
+ declare const growthPhase: (state: GrowthState) => GrowthPhase;
82
+ /** Durations are client-specific: the default is fitted to the menu growing out of its toolbar at
83
+ * M 5:11 (docs/acceptance.md, N9), and an atom at a different scale (an icon opening into a sheet)
84
+ * isn't obligated to match it. The shape of the transition is shared regardless. */
85
+ type GrowthTiming = {
86
+ shrinkOpen: number;
87
+ shrinkClose: number;
88
+ growOpen: number;
89
+ growClose: number;
90
+ /** < 1 gives the target a slight overshoot on open; closing never overshoots. */
91
+ growDampingOpen: number;
92
+ };
93
+ declare const DEFAULT_GROWTH_TIMING: GrowthTiming;
94
+ /** Growth starts once the drop has gathered, and the reverse on close. `reduceMotion` turns off
95
+ * the spring entirely: both phases jump straight to their target in one step, with no overshoot
96
+ * and no defocus phase. */
97
+ declare function stepGrowth(state: GrowthState, dt: number, open: boolean, reduceMotion?: boolean, timing?: GrowthTiming): boolean;
98
+
99
+ type MorphBody = 'navigation' | 'surface' | 'panes' | 'supporting' | 'accessory' | 'toolbar';
100
+ type FormChange = {
101
+ body: MorphBody;
102
+ from: string;
103
+ to: string;
104
+ };
105
+ type FormMorphDescriptor = FormChange & {
106
+ /**
107
+ * `reshape`: the one body changes its box and its content refocuses. `appear` and `disappear`: the body comes and goes by
108
+ * modulating its lens, never by fading. `retract`: its anchor is gone, so it runs back into the anchor's new owner and
109
+ * closes. `none`: nothing moves.
110
+ */
111
+ mode: 'none' | 'reshape' | 'appear' | 'disappear' | 'retract';
112
+ /** An open surface stays open through a change of form. */
113
+ staysOpen: boolean;
114
+ instant: boolean;
115
+ };
116
+ declare function formMorph(change: FormChange, ctx: Pick<AdaptiveContext, 'reduceMotion'>, options?: {
117
+ anchorGone?: boolean;
118
+ }): FormMorphDescriptor;
119
+ /** The menu's opening response, so a class change moves at the pace of the growth; critically damped, because a
120
+ * layout that overshoots would bounce off the window's edge. */
121
+ declare const FORM_MORPH_TIMING: {
122
+ readonly response: number;
123
+ readonly damping: 1;
124
+ };
125
+ /** 0 is the form it comes from, 1 the form it goes to. */
126
+ type FormMorphState = {
127
+ t: Spring;
128
+ fromInk?: number;
129
+ };
130
+ declare const createFormMorph: () => FormMorphState;
131
+ /** Steps toward a form; true while still moving. Called with the other end mid-way, it turns back from where it is. */
132
+ declare function stepFormMorph(state: FormMorphState, dt: number, toward: 0 | 1, instant?: boolean): boolean;
133
+ type MorphBox = Rect & {
134
+ radius: number;
135
+ };
136
+ type FormMorphFrame = {
137
+ box: MorphBox;
138
+ /** The content defocuses out of the old form and into the new one; both are 0 around the middle. */
139
+ ink: {
140
+ outgoing: number;
141
+ incoming: number;
142
+ };
143
+ /** Which form's content shows. */
144
+ showing: 'from' | 'to';
145
+ };
146
+ declare function formMorphFrame(from: MorphBox, to: MorphBox, state: FormMorphState): FormMorphFrame;
147
+ /**
148
+ * A third form while a morph is under way: the box it is in now becomes the new start and the content showing now
149
+ * leaves from the ink it has, so nothing jumps. A rebased start is a moment, not a form; the morph only goes on from it.
150
+ */
151
+ declare function rebaseFormMorph(state: FormMorphState): void;
152
+
153
+ type ControlSize = 'touch' | 'small' | 'medium' | 'large' | 'xlarge';
154
+ declare const CONTROL_SIZES: readonly ControlSize[];
155
+ type SizeSpec = {
156
+ /** Body height; an icon button is a square of this side. */
157
+ height: number;
158
+ paddingX: number;
159
+ icon: number;
160
+ radius: number;
161
+ shape: 'capsule' | 'rounded';
162
+ };
163
+ declare const MIN_TOUCH_TARGET_DP = 44;
164
+ declare const MIN_POINTER_TARGET_DP = 24;
165
+ declare const BUTTON_SIZES: Readonly<Record<ControlSize, SizeSpec>>;
166
+ declare function iconButtonSize(size: ControlSize): SizeSpec & {
167
+ width: number;
168
+ };
169
+ type MenuSizeSpec = {
170
+ /** Row height; the glyph sits centred in a square of it. */
171
+ row: number;
172
+ padding: number;
173
+ /** The active item's highlight. */
174
+ itemRadius: number;
175
+ /** Concentric with the highlight: its radius plus the padding. */
176
+ radius: number;
177
+ icon: number;
178
+ /** The label's inset in an item without a glyph. */
179
+ labelPad: number;
180
+ };
181
+ declare function menuSize(size: ControlSize): MenuSizeSpec;
182
+ declare function nestedRadius(outerRadius: number, inset: number, minimum?: number): number;
183
+ declare function defaultSize(input: {
184
+ touch: boolean;
185
+ }): ControlSize;
186
+ declare function hitSlop(bodyDp: number, minTargetDp: number): number;
187
+ declare function minTarget(input: {
188
+ touch: boolean;
189
+ }): number;
190
+
191
+ /** Layout density, for the whole window: the primary pointer. */
192
+ declare function sizeTier(input: Pick<InputLayers, 'primary'>): ControlSize;
193
+ /** The hit area's floor in dp: any coarse input present. The body keeps its size. */
194
+ declare function hitTarget(input: Pick<InputLayers, 'anyCoarse'>): number;
195
+ /** Hover is the event's: a mouse or a pen over the control hovers, a finger never does. */
196
+ declare function hoversWith(pointerType: string): boolean;
197
+
198
+ type NavigationSpec = {
199
+ /** Destinations shown on every form. */
200
+ primary: number;
201
+ /** Destinations shown only in the sidebar and in a rail expanded over the content. */
202
+ secondary?: number;
203
+ search?: boolean;
204
+ /** The person collapsed the sidebar into a rail; the choice is remembered. */
205
+ sidebarCollapsed?: boolean;
206
+ /** Panes the content shows; two panes on a medium window take the bar. */
207
+ panes?: number;
208
+ };
209
+ type NavigationFormKind = 'bar' | 'rail' | 'sidebar';
210
+ type SearchForm = {
211
+ /** A button at the bar's trailing end, an item of the rail, or a persistent field. */
212
+ form: 'button' | 'item' | 'field';
213
+ place: 'bar' | 'rail' | 'sidebar' | 'toolbar';
214
+ /** A keyboard shortcut focuses it. */
215
+ shortcut: boolean;
216
+ };
217
+ type NavigationForm = {
218
+ form: NavigationFormKind;
219
+ edge: 'bottom' | 'leading';
220
+ /** The same primary destinations on every form, so the body that changes form keeps its items. */
221
+ primary: number;
222
+ /** The secondary destinations plus those the bar has no slot for. */
223
+ secondary: number;
224
+ secondaryVisible: boolean;
225
+ search: SearchForm | null;
226
+ /** A rail that opens wider: over the content on an expanded window, beside it when the person collapsed the sidebar. */
227
+ expandable: 'over' | 'beside' | null;
228
+ /** A sidebar the person can collapse into a rail. */
229
+ collapsible: boolean;
230
+ };
231
+ /** Slots on the bar, the search button included. There is no "More" slot. */
232
+ declare const BAR_SLOTS = 5;
233
+ declare function navigationForm(ctx: AdaptiveContext, spec: NavigationSpec): NavigationForm;
234
+ type BottomAccessoryForm = {
235
+ /** Above the bar; in the bar's row once it has minimised; along the bottom of the content beside a rail or sidebar. */
236
+ placement: 'above-bar' | 'in-bar' | 'content-bottom';
237
+ /** In the bar's row only the transport fits. */
238
+ compact: boolean;
239
+ };
240
+ declare function bottomAccessory(nav: Pick<NavigationForm, 'form'>, state?: {
241
+ barMinimized?: boolean;
242
+ }): BottomAccessoryForm;
243
+
244
+ /** The fixed list pane beside the detail (Material 3 list-detail). */
245
+ declare const LIST_PANE_WIDTH: {
246
+ readonly expanded: 360;
247
+ readonly large: 412;
248
+ };
249
+ declare const SUPPORTING_PANE_MAX_WIDTH = 400;
250
+ type ListDetailState = {
251
+ selected: boolean;
252
+ /** The pane the person is on while one pane shows; the detail needs a selection. Set it to `detail` on a selection. */
253
+ showing: 'list' | 'detail';
254
+ /** Sparse content may split a medium window in half. */
255
+ sparse?: boolean;
256
+ };
257
+ type PaneSplit = {
258
+ kind: 'fixed';
259
+ list: number;
260
+ } | {
261
+ kind: 'half';
262
+ }
263
+ /** Book posture: the fold is the spacer between the panes. */
264
+ | {
265
+ kind: 'fold';
266
+ gap: Rect;
267
+ };
268
+ type PaneLayout = {
269
+ panes: 1 | 2;
270
+ visible: readonly ('list' | 'detail')[];
271
+ split: PaneSplit | null;
272
+ /** Two panes and nothing selected: the detail shows its empty state. */
273
+ placeholder: boolean;
274
+ /** Back exists only where the detail replaces the list. */
275
+ back: boolean;
276
+ /** The selection is drawn in the list only while both panes show. */
277
+ selectionShown: boolean;
278
+ };
279
+ declare function paneLayout(ctx: AdaptiveContext, state: ListDetailState): PaneLayout;
280
+ type SupportingPaneState = {
281
+ /** The person keeps it beside the content; remembered across classes, so it returns when the window widens again. */
282
+ pinned: boolean;
283
+ /** Shown on demand; over the content on an expanded window. */
284
+ open: boolean;
285
+ };
286
+ type SupportingPaneForm = {
287
+ form: 'beside' | 'over' | 'hidden';
288
+ edge: 'trailing';
289
+ maxWidth: number;
290
+ /** Over the content it dims what it covers, like a modal side sheet. */
291
+ scrim: boolean;
292
+ remembered: boolean;
293
+ };
294
+ declare function supportingPane(ctx: AdaptiveContext, state: SupportingPaneState): SupportingPaneForm;
295
+ type PostureSplit = {
296
+ kind: 'single';
297
+ }
298
+ /** Tabletop: what is watched above the fold, its controls below. Book: the leading half and the trailing one. */
299
+ | {
300
+ kind: 'split';
301
+ axis: 'horizontal' | 'vertical';
302
+ primary: Rect;
303
+ secondary: Rect;
304
+ };
305
+ declare function postureSplit(ctx: AdaptiveContext): PostureSplit;
306
+
307
+ /**
308
+ * `menu`: closes after one choice and grows out of its trigger on every class. `picker`: a long choice with a search,
309
+ * which is not a menu but a sheet. `popover`: several picks, stays open. `sheet`: supplementary content beside a
310
+ * parallel task. `modal-sheet`: a modal task with a form. `alert`: a dialog without fields. `dialog`: one with fields.
311
+ */
312
+ type SurfaceKind = 'menu' | 'picker' | 'popover' | 'sheet' | 'modal-sheet' | 'alert' | 'dialog';
313
+ declare const SURFACE_KINDS: readonly SurfaceKind[];
314
+ type PresentationFormKind = 'grown' | 'popover' | 'bottom-sheet' | 'centered-sheet' | 'side-sheet' | 'side-pane' | 'full-screen' | 'basic-dialog' | 'alert';
315
+ type PresentationForm = {
316
+ form: PresentationFormKind;
317
+ /** It grows out of its source (and runs back into it). */
318
+ fromSource: boolean;
319
+ /** What lies behind is dimmed and inert. */
320
+ scrim: boolean;
321
+ /** It narrows the main area instead of covering it. */
322
+ compresses: boolean;
323
+ /** Its widest extent in dp; null where it fills its region. */
324
+ maxWidth: number | null;
325
+ /** Where it lives: its source's segment, or the largest; side sheets in book posture take the trailing one. */
326
+ region: Rect;
327
+ };
328
+ /** A bottom sheet on a medium window stops growing here and centres (Material 3 bottom sheets). */
329
+ declare const BOTTOM_SHEET_MAX_WIDTH = 640;
330
+ /** A side sheet, and the third pane it becomes on an xlarge window. */
331
+ declare const SIDE_SHEET_MAX_WIDTH = 400;
332
+ /** A centred sheet and a basic dialog (Material 3 dialogs). */
333
+ declare const DIALOG_MAX_WIDTH = 560;
334
+ declare function presentationForm(kind: SurfaceKind, ctx: AdaptiveContext, source?: Rect): PresentationForm;
335
+
336
+ type ToolbarItem = {
337
+ id: string;
338
+ kind: 'icon' | 'text';
339
+ /** Higher stays visible longer; 0 by default. */
340
+ priority?: number;
341
+ /** A status on the item: it stays visible at any width. */
342
+ badge?: boolean;
343
+ /** The one tinted main action. It goes to the trailing end and never overflows. */
344
+ primary?: boolean;
345
+ /** Items of one group merge into one capsule; without it, neighbours of a kind share a group. */
346
+ group?: string;
347
+ /** A text item's width in dp, measured by the target; three tier heights by default. An icon is the tier's square. */
348
+ width?: number;
349
+ };
350
+ type ToolbarOrientation = 'horizontal' | 'vertical';
351
+ type ToolbarOptions = {
352
+ /** The container's extent along the toolbar, dp. */
353
+ available: number;
354
+ tier: ControlSize;
355
+ orientation?: ToolbarOrientation;
356
+ maxGroups?: number;
357
+ gap?: number;
358
+ };
359
+ type ToolbarCapsule = {
360
+ kind: 'icon' | 'text' | 'more' | 'primary';
361
+ items: readonly string[];
362
+ extent: number;
363
+ };
364
+ type ToolbarLayout = {
365
+ orientation: ToolbarOrientation;
366
+ stage: 'separate' | 'merged' | 'overflow';
367
+ capsules: readonly ToolbarCapsule[];
368
+ /** In "…", in their order. "…" exists only when this is not empty. */
369
+ overflow: readonly string[];
370
+ /** Text items of a vertical toolbar: text does not stand vertical, so they stay in a horizontal row. */
371
+ aside: readonly string[];
372
+ extent: number;
373
+ /** False only when what may not overflow (status, the main action) is wider than the container. */
374
+ fits: boolean;
375
+ };
376
+ declare const TOOLBAR_GAP = 8;
377
+ declare const MORE_ID = "\u2026";
378
+ type ToolbarPlacement = {
379
+ edge: 'bottom' | 'top' | 'trailing';
380
+ orientation: ToolbarOrientation;
381
+ maxGroups: number;
382
+ };
383
+ /** The window decides where a toolbar sits; its layout then follows its own container. */
384
+ declare function toolbarPlacement(ctx: AdaptiveContext): ToolbarPlacement;
385
+ declare function toolbarLayout(items: readonly ToolbarItem[], options: ToolbarOptions): ToolbarLayout;
3
386
 
4
387
  type VireUIKitAtomKind = 'button' | 'iconButton' | 'switch' | 'slider' | 'segment' | 'chip' | 'field' | 'menu';
5
388
  /** Hover and focus don't exist everywhere: touch has neither. The atom accepts them, but has to
@@ -103,76 +486,6 @@ type ControlTargets = {
103
486
  };
104
487
  declare function controlTargets(state: ControlState): ControlTargets;
105
488
 
106
- /** A spring with `response`-second settle time; `damping` < 1 overshoots. `instant` skips
107
- * integration entirely, for reduced motion. */
108
- type Spring = {
109
- x: number;
110
- v: number;
111
- target: number;
112
- };
113
- declare function stepSpring(s: Spring, dt: number, response: number, damping: number, instant?: boolean): boolean;
114
- declare const smoothstep: (e0: number, e1: number, x: number) => number;
115
- type GrowthPoint = {
116
- x: number;
117
- y: number;
118
- };
119
- /** A shape and its position — it has no role of its own; `growthFrame` assigns the role (body, or
120
- * what the body is merging into). */
121
- type GrowthShape = {
122
- geometry: VireGlassGeometry;
123
- center: GrowthPoint;
124
- };
125
- /** `shrink` — capsule → drop, no overshoot. `grow` — drop → target; may exceed 1 for the
126
- * overshoot. Both are read as-is; `stepGrowth` owns the bounds and the handoff between them. */
127
- type GrowthPhase = {
128
- shrink: number;
129
- grow: number;
130
- };
131
- type GrowthInk = {
132
- /** 0…1: how much of the source's ink is still visible (1) vs. already defocused into the drop (0). */
133
- outgoing: number;
134
- /** 0…1: how much of the target's ink has come into focus (0 = not yet, 1 = fully). */
135
- incoming: number;
136
- };
137
- type GrowthFrame = {
138
- /** The shape worth drawing as glass right now: the larger of the two, which supplies the
139
- * thickness and the bevel. */
140
- body: GrowthShape;
141
- /** The target shape as-is, regardless of role — it drives the response to finger/light while
142
- * the glass hasn't become it yet (while `body` is still the drop). */
143
- grown: GrowthShape;
144
- /** The second merge shape, ready for `toLensProps`/`toSurfaceUniforms`; `undefined` while the
145
- * neck hasn't grown yet or has already retracted. */
146
- merge: VireGlassMorph | undefined;
147
- ink: GrowthInk;
148
- };
149
- declare function growthInk(phase: GrowthPhase): GrowthInk;
150
- /** `anchor` is the corner shared by source and target: the body pins to it, otherwise the target
151
- * would grow out of the drop's center instead of the button's corner. */
152
- declare function growthFrame(source: GrowthShape, target: GrowthShape, anchor: GrowthPoint, phase: GrowthPhase): GrowthFrame;
153
- type GrowthState = {
154
- shrink: Spring;
155
- grow: Spring;
156
- };
157
- declare const createGrowthState: () => GrowthState;
158
- declare const growthPhase: (state: GrowthState) => GrowthPhase;
159
- /** Durations are client-specific: the default is fitted to the menu growing out of its toolbar at
160
- * M 5:11 (docs/acceptance.md, N9), and an atom at a different scale (an icon opening into a sheet)
161
- * isn't obligated to match it. The shape of the transition is shared regardless. */
162
- type GrowthTiming = {
163
- shrinkOpen: number;
164
- shrinkClose: number;
165
- growOpen: number;
166
- growClose: number;
167
- /** < 1 gives the target a slight overshoot on open; closing never overshoots. */
168
- growDampingOpen: number;
169
- };
170
- declare const DEFAULT_GROWTH_TIMING: GrowthTiming;
171
- /** Growth starts once the drop has gathered, and the reverse on close. `reduceMotion` turns off
172
- * the spring entirely: both phases jump straight to their target in one step, with no overshoot
173
- * and no defocus phase. */
174
- declare function stepGrowth(state: GrowthState, dt: number, open: boolean, reduceMotion?: boolean, timing?: GrowthTiming): boolean;
175
-
176
489
  type ControlVisual = {
177
490
  pressed: Spring;
178
491
  hovered: Spring;
@@ -242,16 +555,6 @@ type MenuStep = {
242
555
  };
243
556
  declare const TYPEAHEAD_SECONDS = 0.5;
244
557
  declare function stepMenu(state: MenuState, event: MenuEvent, items: readonly MenuItem[]): MenuStep;
245
- type Rect = {
246
- x: number;
247
- y: number;
248
- width: number;
249
- height: number;
250
- };
251
- type Size = {
252
- width: number;
253
- height: number;
254
- };
255
558
  /** -1 is left or top, 1 is right or bottom. */
256
559
  type MenuCorner = {
257
560
  x: -1 | 1;
@@ -281,13 +584,20 @@ type MenuPlacement = {
281
584
  /** The menu's corner on the pinned side: where its growth ends. */
282
585
  anchor: GrowthPoint;
283
586
  };
284
- /** The trigger's corner nearest the viewport corner it sits in: the open space lies away from it. */
285
- declare function menuCorner(trigger: Rect, viewport: Size): MenuCorner;
587
+ /** Where a menu must stay: its trigger's segment of the window, the whole viewport when nothing folds it. A size
588
+ * alone is a region at the origin. */
589
+ type MenuRegion = Size & {
590
+ x?: number;
591
+ y?: number;
592
+ };
593
+ /** The trigger's corner nearest the corner of its region it sits in: the open space lies away from it. */
594
+ declare function menuCorner(trigger: Rect, region: MenuRegion): MenuCorner;
286
595
  /**
287
- * The menu shares a corner with its trigger and covers it. An automatic corner flips on an axis
288
- * where the menu would leave the viewport and the other side fits; then it slides in.
596
+ * The menu shares a corner with its trigger and covers it. An automatic corner flips on an axis where the menu would
597
+ * leave its region and the other side fits; then it slides in. The region is the trigger's segment, so a menu never
598
+ * crosses a fold.
289
599
  */
290
- declare function menuPlacement(trigger: Rect, menu: Size, viewport: Size, options?: {
600
+ declare function menuPlacement(trigger: Rect, menu: Size, region: MenuRegion, options?: {
291
601
  corner?: MenuCorner | undefined;
292
602
  margin?: number;
293
603
  }): MenuPlacement;
@@ -309,7 +619,9 @@ type MenuSurfaceFrame = {
309
619
  activity: number;
310
620
  };
311
621
  declare function menuActivity(phase: GrowthPhase): number;
312
- declare function menuSurfaceFrame(trigger: RoundedRect, menu: RoundedRect, anchor: GrowthPoint, phase: GrowthPhase): MenuSurfaceFrame;
622
+ /** `bounds`, the menu's region, holds every frame inside it: the overshoot runs away from the anchor and could
623
+ * otherwise reach over a fold for a few frames. */
624
+ declare function menuSurfaceFrame(trigger: RoundedRect, menu: RoundedRect, anchor: GrowthPoint, phase: GrowthPhase, bounds?: Rect): MenuSurfaceFrame;
313
625
  /** A menu is a larger piece of the same glass, and a larger piece scatters light more softly (M 7:19): it
314
626
  * takes the core's sheet scattering, which turns text under it into soft spots without turning it to mush. */
315
627
  declare function menuGlass(base: VireGlassMaterial): VireGlassMaterial;
@@ -331,6 +643,190 @@ declare const MENU_ITEM_FILL: {
331
643
  };
332
644
  declare function menuItemFill(state: MenuItemState): number;
333
645
 
646
+ declare const NAV_SIZES: {
647
+ readonly bar: {
648
+ readonly height: 56;
649
+ readonly item: 74;
650
+ readonly minItem: 44;
651
+ readonly inset: 3;
652
+ readonly margin: 16;
653
+ readonly searchGap: 8;
654
+ readonly minimized: 44;
655
+ };
656
+ readonly rail: {
657
+ readonly width: 80;
658
+ readonly item: 56;
659
+ readonly gap: 4;
660
+ readonly padding: 8;
661
+ readonly margin: 8;
662
+ readonly head: 48;
663
+ };
664
+ readonly sidebar: {
665
+ readonly width: 280;
666
+ readonly row: {
667
+ readonly touch: 44;
668
+ readonly pointer: 36;
669
+ };
670
+ readonly padding: 8;
671
+ readonly margin: 8;
672
+ readonly head: 48;
673
+ readonly separator: 12;
674
+ };
675
+ readonly accessory: {
676
+ readonly height: 48;
677
+ readonly gap: 8;
678
+ readonly inline: 44;
679
+ readonly content: 56;
680
+ readonly margin: 8;
681
+ };
682
+ readonly lens: {
683
+ readonly inflate: 6;
684
+ };
685
+ readonly toggle: 44;
686
+ };
687
+ type NavLayoutInput = {
688
+ form: NavigationFormKind;
689
+ /** The bar as a circle with the search circle; ignored on the other forms. */
690
+ minimized?: boolean;
691
+ window: Size;
692
+ /** Primary destinations shown. */
693
+ items: number;
694
+ /** Rows shown only in the sidebar. */
695
+ secondary: number;
696
+ search: SearchForm | null;
697
+ /** Index into the primary destinations, then the secondary; -1 for none. */
698
+ selected: number;
699
+ tier: 'touch' | 'pointer';
700
+ /** The rail's or sidebar's head holds a toggle. */
701
+ toggle: boolean;
702
+ accessory?: boolean;
703
+ /** The safe area at the bottom. */
704
+ bottomInset?: number;
705
+ };
706
+ type NavLayout = {
707
+ form: NavigationFormKind;
708
+ minimized: boolean;
709
+ body: MorphBox;
710
+ /** Primary, then secondary; the bar has only primary. A minimised bar's items all lie on its circle. */
711
+ items: MorphBox[];
712
+ pill: MorphBox | null;
713
+ /** The bar's separate circle, or the search item (rail) or field (sidebar) inside the body. */
714
+ search: MorphBox | null;
715
+ accessory: MorphBox | null;
716
+ /** The toggle in the head of the rail or sidebar; null where the head holds none. */
717
+ toggle: MorphBox | null;
718
+ accessoryPlacement: BottomAccessoryForm['placement'] | null;
719
+ /** What the content must clear. */
720
+ insets: {
721
+ bottom: number;
722
+ start: number;
723
+ };
724
+ };
725
+ declare function navLayout(input: NavLayoutInput): NavLayout;
726
+
727
+ type NavDragState = {
728
+ active: boolean;
729
+ /** The lens's centre. */
730
+ x: Spring;
731
+ lift: Spring;
732
+ /** 0..MAX_STRETCH: how much wider (and half as much lower) the lens is for the pointer's speed. */
733
+ stretch: number;
734
+ /** The item under the lens. */
735
+ index: number;
736
+ lastX: number;
737
+ };
738
+ declare const createNavDrag: () => NavDragState;
739
+ /** Bar form only; elsewhere a press is just a press. */
740
+ declare function pressNavDrag(state: NavDragState, layout: NavLayout, pointerX: number): void;
741
+ declare function moveNavDrag(state: NavDragState, layout: NavLayout, pointerX: number, dt: number): void;
742
+ /** The nearest item; the lens springs to it and its lift falls to 0. Where the layout is not a bar the press just ends. */
743
+ declare function releaseNavDrag(state: NavDragState, layout: NavLayout): number;
744
+ /** A cancelled press settles back where it was. */
745
+ declare function cancelNavDrag(state: NavDragState, layout: NavLayout, selected: number): void;
746
+ /** True while the lens is held or still moving. */
747
+ declare function stepNavDrag(state: NavDragState, dt: number, reduceMotion?: boolean): boolean;
748
+ declare function navDragFrame(state: NavDragState, layout: NavLayout): {
749
+ lens: MorphBox;
750
+ lift: number;
751
+ hover: number;
752
+ } | null;
753
+
754
+ /**
755
+ * The model's answer held to one form, for a host that knows its layout or opens the sidebar over the content: the
756
+ * destinations stay the same, the search takes the form's own shape and the toggle follows the form.
757
+ */
758
+ declare function fixedNavigationForm(nav: NavigationForm, form: NavigationFormKind): NavigationForm;
759
+
760
+ declare const MINIMIZE_THRESHOLD = 24;
761
+ /** A step against the run shorter than this is counted into it, not taken for a reversal. */
762
+ declare const MINIMIZE_JITTER = 4;
763
+ type MinimizeState = {
764
+ minimized: boolean;
765
+ run: number;
766
+ last: number;
767
+ };
768
+ declare const createMinimize: (scrollTop?: number) => MinimizeState;
769
+ /** Feeds the scroller's position; returns whether the bar is minimised. */
770
+ declare function stepMinimize(state: MinimizeState, scrollTop: number): boolean;
771
+ /** The scroller's position as the bar should see it: a bounce past either end is the end. */
772
+ declare const clampScrollTop: (scrollTop: number, scrollHeight: number, clientHeight: number) => number;
773
+ /** A tap or focus expands the bar and starts the run over. */
774
+ declare function expandMinimize(state: MinimizeState): void;
775
+
776
+ /** Critically damped, fitted so the spring passes 95 % at about 0.8 s (M 8:36.2-8:37.0). */
777
+ declare const NAV_MORPH_TIMING: {
778
+ readonly response: 1.06;
779
+ readonly damping: 1;
780
+ };
781
+ /** The bar contracting to its minimised circle and back moves at the pace of any other form change. */
782
+ declare const NAV_MINIMIZE_TIMING: {
783
+ readonly response: number;
784
+ readonly damping: 1;
785
+ };
786
+ type NavMorphPhase = 'rest' | 'lift' | 'contract' | 'travel' | 'grow' | 'settle';
787
+ type NavMorphFrame = {
788
+ phase: NavMorphPhase;
789
+ body: MorphBox;
790
+ /** The bar's search circle as the body's second shape; smoothing 0 once separate. */
791
+ second: {
792
+ box: MorphBox;
793
+ smoothing: number;
794
+ } | null;
795
+ /** The selection's lens; null at rest. `lift` 0..1 is its glass presence and magnification. */
796
+ lens: {
797
+ box: MorphBox;
798
+ lift: number;
799
+ } | null;
800
+ ink: {
801
+ outgoing: number;
802
+ incoming: number;
803
+ };
804
+ showing: 'from' | 'to';
805
+ };
806
+ declare function navMorphFrame(from: NavLayout, to: NavLayout, t: number, via?: 'lens' | 'contract'): NavMorphFrame;
807
+ type NavMorphState = {
808
+ /** 0 is the form it comes from, 1 the form it goes to. */
809
+ t: Spring;
810
+ from: NavLayout;
811
+ to: NavLayout;
812
+ via: 'lens' | 'contract';
813
+ /** A frozen frame the morph goes on from; see `pivotAt`. */
814
+ pivot: NavMorphFrame | null;
815
+ /** Where `t` stood when a third form froze the lens mid-travel; 0 when the pivot is a new start (t was reset). */
816
+ pivotAt: number;
817
+ };
818
+ /** Resting at `at`. */
819
+ declare const createNavMorph: (at: NavLayout) => NavMorphState;
820
+ declare function currentNavFrame(state: NavMorphState): NavMorphFrame;
821
+ /**
822
+ * Aims the morph at a layout. At rest it starts a new morph. Aimed back at the layout it came from it turns back.
823
+ * A third form before the lens reaches its place swaps the target (freezing the lens if it is already travelling);
824
+ * once the body grows, the frame it is in becomes the start of a new morph that contracts again.
825
+ */
826
+ declare function aimNavMorph(state: NavMorphState, next: NavLayout, via?: 'lens' | 'contract'): void;
827
+ /** True while moving. Reduced motion lands in one step. */
828
+ declare function stepNavMorph(state: NavMorphState, dt: number, reduceMotion?: boolean): boolean;
829
+
334
830
  type PressResponse = {
335
831
  /** Fraction of its own size the body adds at full press. */
336
832
  grow: number;
@@ -339,42 +835,4 @@ type PressResponse = {
339
835
  };
340
836
  declare function pressResponse(kind: VireUIKitAtomKind): PressResponse;
341
837
 
342
- type ControlSize = 'touch' | 'small' | 'medium' | 'large' | 'xlarge';
343
- declare const CONTROL_SIZES: readonly ControlSize[];
344
- type SizeSpec = {
345
- /** Body height; an icon button is a square of this side. */
346
- height: number;
347
- paddingX: number;
348
- icon: number;
349
- radius: number;
350
- shape: 'capsule' | 'rounded';
351
- };
352
- declare const MIN_TOUCH_TARGET_DP = 44;
353
- declare const MIN_POINTER_TARGET_DP = 24;
354
- declare const BUTTON_SIZES: Readonly<Record<ControlSize, SizeSpec>>;
355
- declare function iconButtonSize(size: ControlSize): SizeSpec & {
356
- width: number;
357
- };
358
- type MenuSizeSpec = {
359
- /** Row height; the glyph sits centred in a square of it. */
360
- row: number;
361
- padding: number;
362
- /** The active item's highlight. */
363
- itemRadius: number;
364
- /** Concentric with the highlight: its radius plus the padding. */
365
- radius: number;
366
- icon: number;
367
- /** The label's inset in an item without a glyph. */
368
- labelPad: number;
369
- };
370
- declare function menuSize(size: ControlSize): MenuSizeSpec;
371
- declare function nestedRadius(outerRadius: number, inset: number, minimum?: number): number;
372
- declare function defaultSize(input: {
373
- touch: boolean;
374
- }): ControlSize;
375
- declare function hitSlop(bodyDp: number, minTargetDp: number): number;
376
- declare function minTarget(input: {
377
- touch: boolean;
378
- }): number;
379
-
380
- export { ATOM_AT_REST, type AtomBody, BUTTON_SIZES, CONTROL_IDLE, CONTROL_SIZES, type ControlEvent, type ControlSize, type ControlState, type ControlStep, type ControlTargets, type ControlVisual, DEFAULT_GROWTH_TIMING, type FocusChange, type GrowthFrame, type GrowthInk, type GrowthPhase, type GrowthPoint, type GrowthShape, type GrowthState, type GrowthTiming, type HoverChange, type InputCaps, MENU_CLOSED, MENU_CORNERS, MENU_DROP_BODY, MENU_ITEM_FILL, MIN_POINTER_TARGET_DP, MIN_TOUCH_TARGET_DP, type MenuCorner, type MenuDismissal, type MenuEvent, type MenuFocus, type MenuItem, type MenuItemState, type MenuPlacement, type MenuSizeSpec, type MenuState, type MenuStep, type MenuSurfaceFrame, POINTER_INPUT, PRESS_IDLE, type PressEvent, type PressResponse, type PressSource, type PressState, type PressStep, type Rect, type RoundedRect, type Size, type SizeSpec, type Spring, TOUCH_INPUT, TYPEAHEAD_SECONDS, type VireUIKitAtomKind, type VireUIKitAtomState, atomAtRest, atomBody, atomLift, atomMaterial, controlTargets, createControlVisual, createGrowthState, defaultSize, growthFrame, growthInk, growthPhase, hitSlop, iconButtonSize, menuActivity, menuBody, menuCorner, menuGlass, menuItemFill, menuMaterial, menuPlacement, menuSize, menuSurfaceFrame, minTarget, nestedRadius, pressResponse, settleControlVisual, smoothstep, stepControl, stepControlVisual, stepFocus, stepGrowth, stepHover, stepMenu, stepPress, stepSpring, visualAtomState };
838
+ export { ATOM_AT_REST, AdaptiveContext, type AtomBody, BAR_SLOTS, BOTTOM_SHEET_MAX_WIDTH, BUTTON_SIZES, type BottomAccessoryForm, CONTROL_IDLE, CONTROL_SIZES, type ControlEvent, type ControlSize, type ControlState, type ControlStep, type ControlTargets, type ControlVisual, DEFAULT_GROWTH_TIMING, DIALOG_MAX_WIDTH, FORM_MORPH_TIMING, type FocusChange, type FormChange, type FormMorphDescriptor, type FormMorphFrame, type FormMorphState, type GrowthFrame, type GrowthInk, type GrowthPhase, type GrowthPoint, type GrowthShape, type GrowthState, type GrowthTiming, HEIGHT_BREAKPOINTS, HEIGHT_CLASSES, type HeightClass, type HoverChange, type InputCaps, InputLayers, LIST_PANE_WIDTH, type ListDetailState, MENU_CLOSED, MENU_CORNERS, MENU_DROP_BODY, MENU_ITEM_FILL, MINIMIZE_JITTER, MINIMIZE_THRESHOLD, MIN_POINTER_TARGET_DP, MIN_TOUCH_TARGET_DP, MORE_ID, type MenuCorner, type MenuDismissal, type MenuEvent, type MenuFocus, type MenuItem, type MenuItemState, type MenuPlacement, type MenuRegion, type MenuSizeSpec, type MenuState, type MenuStep, type MenuSurfaceFrame, type MinimizeState, type MorphBody, type MorphBox, NAV_MINIMIZE_TIMING, NAV_MORPH_TIMING, NAV_SIZES, type NavDragState, type NavLayout, type NavLayoutInput, type NavMorphFrame, type NavMorphPhase, type NavMorphState, type NavigationForm, type NavigationFormKind, type NavigationSpec, POINTER_INPUT, PRESS_IDLE, type PaneLayout, type PaneSplit, type PostureSplit, type PresentationForm, type PresentationFormKind, type PressEvent, type PressResponse, type PressSource, type PressState, type PressStep, Rect, type RoundedRect, SIDE_SHEET_MAX_WIDTH, SUPPORTING_PANE_MAX_WIDTH, SURFACE_KINDS, type SearchForm, Size, type SizeSpec, type Spring, type SupportingPaneForm, type SupportingPaneState, type SurfaceKind, TOOLBAR_GAP, TOUCH_INPUT, TYPEAHEAD_SECONDS, type ToolbarCapsule, type ToolbarItem, type ToolbarLayout, type ToolbarOptions, type ToolbarOrientation, type ToolbarPlacement, type VireUIKitAtomKind, type VireUIKitAtomState, WIDTH_BREAKPOINTS, WIDTH_CLASSES, type WidthClass, type WindowClass, aimNavMorph, atomAtRest, atomBody, atomLift, atomMaterial, bottomAccessory, cancelNavDrag, clampScrollTop, classifyWindow, controlTargets, createControlVisual, createFormMorph, createGrowthState, createMinimize, createNavDrag, createNavMorph, currentNavFrame, defaultSize, expandMinimize, fixedNavigationForm, formMorph, formMorphFrame, growthFrame, growthInk, growthPhase, heightClass, hitSlop, hitTarget, hoversWith, iconButtonSize, menuActivity, menuBody, menuCorner, menuGlass, menuItemFill, menuMaterial, menuPlacement, menuSize, menuSurfaceFrame, minTarget, moveNavDrag, navDragFrame, navLayout, navMorphFrame, navigationForm, nestedRadius, paneLayout, postureSplit, presentationForm, pressNavDrag, pressResponse, rebaseFormMorph, releaseNavDrag, settleControlVisual, sizeTier, smoothstep, stepControl, stepControlVisual, stepFocus, stepFormMorph, stepGrowth, stepHover, stepMenu, stepMinimize, stepNavDrag, stepNavMorph, stepPress, stepSpring, supportingPane, toolbarLayout, toolbarPlacement, visualAtomState, widthAtLeast, widthClass };