@kitlangton/knobs 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1270 -2
  3. package/dist/dev/chunk-e13znr7c.js +155 -0
  4. package/dist/dev/chunk-kdewv0mt.js +1334 -0
  5. package/dist/dev/chunk-ktsn42p7.js +15668 -0
  6. package/dist/dev/chunk-qtt698hp.js +1118 -0
  7. package/dist/dev/index.js +1389 -0
  8. package/dist/index.js +2367 -0
  9. package/dist/panel.js +23635 -0
  10. package/dist/react.js +326 -0
  11. package/dist/solid.js +336 -0
  12. package/dist/themes.js +235 -0
  13. package/dist/types/core/adapter.d.ts +21 -0
  14. package/dist/types/core/code.d.ts +3 -0
  15. package/dist/types/core/color.d.ts +27 -0
  16. package/dist/types/core/easing.d.ts +21 -0
  17. package/dist/types/core/internal.d.ts +18 -0
  18. package/dist/types/core/keys.d.ts +21 -0
  19. package/dist/types/core/mutate.d.ts +21 -0
  20. package/dist/types/core/ramp.d.ts +150 -0
  21. package/dist/types/core/random.d.ts +2 -0
  22. package/dist/types/core/readout.d.ts +73 -0
  23. package/dist/types/core/remote.d.ts +258 -0
  24. package/dist/types/core/schema.d.ts +526 -0
  25. package/dist/types/core/spring.d.ts +38 -0
  26. package/dist/types/core/storage.d.ts +8 -0
  27. package/dist/types/core/store.d.ts +474 -0
  28. package/dist/types/core/theme.d.ts +27 -0
  29. package/dist/types/core/variants.d.ts +17 -0
  30. package/dist/types/index.d.ts +13 -0
  31. package/dist/types/panel/attach.d.ts +5 -0
  32. package/dist/types/panel/choice.d.ts +56 -0
  33. package/dist/types/panel/die.d.ts +121 -0
  34. package/dist/types/panel/editors.d.ts +159 -0
  35. package/dist/types/panel/folds.d.ts +14 -0
  36. package/dist/types/panel/give.d.ts +73 -0
  37. package/dist/types/panel/gpu/atlas.d.ts +40 -0
  38. package/dist/types/panel/gpu/color.d.ts +128 -0
  39. package/dist/types/panel/gpu/die.wgsl.d.ts +1 -0
  40. package/dist/types/panel/gpu/renderer.d.ts +712 -0
  41. package/dist/types/panel/gpu/shader.wgsl.d.ts +2 -0
  42. package/dist/types/panel/haptics.d.ts +8 -0
  43. package/dist/types/panel/has.d.ts +14 -0
  44. package/dist/types/panel/hints.d.ts +45 -0
  45. package/dist/types/panel/icons.d.ts +30 -0
  46. package/dist/types/panel/index.d.ts +10 -0
  47. package/dist/types/panel/inspector.d.ts +20 -0
  48. package/dist/types/panel/measure.d.ts +6 -0
  49. package/dist/types/panel/menu.d.ts +38 -0
  50. package/dist/types/panel/morph.d.ts +38 -0
  51. package/dist/types/panel/motion.d.ts +77 -0
  52. package/dist/types/panel/page-hints.d.ts +82 -0
  53. package/dist/types/panel/page.d.ts +423 -0
  54. package/dist/types/panel/panel.d.ts +43 -0
  55. package/dist/types/panel/position.d.ts +5 -0
  56. package/dist/types/panel/prefs.d.ts +28 -0
  57. package/dist/types/panel/presets.d.ts +48 -0
  58. package/dist/types/panel/ramps.d.ts +14 -0
  59. package/dist/types/panel/readout-row.d.ts +21 -0
  60. package/dist/types/panel/reveal.d.ts +15 -0
  61. package/dist/types/panel/rolling-css.d.ts +2 -0
  62. package/dist/types/panel/search.d.ts +36 -0
  63. package/dist/types/panel/seg.d.ts +15 -0
  64. package/dist/types/panel/session.d.ts +21 -0
  65. package/dist/types/panel/slider.d.ts +92 -0
  66. package/dist/types/panel/smear.d.ts +7 -0
  67. package/dist/types/panel/spin.d.ts +13 -0
  68. package/dist/types/panel/styles.d.ts +11 -0
  69. package/dist/types/panel/swipe.d.ts +114 -0
  70. package/dist/types/panel/switcher.d.ts +20 -0
  71. package/dist/types/panel/tabs.d.ts +31 -0
  72. package/dist/types/panel/thumb.d.ts +36 -0
  73. package/dist/types/react/Knobs.d.ts +15 -0
  74. package/dist/types/react/VariantSwitcher.d.ts +10 -0
  75. package/dist/types/react/index.d.ts +3 -0
  76. package/dist/types/react/useControls.d.ts +11 -0
  77. package/dist/types/solid/index.d.ts +50 -0
  78. package/dist/types/themes/index.d.ts +1 -0
  79. package/dist/types/ui/bento-css.d.ts +1 -0
  80. package/dist/types/ui/bento-model.d.ts +96 -0
  81. package/dist/types/ui/bento.d.ts +77 -0
  82. package/dist/types/ui/commands.d.ts +11 -0
  83. package/dist/types/ui/confirm.d.ts +13 -0
  84. package/dist/types/ui/index.d.ts +20 -0
  85. package/dist/types/ui/layer.d.ts +70 -0
  86. package/dist/types/ui/look.d.ts +53 -0
  87. package/dist/types/ui/menu.d.ts +63 -0
  88. package/dist/types/ui/overlay-css.d.ts +1 -0
  89. package/dist/types/ui/overlay-model.d.ts +87 -0
  90. package/dist/types/ui/palette.d.ts +38 -0
  91. package/dist/types/ui/popover.d.ts +35 -0
  92. package/dist/types/ui/react.d.ts +45 -0
  93. package/dist/types/ui/readout-css.d.ts +1 -0
  94. package/dist/types/ui/readout-model.d.ts +137 -0
  95. package/dist/types/ui/readouts.d.ts +85 -0
  96. package/dist/types/ui/rgba.d.ts +6 -0
  97. package/dist/types/ui/solid.d.ts +40 -0
  98. package/dist/types/ui/split-model.d.ts +81 -0
  99. package/dist/types/ui/split.d.ts +75 -0
  100. package/dist/types/ui/timeline-model.d.ts +83 -0
  101. package/dist/types/ui/timeline.d.ts +76 -0
  102. package/dist/types/ui/toast.d.ts +19 -0
  103. package/dist/types/ui/tooltip.d.ts +19 -0
  104. package/dist/types/ui/tree-model.d.ts +125 -0
  105. package/dist/types/ui/tree.d.ts +95 -0
  106. package/dist/ui-react.js +6558 -0
  107. package/dist/ui-solid.js +6511 -0
  108. package/dist/ui.js +9238 -0
  109. package/package.json +99 -4
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Places a segmented control's selected pill (`.knobs-seg-pill`) under the button whose
3
+ * `data-value` is `value`. The pill is placed by its edges so the edge facing the move can lead
4
+ * (see .knobs-seg-pill); a resize (a token gliding) carries it with its option instead of
5
+ * animating it, and the first placement doesn't animate. With no matching button the pill fades.
6
+ * While a pan holds the pill (see `panOptions`), it stays with the fingers. Showing the value it
7
+ * already shows reads no layout (a resize, `resized`, re-places it).
8
+ */
9
+ export declare function placeSegPill(seg: HTMLElement, buttons: readonly HTMLButtonElement[], value: string, resized?: boolean): void;
10
+ /**
11
+ * Reads where each option sits ahead of a batch's writes (see measure.ts), while layout is still
12
+ * clean: choosing an option doesn't move any, so the pill then places without reading layout
13
+ * after the batch has dirtied it.
14
+ */
15
+ export declare function primeSegPill(seg: HTMLElement, buttons: readonly HTMLElement[]): void;
@@ -0,0 +1,21 @@
1
+ /** What a reload should bring back, per tab: open panels, open editors and the focused stop. */
2
+ export interface PanelSession {
3
+ readonly open?: boolean;
4
+ readonly expanded?: readonly string[];
5
+ readonly focus?: {
6
+ readonly inline: boolean;
7
+ readonly stop: number;
8
+ };
9
+ /** A page's collapsible custom cards, by title: whether each is folded. */
10
+ readonly folded?: Readonly<Record<string, boolean>>;
11
+ /** A panel's foldable groups, by id: whether each is folded. */
12
+ readonly groups?: Readonly<Record<string, boolean>>;
13
+ /** A panel's sub-headings, by group and name: whether each is folded. */
14
+ readonly subsections?: Readonly<Record<string, boolean>>;
15
+ /** The tab a panel showed. */
16
+ readonly tab?: string;
17
+ }
18
+ /** The tab's sessionStorage, where there is one. */
19
+ export declare const tabStorage: () => Storage | null;
20
+ export declare function readSession(id: string): PanelSession;
21
+ export declare function writeSession(id: string, patch: Partial<PanelSession>): void;
@@ -0,0 +1,92 @@
1
+ import type { Knob as KnobField } from "../core/schema.js";
2
+ import { Roll, Spring, VelocityTracker } from "./motion.js";
3
+ export interface DigitPlace {
4
+ ch: string;
5
+ /** Unbounded wheel position for digits (… 9, 10, 11 …); face = position mod 10. */
6
+ readonly wheel: Roll;
7
+ /** 1 while shown, 0 once gone: new places fade in and rise, leaving places fade out. */
8
+ readonly presence: Roll;
9
+ /** When this place started entering, and how long the entrance lasts (ms). */
10
+ enterAt: number;
11
+ enterMs: number;
12
+ exiting: boolean;
13
+ }
14
+ export interface Slider {
15
+ element: HTMLElement;
16
+ /** The focusable, keyboard-driven surface. */
17
+ track: HTMLElement;
18
+ /** The visible label element. */
19
+ label: HTMLElement;
20
+ /** The value button element. */
21
+ valueButton: HTMLButtonElement;
22
+ /** The inline number editor (hidden until the value is clicked). */
23
+ number: HTMLInputElement;
24
+ /**
25
+ * Spring driving the fill's edge [0..1]: the value, or while a stepped slider is in hand, the
26
+ * value pulled part way toward the pointer (--k-step-resist).
27
+ */
28
+ fillSpring: Spring;
29
+ /** Spring driving handle visibility/scale [0..1] (the GPU renderer sets its target). */
30
+ handleSpring: Spring;
31
+ /** Per-character springs for GPU rolling number digits. */
32
+ /** Readout characters keyed by place from the right, so 99 → 100 keeps the 9s in place. */
33
+ digitPlaces: DigitPlace[];
34
+ /** Velocity tracker on normalized fill ratio for drag motion blur. */
35
+ velocity: VelocityTracker;
36
+ /** Number of tick marks along the track. */
37
+ readonly tickCount: number;
38
+ /** Where the default value sits along the track [0..1]. */
39
+ readonly notchRatio: number;
40
+ /** Whether a drag is currently held on the default by the notch's magnet. */
41
+ isMagnetized(): boolean;
42
+ /** When the readout last popped (a click landing on a decile, or the default's magnet). */
43
+ poppedAt: number;
44
+ /** Formatted numeric string without unit (e.g. "0.45" or "98"). */
45
+ getDigitsText(): string;
46
+ /** Optional unit string (e.g. "px", "s"). */
47
+ getUnitText(): string;
48
+ /** Whether pointer drag is currently active. */
49
+ isDragging(): boolean;
50
+ /** Whether the inline number input is open. */
51
+ isEditing(): boolean;
52
+ /**
53
+ * `delay` (ms) holds this row's motion for its turn in a preset cascade; `instant` places the
54
+ * value without springing or rolling (a different item arrived).
55
+ */
56
+ update(value: number, delay?: number, instant?: boolean): void;
57
+ /**
58
+ * Reads its design tokens now. A batch (a preset) primes every row it will move before writing
59
+ * any, so no row's read lands after another's writes and forces a style pass.
60
+ */
61
+ prime(): void;
62
+ /** Takes a new range, step, label or unit in place, keeping the DOM (see `RemoteControls.update`). */
63
+ retune(field: KnobField): void;
64
+ /** The field it currently draws. */
65
+ readonly field: KnobField;
66
+ /** Open the inline editor, with `initial` in place of the value (a digit typed on the track). */
67
+ edit(initial?: string): void;
68
+ /** Puts a row accessory just after the label; the ticks and handle keep clear of it too. */
69
+ slot(node: HTMLElement): void;
70
+ dispose(): void;
71
+ }
72
+ export declare const decimals: (step: number) => number;
73
+ /**
74
+ * `steps` grid steps from `value` (negative goes down), on the slider's grid counted from `min`.
75
+ * From a value between grid points (a declared default like 0.4 on a 0.02 grid from 0.15) the
76
+ * first step lands on the next grid point that way, not past it.
77
+ */
78
+ export declare function stepValue(field: {
79
+ min: number;
80
+ max: number;
81
+ step: number;
82
+ }, value: number, steps: number): number;
83
+ /**
84
+ * A DialKit-style slider: the whole row is the track, the fill is a full-height block,
85
+ * the handle is a hairline that only shows on hover, and the label + value sit on the
86
+ * track itself. Dragging follows the pointer exactly (⌥ for 1/10 speed) and the default's notch
87
+ * pulls it in from a few px away; a click snaps to the nearest step (or nearest 10% when there
88
+ * are many steps) with a short spring-like ease. A sideways trackpad pan moves it like a drag.
89
+ */
90
+ export declare function createSlider(doc: Document, key: string, initialField: KnobField, set: (value: number, options?: {
91
+ coalesce?: boolean;
92
+ }) => void, onMotionWake?: () => void): Slider;
@@ -0,0 +1,7 @@
1
+ /** Smears `pill` by its speed while it glides: `cap` is the widest smear (px), as on the GPU. */
2
+ export declare function smearPill(pill: HTMLElement, cap: number): void;
3
+ /**
4
+ * How wide a range band's end smears (px) at `speed` px/s, by `strength` (--k-range-blur, 0 to 1):
5
+ * the slider fill's edge ramp, so both read alike. 0 under a quarter pixel, so it rests crisp.
6
+ */
7
+ export declare function rangeSmear(speed: number, strength: number): number;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * One more turn for the ↺ in `button` (its first `.knobs-icon`), from wherever it is now; `rest`
3
+ * runs once it has stopped.
4
+ */
5
+ export declare function spin(button: HTMLElement, wake?: () => void, rest?: () => void): void;
6
+ /** Whether `button`'s ↺ is turning (or blinking). */
7
+ export declare const spinning: (button: Element) => boolean;
8
+ /** Where `button`'s ↺ is now, for the GPU renderer: its turn (radians, clockwise) and opacity. */
9
+ export declare function spinOf(button: Element, now: number): {
10
+ angle: number;
11
+ alpha: number;
12
+ moving: boolean;
13
+ };
@@ -0,0 +1,11 @@
1
+ import { type TokenPreset } from "../core/theme.js";
2
+ /**
3
+ * What DELAY times: a row's turn in a preset's cascade is set on these (see `showValues` in
4
+ * panel.ts) rather than on the row, where the custom property would restyle all of the row.
5
+ */
6
+ export declare const DELAYED = ".knobs-fill,.knobs-handle,.knobs-seg-pill,.knobs-toggle,.knobs-toggle i,.knobs-arm,.knobs-curve,.knobs-swatch,.knobs-strip-thumb,.knobs-pad>i,.knobs-ease-handle,.knobs-range-fill,.knobs-range-thumb,.knobs-dial-hand,.knobs-grad,.knobs-grad-mini,.knobs-grad-mini>i,.knobs-grad-stop,.knobs-graph-pt";
7
+ /** Adds a token preset's stylesheet once per document. */
8
+ export declare function injectPreset(doc: Document, preset: TokenPreset): void;
9
+ /** The rules' `:has()` as the attributes has.ts keeps (see there for why). */
10
+ export declare const withoutHas: (css: string) => string;
11
+ export declare function injectStyles(doc: Document): void;
@@ -0,0 +1,114 @@
1
+ /** A pause this long (ms) with no wheel events ends a gesture. */
2
+ export declare const GESTURE_GAP = 120;
3
+ export interface WheelSample {
4
+ readonly deltaX: number;
5
+ readonly deltaY: number;
6
+ readonly deltaMode: number;
7
+ readonly shiftKey: boolean;
8
+ /** ms */
9
+ readonly timeStamp: number;
10
+ /** Safari only: false when natural scrolling is off, so the deltas follow the fingers. */
11
+ readonly webkitDirectionInvertedFromDevice?: boolean;
12
+ /**
13
+ * Chromium 151+ (Chrome, Edge, Brave): true for the inertia the OS sends after the fingers lift
14
+ * (the platform's own momentum phase). Where it exists the pan needs no guessing; where it
15
+ * doesn't (Safari, Firefox), the lift is recognised from the stream's shape.
16
+ */
17
+ readonly momentum?: boolean;
18
+ }
19
+ export interface PanHandlers {
20
+ /** Asked at the start of each gesture or notch; false leaves it to the page. */
21
+ accept?(): boolean;
22
+ /** Finger travel (px) since the last call: positive rightwards and downwards. */
23
+ move(dx: number, dy: number): void;
24
+ /**
25
+ * The fingers seem to have lifted: a cue to let go visually. It comes as soon as a lift is
26
+ * suspected, before `end` (which waits for proof); a move after it means they hadn't.
27
+ */
28
+ lift?(): void;
29
+ /** After one or more moves: the fingers lifted (their momentum is ignored) or went still. */
30
+ end(): void;
31
+ /** A mouse wheel notch (or ⇧ + wheel): one step, rightwards or down being +1. */
32
+ step?(direction: -1 | 1): void;
33
+ }
34
+ /**
35
+ * Turns wheel events into direct manipulation. A horizontal two-finger pan moves 1:1 with the
36
+ * fingers from its first event; when they lift, the momentum macOS keeps sending is recognised
37
+ * and ignored, so things stop where the fingers stopped. Vertical gestures are never claimed.
38
+ *
39
+ * A lift is spotted the moment it happens, by its pause and spike, or by a steady fade when a
40
+ * stream has no pause. Suspect events are held back rather than applied: a finger event in their
41
+ * place releases them, and so does one breaking the coast with no pause (a new touch always
42
+ * pauses), so a false alarm costs a few ms, never distance. A push stronger than the momentum it
43
+ * interrupts starts a new gesture at once.
44
+ *
45
+ * `feed` returns whether the event belongs to a claimed gesture, in which case the caller should
46
+ * `preventDefault()` it (so the browser doesn't scroll or swipe back a page). `idle` ends the
47
+ * gesture once the wheel has been quiet for `GESTURE_GAP`.
48
+ */
49
+ export declare function createPan(handlers: PanHandlers, options?: {
50
+ axes?: "x" | "xy";
51
+ }): {
52
+ feed: (event: WheelSample) => boolean;
53
+ /** The wheel went quiet: whatever was in hand is over. */
54
+ idle(): void;
55
+ };
56
+ /** Feeds `el`'s wheel events to a pan (see `createPan`), ending gestures after a quiet spell. */
57
+ export declare function onPan(el: HTMLElement, handlers: PanHandlers, options?: {
58
+ axes?: "x" | "xy";
59
+ }): {
60
+ feed: (event: WheelSample) => boolean;
61
+ /** The wheel went quiet: whatever was in hand is over. */
62
+ idle(): void;
63
+ };
64
+ /** An option's box in its container (px): where the selection pill sits on it. */
65
+ export interface Slot {
66
+ readonly left: number;
67
+ readonly right: number;
68
+ readonly top: number;
69
+ }
70
+ /** A pill mid-pan: the option it belongs to, and how far (px) the fingers have pushed it off. */
71
+ export interface Slide {
72
+ readonly index: number;
73
+ readonly offset: number;
74
+ }
75
+ /** Whether a pill's offset is past an end: its give (see give.ts). */
76
+ export declare const giving: (slots: readonly Slot[], { index, offset }: Slide) => boolean;
77
+ /**
78
+ * Pushes the pill `delta` px; it joins a neighbour once past halfway to it. Past an end it gives
79
+ * like every control does (see give.ts): a little, less and less, and turning back takes the give
80
+ * up 1:1 before it moves on.
81
+ */
82
+ export declare function slide(slots: readonly Slot[], from: Slide, delta: number): Slide;
83
+ /**
84
+ * The pill's live edges: its centre is its option's plus the offset, and its width blends toward
85
+ * the neighbour it heads for, so it's continuous as the selection changes hands. Past the ends the
86
+ * offset is its give.
87
+ */
88
+ export declare function pillAt(slots: readonly Slot[], { index, offset }: Slide): Slot;
89
+ /** The selection pill's edges while a pan holds it (px in `box`), so the GPU can draw it there. */
90
+ export declare const livePill: (box: Element) => Slot | undefined;
91
+ /**
92
+ * Pans a row of options: the selection pill follows the fingers, the selection changes to
93
+ * whichever option the pill passes halfway to (so one pan can cross several), and when the
94
+ * gesture ends the pill springs back into its option's slot. A notch steps one option.
95
+ */
96
+ export declare function panOptions(el: HTMLElement, o: {
97
+ /** The pill's positioning box. */
98
+ readonly box: HTMLElement;
99
+ readonly pill: HTMLElement;
100
+ buttons(): readonly HTMLElement[];
101
+ /** The selected option's index, or -1. */
102
+ selected(): number;
103
+ /** `coalesce`: a later change in the same gesture, folded into its undo step. */
104
+ select(index: number, coalesce: boolean): void;
105
+ /** Puts the pill back on the selected option, with its transition. */
106
+ rest(): void;
107
+ wake?(): void;
108
+ /** The pill's narrowest width (px). */
109
+ readonly minWidth?: number;
110
+ /** Places the pill's top too, for options that wrap onto rows. */
111
+ readonly rows?: boolean;
112
+ /** A row held in its active state (`knobs-active`) while the pill is in hand. */
113
+ readonly row?: HTMLElement;
114
+ }): void;
@@ -0,0 +1,20 @@
1
+ import type { ChoiceKey } from "../core/adapter.js";
2
+ import { type Schema, type Values } from "../core/schema.js";
3
+ import type { Controls } from "../core/store.js";
4
+ export type { ChoiceKey };
5
+ export interface SwitcherOptions<S extends Schema> {
6
+ readonly field?: ChoiceKey<S>;
7
+ readonly label?: string;
8
+ readonly onUpdate?: (activeName: string) => void;
9
+ }
10
+ export declare function getSwitcherOptions<S extends Schema>(controls: Controls<S>, field?: ChoiceKey<S>): {
11
+ options: {
12
+ id: string;
13
+ name: string;
14
+ values?: Values<S>;
15
+ choose(): void;
16
+ }[];
17
+ index: number;
18
+ activeName: string;
19
+ };
20
+ export declare function attachVariantSwitcher<S extends Schema>(element: HTMLElement, controls: Controls<S>, switcherOptions?: SwitcherOptions<S>): () => void;
@@ -0,0 +1,31 @@
1
+ import { Edges } from "./motion.js";
2
+ export interface TabSpec {
3
+ readonly id: string;
4
+ readonly label: string;
5
+ }
6
+ /**
7
+ * A panel's tab strip: text tabs over a hairline, the chosen one marked by an underline that
8
+ * slides to it (its leading edge first, the trailing one following, as the selection pills do).
9
+ * The strip is one ↑↓ stop; ←/→ on it switch tabs.
10
+ */
11
+ export interface TabStrip {
12
+ readonly element: HTMLElement;
13
+ readonly bar: HTMLElement;
14
+ /** The underline's edges for the GPU overlay (px from the strip's left). */
15
+ readonly edges: Edges;
16
+ buttons: HTMLButtonElement[];
17
+ readonly tabs: readonly TabSpec[];
18
+ readonly active: string | undefined;
19
+ /** Swaps in another set of tabs, keeping the strip. */
20
+ set(tabs: readonly TabSpec[], active: string | undefined): void;
21
+ /** Marks `id` chosen and slides the underline to it (or places it, when `animate` is false). */
22
+ select(id: string, animate?: boolean): void;
23
+ /**
24
+ * A small count after a tab's label ("" takes it away); `label` is what it means, for
25
+ * assistive tech and the tooltip.
26
+ */
27
+ count(id: string, text: string, label?: string): void;
28
+ focus(options?: FocusOptions): void;
29
+ dispose(): void;
30
+ }
31
+ export declare function createTabStrip(doc: Document, choose: (id: string) => void): TabStrip;
@@ -0,0 +1,36 @@
1
+ import { type Band } from "./give.js";
2
+ /** A switch's geometry (px): the thumb's free travel at rest, its size, its inset, and how much wider a press makes it. */
3
+ export interface SwitchBox {
4
+ readonly travel: number;
5
+ readonly size: number;
6
+ readonly pad: number;
7
+ readonly press: number;
8
+ }
9
+ /** How far a held thumb's leading edge stretches past an end (px): a switch is small. */
10
+ export declare const SWITCH_GIVE = 3;
11
+ /** A lift faster than this (px/ms) is a flick: it decides by its direction. */
12
+ export declare const FLICK = 0.2;
13
+ /** Where a pressed thumb sits for a value (px from its leftmost spot); mixed waits in the middle. */
14
+ export declare const heldAt: (box: SwitchBox, on: boolean | "mixed") => number;
15
+ /** Whether a thumb at `at` reads as on: past the middle. */
16
+ export declare const onAt: (box: SwitchBox, at: number) => boolean;
17
+ /** Moves a held thumb `d` px, giving at the ends. */
18
+ export declare const pushThumb: (box: SwitchBox, from: Band, d: number) => Band;
19
+ /** Finger travel (px) over which a held thumb widens to its pressed size, so it never jumps. */
20
+ export declare const PRESS_IN = 6;
21
+ /**
22
+ * A held thumb's edges (px from the switch's left): pressed wide by `pressed` (0…1) of its press,
23
+ * toward the side it can go (so unpressed it's exactly the resting thumb), its leading edge
24
+ * giving past an end.
25
+ */
26
+ export declare function thumbEdges(box: SwitchBox, { at, give }: Band, pressed?: number): [number, number];
27
+ /** How a pan lets go: a flick goes its way, anything slower stays on the side the thumb is on. */
28
+ export declare const settleOn: (box: SwitchBox, at: number, speed: number) => boolean;
29
+ /** A held thumb's edges (px from the switch's left), so the GPU can draw it there too. */
30
+ export declare const liveThumb: (toggle: Element) => readonly [number, number] | undefined;
31
+ /** Pans `toggle`'s thumb from anywhere on `row`; `set` changes the value (`coalesce`: a later change in one gesture). */
32
+ export declare function panSwitch(row: HTMLElement, toggle: HTMLElement, o: {
33
+ value(): boolean | "mixed";
34
+ set(on: boolean, coalesce: boolean): void;
35
+ wake(): void;
36
+ }): void;
@@ -0,0 +1,15 @@
1
+ import { type ReactElement, type Ref } from "react";
2
+ import { type KnobsPanelProps } from "../core/adapter.js";
3
+ import type { Schema } from "../core/schema.js";
4
+ import type { Controls } from "../core/store.js";
5
+ export type { KnobsPanelProps };
6
+ export declare function useKnobs<S extends Schema, E extends HTMLElement = HTMLElement>(controls: Controls<S>): (element: E | null) => void;
7
+ export declare function Knobs<S extends Schema>({ controls, children, }: {
8
+ controls: Controls<S>;
9
+ children: ReactElement<{
10
+ ref?: Ref<HTMLElement>;
11
+ }>;
12
+ }): ReactElement<{
13
+ ref?: Ref<HTMLElement>;
14
+ }, string | import("react").JSXElementConstructor<any>>;
15
+ export declare function KnobsPanel<S extends Schema>(props: KnobsPanelProps<S>): import("react").JSX.Element | null;
@@ -0,0 +1,10 @@
1
+ import { type ReactNode } from "react";
2
+ import type { ChoiceKey } from "../core/adapter.js";
3
+ import type { Schema } from "../core/schema.js";
4
+ import type { Controls } from "../core/store.js";
5
+ export declare function VariantSwitcher<S extends Schema>({ controls, field, children, label, }: {
6
+ controls: Controls<S>;
7
+ field?: ChoiceKey<S>;
8
+ children?: ReactNode;
9
+ label?: string;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,3 @@
1
+ export { useControls, useModified, useValue } from "./useControls.js";
2
+ export { Knobs, KnobsPanel, useKnobs, type KnobsPanelProps } from "./Knobs.js";
3
+ export { VariantSwitcher } from "./VariantSwitcher.js";
@@ -0,0 +1,11 @@
1
+ import type { Schema, Values } from "../core/schema.js";
2
+ import type { Controls } from "../core/store.js";
3
+ export declare function useControls<S extends Schema>(controls: Controls<S>): Values<S>;
4
+ /**
5
+ * One key's value, re-rendering the component only when that key changes.
6
+ */
7
+ export declare function useValue<S extends Schema, K extends keyof Values<S> & string>(controls: Controls<S>, key: K): Values<S>[K];
8
+ /**
9
+ * Whether a key (or, without one, anything) differs from its default: a "modified" dot beside a heading.
10
+ */
11
+ export declare function useModified<S extends Schema>(controls: Controls<S>, key?: keyof S & string): boolean;
@@ -0,0 +1,50 @@
1
+ import { type Accessor, type JSX } from "solid-js";
2
+ import { type ChoiceKey, type KnobsPanelProps } from "../core/adapter.js";
3
+ import { type Schema, type Values } from "../core/schema.js";
4
+ import type { Controls } from "../core/store.js";
5
+ export type { KnobsPanelProps };
6
+ /**
7
+ * Reactive Solid accessor for a controls store.
8
+ * Returns `controls.getServerSnapshot()` (`locked`) during SSR, and updates reactively on the client.
9
+ */
10
+ export declare function useControls<S extends Schema>(controls: Controls<S>): Accessor<Values<S>>;
11
+ /**
12
+ * One key's value as an accessor that changes only when that key does, so a component reading
13
+ * it re-runs for its own row and no other.
14
+ */
15
+ export declare function useValue<S extends Schema, K extends keyof Values<S> & string>(controls: Controls<S>, key: K): Accessor<Values<S>[K]>;
16
+ /**
17
+ * Whether a key (or, without one, anything) differs from its default, as an accessor that
18
+ * changes only when the answer does: a "modified" dot beside a heading.
19
+ */
20
+ export declare function useModified<S extends Schema>(controls: Controls<S>, key?: keyof S & string): Accessor<boolean>;
21
+ /**
22
+ * Solid ref callback (`ref={useKnobs(controls)}`) that attaches the floating tuning chip and panel.
23
+ */
24
+ export declare function useKnobs<S extends Schema, E extends HTMLElement = HTMLElement>(controls: Controls<S>): (element: E) => void;
25
+ /**
26
+ * Solid `use:knobs={controls}` directive for attaching controls to an element.
27
+ */
28
+ export declare function knobs<S extends Schema>(element: HTMLElement, accessor: Accessor<Controls<S>>): void;
29
+ /**
30
+ * Attaches `props.controls` directly to its single HTML child element without adding wrapper DOM.
31
+ */
32
+ export declare function Knobs<S extends Schema>(props: {
33
+ controls: Controls<S>;
34
+ children: JSX.Element;
35
+ }): JSX.Element;
36
+ /**
37
+ * Mounts an inline `knobs` panel inside a Solid component tree. The panel mounts once per
38
+ * `controls`; `descriptors` and `values` flow into it as updates, so a change touches only the rows
39
+ * it changes and the panel never re-renders as a whole.
40
+ */
41
+ export declare function KnobsPanel<S extends Schema>(props: KnobsPanelProps<S>): JSX.Element;
42
+ /**
43
+ * In-place keyboard (`1–9`, `←/→`) and hover switcher for a choice field or presets/variants in Solid.
44
+ */
45
+ export declare function VariantSwitcher<S extends Schema>(props: {
46
+ controls: Controls<S>;
47
+ field?: ChoiceKey<S>;
48
+ label?: string;
49
+ children?: JSX.Element;
50
+ }): JSX.Element;
@@ -0,0 +1 @@
1
+ export { labPreset, presetCss, problemsPreset, type TokenPreset } from "../core/theme.js";
@@ -0,0 +1 @@
1
+ export declare const bentoCss = "\n.knobs-ui.knobs-bento{--ku-bento-radius:var(--k-ui-bento-radius,max(0px,calc(var(--ku-radius) - 5px)));\n position:relative;width:100%;height:100%;overflow:hidden auto;outline:none;background:var(--k-ui-bento-bg,var(--ku-page));\n scrollbar-width:thin;scrollbar-gutter:stable;overscroll-behavior:contain;touch-action:pan-y;user-select:none;-webkit-user-select:none;\n font:var(--ku-font-weight) var(--ku-font-size)/1.35 var(--ku-font);color:var(--ku-text);letter-spacing:normal;text-align:left}\n.knobs-bento[data-frame=\"flat\"]{--ku-bento-radius:var(--k-ui-bento-radius,0px)}\n.knobs-bento[data-pending] .knobs-bento-space{visibility:hidden}\n:where(.knobs-bento-space,.knobs-bento-space *){box-sizing:border-box}\n.knobs-bento-space{position:relative;width:100%}\n.knobs-bento-tile{position:absolute;left:0;top:0;transform-origin:0 0;overflow:hidden;border-radius:var(--ku-bento-radius);\n background:var(--ku-surface);contain:strict;cursor:zoom-in}\n.knobs-bento-tile[data-focus]{z-index:2;cursor:zoom-out}\n.knobs-bento-tile[data-focus][data-live]{cursor:auto}\n.knobs-bento-img{display:block;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .18s cubic-bezier(.2,.8,.2,1);-webkit-user-drag:none}\n.knobs-bento-tile[data-ready] .knobs-bento-img{opacity:1}\n.knobs-bento-tile[data-instant] .knobs-bento-img{transition:none}\n.knobs-bento-body{position:absolute;inset:0;pointer-events:none}\n.knobs-bento-tile[data-focus] .knobs-bento-body{pointer-events:auto}\n.knobs-bento-label{position:absolute;left:9px;right:9px;bottom:7px;color:#fff;font-size:var(--ku-text-meta);line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;\n text-shadow:0 0 6px rgba(0,0,0,.55),0 0 1px rgba(0,0,0,.5);opacity:0;pointer-events:none;transition:opacity calc(var(--ku-hover-out) * 1ms)}\n.knobs-bento:not([data-keyboard]) .knobs-bento-tile:hover .knobs-bento-label,\n.knobs-bento[data-keyboard]:focus .knobs-bento-tile[data-active] .knobs-bento-label,\n.knobs-bento-tile[data-focus] .knobs-bento-label{opacity:1;transition-duration:calc(var(--ku-hover-in) * 1ms)}\n/* A pick: a small dot in the accent, ringed in the page so it reads on any picture. */\n.knobs-bento-tile::after{content:\"\";position:absolute;top:8px;right:8px;width:8px;height:8px;border-radius:50%;background:var(--ku-accent);\n box-shadow:0 0 0 1.5px color-mix(in srgb,var(--ku-page) 70%,transparent);opacity:0;scale:.4;pointer-events:none;\n transition:opacity .11s cubic-bezier(.4,0,1,1),scale var(--ku-ease-ms) var(--ku-ease)}\n.knobs-bento-tile[aria-selected=\"true\"]::after{opacity:1;scale:1;transition:opacity .15s cubic-bezier(.2,.8,.2,1),scale var(--ku-ease-ms) var(--ku-ease)}\n.knobs-bento-wash{position:absolute;left:0;top:0;width:100px;height:100px;transform-origin:0 0;z-index:3;pointer-events:none;border-radius:var(--ku-bento-radius);\n background:color-mix(in srgb,var(--ku-accent) 12%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ku-accent) 45%,transparent);\n opacity:0;transition:opacity .15s}\n.knobs-bento[data-keyboard]:focus:not([data-zoomed]) .knobs-bento-wash[data-on]{opacity:1}\n.knobs-bento-scrim{position:absolute;left:0;top:0;width:100%;z-index:1;background:var(--k-ui-scrim,var(--k-ui-bento-bg,var(--ku-page)));opacity:0;pointer-events:none;cursor:zoom-out}\n.knobs-bento[data-zoomed] .knobs-bento-scrim{pointer-events:auto}\n.knobs-bento-head{position:absolute;left:0;top:0;display:flex;align-items:flex-end;padding:0 0 6px 2px;color:var(--ku-label);font-weight:var(--ku-label-weight);\n font-size:var(--ku-text-meta);white-space:nowrap;pointer-events:none}\n";
@@ -0,0 +1,96 @@
1
+ export interface BentoRect {
2
+ readonly x: number;
3
+ readonly y: number;
4
+ readonly w: number;
5
+ readonly h: number;
6
+ }
7
+ /**
8
+ * `fit`: everything on one screen in justified rows. `hero`: one screen, one tile large beside or
9
+ * above the rest. `rows`: justified rows that scroll.
10
+ */
11
+ export type BentoMode = "fit" | "hero" | "rows";
12
+ export interface BentoPlan {
13
+ readonly mode: BentoMode;
14
+ /** One per item, in the items' order, in content coordinates. */
15
+ readonly rects: readonly BentoRect[];
16
+ readonly width: number;
17
+ /** The content's height: the view's in `fit` and `hero`, every row's in `rows`. */
18
+ readonly height: number;
19
+ /** Tile area over the area it lays out in (the view for one screen, the content for rows). */
20
+ readonly coverage: number;
21
+ /** The most any tile is cropped: its shown aspect over its own, less one (0: none). */
22
+ readonly crop: number;
23
+ /** Group headings (`rows` with groups): the group, its top and the first item's index. */
24
+ readonly headers: readonly {
25
+ readonly group: string;
26
+ readonly y: number;
27
+ readonly index: number;
28
+ }[];
29
+ }
30
+ export interface BentoLayout {
31
+ /** The view's size. */
32
+ readonly width: number;
33
+ readonly height: number;
34
+ /** Between tiles (2). */
35
+ readonly gap?: number;
36
+ /** Around the tiles (the gap). */
37
+ readonly padding?: number;
38
+ /** How far a tile may be cropped to fill a row, as a fraction of its width (.08). */
39
+ readonly tolerance?: number;
40
+ /** Scrolling rows' target height (about a third of the view, 120–420). */
41
+ readonly rowHeight?: number;
42
+ /** Whether a few items may lay out on one screen (true). */
43
+ readonly fit?: boolean;
44
+ /** The shortest a tile may be on one screen before the set scrolls instead (a sixth of the view, at least 80). */
45
+ readonly minTile?: number;
46
+ /** One per item: items with the same group sit together under its heading (scrolling rows). */
47
+ readonly groups?: readonly (string | undefined)[];
48
+ /** Heading height for groups (28). */
49
+ readonly header?: number;
50
+ /** An item that asks to be the hero; otherwise the first item becomes one when that shows more. */
51
+ readonly hero?: number;
52
+ }
53
+ /** Above this many items, sets scroll rather than fit one screen. */
54
+ export declare const FIT_MAX = 24;
55
+ /** Scrolling rows' target height for a view. */
56
+ export declare const rowTarget: (width: number, height: number) => number;
57
+ interface Row {
58
+ start: number;
59
+ end: number;
60
+ h: number;
61
+ /** Widths, one per item. */
62
+ ws: number[];
63
+ }
64
+ /**
65
+ * Greedy justified rows: items join a row until it would be shorter than `target`, and the row
66
+ * ends at whichever count lands nearer the target. Every full row spans the width at its own
67
+ * height, so nothing is cropped. A row only looks at items from its own start, so a change never
68
+ * moves the rows before it. The last row spans the width when that leaves it no more than a
69
+ * quarter taller than the target, or when cropping its tiles within `tol` fills it; otherwise it
70
+ * stays at the target height, left-aligned.
71
+ */
72
+ export declare function justify(aspects: readonly number[], start: number, end: number, width: number, target: number, gap: number, tol: number): Row[];
73
+ /** Splits `xs` in order into `k` runs whose largest total is as small as possible. */
74
+ export declare function partition(xs: readonly number[], k: number): number[];
75
+ /**
76
+ * Plans the tiles for a view. A set of up to `FIT_MAX` items lays out on one screen when its
77
+ * smallest tile stays at least `minTile` tall: as justified rows (the row count that shows the
78
+ * most), or with a hero when one is asked for or the first item as a hero shows 4% more. Larger
79
+ * sets, groups and narrow views scroll in greedy justified rows near `rowHeight`. Tiles keep their
80
+ * own aspect except where a crop within `tolerance` fills a row.
81
+ */
82
+ export declare function planBento(aspects: readonly number[], layout: BentoLayout): BentoPlan;
83
+ /** The indices whose rects meet `top…bottom`; `rows` plans are in reading order, so this bisects. */
84
+ export declare function windowOf(plan: BentoPlan, top: number, bottom: number): number[];
85
+ export type BentoDirection = "left" | "right" | "up" | "down";
86
+ /**
87
+ * The tile an arrow reaches, by geometry. Left and right step along the row and wrap to the next
88
+ * or previous item at its ends; up and down go to the nearest row that way and the tile there
89
+ * whose centre is closest. Returns `from` when there's nowhere to go.
90
+ */
91
+ export declare function neighbour(rects: readonly BentoRect[], from: number, dir: BentoDirection): number;
92
+ /** `rect` fitted inside `w × h` at `aspect`, centred, `inset` from the edges. */
93
+ export declare function fitRect(aspect: number, w: number, h: number, inset?: number): BentoRect;
94
+ /** Motion blur for a tile moving at `speed` px/s: rises with speed to `max`, 0 below a crawl. */
95
+ export declare function motionBlur(speed: number, max?: number, per?: number): number;
96
+ export {};