@ambientcss/components 2.1.0 → 3.0.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.
Files changed (45) hide show
  1. package/README.md +35 -0
  2. package/dist/index.cjs +1540 -391
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +749 -52
  5. package/dist/index.d.ts +749 -52
  6. package/dist/index.js +1501 -391
  7. package/dist/index.js.map +1 -1
  8. package/dist/styles.css +998 -291
  9. package/package.json +2 -2
  10. package/src/components/AmbientButton.tsx +23 -27
  11. package/src/components/AmbientFader.tsx +29 -115
  12. package/src/components/AmbientKnob.tsx +58 -220
  13. package/src/components/AmbientPanel.tsx +8 -2
  14. package/src/components/AmbientProvider.tsx +3 -0
  15. package/src/components/AmbientSelect.tsx +40 -0
  16. package/src/components/AmbientSlider.tsx +28 -113
  17. package/src/components/AmbientSwitch.tsx +58 -58
  18. package/src/controls/AmbientBank.tsx +138 -0
  19. package/src/controls/AmbientLatch.tsx +78 -0
  20. package/src/controls/AmbientPress.tsx +74 -0
  21. package/src/controls/AmbientRotary.tsx +94 -0
  22. package/src/controls/AmbientTravel.tsx +83 -0
  23. package/src/core/context.tsx +46 -0
  24. package/src/core/controllable.ts +33 -0
  25. package/src/core/dev.ts +15 -0
  26. package/src/core/frames.tsx +63 -0
  27. package/src/core/kit.tsx +107 -0
  28. package/src/core/material.ts +27 -0
  29. package/src/core/numeric.ts +88 -0
  30. package/src/core/types.ts +116 -0
  31. package/src/core/useBank.ts +163 -0
  32. package/src/core/useLatch.ts +54 -0
  33. package/src/core/usePress.ts +120 -0
  34. package/src/core/useRotary.ts +253 -0
  35. package/src/core/useTravel.ts +141 -0
  36. package/src/index.ts +122 -2
  37. package/src/kits/console.tsx +80 -0
  38. package/src/kits/grounded.tsx +113 -0
  39. package/src/parts/bank.tsx +32 -0
  40. package/src/parts/console.tsx +99 -0
  41. package/src/parts/knob.tsx +203 -0
  42. package/src/parts/latch.tsx +33 -0
  43. package/src/parts/press.tsx +56 -0
  44. package/src/parts/travel.tsx +70 -0
  45. package/src/styles.css +998 -291
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { PropsWithChildren, CSSProperties, HTMLAttributes, ButtonHTMLAttributes } from 'react';
2
+ import * as react from 'react';
3
+ import { PropsWithChildren, CSSProperties, HTMLAttributes, ReactNode, PointerEvent, ButtonHTMLAttributes, KeyboardEvent } from 'react';
3
4
 
