vireuikit 0.7.0 → 0.8.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.cts 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.cjs';
2
- import { VireGlassMaterial, VireGlassGeometry, VireGlassMorph } from 'vireglass';
1
+ import { S as Size, R as Rect, A as AdaptiveContext, I as InputLayers } from './context-DQaAcRq-.cjs';
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-.cjs';
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;
@@ -339,42 +651,4 @@ type PressResponse = {
339
651
  };
340
652
  declare function pressResponse(kind: VireUIKitAtomKind): PressResponse;
341
653
 
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 };
654
+ 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, 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 MorphBody, type MorphBox, 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, atomAtRest, atomBody, atomLift, atomMaterial, bottomAccessory, classifyWindow, controlTargets, createControlVisual, createFormMorph, createGrowthState, defaultSize, formMorph, formMorphFrame, growthFrame, growthInk, growthPhase, heightClass, hitSlop, hitTarget, hoversWith, iconButtonSize, menuActivity, menuBody, menuCorner, menuGlass, menuItemFill, menuMaterial, menuPlacement, menuSize, menuSurfaceFrame, minTarget, navigationForm, nestedRadius, paneLayout, postureSplit, presentationForm, pressResponse, rebaseFormMorph, settleControlVisual, sizeTier, smoothstep, stepControl, stepControlVisual, stepFocus, stepFormMorph, stepGrowth, stepHover, stepMenu, stepPress, stepSpring, supportingPane, toolbarLayout, toolbarPlacement, visualAtomState, widthAtLeast, widthClass };