vireuikit 0.5.0 → 0.7.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,3 +1,4 @@
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';
1
2
  import { VireGlassMaterial, VireGlassGeometry, VireGlassMorph } from 'vireglass';
2
3
 
3
4
  type VireUIKitAtomKind = 'button' | 'iconButton' | 'switch' | 'slider' | 'segment' | 'chip' | 'field' | 'menu';
@@ -27,6 +28,80 @@ declare function atomMaterial(base: VireGlassMaterial, kind: VireUIKitAtomKind,
27
28
  /** Whether every field on the atom matches rest — lets a client skip redraws for states its own
28
29
  * input can never produce (hover on touch, focus on Android). */
29
30
  declare function atomAtRest(state: VireUIKitAtomState): boolean;
31
+ /** The veil over the backdrop: the surroundings' colour, `white` of it swapped for white, lifted by `lift` in OKLab lightness. */
32
+ type AtomBody = {
33
+ alpha: number;
34
+ white: number;
35
+ lift: number;
36
+ };
37
+ /** The body for a state; reduced transparency raises it to the density `vireglass` asks of the glass in that mode. */
38
+ declare function atomBody(base: VireGlassMaterial, state?: VireUIKitAtomState, frost?: boolean): AtomBody;
39
+
40
+ type FocusChange = {
41
+ type: 'focus';
42
+ visible: boolean;
43
+ } | {
44
+ type: 'blur';
45
+ };
46
+ declare function stepFocus(event: FocusChange): boolean;
47
+
48
+ type HoverChange = {
49
+ type: 'enter';
50
+ } | {
51
+ type: 'leave';
52
+ };
53
+ declare function stepHover(event: HoverChange, canHover: boolean): boolean;
54
+
55
+ type PressSource = 'pointer' | 'keyboard';
56
+ type PressState = {
57
+ by: PressSource | null;
58
+ };
59
+ type PressEvent = {
60
+ type: 'down';
61
+ source: PressSource;
62
+ } | {
63
+ type: 'up';
64
+ source: PressSource;
65
+ } | {
66
+ type: 'cancel';
67
+ } | {
68
+ type: 'leave';
69
+ };
70
+ declare const PRESS_IDLE: PressState;
71
+ type PressStep = {
72
+ state: PressState;
73
+ activated: boolean;
74
+ };
75
+ declare function stepPress(state: PressState, event: PressEvent, enabled: boolean): PressStep;
76
+
77
+ type InputCaps = {
78
+ hover: boolean;
79
+ };
80
+ declare const TOUCH_INPUT: InputCaps;
81
+ declare const POINTER_INPUT: InputCaps;
82
+ type ControlState = {
83
+ press: PressState;
84
+ hovered: boolean;
85
+ focused: boolean;
86
+ disabled: boolean;
87
+ };
88
+ declare const CONTROL_IDLE: ControlState;
89
+ type ControlEvent = PressEvent | HoverChange | FocusChange | {
90
+ type: 'disable';
91
+ disabled: boolean;
92
+ };
93
+ type ControlStep = {
94
+ state: ControlState;
95
+ activated: boolean;
96
+ };
97
+ declare function stepControl(state: ControlState, event: ControlEvent, caps: InputCaps): ControlStep;
98
+ type ControlTargets = {
99
+ pressed: number;
100
+ hovered: number;
101
+ focus: number;
102
+ disabled: boolean;
103
+ };
104
+ declare function controlTargets(state: ControlState): ControlTargets;
30
105
 
31
106
  /** A spring with `response`-second settle time; `damping` < 1 overshoots. `instant` skips
32
107
  * integration entirely, for reduced motion. */
@@ -81,9 +156,9 @@ type GrowthState = {
81
156
  };
82
157
  declare const createGrowthState: () => GrowthState;
83
158
  declare const growthPhase: (state: GrowthState) => GrowthPhase;
84
- /** Durations are client-specific: this set is tuned for the capsule-to-menu transition, and an
85
- * atom at a different scale (an icon opening into a sheet) isn't obligated to match it. The
86
- * shape of the transition is shared regardless. */
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. */
87
162
  type GrowthTiming = {
88
163
  shrinkOpen: number;
89
164
  shrinkClose: number;
@@ -98,4 +173,208 @@ declare const DEFAULT_GROWTH_TIMING: GrowthTiming;
98
173
  * and no defocus phase. */
99
174
  declare function stepGrowth(state: GrowthState, dt: number, open: boolean, reduceMotion?: boolean, timing?: GrowthTiming): boolean;
100
175
 
101
- export { ATOM_AT_REST, DEFAULT_GROWTH_TIMING, type GrowthFrame, type GrowthInk, type GrowthPhase, type GrowthPoint, type GrowthShape, type GrowthState, type GrowthTiming, type Spring, type VireUIKitAtomKind, type VireUIKitAtomState, atomAtRest, atomLift, atomMaterial, createGrowthState, growthFrame, growthInk, growthPhase, smoothstep, stepGrowth, stepSpring };
176
+ type ControlVisual = {
177
+ pressed: Spring;
178
+ hovered: Spring;
179
+ focus: Spring;
180
+ };
181
+ declare const createControlVisual: () => ControlVisual;
182
+ declare function stepControlVisual(visual: ControlVisual, dt: number, state: ControlState, instant?: boolean): boolean;
183
+ declare function visualAtomState(visual: ControlVisual, state: ControlState): VireUIKitAtomState;
184
+ declare function settleControlVisual(visual: ControlVisual, state: ControlState): void;
185
+
186
+ /**
187
+ * `disabled`: cannot be chosen. `focusable` (default: not disabled): whether focus can land on it. A native
188
+ * disabled button cannot take focus, so the keyboard passes over it; an aria-disabled one can (WAI-ARIA APG).
189
+ */
190
+ type MenuItem = {
191
+ label: string;
192
+ disabled: boolean;
193
+ focusable?: boolean;
194
+ };
195
+ type MenuState = {
196
+ open: boolean;
197
+ /** The active item, or -1 while the menu itself holds focus. */
198
+ active: number;
199
+ typed: string;
200
+ /** Seconds, on whatever clock the events carry. */
201
+ typedAt: number;
202
+ };
203
+ declare const MENU_CLOSED: MenuState;
204
+ type MenuDismissal = 'escape' | 'outside' | 'tab' | 'trigger';
205
+ type MenuEvent = {
206
+ type: 'open';
207
+ focus: 'menu' | 'first' | 'last';
208
+ } | {
209
+ type: 'move';
210
+ to: 'next' | 'previous' | 'first' | 'last';
211
+ } | {
212
+ type: 'point';
213
+ index: number;
214
+ } | {
215
+ type: 'type';
216
+ key: string;
217
+ at: number;
218
+ } | {
219
+ type: 'choose';
220
+ index: number;
221
+ } | {
222
+ type: 'dismiss';
223
+ reason: MenuDismissal;
224
+ };
225
+ /** Where focus goes after a step. `none` leaves it where the person put it (a press outside). */
226
+ type MenuFocus = {
227
+ to: 'item';
228
+ index: number;
229
+ } | {
230
+ to: 'menu';
231
+ } | {
232
+ to: 'trigger';
233
+ } | {
234
+ to: 'none';
235
+ } | {
236
+ to: 'stay';
237
+ };
238
+ type MenuStep = {
239
+ state: MenuState;
240
+ focus: MenuFocus;
241
+ chosen: number | null;
242
+ };
243
+ declare const TYPEAHEAD_SECONDS = 0.5;
244
+ 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
+ /** -1 is left or top, 1 is right or bottom. */
256
+ type MenuCorner = {
257
+ x: -1 | 1;
258
+ y: -1 | 1;
259
+ };
260
+ declare const MENU_CORNERS: {
261
+ readonly 'top-left': {
262
+ readonly x: -1;
263
+ readonly y: -1;
264
+ };
265
+ readonly 'top-right': {
266
+ readonly x: 1;
267
+ readonly y: -1;
268
+ };
269
+ readonly 'bottom-left': {
270
+ readonly x: -1;
271
+ readonly y: 1;
272
+ };
273
+ readonly 'bottom-right': {
274
+ readonly x: 1;
275
+ readonly y: 1;
276
+ };
277
+ };
278
+ type MenuPlacement = {
279
+ rect: Rect;
280
+ corner: MenuCorner;
281
+ /** The menu's corner on the pinned side: where its growth ends. */
282
+ anchor: GrowthPoint;
283
+ };
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;
286
+ /**
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.
289
+ */
290
+ declare function menuPlacement(trigger: Rect, menu: Size, viewport: Size, options?: {
291
+ corner?: MenuCorner | undefined;
292
+ margin?: number;
293
+ }): MenuPlacement;
294
+ type RoundedRect = Rect & {
295
+ radius: number;
296
+ };
297
+ type MenuSurfaceFrame = {
298
+ /** The surface's box this frame: the growth frame's body. */
299
+ box: RoundedRect;
300
+ /** The second shape for the lens's `setMorph`, offset from the box's centre; undefined once there is one body. */
301
+ morph: VireGlassMorph | undefined;
302
+ /** Where the content's origin sits relative to the box's: it keeps its final place while the box moves. */
303
+ content: {
304
+ x: number;
305
+ y: number;
306
+ };
307
+ ink: GrowthInk;
308
+ /** How much of a drop the surface is: 0 as the trigger and as the menu, highest in between. */
309
+ activity: number;
310
+ };
311
+ declare function menuActivity(phase: GrowthPhase): number;
312
+ declare function menuSurfaceFrame(trigger: RoundedRect, menu: RoundedRect, anchor: GrowthPoint, phase: GrowthPhase): MenuSurfaceFrame;
313
+ /** A menu is a larger piece of the same glass, and a larger piece scatters light more softly (M 7:19): it
314
+ * takes the core's sheet scattering, which turns text under it into soft spots without turning it to mush. */
315
+ declare function menuGlass(base: VireGlassMaterial): VireGlassMaterial;
316
+ /** The drop is the medium at its active end, the menu at rest is the resting glass. */
317
+ declare function menuMaterial(base: VireGlassMaterial, activity: number): VireGlassMaterial;
318
+ /** The gathered medium lit by a concentrate of its surroundings: about 0.2 lighter than what it covers at
319
+ * M 5:11, so white over a light backdrop, and only brighter over a dark one. */
320
+ declare const MENU_DROP_BODY: AtomBody;
321
+ declare function menuBody(base: VireGlassMaterial, activity: number, frost?: boolean): AtomBody;
322
+ type MenuItemState = {
323
+ active: boolean;
324
+ pressed: boolean;
325
+ disabled: boolean;
326
+ };
327
+ /** Ink laid on the glass under an item, as a fraction of the ink colour. */
328
+ declare const MENU_ITEM_FILL: {
329
+ readonly active: 0.1;
330
+ readonly pressed: 0.18;
331
+ };
332
+ declare function menuItemFill(state: MenuItemState): number;
333
+
334
+ type PressResponse = {
335
+ /** Fraction of its own size the body adds at full press. */
336
+ grow: number;
337
+ /** Glyph blur at full press, px. */
338
+ defocus: number;
339
+ };
340
+ declare function pressResponse(kind: VireUIKitAtomKind): PressResponse;
341
+
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 };
package/dist/index.js CHANGED
@@ -1,188 +1,3 @@
1
- import { activeMaterial, halfMinDp } from 'vireglass';
2
-
3
- // src/kit/atom.ts
4
- var ATOM_AT_REST = {
5
- pressed: 0,
6
- hovered: 0,
7
- selected: 0,
8
- disabled: false
9
- };
10
- function atomLift(kind) {
11
- switch (kind) {
12
- case "iconButton":
13
- case "switch":
14
- case "slider":
15
- return 1;
16
- default:
17
- return 0;
18
- }
19
- }
20
- var DISABLED_PRESENCE = 0.25;
21
- var HOVER_GAIN = 0.35;
22
- function atomMaterial(base, kind, state = ATOM_AT_REST) {
23
- const clamp = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
24
- const on = Math.max(clamp(state.selected), clamp(state.pressed), clamp(state.hovered) * HOVER_GAIN);
25
- const out = activeMaterial(base, state.disabled ? 0 : on);
26
- if (!state.disabled) return out;
27
- return { ...out, presence: out.presence * DISABLED_PRESENCE };
28
- }
29
- function atomAtRest(state) {
30
- return state.pressed === 0 && state.hovered === 0 && state.selected === 0 && state.disabled === false;
31
- }
32
- function stepSpring(s, dt, response, damping, instant = false) {
33
- if (instant) {
34
- const moved = s.x !== s.target || s.v !== 0;
35
- s.x = s.target;
36
- s.v = 0;
37
- return moved;
38
- }
39
- const stiffness = (2 * Math.PI / response) ** 2;
40
- const friction = 4 * Math.PI * damping / response;
41
- const h = 1 / 240;
42
- for (let left = Math.min(dt, 0.05); left > 0; left -= h) {
43
- const step = Math.min(left, h);
44
- s.v += (stiffness * (s.target - s.x) - friction * s.v) * step;
45
- s.x += s.v * step;
46
- }
47
- if (Math.abs(s.target - s.x) < 1e-3 && Math.abs(s.v) < 0.01) {
48
- s.x = s.target;
49
- s.v = 0;
50
- return false;
51
- }
52
- return true;
53
- }
54
- var smoothstep = (e0, e1, x) => {
55
- const t = Math.min(Math.max((x - e0) / (e1 - e0), 0), 1);
56
- return t * t * (3 - 2 * t);
57
- };
58
- var mix = (a, b, t) => a + (b - a) * t;
59
- var DROP_DIAMETER = 1.1;
60
- var DROP_LIFT = 0.5;
61
- var NECK_GAIN = 0.3;
62
- var NECK_RAMP = 4;
63
- var CORNER_EASE_START = 0.55;
64
- var INK_OUT_END = 0.4;
65
- var INK_IN_START = 0.45;
66
- var INK_IN_END = 0.95;
67
- function growthInk(phase) {
68
- const a = Math.min(Math.max(phase.shrink, 0), 1);
69
- const b = Math.max(phase.grow, 0);
70
- return {
71
- outgoing: 1 - smoothstep(0, INK_OUT_END, a),
72
- incoming: smoothstep(INK_IN_START, INK_IN_END, b)
73
- };
74
- }
75
- function largerOf(a, b) {
76
- return halfMinDp(a.geometry) >= halfMinDp(b.geometry) ? { main: a, other: b } : { main: b, other: a };
77
- }
78
- function growthFrame(source, target, anchor, phase) {
79
- const dx = target.center.x - source.center.x;
80
- const dy = target.center.y - source.center.y;
81
- const len = Math.hypot(dx, dy) || 1;
82
- const sourceSize = halfMinDp(source.geometry) * 2;
83
- const lift = DROP_LIFT * sourceSize;
84
- const drop = {
85
- x: source.center.x + dx / len * lift,
86
- y: source.center.y + dy / len * lift
87
- };
88
- const a = Math.min(Math.max(phase.shrink, 0), 1);
89
- const b = Math.max(phase.grow, 0);
90
- const keep = Math.max(1 - b, 0);
91
- const d = DROP_DIAMETER * sourceSize;
92
- const shrunkGeometry = {
93
- width: mix(source.geometry.width, 0.6 * d, a) * keep,
94
- height: mix(source.geometry.height, 0.6 * d, a) * keep,
95
- cornerRadius: 0
96
- };
97
- shrunkGeometry.cornerRadius = Math.min(
98
- mix(source.geometry.cornerRadius, halfMinDp(shrunkGeometry), a),
99
- halfMinDp(shrunkGeometry)
100
- );
101
- const shrunk = {
102
- geometry: shrunkGeometry,
103
- center: { x: mix(source.center.x, drop.x, a), y: mix(source.center.y, drop.y, a) }
104
- };
105
- const seed = d * Math.sqrt(a);
106
- const cornerX = Math.sign(anchor.x - source.center.x);
107
- const cornerY = Math.sign(anchor.y - source.center.y);
108
- const edgeX = mix(drop.x + cornerX * (seed / 2), anchor.x, Math.min(b, 1));
109
- const edgeY = mix(drop.y + cornerY * (seed / 2), anchor.y, Math.min(b, 1));
110
- const width = mix(seed, target.geometry.width, b);
111
- const height = mix(seed, target.geometry.height, b);
112
- const grownGeometry = { width, height, cornerRadius: 0 };
113
- grownGeometry.cornerRadius = Math.min(
114
- mix(halfMinDp(grownGeometry), target.geometry.cornerRadius, smoothstep(CORNER_EASE_START, 1, b)),
115
- halfMinDp(grownGeometry)
116
- );
117
- const grown = {
118
- geometry: grownGeometry,
119
- center: { x: edgeX - cornerX * (width / 2), y: edgeY - cornerY * (height / 2) }
120
- };
121
- const { main, other } = largerOf(grown, shrunk);
122
- const neck = NECK_GAIN * sourceSize * Math.min(a * NECK_RAMP, 1) * keep;
123
- const united = neck > 0.01 && other.geometry.width > 0.5 && other.geometry.height > 0.5;
124
- return {
125
- body: main,
126
- grown,
127
- merge: united ? {
128
- offsetX: other.center.x - main.center.x,
129
- offsetY: other.center.y - main.center.y,
130
- width: other.geometry.width,
131
- height: other.geometry.height,
132
- cornerRadius: other.geometry.cornerRadius,
133
- smoothing: neck
134
- } : void 0,
135
- ink: growthInk(phase)
136
- };
137
- }
138
- var createGrowthState = () => ({
139
- shrink: { x: 0, v: 0, target: 0 },
140
- grow: { x: 0, v: 0, target: 0 }
141
- });
142
- var growthPhase = (state) => ({
143
- shrink: state.shrink.x,
144
- grow: state.grow.x
145
- });
146
- var DEFAULT_GROWTH_TIMING = {
147
- shrinkOpen: 0.38,
148
- shrinkClose: 0.3,
149
- growOpen: 0.46,
150
- growClose: 0.4,
151
- growDampingOpen: 0.72
152
- };
153
- var GROW_DAMPING_CLOSE = 1;
154
- var SHRINK_DAMPING = 1;
155
- var GROW_STARTS_AT = 0.8;
156
- var SHRINK_RETURNS_AT = 0.12;
157
- function stepGrowth(state, dt, open, reduceMotion = false, timing = DEFAULT_GROWTH_TIMING) {
158
- const { shrink, grow } = state;
159
- if (reduceMotion) {
160
- shrink.target = open ? 1 : 0;
161
- grow.target = open ? 1 : 0;
162
- } else if (open) {
163
- shrink.target = 1;
164
- if (shrink.x > GROW_STARTS_AT) grow.target = 1;
165
- } else {
166
- grow.target = 0;
167
- if (grow.x < SHRINK_RETURNS_AT) shrink.target = 0;
168
- }
169
- const movedShrink = stepSpring(
170
- shrink,
171
- dt,
172
- open ? timing.shrinkOpen : timing.shrinkClose,
173
- SHRINK_DAMPING,
174
- reduceMotion
175
- );
176
- const movedGrow = stepSpring(
177
- grow,
178
- dt,
179
- open ? timing.growOpen : timing.growClose,
180
- open ? timing.growDampingOpen : GROW_DAMPING_CLOSE,
181
- reduceMotion
182
- );
183
- return movedShrink || movedGrow;
184
- }
185
-
186
- export { ATOM_AT_REST, DEFAULT_GROWTH_TIMING, atomAtRest, atomLift, atomMaterial, createGrowthState, growthFrame, growthInk, growthPhase, smoothstep, stepGrowth, stepSpring };
1
+ export { A11Y_NONE, ATOM_AT_REST, BUTTON_SIZES, CONTROL_IDLE, CONTROL_SIZES, DEFAULT_GROWTH_TIMING, MENU_CLOSED, MENU_CORNERS, MENU_DROP_BODY, MENU_ITEM_FILL, MIN_POINTER_TARGET_DP, MIN_TOUCH_TARGET_DP, POINTER_INPUT, PRESS_IDLE, TOUCH_INPUT, TYPEAHEAD_SECONDS, atomAtRest, atomBody, atomLift, atomMaterial, controlTargets, createControlVisual, createGrowthState, defaultSize, growthFrame, growthInk, growthPhase, hitSlop, iconButtonSize, menuActivity, menuBody, menuCorner, menuGlass, menuItemFill, menuMaterial, menuPlacement, menuSize, menuSurfaceFrame, minTarget, nestedRadius, presentationFor, pressResponse, resolveA11y, sameA11y, settleControlVisual, smoothstep, stepControl, stepControlVisual, stepFocus, stepGrowth, stepHover, stepMenu, stepPress, stepSpring, visualAtomState } from './chunk-AR2AM3BR.js';
187
2
  //# sourceMappingURL=index.js.map
188
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/kit/atom.ts","../src/kit/morph.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Atom states — shared across every client: the set of states, what they mean, and what they do\n// to the material. Sizes, layout and per-input idioms are client-specific and live elsewhere.\nimport { activeMaterial, type VireGlassMaterial } from 'vireglass';\n\nexport type VireUIKitAtomKind =\n | 'button'\n | 'iconButton'\n | 'switch'\n | 'slider'\n | 'segment'\n | 'chip'\n | 'field'\n | 'menu';\n\n/** Hover and focus don't exist everywhere: touch has neither. The atom accepts them, but has to\n * look complete at zero too — otherwise a touch client gets an unfinished look. */\nexport type VireUIKitAtomState = {\n pressed: number;\n hovered: number;\n selected: number;\n disabled: boolean;\n};\n\nexport const ATOM_AT_REST: VireUIKitAtomState = {\n pressed: 0,\n hovered: 0,\n selected: 0,\n disabled: false,\n};\n\n/**\n * Whether the piece lifts INTO the glass under press (1) or sinks into the backing (0).\n *\n * Lift isn't decoration: a small target has the finger covering it entirely, and an enlarged\n * transparent lens underneath is the only sign of a hit. A large target stays visible past the\n * finger, so it reads more naturally sinking toward the backing instead.\n */\nexport function atomLift(kind: VireUIKitAtomKind): number {\n switch (kind) {\n case 'iconButton':\n case 'switch':\n case 'slider':\n return 1;\n default:\n return 0;\n }\n}\n\n/** A disabled piece stays glass but stops asserting itself: the legibility requirement is lifted\n * and tint turns off. Hiding it via transparency won't do — the material is already transparent. */\nconst DISABLED_PRESENCE = 0.25;\n/** Hover is halfway to a press: the cursor is already on the target, but nothing is chosen yet. */\nconst HOVER_GAIN = 0.35;\n\n/**\n * The atom's material for a given state. Press and selection go through `activeMaterial` — for\n * glass, state is a change in the MEDIUM itself (denser, thicker, clearer), not a highlight\n * drawn over it.\n */\nexport function atomMaterial(\n base: VireGlassMaterial,\n kind: VireUIKitAtomKind,\n state: VireUIKitAtomState = ATOM_AT_REST,\n): VireGlassMaterial {\n const clamp = (v: number) => (v < 0 ? 0 : v > 1 ? 1 : v);\n const on = Math.max(clamp(state.selected), clamp(state.pressed), clamp(state.hovered) * HOVER_GAIN);\n const out = activeMaterial(base, state.disabled ? 0 : on);\n if (!state.disabled) return out;\n return { ...out, presence: out.presence * DISABLED_PRESENCE };\n}\n\n/** Whether every field on the atom matches rest — lets a client skip redraws for states its own\n * input can never produce (hover on touch, focus on Android). */\nexport function atomAtRest(state: VireUIKitAtomState): boolean {\n return (\n state.pressed === 0 && state.hovered === 0 && state.selected === 0 && state.disabled === false\n );\n}\n","// Two-phase \"capsule → drop → target\" growth: atom behavior, not layout. The body contracts into\n// a drop, a neck grows from its side toward the target, the drop grows into the target with a\n// slight overshoot; closing retraces the same path in reverse, because the model holds no\n// direction, only the current phase. Source and target sizes are layout, supplied as parameters.\nimport { halfMinDp, type VireGlassGeometry, type VireGlassMorph } from 'vireglass';\n\n/** A spring with `response`-second settle time; `damping` < 1 overshoots. `instant` skips\n * integration entirely, for reduced motion. */\nexport type Spring = { x: number; v: number; target: number };\n\nexport function stepSpring(\n s: Spring,\n dt: number,\n response: number,\n damping: number,\n instant = false,\n): boolean {\n if (instant) {\n const moved = s.x !== s.target || s.v !== 0;\n s.x = s.target;\n s.v = 0;\n return moved;\n }\n const stiffness = ((2 * Math.PI) / response) ** 2;\n const friction = (4 * Math.PI * damping) / response;\n const h = 1 / 240;\n // Fixed step, but not a fixed frame budget: without this, a rare slow frame hands the spring a\n // huge dt and it diverges.\n for (let left = Math.min(dt, 0.05); left > 0; left -= h) {\n const step = Math.min(left, h);\n s.v += (stiffness * (s.target - s.x) - friction * s.v) * step;\n s.x += s.v * step;\n }\n if (Math.abs(s.target - s.x) < 1e-3 && Math.abs(s.v) < 1e-2) {\n s.x = s.target;\n s.v = 0;\n return false;\n }\n return true;\n}\n\nexport const smoothstep = (e0: number, e1: number, x: number): number => {\n const t = Math.min(Math.max((x - e0) / (e1 - e0), 0), 1);\n return t * t * (3 - 2 * t);\n};\n\nconst mix = (a: number, b: number, t: number) => a + (b - a) * t;\n\nexport type GrowthPoint = { x: number; y: number };\n\n/** A shape and its position — it has no role of its own; `growthFrame` assigns the role (body, or\n * what the body is merging into). */\nexport type GrowthShape = { geometry: VireGlassGeometry; center: GrowthPoint };\n\n/** `shrink` — capsule → drop, no overshoot. `grow` — drop → target; may exceed 1 for the\n * overshoot. Both are read as-is; `stepGrowth` owns the bounds and the handoff between them. */\nexport type GrowthPhase = { shrink: number; grow: number };\n\nexport type GrowthInk = {\n /** 0…1: how much of the source's ink is still visible (1) vs. already defocused into the drop (0). */\n outgoing: number;\n /** 0…1: how much of the target's ink has come into focus (0 = not yet, 1 = fully). */\n incoming: number;\n};\n\nexport type GrowthFrame = {\n /** The shape worth drawing as glass right now: the larger of the two, which supplies the\n * thickness and the bevel. */\n body: GrowthShape;\n /** The target shape as-is, regardless of role — it drives the response to finger/light while\n * the glass hasn't become it yet (while `body` is still the drop). */\n grown: GrowthShape;\n /** The second merge shape, ready for `toLensProps`/`toSurfaceUniforms`; `undefined` while the\n * neck hasn't grown yet or has already retracted. */\n merge: VireGlassMorph | undefined;\n ink: GrowthInk;\n};\n\n/** The drop's size — a fraction of the source's diameter, with headroom for the neck. */\nconst DROP_DIAMETER = 1.1;\n/** How far the drop travels from the source toward the target, as a fraction of the source's diameter. */\nconst DROP_LIFT = 0.5;\nconst NECK_GAIN = 0.3;\nconst NECK_RAMP = 4;\nconst CORNER_EASE_START = 0.55;\nconst INK_OUT_END = 0.4;\nconst INK_IN_START = 0.45;\nconst INK_IN_END = 0.95;\n\nexport function growthInk(phase: GrowthPhase): GrowthInk {\n const a = Math.min(Math.max(phase.shrink, 0), 1);\n const b = Math.max(phase.grow, 0);\n // Ink leaves and arrives by going out of focus, not by plain transparency — the blur amount is\n // the drawer's job, this only reports the fraction of focus.\n return {\n outgoing: 1 - smoothstep(0, INK_OUT_END, a),\n incoming: smoothstep(INK_IN_START, INK_IN_END, b),\n };\n}\n\n/** The main shape is the larger one by half-size: it supplies the thickness and the bevel, so the\n * roles swap exactly at equal half-sizes, which is invisible on screen. */\nfunction largerOf(a: GrowthShape, b: GrowthShape): { main: GrowthShape; other: GrowthShape } {\n return halfMinDp(a.geometry) >= halfMinDp(b.geometry) ? { main: a, other: b } : { main: b, other: a };\n}\n\n/** `anchor` is the corner shared by source and target: the body pins to it, otherwise the target\n * would grow out of the drop's center instead of the button's corner. */\nexport function growthFrame(\n source: GrowthShape,\n target: GrowthShape,\n anchor: GrowthPoint,\n phase: GrowthPhase,\n): GrowthFrame {\n const dx = target.center.x - source.center.x;\n const dy = target.center.y - source.center.y;\n const len = Math.hypot(dx, dy) || 1;\n const sourceSize = halfMinDp(source.geometry) * 2;\n const lift = DROP_LIFT * sourceSize;\n const drop: GrowthPoint = {\n x: source.center.x + (dx / len) * lift,\n y: source.center.y + (dy / len) * lift,\n };\n\n const a = Math.min(Math.max(phase.shrink, 0), 1);\n const b = Math.max(phase.grow, 0);\n const keep = Math.max(1 - b, 0);\n const d = DROP_DIAMETER * sourceSize;\n\n const shrunkGeometry: VireGlassGeometry = {\n width: mix(source.geometry.width, 0.6 * d, a) * keep,\n height: mix(source.geometry.height, 0.6 * d, a) * keep,\n cornerRadius: 0,\n };\n // The source's radius EASES into the drop's rather than being replaced by it outright —\n // otherwise a field with a partial corner radius at rest would render as a full pill.\n shrunkGeometry.cornerRadius = Math.min(\n mix(source.geometry.cornerRadius, halfMinDp(shrunkGeometry), a),\n halfMinDp(shrunkGeometry),\n );\n const shrunk: GrowthShape = {\n geometry: shrunkGeometry,\n center: { x: mix(source.center.x, drop.x, a), y: mix(source.center.y, drop.y, a) },\n };\n\n const seed = d * Math.sqrt(a);\n // The sign of the corner the body pins to. Zero means the anchor sits on the same axis as the\n // source's center, so growth along that axis centers instead of hugging an edge.\n const cornerX = Math.sign(anchor.x - source.center.x);\n const cornerY = Math.sign(anchor.y - source.center.y);\n const edgeX = mix(drop.x + cornerX * (seed / 2), anchor.x, Math.min(b, 1));\n const edgeY = mix(drop.y + cornerY * (seed / 2), anchor.y, Math.min(b, 1));\n const width = mix(seed, target.geometry.width, b);\n const height = mix(seed, target.geometry.height, b);\n const grownGeometry: VireGlassGeometry = { width, height, cornerRadius: 0 };\n grownGeometry.cornerRadius = Math.min(\n mix(halfMinDp(grownGeometry), target.geometry.cornerRadius, smoothstep(CORNER_EASE_START, 1, b)),\n halfMinDp(grownGeometry),\n );\n const grown: GrowthShape = {\n geometry: grownGeometry,\n center: { x: edgeX - cornerX * (width / 2), y: edgeY - cornerY * (height / 2) },\n };\n\n const { main, other } = largerOf(grown, shrunk);\n const neck = NECK_GAIN * sourceSize * Math.min(a * NECK_RAMP, 1) * keep;\n const united = neck > 0.01 && other.geometry.width > 0.5 && other.geometry.height > 0.5;\n\n return {\n body: main,\n grown,\n merge: united\n ? {\n offsetX: other.center.x - main.center.x,\n offsetY: other.center.y - main.center.y,\n width: other.geometry.width,\n height: other.geometry.height,\n cornerRadius: other.geometry.cornerRadius,\n smoothing: neck,\n }\n : undefined,\n ink: growthInk(phase),\n };\n}\n\nexport type GrowthState = { shrink: Spring; grow: Spring };\n\nexport const createGrowthState = (): GrowthState => ({\n shrink: { x: 0, v: 0, target: 0 },\n grow: { x: 0, v: 0, target: 0 },\n});\n\nexport const growthPhase = (state: GrowthState): GrowthPhase => ({\n shrink: state.shrink.x,\n grow: state.grow.x,\n});\n\n/** Durations are client-specific: this set is tuned for the capsule-to-menu transition, and an\n * atom at a different scale (an icon opening into a sheet) isn't obligated to match it. The\n * shape of the transition is shared regardless. */\nexport type GrowthTiming = {\n shrinkOpen: number;\n shrinkClose: number;\n growOpen: number;\n growClose: number;\n /** < 1 gives the target a slight overshoot on open; closing never overshoots. */\n growDampingOpen: number;\n};\n\nexport const DEFAULT_GROWTH_TIMING: GrowthTiming = {\n shrinkOpen: 0.38,\n shrinkClose: 0.3,\n growOpen: 0.46,\n growClose: 0.4,\n growDampingOpen: 0.72,\n};\n\nconst GROW_DAMPING_CLOSE = 1;\nconst SHRINK_DAMPING = 1;\nconst GROW_STARTS_AT = 0.8;\nconst SHRINK_RETURNS_AT = 0.12;\n\n/** Growth starts once the drop has gathered, and the reverse on close. `reduceMotion` turns off\n * the spring entirely: both phases jump straight to their target in one step, with no overshoot\n * and no defocus phase. */\nexport function stepGrowth(\n state: GrowthState,\n dt: number,\n open: boolean,\n reduceMotion = false,\n timing: GrowthTiming = DEFAULT_GROWTH_TIMING,\n): boolean {\n const { shrink, grow } = state;\n if (reduceMotion) {\n shrink.target = open ? 1 : 0;\n grow.target = open ? 1 : 0;\n } else if (open) {\n shrink.target = 1;\n if (shrink.x > GROW_STARTS_AT) grow.target = 1;\n } else {\n grow.target = 0;\n if (grow.x < SHRINK_RETURNS_AT) shrink.target = 0;\n }\n const movedShrink = stepSpring(\n shrink,\n dt,\n open ? timing.shrinkOpen : timing.shrinkClose,\n SHRINK_DAMPING,\n reduceMotion,\n );\n const movedGrow = stepSpring(\n grow,\n dt,\n open ? timing.growOpen : timing.growClose,\n open ? timing.growDampingOpen : GROW_DAMPING_CLOSE,\n reduceMotion,\n );\n return movedShrink || movedGrow;\n}\n"]}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}