4
5
  type AmbientTheme = {
5
6
  lightX?: number;
@@ -18,9 +19,30 @@ type AmbientProviderProps = PropsWithChildren<{
18
19
  }>;
19
20
  declare function AmbientProvider({ children, className, style, theme }: AmbientProviderProps): react_jsx_runtime.JSX.Element;
20
21
 
22
+ /** The grounded surface finishes from @ambientcss/css.
23
+ *
24
+ * Lives on parts and presets, never on a mechanism: which element a
25
+ * material belongs on is a fact about a particular control's construction,
26
+ * and a mechanism cannot know that once the parts are yours.
27
+ *
28
+ * `brushed`, `brushed-round` and `blasted` are micro-relief materials: they
29
+ * paint their grain into BOTH of the host's pseudo-elements, so a part that
30
+ * already spends one of its own has to give them an inner layer rather than
31
+ * wear them directly. `ButtonCap` is the only part in this package that does.
32
+ *
33
+ * `brushed-round` is the same aluminium as `brushed` with the grain spun
34
+ * round the element's centre, so it belongs on round faces — a knob cap, a
35
+ * round button — where the centre it turns about is a real feature. */
36
+ type AmbientMaterial = "matte" | "shiny" | "glass" | "brushed" | "brushed-round" | "blasted";
37
+
21
38
  interface AmbientPanelProps extends HTMLAttributes<HTMLDivElement> {
22
- material?: "matte" | "shiny" | "glass";
39
+ material?: AmbientMaterial;
23
40
  }
41
+ /** A panel is a plain surface, so it can wear any finish directly: it spends
42
+ * neither pseudo-element of its own, which is what the two micro-relief
43
+ * materials need. Their grain paints below the panel's children, and their
44
+ * `overflow: hidden` clips whatever hangs outside it — a control's drop
45
+ * shadow at the very edge included. */
24
46
  declare function AmbientPanel({ className, material, ...props }: AmbientPanelProps): react_jsx_runtime.JSX.Element;
25
47
 
26
48
  type AmbientRackGap = "tight" | "normal" | "loose";
@@ -33,66 +55,741 @@ interface AmbientRackProps extends HTMLAttributes<HTMLDivElement> {
33
55
  }
34
56
  declare function AmbientRack({ className, gap, direction, ...props }: AmbientRackProps): react_jsx_runtime.JSX.Element;
35
57
 
58
+ /** The four frames every control renders, in paint order.
59
+ *
60
+ * - `panel` static, behind the control, allowed to overflow its box
61
+ * - `base` static, the control's own footprint
62
+ * - `actuator` the moving part: the frame the control transforms
63
+ * - `fixture` static, above the actuator
64
+ *
65
+ * A part dropped into a frame is ordinary markup. The control moves the
66
+ * frame; the part only has to look like something. */
67
+ type ControlParts = {
68
+ panel?: ReactNode | undefined;
69
+ base?: ReactNode | undefined;
70
+ actuator?: ReactNode | undefined;
71
+ fixture?: ReactNode | undefined;
72
+ };
73
+ type FrameName = keyof ControlParts;
74
+ /** `"sm" | "md" | "lg"` picks a size from the family's table; any other
75
+ * string is used as a CSS length for `--ambx-size`. */
76
+ type ControlSize = "sm" | "md" | "lg" | (string & {});
77
+ /** How the actuator moves to a new position.
78
+ *
79
+ * - `follow` 1:1 with the pointer, no transition — right while dragging
80
+ * - `ease` transitions, right for keyboard and click-to-set
81
+ * - `snap` instant, for a detented control that should read as clicking
82
+ *
83
+ * The default is contextual: follow while `data-dragging` is on the root,
84
+ * ease otherwise. That needs no JS — see `.ambx-control` in styles.css. */
85
+ type ControlAnimate = "auto" | "follow" | "ease" | "snap";
86
+ /** What a part can read, via `useControlState()`. The same values are on
87
+ * the control root as custom properties, which are canonical; this is a
88
+ * typed view of them for parts that genuinely need JS. */
89
+ type ControlState = {
90
+ /** The raw value. Booleans arrive as 0/1, selections as the index. */
91
+ value: number;
92
+ min: number;
93
+ max: number;
94
+ /** Normalised position, 0-1. */
95
+ percent: number;
96
+ /** Degrees clockwise from 12 o'clock. 0 for non-rotary controls. */
97
+ angle: number;
98
+ /** Sweep origin and extent in degrees. 0 for non-rotary controls. */
99
+ travelStart: number;
100
+ travelSweep: number;
101
+ /** Rest positions along the travel; 0 means continuous. */
102
+ detents: number;
103
+ dragging: boolean;
104
+ disabled: boolean;
105
+ atMin: boolean;
106
+ atMax: boolean;
107
+ };
108
+
109
+ /** A transport key, a latching mute and an auto-repeating nudge button are
110
+ * the same object with three state machines and identical paint. */
111
+ type PressMode = "momentary" | "toggle" | "repeat";
112
+ type UsePressOptions = {
113
+ mode?: PressMode | undefined;
114
+ /** `toggle` mode only: the pressed-in state. */
115
+ value?: boolean | undefined;
116
+ defaultValue?: boolean | undefined;
117
+ onChange?: ((next: boolean) => void) | undefined;
118
+ /** Fires once per activation, and repeatedly in `repeat` mode. */
119
+ onPress?: (() => void) | undefined;
120
+ /** `repeat` mode: delay before repeating starts, then its interval. */
121
+ repeatDelay?: number | undefined;
122
+ repeatInterval?: number | undefined;
123
+ disabled?: boolean | undefined;
124
+ };
125
+ declare function usePress(options: UsePressOptions): {
126
+ state: ControlState;
127
+ rootProps: {
128
+ onPointerDown: (event: PointerEvent<HTMLButtonElement>) => void;
129
+ onPointerUp: () => void;
130
+ onPointerCancel: () => void;
131
+ onPointerLeave: () => void;
132
+ onClick: () => void;
133
+ type: "button";
134
+ disabled: boolean;
135
+ "aria-pressed": boolean | undefined;
136
+ style: CSSProperties;
137
+ "data-pressed": string | undefined;
138
+ "data-mode": PressMode;
139
+ };
140
+ pressed: boolean;
141
+ on: boolean;
142
+ setOn: (next: boolean) => void;
143
+ };
144
+
145
+ type AmbientPressProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onChange" | "value" | "defaultValue" | "type"> & UsePressOptions & {
146
+ parts?: ControlParts | undefined;
147
+ size?: ControlSize | undefined;
148
+ };
149
+ /** A key that sinks under a finger, with no appearance of its own.
150
+ *
151
+ * Its frames are `display: contents` markers rather than boxes, because a
152
+ * button is sized by its cap: the width is `min-width` plus the legend.
153
+ * Wrapping the cap in a positioned frame would collapse the control. */
154
+ declare function AmbientPress({ parts, size, className, mode, value, defaultValue, onChange, onPress, repeatDelay, repeatInterval, disabled, children, ...rest }: AmbientPressProps): react_jsx_runtime.JSX.Element;
155
+
156
+ /** How pointer movement becomes value.
157
+ *
158
+ * These are three different mappings, not one with a parameter, and the
159
+ * library was ambiguous about which it implemented: the code did `angle`
160
+ * while knob.md documented `drag`. Naming it makes that drift impossible.
161
+ *
162
+ * - `drag` pointer DISTANCE -> value delta. The v3 default: it is what the
163
+ * docs always described, what most audio software does, and the
164
+ * only one that behaves on touch, where a finger covers the knob.
165
+ * - `angle` pointer POSITION -> absolute angle -> value. Needs a dead zone,
166
+ * and needs a sweep under a full turn to be unambiguous.
167
+ * - `delta` accumulated angular change. The endless-encoder mapping: no
168
+ * ends, no dead zone, and the value may wrap independently of
169
+ * where the pointer happens to be. */
170
+ type RotaryInput = "drag" | "angle" | "delta";
171
+ /** The pot's travel in degrees clockwise from 12 o'clock. A bare number is
172
+ * a sweep centred on 12 o'clock, so `270` is the default 8-to-4 pot. */
173
+ type RotaryTravel = number | {
174
+ start: number;
175
+ sweep: number;
176
+ };
177
+ type UseRotaryOptions = {
178
+ value?: number | undefined;
179
+ defaultValue?: number | undefined;
180
+ min?: number | undefined;
181
+ max?: number | undefined;
182
+ /** Quantises the VALUE. `0` leaves it continuous. */
183
+ step?: number | undefined;
184
+ /** Quantises the TRAVEL: rest positions along the arc. Defaults to the
185
+ * step grid, but the two are independent — an endless encoder can have
186
+ * 24 detents per turn while its value stays continuous. */
187
+ detents?: number | undefined;
188
+ travel?: RotaryTravel | undefined;
189
+ input?: RotaryInput | undefined;
190
+ /** Pixels of drag for one full range, in `drag` mode. */
191
+ dragDistance?: number | undefined;
192
+ /** `delta` mode only: run past the ends and come round again. */
193
+ wrap?: boolean | undefined;
194
+ disabled?: boolean | undefined;
195
+ onChange?: ((next: number) => void) | undefined;
196
+ };
197
+ declare function useRotary(options: UseRotaryOptions): {
198
+ state: ControlState;
199
+ rootProps: {
200
+ onPointerDown: (event: PointerEvent<HTMLDivElement>) => void;
201
+ onPointerMove: (event: PointerEvent<HTMLDivElement>) => void;
202
+ onPointerUp: () => void;
203
+ onPointerCancel: () => void;
204
+ onKeyDown: (event: KeyboardEvent<HTMLDivElement>) => void;
205
+ ref: react.RefObject<HTMLDivElement | null>;
206
+ role: "slider";
207
+ "aria-valuemin": number;
208
+ "aria-valuemax": number;
209
+ "aria-valuenow": number;
210
+ "aria-orientation": "vertical";
211
+ "aria-disabled": true | undefined;
212
+ tabIndex: number;
213
+ style: CSSProperties;
214
+ };
215
+ setValue: (next: number) => void;
216
+ };
217
+
218
+ /** The five control families a kit can dress. */
219
+ type ControlFamily = "rotary" | "travel" | "press" | "latch" | "bank";
220
+ /** What a preset hands its kit: the look options the caller asked for.
221
+ *
222
+ * Deliberately loose. Look props are *kit vocabulary* — `knurling` and
223
+ * `markers` are words the grounded kit made up — so a kit reads the keys it
224
+ * understands and ignores the rest, exactly as a stylesheet that never
225
+ * implemented a class simply does not react to it. See `looks` below for
226
+ * how that stays honest rather than silent. */
227
+ type KitLook = Record<string, unknown>;
228
+ /** A dressed control: what goes in the frames, and what the root wears.
229
+ *
230
+ * The class comes from the kit rather than the preset because it is part of
231
+ * the look — `.amb-knob` carries the grounded knob's own token table, and a
232
+ * kit that replaces the parts has no use for it. */
233
+ type KitDress = {
234
+ parts: ControlParts;
235
+ className?: string | undefined;
236
+ };
237
+ /** Presentation defaults a visual identity may legitimately set.
238
+ *
239
+ * Narrow on purpose: a kit says how its controls should *feel* to turn, not
240
+ * what they are worth. Anything touching value, range or handlers stays with
241
+ * the caller. */
242
+ type KitDefaults = {
243
+ rotary?: {
244
+ travel?: RotaryTravel;
245
+ input?: RotaryInput;
246
+ animate?: ControlAnimate;
247
+ } | undefined;
248
+ travel?: {
249
+ animate?: ControlAnimate;
250
+ } | undefined;
251
+ latch?: {
252
+ animate?: ControlAnimate;
253
+ } | undefined;
254
+ };
255
+ type ControlKit = {
256
+ name: string;
257
+ rotary?: ((look: KitLook) => KitDress) | undefined;
258
+ travel?: ((look: KitLook) => KitDress) | undefined;
259
+ press?: ((look: KitLook) => KitDress) | undefined;
260
+ latch?: ((look: KitLook) => KitDress) | undefined;
261
+ bank?: ((look: KitLook) => KitDress) | undefined;
262
+ defaults?: KitDefaults | undefined;
263
+ /** Look keys each family honours. Used only to warn in development when a
264
+ * caller passes a prop the active kit is going to drop on the floor —
265
+ * the one real cost of letting look props pass through untyped. */
266
+ looks?: Partial<Record<ControlFamily, readonly string[]>> | undefined;
267
+ };
268
+ /** Dress every control below this point in `kit`.
269
+ *
270
+ * A kit is a plain object, so shipping one is shipping a module: no
271
+ * registry, no lifecycle, nothing to initialise. A kit that leaves a family
272
+ * undefined falls through to the default, which is what a real third-party
273
+ * kit will do for most of them. */
274
+ declare function AmbientKitProvider({ kit, children }: PropsWithChildren<{
275
+ kit: ControlKit;
276
+ }>): react_jsx_runtime.JSX.Element;
277
+ declare function useKit(): ControlKit | null;
278
+ /** Resolve a family's dressing: the active kit if it dresses this family,
279
+ * otherwise the fallback the preset was built around. */
280
+ declare function useDress<F extends ControlFamily>(family: F, look: KitLook, fallback: (look: KitLook) => KitDress): {
281
+ dress: KitDress;
282
+ defaults: F extends keyof KitDefaults ? KitDefaults[F] : undefined;
283
+ };
284
+
36
285
  type AmbientButtonShape = "pill" | "round" | "square";
37
286
  type AmbientButtonSize = "sm" | "md" | "lg";
38
- interface AmbientButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
39
- material?: "matte" | "shiny" | "glass";
40
- shape?: AmbientButtonShape;
41
- size?: AmbientButtonSize;
42
- }
43
- declare function AmbientButton({ className, children, material, shape, size, ...props }: AmbientButtonProps): react_jsx_runtime.JSX.Element;
287
+ type AmbientButtonProps = Omit<AmbientPressProps, "parts" | "size"> & {
288
+ material?: AmbientMaterial | undefined;
289
+ shape?: AmbientButtonShape | undefined;
290
+ size?: AmbientButtonSize | undefined;
291
+ /** Look options in the active kit's vocabulary. */
292
+ look?: KitLook | undefined;
293
+ };
294
+ /** A key cap seated in a clearance well. */
295
+ declare function AmbientButton({ className, children, look, material, shape, size, ...rest }: AmbientButtonProps): react_jsx_runtime.JSX.Element;
296
+
297
+ type UseLatchOptions = {
298
+ value?: boolean | undefined;
299
+ defaultValue?: boolean | undefined;
300
+ onChange?: ((next: boolean) => void) | undefined;
301
+ disabled?: boolean | undefined;
302
+ };
303
+ /** A two-position travel. Kept distinct from Press because its actuator
304
+ * genuinely slides rather than sinking, and because its ARIA is
305
+ * `role="switch"` — a switch is a state, not an action. */
306
+ declare function useLatch(options: UseLatchOptions): {
307
+ state: ControlState;
308
+ rootProps: {
309
+ onClick: () => void;
310
+ type: "button";
311
+ role: "switch";
312
+ "aria-checked": boolean;
313
+ disabled: boolean;
314
+ style: CSSProperties;
315
+ };
316
+ on: boolean;
317
+ setOn: (next: boolean) => void;
318
+ };
319
+
320
+ type AmbientLatchProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onChange" | "value" | "defaultValue" | "type"> & UseLatchOptions & {
321
+ parts?: ControlParts | undefined;
322
+ size?: ControlSize | undefined;
323
+ animate?: ControlAnimate | undefined;
324
+ label?: ReactNode | undefined;
325
+ };
326
+ /** A two-position slide, with no appearance of its own. The control IS the
327
+ * track: its actuator is a pill-sized frame that travels across it, which
328
+ * is why anything sitting beside a switch — a lamp, a legend — belongs to
329
+ * whatever composes it rather than to the switch. */
330
+ declare function AmbientLatch({ parts, size, animate, label, className, value, defaultValue, onChange, disabled, children, ...rest }: AmbientLatchProps): react_jsx_runtime.JSX.Element;
44
331
 
45
332
  type AmbientSwitchSize = "sm" | "md" | "lg";
46
- type AmbientSwitchProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onChange"> & {
47
- checked?: boolean;
48
- defaultChecked?: boolean;
49
- onCheckedChange?: (checked: boolean) => void;
50
- size?: AmbientSwitchSize;
51
- label?: string;
52
- /** Show a small LED indicator that lights up when the switch is on. Pass `true` for default green or a CSS color string. */
53
- led?: boolean | string;
54
- };
55
- declare function AmbientSwitch({ className, checked, defaultChecked, onCheckedChange, onClick, size, label, led, children, ...props }: AmbientSwitchProps): react_jsx_runtime.JSX.Element;
56
-
57
- type AmbientKnobVariant = "dot" | "line" | "flute" | "cap" | "wheel";
333
+ type AmbientSwitchProps = Omit<AmbientLatchProps, "parts" | "size"> & {
334
+ size?: AmbientSwitchSize | undefined;
335
+ /** A lamp above the switch. `true` for the scene's own colour, or any
336
+ * CSS colour string. */
337
+ led?: boolean | string | undefined;
338
+ /** Look options in the active kit's vocabulary. */
339
+ look?: KitLook | undefined;
340
+ };
341
+ /** A pill sliding in a dark stadium recess, with an optional lamp above.
342
+ *
343
+ * The lamp is mounted beside the switch rather than inside it: the latch
344
+ * control IS the track, so anything that is neither track nor pill belongs
345
+ * to whatever composes them. That means the preset has to hold the state —
346
+ * the lamp and the pill read the same boolean, and only their common
347
+ * parent can see both.
348
+ */
349
+ declare function AmbientSwitch({ size, led, label, look, value, defaultValue, onChange, animate, className, ...rest }: AmbientSwitchProps): react_jsx_runtime.JSX.Element;
350
+
351
+ type BankOption = {
352
+ value: string;
353
+ /** Key legend — a numeral in the referent, but any node works. */
354
+ label?: ReactNode | undefined;
355
+ /** Accessible name, and the hover title. Needed whenever the legend is a
356
+ * glyph or an icon: a key reading "*" has no name otherwise. */
357
+ ariaLabel?: string | undefined;
358
+ /** Overrides the bank's lamp colour for this key only. */
359
+ color?: string | undefined;
360
+ disabled?: boolean | undefined;
361
+ };
362
+ type BankOrientation = "vertical" | "horizontal";
363
+ type UseBankOptions = {
364
+ options: BankOption[];
365
+ value?: string | string[] | undefined;
366
+ defaultValue?: string | string[] | undefined;
367
+ onChange?: ((value: string | string[]) => void) | undefined;
368
+ /** Let more than one lamp be lit at a time. */
369
+ multiple?: boolean | undefined;
370
+ orientation?: BankOrientation | undefined;
371
+ disabled?: boolean | undefined;
372
+ };
373
+ /** N presses sharing a selection model.
374
+ *
375
+ * The roving tabindex, the radiogroup-versus-checkbox-group keyboard
376
+ * difference and the selection-follows-focus rule are the reason this
377
+ * mechanism exists: they are the part nobody should have to re-derive in
378
+ * order to change what a key looks like. */
379
+ declare function useBank(options: UseBankOptions): {
380
+ selected: string[];
381
+ select: (option: BankOption | undefined) => void;
382
+ rootProps: {
383
+ role: "group" | "radiogroup";
384
+ "aria-orientation": BankOrientation | undefined;
385
+ "aria-disabled": true | undefined;
386
+ "data-orientation": BankOrientation;
387
+ "data-disabled": string | undefined;
388
+ };
389
+ keyProps: (option: BankOption, index: number) => {
390
+ key: string;
391
+ type: "button";
392
+ ref: (node: HTMLButtonElement | null) => void;
393
+ role: "checkbox" | "radio";
394
+ "aria-checked": boolean;
395
+ "aria-label": string | undefined;
396
+ title: string | undefined;
397
+ disabled: boolean;
398
+ tabIndex: number;
399
+ "data-on": string | undefined;
400
+ onClick: () => void;
401
+ onKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => void;
402
+ };
403
+ };
404
+
405
+ type AmbientBankProps = Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> & UseBankOptions & {
406
+ /** Parts applied to every key. The bank's own frames go in `parts`. */
407
+ keyParts?: ControlParts | undefined;
408
+ parts?: ControlParts | undefined;
409
+ size?: ControlSize | undefined;
410
+ /** Lamp colour. Defaults to the scene's `--amb-highlight-color`. */
411
+ color?: string | undefined;
412
+ label?: ReactNode | undefined;
413
+ /** Per-key override, for a bank whose keys are not interchangeable. */
414
+ renderKey?: ((option: BankOption, on: boolean) => ReactNode) | undefined;
415
+ };
416
+ /** N keys sharing a selection model, with no appearance of its own.
417
+ *
418
+ * Parts apply per key rather than once, because that is what a bank is.
419
+ * The focusable element is the key `<button>`, which this mechanism
420
+ * renders — your parts go inside it, which is why the presentational-parts
421
+ * rule still holds here. */
422
+ declare function AmbientBank({ options, keyParts, parts, size, color, label, renderKey, className, style, value, defaultValue, onChange, multiple, orientation, disabled, ...rest }: AmbientBankProps): react_jsx_runtime.JSX.Element;
423
+
424
+ type AmbientSelectOption = BankOption;
425
+ type AmbientSelectOrientation = BankOrientation;
426
+ type AmbientSelectSize = "sm" | "md" | "lg";
427
+ type AmbientSelectProps = Omit<AmbientBankProps, "keyParts" | "parts" | "size"> & {
428
+ size?: AmbientSelectSize | undefined;
429
+ /** Look options in the active kit's vocabulary. */
430
+ look?: KitLook | undefined;
431
+ };
432
+ /**
433
+ * A bank of lamp-lit keys in a shared rail — the hardware idiom for a
434
+ * mode selector, where the state is a lamp rather than a mark.
435
+ *
436
+ * Three physical layers per key, and the ORDER is the whole trick: the key
437
+ * itself is the pocket floor, the lens is a disc lying on it, and the cap
438
+ * is a translucent diffuser over both. The cap is `.amb-mat-glass`, so its
439
+ * backdrop-filter blurs the lens behind it — which is why the lens goes in
440
+ * the `base` frame and the cap in `actuator`, and why swapping them would
441
+ * put out every lamp.
442
+ */
443
+ declare function AmbientSelect({ size, look, className, ...rest }: AmbientSelectProps): react_jsx_runtime.JSX.Element;
444
+
445
+ type AmbientRotaryProps = Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> & UseRotaryOptions & {
446
+ parts?: ControlParts | undefined;
447
+ size?: ControlSize | undefined;
448
+ animate?: ControlAnimate | undefined;
449
+ label?: ReactNode | undefined;
450
+ };
451
+ /** A rotary mechanism with no appearance of its own.
452
+ *
453
+ * It owns the value, the three pointer mappings, the sweep, the keyboard
454
+ * contract and the ARIA; what it looks like is entirely the `parts` you
455
+ * give it. There is deliberately no `material` prop: which element a
456
+ * material belongs on is a fact about a particular knob's construction —
457
+ * on the clipped face when it is knurled, on the body when it is not —
458
+ * and a mechanism cannot know that once the body is yours. Presets carry
459
+ * `material`, because a preset knows its own parts. */
460
+ declare function AmbientRotary({ parts, size, animate, label, className, value, defaultValue, min, max, step, detents, travel, input, dragDistance, wrap, disabled, onChange, ...rest }: AmbientRotaryProps): react_jsx_runtime.JSX.Element;
461
+
462
+ /** Printed scale dots on the panel around the knob: the arc's two ends, the
463
+ * full graduated ring, or nothing. */
464
+ type AmbientKnobMarkers = "none" | "ends" | "full";
465
+ /** The pointer riding the rotating face: a radial bar or an offset dot. */
466
+ type AmbientKnobIndicator = "rectangle" | "circle";
58
467
  type AmbientKnobSize = "sm" | "md" | "lg";
59
- type AmbientKnobProps = Omit<HTMLAttributes<HTMLDivElement>, "onChange"> & {
60
- value: number;
61
- min?: number;
62
- max?: number;
63
- step?: number;
64
- label?: string;
65
- material?: "matte" | "shiny" | "glass";
66
- variant?: AmbientKnobVariant;
67
- size?: AmbientKnobSize;
68
- onChange?: (nextValue: number) => void;
468
+ type AmbientKnobProps = Omit<AmbientRotaryProps, "parts" | "size"> & {
469
+ material?: AmbientMaterial | undefined;
470
+ /** Ribbed grip around the rotating body. Off gives a smooth turned body. */
471
+ knurling?: boolean | undefined;
472
+ /** The grip ring's own colour, for a two-tone knob — any CSS colour, read
473
+ * as an albedo, so it still takes the scene's light rather than being
474
+ * painted flat. Unset, the ring is the same material as the cap. */
475
+ knurlColor?: string | undefined;
476
+ markers?: AmbientKnobMarkers | undefined;
477
+ indicator?: AmbientKnobIndicator | undefined;
478
+ size?: AmbientKnobSize | undefined;
479
+ /** Look options in another kit's vocabulary.
480
+ *
481
+ * The five props above are the *grounded* kit's words. A kit that dresses
482
+ * knobs differently has its own, and they arrive here rather than as
483
+ * loose props so the common path stays typed and a misspelt `knurling`
484
+ * is still a compile error. Kits usually ship a preset of their own that
485
+ * types this properly — see `ConsoleKnob`. */
486
+ look?: KitLook | undefined;
487
+ };
488
+ /**
489
+ * The rotary preset: `AmbientRotary` wearing whatever the active kit says a
490
+ * knob looks like, and the grounded hardware knob when no kit is set.
491
+ */
492
+ declare function AmbientKnob({ material, knurling, knurlColor, markers, indicator, look, size, className, travel, input, animate, ...rest }: AmbientKnobProps): react_jsx_runtime.JSX.Element;
493
+
494
+ type TravelOrientation = "horizontal" | "vertical";
495
+ type UseTravelOptions = {
496
+ value?: number | undefined;
497
+ defaultValue?: number | undefined;
498
+ min?: number | undefined;
499
+ max?: number | undefined;
500
+ step?: number | undefined;
501
+ detents?: number | undefined;
502
+ orientation?: TravelOrientation | undefined;
503
+ /** Flip which end of the axis is `min`. */
504
+ invert?: boolean | undefined;
505
+ disabled?: boolean | undefined;
506
+ onChange?: ((next: number) => void) | undefined;
507
+ };
508
+ /** A value riding a straight track.
509
+ *
510
+ * One mechanism behind both the slider and the fader, which before v3 were
511
+ * the same file twice with X and Y swapped. The only real difference is
512
+ * which axis the pointer reads — and which end is `min`, where the two
513
+ * disagree for a physical reason rather than a stylistic one: a horizontal
514
+ * track runs min-at-the-left, and an upright one runs min-at-the-bottom,
515
+ * because that is how a fader is built. Both are the default here, so
516
+ * neither preset has to ask. */
517
+ declare function useTravel(options: UseTravelOptions): {
518
+ state: ControlState;
519
+ rootProps: {
520
+ onPointerDown: (event: PointerEvent<HTMLDivElement>) => void;
521
+ onPointerMove: (event: PointerEvent<HTMLDivElement>) => void;
522
+ onPointerUp: () => void;
523
+ onPointerCancel: () => void;
524
+ onKeyDown: (event: KeyboardEvent<HTMLDivElement>) => void;
525
+ ref: react.RefObject<HTMLDivElement | null>;
526
+ role: "slider";
527
+ "aria-valuemin": number;
528
+ "aria-valuemax": number;
529
+ "aria-valuenow": number;
530
+ "aria-orientation": TravelOrientation;
531
+ "aria-disabled": true | undefined;
532
+ tabIndex: number;
533
+ style: CSSProperties;
534
+ "data-orientation": TravelOrientation;
535
+ };
536
+ setValue: (next: number) => void;
537
+ };
538
+
539
+ type AmbientTravelProps = Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> & UseTravelOptions & {
540
+ parts?: ControlParts | undefined;
541
+ size?: ControlSize | undefined;
542
+ animate?: ControlAnimate | undefined;
543
+ label?: ReactNode | undefined;
69
544
  };
70
- declare function AmbientKnob({ value, min, max, step, label, material, variant, size, onChange, className, ...props }: AmbientKnobProps): react_jsx_runtime.JSX.Element;
545
+ /** A value on a straight track, with no appearance of its own. */
546
+ declare function AmbientTravel({ parts, size, animate, label, className, value, defaultValue, min, max, step, detents, orientation, invert, disabled, onChange, ...rest }: AmbientTravelProps): react_jsx_runtime.JSX.Element;
71
547
 
72
548
  type AmbientFaderSize = "sm" | "md" | "lg";
73
- type AmbientFaderProps = Omit<HTMLAttributes<HTMLDivElement>, "onChange"> & {
74
- value: number;
75
- min?: number;
76
- max?: number;
77
- step?: number;
78
- label?: string;
79
- material?: "matte" | "shiny" | "glass";
80
- size?: AmbientFaderSize;
81
- onChange?: (nextValue: number) => void;
549
+ type AmbientFaderProps = Omit<AmbientTravelProps, "parts" | "size" | "orientation"> & {
550
+ material?: AmbientMaterial | undefined;
551
+ size?: AmbientFaderSize | undefined;
552
+ /** Look options in the active kit's vocabulary. */
553
+ look?: KitLook | undefined;
82
554
  };
83
- declare function AmbientFader({ value, min, max, step, label, material, size, onChange, className, ...props }: AmbientFaderProps): react_jsx_runtime.JSX.Element;
555
+ /** A pill cap on a stem, riding an upright slot. Min is at the bottom,
556
+ * which `AmbientTravel` already does for a vertical track. */
557
+ declare function AmbientFader({ material, look, size, animate, className, ...rest }: AmbientFaderProps): react_jsx_runtime.JSX.Element;
84
558
 
85
559
  type AmbientSliderSize = "sm" | "md" | "lg";
86
- type AmbientSliderProps = Omit<HTMLAttributes<HTMLDivElement>, "onChange"> & {
87
- value: number;
88
- min?: number;
89
- max?: number;
90
- step?: number;
91
- label?: string;
92
- material?: "matte" | "shiny" | "glass";
93
- size?: AmbientSliderSize;
94
- onChange?: (nextValue: number) => void;
560
+ type AmbientSliderProps = Omit<AmbientTravelProps, "parts" | "size" | "orientation"> & {
561
+ material?: AmbientMaterial | undefined;
562
+ size?: AmbientSliderSize | undefined;
563
+ /** Look options in the active kit's vocabulary. */
564
+ look?: KitLook | undefined;
565
+ };
566
+ /** A domed disc gliding over a shallow concave channel. */
567
+ declare function AmbientSlider({ material, look, size, animate, className, ...rest }: AmbientSliderProps): react_jsx_runtime.JSX.Element;
568
+
569
+ /** The Blender-grounded hardware look: the default every preset falls back to. */
570
+ declare const groundedKit: ControlKit;
571
+
572
+ declare const consoleKit: ControlKit;
573
+ type ConsoleKnobProps = Omit<AmbientKnobProps, "look" | "material" | "knurling" | "knurlColor" | "markers" | "indicator"> & {
574
+ /** The accent centre mark printed above the knob. */
575
+ mark?: boolean | undefined;
576
+ /** The −/+ legends at the ends of the travel. */
577
+ legend?: boolean | undefined;
578
+ };
579
+ declare function ConsoleKnob({ mark, legend, ...rest }: ConsoleKnobProps): react_jsx_runtime.JSX.Element;
580
+ type ConsoleToggleProps = Omit<AmbientSwitchProps, "look">;
581
+ declare function ConsoleToggle(props: ConsoleToggleProps): react_jsx_runtime.JSX.Element;
582
+
583
+ /** The knob's base: a flat face housed in a circular groove.
584
+ *
585
+ * Two elements, because they are two pieces of the panel: the groove is the
586
+ * cut, and the face is the flat disc sitting in it, showing the ring of the
587
+ * cut around itself. Neither carries `.ambient` — the face has no body, so
588
+ * there is no edge to cut and nothing to cast, and every cue that reads as
589
+ * depth here belongs either to the walls of the housing or to the bar
590
+ * standing on the face.
591
+ *
592
+ * It sits in the `base` frame, so it does not turn. A plain disc looks the
593
+ * same at every angle, and leaving it still keeps the scene's light on it
594
+ * untouched — only the bar has to do the work below. */
595
+ declare function ConsoleWell({ className }: {
596
+ className?: string | undefined;
597
+ }): react_jsx_runtime.JSX.Element;
598
+ /** The actuator: a cuboid bar lying across the disc on its diameter.
599
+ *
600
+ * Two spans, and the nesting is load-bearing. The bar rides the rotating
601
+ * actuator frame, so a chamfer highlight painted from the inherited light
602
+ * would turn with it and put the lit edge on the wrong side of the screen
603
+ * at half the angles. The fix is to rotate the LIGHT the other way: the
604
+ * outer span captures the scene's light vector, the inner one re-states it
605
+ * in the frame's own turned coordinates, so the bright edge and the drop
606
+ * shadow both stay put on screen while the bar sweeps under them.
607
+ *
608
+ * It has to be two elements because a custom property cannot read itself —
609
+ * `--amb-light-x: calc(var(--amb-light-x) ...)` is a cycle, which resolves
610
+ * to invalid at computed-value time and takes the whole `box-shadow`
611
+ * composite down with it, silently. */
612
+ declare function ConsoleBar({ className }: {
613
+ className?: string | undefined;
614
+ }): react_jsx_runtime.JSX.Element;
615
+ /** Panel graphics around the knob: the accent centre mark above it, and the
616
+ * −/+ legends at the ends of the travel. Both sit outside the knob's own
617
+ * box, which is what the `panel` frame is for. */
618
+ declare function ConsoleMarks({ mark, legend, className }: {
619
+ mark?: boolean | undefined;
620
+ legend?: boolean | undefined;
621
+ className?: string | undefined;
622
+ }): react_jsx_runtime.JSX.Element;
623
+ /** The toggle's track: a pill groove that fills with the accent as the
624
+ * switch travels.
625
+ *
626
+ * A pure-CSS part — it reads `--ambx-percent` off the control root and
627
+ * mixes its own colour from it, so the mechanism does not know this element
628
+ * exists and no React state reaches it. */
629
+ declare function ToggleTrack({ className }: {
630
+ className?: string | undefined;
631
+ }): react_jsx_runtime.JSX.Element;
632
+ /** The travelling thumb: an accent disc inside a white ring.
633
+ *
634
+ * Flat on top and deliberately so — no chamfer, no fillet — but still a
635
+ * knob-scale body, so it casts. That pairing is why the classes are spelt
636
+ * out rather than reached through `.amb-fillet-2`: the edge treatments set
637
+ * a thickness of their own, and here the thickness is wanted without the
638
+ * cut that usually comes with it. */
639
+ declare function ToggleThumb({ className }: {
640
+ className?: string | undefined;
641
+ }): react_jsx_runtime.JSX.Element;
642
+
643
+ /** Read the enclosing control's state from inside a part.
644
+ *
645
+ * This is the third outlet of the state channel, and the one to reach for
646
+ * last: the custom properties on the control root are canonical, and a
647
+ * part that can be styled from CSS should be. Use this when a part needs
648
+ * the value as a JS number — a readout, a tick ring that has to emit N
649
+ * children, an SVG whose path data depends on the value. */
650
+ declare function useControlState(): ControlState;
651
+ /** A bank key carries more than a number: its legend, its accessible name
652
+ * and its own lamp colour all belong to the option, not to the state. So a
653
+ * key's parts get this alongside `useControlState()`. */
654
+ type BankKeyState = {
655
+ option: BankOption;
656
+ on: boolean;
657
+ index: number;
658
+ };
659
+ declare function useBankKey(): BankKeyState;
660
+
661
+ /** The knob's cap: the smooth chamfered disc that is most of what you see,
662
+ * and the element that carries the drop shadow.
663
+ *
664
+ * `flush` takes the full width, which is what a smooth turned knob wants.
665
+ * The default sits back by the knurl band so a `KnurledFace` can ring it —
666
+ * the same cap either way, and the chamfer the referent cuts on every knob
667
+ * (knob.py's `chamfer=0.35`, regardless of rib count) either way too. */
668
+ declare function KnobBody({ material, flush, className }: {
669
+ material?: AmbientMaterial | undefined;
670
+ flush?: boolean | undefined;
671
+ className?: string | undefined;
672
+ }): react_jsx_runtime.JSX.Element;
673
+ /** The rotating knurl: a rim ring of ribs around the cap, clipped to the
674
+ * toothed annulus so the ribs break the outline instead of being painted
675
+ * inside a circle, and shaded per tooth — a lit flank climbing to each
676
+ * ridge, a shaded one falling away — with a contact-occlusion band along
677
+ * its inner edge where the cap overhangs it.
678
+ *
679
+ * The clip is the part's own business — it generates the path, emits its
680
+ * own `<defs>` and references it by a local id. A rotary mechanism has no
681
+ * idea any of this is happening, which is exactly the point: if this part
682
+ * needed help from the control to exist, the split would not be clean. */
683
+ declare function KnurledFace({ material, color, className }: {
684
+ material?: AmbientMaterial | undefined;
685
+ /** The ribs' own colour, as an albedo. */
686
+ color?: string | undefined;
687
+ className?: string | undefined;
688
+ }): react_jsx_runtime.JSX.Element;
689
+ /** The grounded referent's offset indicator dot (knob() dot_frac 0.12,
690
+ * dot_offset 0.68). Put it in the `actuator` frame and it sweeps; put it
691
+ * in `base` and it stays put while everything else turns. */
692
+ declare function IndicatorDot({ className }: {
693
+ className?: string | undefined;
694
+ }): react_jsx_runtime.JSX.Element;
695
+ /** A short radial bar out near the rim, running 0.50R to 0.84R. */
696
+ declare function IndicatorBar({ className }: {
697
+ className?: string | undefined;
698
+ }): react_jsx_runtime.JSX.Element;
699
+ type ScaleRingProps = {
700
+ /** Dots to print. `2` is the pair the travel starts and stops at. */
701
+ count?: number | undefined;
702
+ className?: string | undefined;
703
+ children?: ReactNode | undefined;
95
704
  };
96
- declare function AmbientSlider({ value, min, max, step, label, material, size, onChange, className, ...props }: AmbientSliderProps): react_jsx_runtime.JSX.Element;
705
+ /** Printed scale dots on the panel around a rotary, on the same arc the
706
+ * value sweeps.
707
+ *
708
+ * This is the part that has to read state as JS rather than CSS: the
709
+ * angles come from the control's own travel, and there is no way to emit
710
+ * N children from a stylesheet. It is also the proof that the context
711
+ * outlet works — the dots land on the sweep whatever `travel` is set to,
712
+ * without the ring being told. */
713
+ declare function ScaleRing({ count, className, children }: ScaleRingProps): react_jsx_runtime.JSX.Element;
714
+
715
+ /** The track a thumb rides in.
716
+ *
717
+ * Both grounded referents are grooves with a lume interior — dark in
718
+ * bright light, glowing in low light — but they are cut to different
719
+ * depths: a fader runs in a through-slot, a slider in a shallow concave
720
+ * channel (slider.py, 1mm deep = thickness 0.22). */
721
+ declare function TravelTrack({ depth, className }: {
722
+ depth?: "slot" | "channel" | undefined;
723
+ className?: string | undefined;
724
+ }): react_jsx_runtime.JSX.Element;
725
+ /** Fader cap: the referent (fader.py) is a pill on a stem — 7mm tall
726
+ * (thickness 1.5) riding 2.2mm above the plate (elevation 0.28) — with a
727
+ * single grip line across the top. */
728
+ declare function FaderCap({ material, className }: {
729
+ material?: AmbientMaterial | undefined;
730
+ className?: string | undefined;
731
+ }): react_jsx_runtime.JSX.Element;
732
+ /** Slider thumb: a domed disc gliding over the channel. */
733
+ declare function SliderThumb({ material, className }: {
734
+ material?: AmbientMaterial | undefined;
735
+ className?: string | undefined;
736
+ }): react_jsx_runtime.JSX.Element;
737
+
738
+ /** The key cap: a chamfered, subtly dished top that sinks on `:active`.
739
+ *
740
+ * The cap is what sizes a press control — its legend sets the width above
741
+ * the well's `min-width` — which is why a press control's frames are
742
+ * `display: contents` markers rather than boxes.
743
+ *
744
+ * The cap spends its own `::after` on the dish, so the two micro-relief
745
+ * materials cannot ride on it: their grain wants both pseudo-elements, and
746
+ * the dish's `background` shorthand and the grain's tile would each silently
747
+ * win half of the other's declarations. They get `.ambx-cap-face` instead —
748
+ * an inner layer under the dish and under the legend, which is the inner
749
+ * layer @ambientcss/css's own note prescribes. The cap itself stays a plain
750
+ * `amb-surface` when relief is down there: the dish's overlay alphas are
751
+ * derived from `--amb-shade` the ordinary way, and since the relief
752
+ * materials carry no `--amb-albedo` of their own any more, that derivation
753
+ * is already correct for them too — no per-material tone correction to
754
+ * apply. */
755
+ declare function ButtonCap({ material, className, children }: {
756
+ material?: AmbientMaterial | undefined;
757
+ className?: string | undefined;
758
+ children?: ReactNode | undefined;
759
+ }): react_jsx_runtime.JSX.Element;
760
+
761
+ /** The recess the pill slides in (switch.py: a 1.5mm well, thickness 0.33). */
762
+ declare function SwitchTrack({ className }: {
763
+ className?: string | undefined;
764
+ }): react_jsx_runtime.JSX.Element;
765
+ /** The sliding pill, standing 2.6mm above the recess floor. */
766
+ declare function SwitchPill({ className }: {
767
+ className?: string | undefined;
768
+ }): react_jsx_runtime.JSX.Element;
769
+ /** A pinprick indicator lamp. `color` is any CSS colour; unset it takes the
770
+ * scene's own lamp colour, the same `--amb-led-color` a bank reads. */
771
+ declare function Led({ on, color, className }: {
772
+ on?: boolean | undefined;
773
+ color?: string | undefined;
774
+ className?: string | undefined;
775
+ }): react_jsx_runtime.JSX.Element;
776
+
777
+ /** The lamp under a key: a big disc lying on the pocket floor.
778
+ *
779
+ * It has to paint BEFORE the cap, because the cap's `backdrop-filter` is
780
+ * what diffuses it — which is the whole trick, and the reason the lens
781
+ * belongs in the `base` frame and the cap in `actuator`. */
782
+ declare function KeyLens({ className }: {
783
+ className?: string | undefined;
784
+ }): react_jsx_runtime.JSX.Element;
785
+ /** The translucent diffuser over the lamp, carrying the key's legend.
786
+ *
787
+ * With no children it prints the option's own label, which is why a bank
788
+ * of numerals needs no `renderKey`: the part reads the option it belongs
789
+ * to out of the key context. */
790
+ declare function KeyCap({ className, children }: {
791
+ className?: string | undefined;
792
+ children?: ReactNode | undefined;
793
+ }): react_jsx_runtime.JSX.Element;
97
794
 
98
- export { AmbientButton, type AmbientButtonProps, type AmbientButtonShape, type AmbientButtonSize, AmbientFader, type AmbientFaderProps, type AmbientFaderSize, AmbientKnob, type AmbientKnobProps, type AmbientKnobSize, type AmbientKnobVariant, AmbientPanel, type AmbientPanelProps, AmbientProvider, type AmbientProviderProps, AmbientRack, type AmbientRackGap, type AmbientRackProps, AmbientSlider, type AmbientSliderProps, type AmbientSliderSize, AmbientSwitch, type AmbientSwitchProps, type AmbientSwitchSize, type AmbientTheme };
795
+ export { AmbientBank, type AmbientBankProps, AmbientButton, type AmbientButtonProps, type AmbientButtonShape, type AmbientButtonSize, AmbientFader, type AmbientFaderProps, type AmbientFaderSize, AmbientKitProvider, AmbientKnob, type AmbientKnobIndicator, type AmbientKnobMarkers, type AmbientKnobProps, type AmbientKnobSize, AmbientLatch, type AmbientLatchProps, type AmbientMaterial, AmbientPanel, type AmbientPanelProps, AmbientPress, type AmbientPressProps, AmbientProvider, type AmbientProviderProps, AmbientRack, type AmbientRackGap, type AmbientRackProps, AmbientRotary, type AmbientRotaryProps, AmbientSelect, type AmbientSelectOption, type AmbientSelectOrientation, type AmbientSelectProps, type AmbientSelectSize, AmbientSlider, type AmbientSliderProps, type AmbientSliderSize, AmbientSwitch, type AmbientSwitchProps, type AmbientSwitchSize, type AmbientTheme, AmbientTravel, type AmbientTravelProps, type BankKeyState, type BankOption, type BankOrientation, ButtonCap, ConsoleBar, ConsoleKnob, type ConsoleKnobProps, ConsoleMarks, ConsoleToggle, type ConsoleToggleProps, ConsoleWell, type ControlAnimate, type ControlFamily, type ControlKit, type ControlParts, type ControlSize, type ControlState, FaderCap, type FrameName, IndicatorBar, IndicatorDot, KeyCap, KeyLens, type KitDefaults, type KitDress, type KitLook, KnobBody, KnurledFace, Led, type PressMode, type RotaryInput, type RotaryTravel, ScaleRing, type ScaleRingProps, SliderThumb, SwitchPill, SwitchTrack, ToggleThumb, ToggleTrack, type TravelOrientation, TravelTrack, type UseBankOptions, type UseLatchOptions, type UsePressOptions, type UseRotaryOptions, type UseTravelOptions, consoleKit, groundedKit, useBank, useBankKey, useControlState, useDress, useKit, useLatch, usePress, useRotary, useTravel };