@kitlangton/knobs 0.4.0-next.10 → 0.4.0-next.12
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/README.md +115 -13
- package/dist/dev/{chunk-3ygqgy1x.js → chunk-7kns3qe6.js} +43 -26
- package/dist/dev/{chunk-5pb7ymrd.js → chunk-9qtvxa6k.js} +1465 -1104
- package/dist/dev/{chunk-a7cjgy8x.js → chunk-ab7vfghg.js} +2 -5
- package/dist/dev/{chunk-dyq4m24m.js → chunk-nwfm1bfj.js} +92 -37
- package/dist/dev/index.js +71 -25
- package/dist/index.js +107 -23
- package/dist/panel.js +3091 -2528
- package/dist/review-react.js +8 -4
- package/dist/review-solid.js +10 -5
- package/dist/review.js +8 -4
- package/dist/types/core/remote.d.ts +10 -2
- package/dist/types/core/schema.d.ts +65 -5
- package/dist/types/core/store.d.ts +15 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/panel/dock.d.ts +9 -0
- package/dist/types/panel/gpu/renderer.d.ts +13 -1
- package/dist/types/panel/inspector.d.ts +11 -3
- package/dist/types/panel/presets.d.ts +21 -1
- package/dist/types/panel/scope.d.ts +15 -4
- package/dist/types/ui/canvas-css.d.ts +1 -0
- package/dist/types/ui/canvas-model.d.ts +116 -0
- package/dist/types/ui/canvas.d.ts +66 -0
- package/dist/types/ui/index.d.ts +2 -0
- package/dist/types/ui/react.d.ts +12 -0
- package/dist/types/ui/solid.d.ts +9 -0
- package/dist/ui-react.js +1020 -17
- package/dist/ui-solid.js +1032 -25
- package/dist/ui.js +986 -4
- package/package.json +1 -1
package/dist/review-react.js
CHANGED
|
@@ -2965,13 +2965,17 @@ var css2 = `
|
|
|
2965
2965
|
.knobs-tree-row[aria-expanded="true"]>.knobs-tree-caret{transform:rotate(90deg)}
|
|
2966
2966
|
.knobs-tree-row:not([aria-expanded])>.knobs-tree-caret{visibility:hidden}
|
|
2967
2967
|
.knobs-tree-icon{flex:none;display:flex;align-items:center;justify-content:center;width:14px;margin-right:6px;color:var(--_k-fg-3);font-size:calc(var(--_k-font-size) - 1px)}
|
|
2968
|
-
|
|
2968
|
+
/* The label wins the room: it keeps its width while the detail (which only takes what's left)
|
|
2969
|
+
fades out at its end, and gives way only once the detail is gone. Each fades where it's cut. */
|
|
2970
|
+
.knobs-tree-label{position:relative;flex:0 1 auto;min-width:0;overflow:hidden;font-weight:var(--_k-weight)}
|
|
2971
|
+
.knobs-tree-row[data-editing]>.knobs-tree-label{flex-grow:1}
|
|
2969
2972
|
.knobs-tree-row[data-ancestor] .knobs-tree-label{color:var(--_k-fg-2)}
|
|
2970
2973
|
.knobs-tree-label .knobs-tree-mark{color:var(--_k-accent)}
|
|
2971
2974
|
.knobs-tree-row[data-off~="visible"] :is(.knobs-tree-label,.knobs-tree-icon){opacity:.45}
|
|
2972
2975
|
.knobs-tree-row[data-off~="locked"] .knobs-tree-label{color:var(--_k-fg-2)}
|
|
2973
|
-
.knobs-tree-detail{flex:
|
|
2974
|
-
.knobs-tree-
|
|
2976
|
+
.knobs-tree-detail{flex:1 1 0;min-width:0;overflow:hidden;padding-left:6px;text-align:right;color:var(--_k-fg-3);font-variant-numeric:tabular-nums}
|
|
2977
|
+
.knobs-tree-row[data-editing]>.knobs-tree-detail{flex-grow:0;visibility:hidden}
|
|
2978
|
+
.knobs-tree-tools{flex:none;display:flex;align-items:center;gap:1px;margin-left:auto;padding-left:4px}
|
|
2975
2979
|
.knobs-ui .knobs-tree-toggle{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:calc(var(--_k-radius) * 999);color:var(--_k-fg-3);cursor:pointer;opacity:0;transition:opacity .12s,color .12s,background .12s}
|
|
2976
2980
|
.knobs-ui .knobs-tree-toggle svg{width:12px;height:12px}
|
|
2977
2981
|
.knobs-tree-row:is(:hover,[data-active],[aria-selected="true"]) .knobs-tree-toggle,.knobs-ui .knobs-tree-toggle[aria-pressed="false"]{opacity:1}
|
|
@@ -5327,7 +5331,7 @@ function tree(container, options) {
|
|
|
5327
5331
|
text.className = "knobs-tree-text";
|
|
5328
5332
|
label.append(text);
|
|
5329
5333
|
const detail = doc.createElement("span");
|
|
5330
|
-
detail.className = "knobs-tree-detail";
|
|
5334
|
+
detail.className = "knobs-tree-detail knobs-scroll-fade-x";
|
|
5331
5335
|
const tools = doc.createElement("span");
|
|
5332
5336
|
tools.className = "knobs-tree-tools";
|
|
5333
5337
|
el.append(caret, icon, label, detail, tools);
|
package/dist/review-solid.js
CHANGED
|
@@ -2964,13 +2964,17 @@ var css2 = `
|
|
|
2964
2964
|
.knobs-tree-row[aria-expanded="true"]>.knobs-tree-caret{transform:rotate(90deg)}
|
|
2965
2965
|
.knobs-tree-row:not([aria-expanded])>.knobs-tree-caret{visibility:hidden}
|
|
2966
2966
|
.knobs-tree-icon{flex:none;display:flex;align-items:center;justify-content:center;width:14px;margin-right:6px;color:var(--_k-fg-3);font-size:calc(var(--_k-font-size) - 1px)}
|
|
2967
|
-
|
|
2967
|
+
/* The label wins the room: it keeps its width while the detail (which only takes what's left)
|
|
2968
|
+
fades out at its end, and gives way only once the detail is gone. Each fades where it's cut. */
|
|
2969
|
+
.knobs-tree-label{position:relative;flex:0 1 auto;min-width:0;overflow:hidden;font-weight:var(--_k-weight)}
|
|
2970
|
+
.knobs-tree-row[data-editing]>.knobs-tree-label{flex-grow:1}
|
|
2968
2971
|
.knobs-tree-row[data-ancestor] .knobs-tree-label{color:var(--_k-fg-2)}
|
|
2969
2972
|
.knobs-tree-label .knobs-tree-mark{color:var(--_k-accent)}
|
|
2970
2973
|
.knobs-tree-row[data-off~="visible"] :is(.knobs-tree-label,.knobs-tree-icon){opacity:.45}
|
|
2971
2974
|
.knobs-tree-row[data-off~="locked"] .knobs-tree-label{color:var(--_k-fg-2)}
|
|
2972
|
-
.knobs-tree-detail{flex:
|
|
2973
|
-
.knobs-tree-
|
|
2975
|
+
.knobs-tree-detail{flex:1 1 0;min-width:0;overflow:hidden;padding-left:6px;text-align:right;color:var(--_k-fg-3);font-variant-numeric:tabular-nums}
|
|
2976
|
+
.knobs-tree-row[data-editing]>.knobs-tree-detail{flex-grow:0;visibility:hidden}
|
|
2977
|
+
.knobs-tree-tools{flex:none;display:flex;align-items:center;gap:1px;margin-left:auto;padding-left:4px}
|
|
2974
2978
|
.knobs-ui .knobs-tree-toggle{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:calc(var(--_k-radius) * 999);color:var(--_k-fg-3);cursor:pointer;opacity:0;transition:opacity .12s,color .12s,background .12s}
|
|
2975
2979
|
.knobs-ui .knobs-tree-toggle svg{width:12px;height:12px}
|
|
2976
2980
|
.knobs-tree-row:is(:hover,[data-active],[aria-selected="true"]) .knobs-tree-toggle,.knobs-ui .knobs-tree-toggle[aria-pressed="false"]{opacity:1}
|
|
@@ -5326,7 +5330,7 @@ function tree(container, options) {
|
|
|
5326
5330
|
text.className = "knobs-tree-text";
|
|
5327
5331
|
label.append(text);
|
|
5328
5332
|
const detail = doc.createElement("span");
|
|
5329
|
-
detail.className = "knobs-tree-detail";
|
|
5333
|
+
detail.className = "knobs-tree-detail knobs-scroll-fade-x";
|
|
5330
5334
|
const tools = doc.createElement("span");
|
|
5331
5335
|
tools.className = "knobs-tree-tools";
|
|
5332
5336
|
el.append(caret, icon, label, detail, tools);
|
|
@@ -12702,11 +12706,12 @@ var init_player = __esm(() => {
|
|
|
12702
12706
|
|
|
12703
12707
|
// src/ui/solid.ts
|
|
12704
12708
|
import {
|
|
12705
|
-
children,
|
|
12706
12709
|
createEffect,
|
|
12710
|
+
createMemo,
|
|
12707
12711
|
createRoot,
|
|
12708
12712
|
getOwner,
|
|
12709
12713
|
onCleanup,
|
|
12714
|
+
splitProps,
|
|
12710
12715
|
untrack
|
|
12711
12716
|
} from "solid-js";
|
|
12712
12717
|
import { insert } from "solid-js/web";
|
package/dist/review.js
CHANGED
|
@@ -3006,13 +3006,17 @@ var css2 = `
|
|
|
3006
3006
|
.knobs-tree-row[aria-expanded="true"]>.knobs-tree-caret{transform:rotate(90deg)}
|
|
3007
3007
|
.knobs-tree-row:not([aria-expanded])>.knobs-tree-caret{visibility:hidden}
|
|
3008
3008
|
.knobs-tree-icon{flex:none;display:flex;align-items:center;justify-content:center;width:14px;margin-right:6px;color:var(--_k-fg-3);font-size:calc(var(--_k-font-size) - 1px)}
|
|
3009
|
-
|
|
3009
|
+
/* The label wins the room: it keeps its width while the detail (which only takes what's left)
|
|
3010
|
+
fades out at its end, and gives way only once the detail is gone. Each fades where it's cut. */
|
|
3011
|
+
.knobs-tree-label{position:relative;flex:0 1 auto;min-width:0;overflow:hidden;font-weight:var(--_k-weight)}
|
|
3012
|
+
.knobs-tree-row[data-editing]>.knobs-tree-label{flex-grow:1}
|
|
3010
3013
|
.knobs-tree-row[data-ancestor] .knobs-tree-label{color:var(--_k-fg-2)}
|
|
3011
3014
|
.knobs-tree-label .knobs-tree-mark{color:var(--_k-accent)}
|
|
3012
3015
|
.knobs-tree-row[data-off~="visible"] :is(.knobs-tree-label,.knobs-tree-icon){opacity:.45}
|
|
3013
3016
|
.knobs-tree-row[data-off~="locked"] .knobs-tree-label{color:var(--_k-fg-2)}
|
|
3014
|
-
.knobs-tree-detail{flex:
|
|
3015
|
-
.knobs-tree-
|
|
3017
|
+
.knobs-tree-detail{flex:1 1 0;min-width:0;overflow:hidden;padding-left:6px;text-align:right;color:var(--_k-fg-3);font-variant-numeric:tabular-nums}
|
|
3018
|
+
.knobs-tree-row[data-editing]>.knobs-tree-detail{flex-grow:0;visibility:hidden}
|
|
3019
|
+
.knobs-tree-tools{flex:none;display:flex;align-items:center;gap:1px;margin-left:auto;padding-left:4px}
|
|
3016
3020
|
.knobs-ui .knobs-tree-toggle{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:calc(var(--_k-radius) * 999);color:var(--_k-fg-3);cursor:pointer;opacity:0;transition:opacity .12s,color .12s,background .12s}
|
|
3017
3021
|
.knobs-ui .knobs-tree-toggle svg{width:12px;height:12px}
|
|
3018
3022
|
.knobs-tree-row:is(:hover,[data-active],[aria-selected="true"]) .knobs-tree-toggle,.knobs-ui .knobs-tree-toggle[aria-pressed="false"]{opacity:1}
|
|
@@ -10484,7 +10488,7 @@ function tree(container, options) {
|
|
|
10484
10488
|
text.className = "knobs-tree-text";
|
|
10485
10489
|
label.append(text);
|
|
10486
10490
|
const detail = doc.createElement("span");
|
|
10487
|
-
detail.className = "knobs-tree-detail";
|
|
10491
|
+
detail.className = "knobs-tree-detail knobs-scroll-fade-x";
|
|
10488
10492
|
const tools = doc.createElement("span");
|
|
10489
10493
|
tools.className = "knobs-tree-tools";
|
|
10490
10494
|
el.append(caret, icon, label, detail, tools);
|
|
@@ -45,6 +45,8 @@ export interface ToolbarDescriptor extends DescriptorMeta {
|
|
|
45
45
|
readonly global?: boolean;
|
|
46
46
|
readonly disabled?: boolean;
|
|
47
47
|
}[];
|
|
48
|
+
/** `false`: icons only (see `toolbar`). */
|
|
49
|
+
readonly labels?: boolean;
|
|
48
50
|
readonly default?: undefined;
|
|
49
51
|
readonly bind?: undefined;
|
|
50
52
|
}
|
|
@@ -77,11 +79,11 @@ export interface ReadoutDescriptor extends DescriptorMeta {
|
|
|
77
79
|
export type ControlDescriptor = ValueDescriptor | SpringBindingDescriptor | ActionDescriptor | ToolbarDescriptor | ReadoutDescriptor;
|
|
78
80
|
export interface ValueDescriptor extends DescriptorMeta {
|
|
79
81
|
readonly bind?: undefined;
|
|
80
|
-
readonly kind: "knob" | "number" | "choice" | "toggle" | "bool" | "spring" | "color" | "point" | "easing" | "vec2" | "vec3" | "range" | "angle" | "seed" | "keybind" | "gradient" | "curve";
|
|
82
|
+
readonly kind: "knob" | "number" | "choice" | "toggle" | "bool" | "spring" | "color" | "point" | "easing" | "vec2" | "vec3" | "range" | "angle" | "seed" | "keybind" | "text" | "gradient" | "curve";
|
|
81
83
|
/**
|
|
82
84
|
* Springs take `{ duration, bounce }`, colours a CSS string, points and vec2 `{ x, y }`, vec3
|
|
83
85
|
* `{ x, y, z }`, easings `[x1, y1, x2, y2]`, ranges `[low, high]`, keybinds a combination
|
|
84
|
-
* string (`"Meta+K"`), gradients `[{ at, color }]` and curves `[{ x, y }]`.
|
|
86
|
+
* string (`"Meta+K"`), text a string, gradients `[{ at, color }]` and curves `[{ x, y }]`.
|
|
85
87
|
*/
|
|
86
88
|
readonly default: Value;
|
|
87
89
|
readonly min?: number;
|
|
@@ -103,6 +105,10 @@ export interface ValueDescriptor extends DescriptorMeta {
|
|
|
103
105
|
readonly axes?: readonly string[];
|
|
104
106
|
/** Angles: carry past the ends a whole turn (on for a full turn by default). */
|
|
105
107
|
readonly wrap?: boolean;
|
|
108
|
+
/** Text: lines in a box that grows with them (see `text`). */
|
|
109
|
+
readonly multiline?: boolean;
|
|
110
|
+
/** Text: shown in the empty field. */
|
|
111
|
+
readonly placeholder?: string;
|
|
106
112
|
/** Gradients: blend in `oklab` (default) or `srgb`. */
|
|
107
113
|
readonly space?: GradientSpace;
|
|
108
114
|
/** Curves: the y range, `[0, 1]` by default (`min` and `max` work too). */
|
|
@@ -130,6 +136,8 @@ export interface RemoteControlsOptions {
|
|
|
130
136
|
readonly nullDefaults?: boolean;
|
|
131
137
|
readonly values?: State;
|
|
132
138
|
readonly presets?: Readonly<Record<string, State>>;
|
|
139
|
+
/** Groups pinned above the tab strip, outside the presets (see `ControlsOptions.pinned`). */
|
|
140
|
+
readonly pinned?: readonly string[];
|
|
133
141
|
/** Numbered presets per group, e.g. the phone's preset sets (see `ControlsOptions.groupPresets`). */
|
|
134
142
|
readonly groupPresets?: Readonly<Record<string, readonly GroupPreset[]>>;
|
|
135
143
|
/** Display labels by group id, section id and preset id (see `ControlsOptions.groupLabels`). */
|
|
@@ -50,6 +50,12 @@ export interface FieldMeta {
|
|
|
50
50
|
* Fields without a tab show on every tab.
|
|
51
51
|
*/
|
|
52
52
|
readonly tab?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Pins the field's group to the top of a panel, under the header and above the tab strip, on
|
|
55
|
+
* every tab (the first field of a group that says wins; a field without a group pins itself).
|
|
56
|
+
* Panel presets and saved variants leave pinned fields alone. See `ControlsOptions.pinned`.
|
|
57
|
+
*/
|
|
58
|
+
readonly pinned?: boolean;
|
|
53
59
|
/**
|
|
54
60
|
* A collapsible sub-heading inside the field's group: a flat heading with a hairline, never a
|
|
55
61
|
* card inside the card. Consecutive fields that share it sit under one heading.
|
|
@@ -57,7 +63,7 @@ export interface FieldMeta {
|
|
|
57
63
|
readonly subsection?: string;
|
|
58
64
|
}
|
|
59
65
|
/** Meta keys copied from a field's options or a descriptor onto the field. */
|
|
60
|
-
export declare const META_KEYS: readonly ["label", "group", "groupLabel", "section", "sectionLabel", "groupDescription", "sectionDescription", "shortcut", "random", "disabled", "readOnly", "folded", "sectionFolded", "tab", "subsection"];
|
|
66
|
+
export declare const META_KEYS: readonly ["label", "group", "groupLabel", "section", "sectionLabel", "groupDescription", "sectionDescription", "shortcut", "random", "disabled", "readOnly", "folded", "sectionFolded", "tab", "pinned", "subsection"];
|
|
61
67
|
/** The meta `source` gives, without undefined keys. */
|
|
62
68
|
export declare function pickMeta(source: FieldMeta | undefined, skip?: readonly string[]): FieldMeta;
|
|
63
69
|
/** Marks a knob as half of a flat spring: two number keys drawn as one spring card. */
|
|
@@ -95,6 +101,12 @@ export interface Choice<T extends string = string> extends FieldMeta {
|
|
|
95
101
|
* spare and there are at most four options, and a menu otherwise, switching as the width changes.
|
|
96
102
|
*/
|
|
97
103
|
readonly display?: "menu" | "segmented";
|
|
104
|
+
/**
|
|
105
|
+
* The options are versions of the design: each is a preset in the panel's top strip (the
|
|
106
|
+
* default option is the default preset), which is where you switch them, and the choice gets no
|
|
107
|
+
* row of its own. See `choice`.
|
|
108
|
+
*/
|
|
109
|
+
readonly variants?: true;
|
|
98
110
|
}
|
|
99
111
|
export interface Toggle extends FieldMeta {
|
|
100
112
|
readonly kind: "toggle";
|
|
@@ -209,6 +221,15 @@ export interface KeybindField extends FieldMeta {
|
|
|
209
221
|
readonly kind: "keybind";
|
|
210
222
|
readonly value: string;
|
|
211
223
|
}
|
|
224
|
+
/** Words: a name, a caption, a prompt. Typed into a field; `multiline` for a box of lines. */
|
|
225
|
+
export interface TextField extends FieldMeta {
|
|
226
|
+
readonly kind: "text";
|
|
227
|
+
readonly value: string;
|
|
228
|
+
/** Lines (Enter starts a new one) in a box that grows with them; one line otherwise. */
|
|
229
|
+
readonly multiline?: boolean;
|
|
230
|
+
/** Shown in the empty field. */
|
|
231
|
+
readonly placeholder?: string;
|
|
232
|
+
}
|
|
212
233
|
/**
|
|
213
234
|
* A toolbar button's key and where it presses the button: while the panel has focus (`"panel"`),
|
|
214
235
|
* anywhere on the page but a text field (`"page"`), or nowhere (`"none"`: the chip only, for a
|
|
@@ -239,8 +260,10 @@ export interface ToolbarItem {
|
|
|
239
260
|
export interface ToolbarField extends FieldMeta {
|
|
240
261
|
readonly kind: "toolbar";
|
|
241
262
|
readonly items: readonly ToolbarItem[];
|
|
263
|
+
/** `false`: icons only, each label in its tooltip (see `toolbar`). */
|
|
264
|
+
readonly labels?: boolean;
|
|
242
265
|
}
|
|
243
|
-
export type ValueField = Knob | Choice | Toggle | SpringField | ColorField | PointField | EasingField | VecField | RangeField | AngleField | SeedField | KeybindField | GradientField | CurveField;
|
|
266
|
+
export type ValueField = Knob | Choice | Toggle | SpringField | ColorField | PointField | EasingField | VecField | RangeField | AngleField | SeedField | KeybindField | TextField | GradientField | CurveField;
|
|
244
267
|
/** A readout's own: polled while its row shows, instead of pushed with `controls.watch`. */
|
|
245
268
|
interface ReadoutMeta extends FieldMeta {
|
|
246
269
|
/** Read each frame while the row is on screen (pushing with `controls.watch` is the other way). */
|
|
@@ -329,7 +352,20 @@ export declare const isReadout: (field: {
|
|
|
329
352
|
/** Whether a field holds a value (actions, toolbars and readouts don't). */
|
|
330
353
|
export declare const hasValue: (field: Field) => field is ValueField;
|
|
331
354
|
export type Schema = Readonly<Record<string, Field>>;
|
|
332
|
-
|
|
355
|
+
/**
|
|
356
|
+
* The schema's variants choice (`choice(…, { variants: true })`), if it has one. A pinned choice
|
|
357
|
+
* (`isPinned`) is never the variants choice: pinned wins, and it stays an ordinary choice.
|
|
358
|
+
*/
|
|
359
|
+
export declare function variantsOf(schema: Schema, isPinned?: (key: string) => boolean): {
|
|
360
|
+
key: string;
|
|
361
|
+
field: Choice;
|
|
362
|
+
} | undefined;
|
|
363
|
+
/**
|
|
364
|
+
* The schema's pinned keys (values, actions and readouts alike): every key of a group in `groups`,
|
|
365
|
+
* or whose first field that gives `pinned` says `true`, and ungrouped fields that say so themselves.
|
|
366
|
+
*/
|
|
367
|
+
export declare function pinnedKeys(schema: Schema, groups?: readonly string[]): ReadonlySet<string>;
|
|
368
|
+
export type FieldValue<F extends Field> = F extends Knob ? number : F extends Choice<infer T> ? T : F extends Toggle ? boolean : F extends SpringField ? SpringValue : F extends ColorField ? string : F extends PointField ? Point : F extends EasingField ? Bezier : F extends VecField<3> ? Vec3 : F extends VecField<2> ? Point : F extends RangeField ? readonly [number, number] : F extends AngleField | SeedField ? number : F extends KeybindField | TextField ? string : F extends GradientField ? readonly GradientStop[] : F extends CurveField ? readonly CurvePoint[] : never;
|
|
333
369
|
/** The keys of a schema's readouts (what `controls.watch` takes). */
|
|
334
370
|
export type ReadoutKeys<S extends Schema> = {
|
|
335
371
|
[K in keyof S]: [S[K] & ReadoutField] extends [never] ? never : K;
|
|
@@ -360,9 +396,17 @@ export declare function valueKeys(schema: Schema): string[];
|
|
|
360
396
|
export declare function knob(value: number, options: Omit<Knob, "kind" | "value" | "step"> & {
|
|
361
397
|
step?: number;
|
|
362
398
|
}): Knob;
|
|
399
|
+
/**
|
|
400
|
+
* `choice("glass", ["glass", "solid", "flat"])`: one of a set of options, as segments or a menu.
|
|
401
|
+
* With `variants: true` the options are versions of the design instead: each becomes a preset in
|
|
402
|
+
* the panel's top strip (the default option is the default preset, the others follow in order),
|
|
403
|
+
* the choice has no row, and Random leaves it be. The store's `presets` add values to a variant
|
|
404
|
+
* by its option (`{ Roll: { speed: 0.8 } }`).
|
|
405
|
+
*/
|
|
363
406
|
export declare function choice<const T extends readonly [string, ...string[]]>(value: T[number], options: T, meta?: FieldMeta & {
|
|
364
407
|
readonly labels?: Readonly<Partial<Record<T[number], string>>>;
|
|
365
408
|
readonly display?: "menu" | "segmented";
|
|
409
|
+
readonly variants?: boolean;
|
|
366
410
|
}): Choice<T[number]>;
|
|
367
411
|
/**
|
|
368
412
|
* `action("Replay", { run, shortcut: "Shift+K", global: true, rebind: true })`: a button row. Its
|
|
@@ -449,6 +493,7 @@ export declare function range(value: readonly [number, number], options: FieldMe
|
|
|
449
493
|
folded?: boolean;
|
|
450
494
|
sectionFolded?: boolean;
|
|
451
495
|
tab?: string;
|
|
496
|
+
pinned?: boolean;
|
|
452
497
|
subsection?: string;
|
|
453
498
|
};
|
|
454
499
|
/** Degrees on a dial (0–360 and wrapping by default): `angle(45)`, `angle(30, { min: -90, max: 90 })`. */
|
|
@@ -465,6 +510,15 @@ export declare function seed(value: number, options?: FieldMeta & {
|
|
|
465
510
|
}): SeedField;
|
|
466
511
|
/** A key combination captured by pressing it (`"Meta+Shift+K"`, `""` for none). */
|
|
467
512
|
export declare function keybind(value?: string, meta?: FieldMeta): KeybindField;
|
|
513
|
+
/**
|
|
514
|
+
* Words typed into a field: `text("Hello")`, `text("", { multiline: true, placeholder: "Notes" })`.
|
|
515
|
+
* One line unless `multiline` (a line break typed or set there becomes a space). Out of Random
|
|
516
|
+
* and Mutate.
|
|
517
|
+
*/
|
|
518
|
+
export declare function text(value?: string, options?: FieldMeta & {
|
|
519
|
+
multiline?: boolean;
|
|
520
|
+
placeholder?: string;
|
|
521
|
+
}): TextField;
|
|
468
522
|
/**
|
|
469
523
|
* Colour over a 0…1 parameter: `gradient([{ at: 0, color: "#fff4d6" }, { at: 1, color: "#20242c" }])`.
|
|
470
524
|
* Colours take what `color` takes and are kept as `oklch()` strings. `space` blends in `oklab`
|
|
@@ -503,8 +557,14 @@ type ResolvedShortcut = {
|
|
|
503
557
|
export declare function toolbarShortcut(item: ToolbarItem): ResolvedShortcut | undefined;
|
|
504
558
|
/** An action row's shortcut as `{ key, scope }`, or undefined without one. */
|
|
505
559
|
export declare function actionShortcut(field: ActionField): ResolvedShortcut | undefined;
|
|
506
|
-
/**
|
|
507
|
-
|
|
560
|
+
/**
|
|
561
|
+
* A wrapping row of buttons with icons and shortcut chips; holds no value. When every item has an
|
|
562
|
+
* icon and the labels don't fit on one row, it's one row of icons, each label in its tooltip;
|
|
563
|
+
* `labels: false` always shows icons only, `labels: true` always shows the labels.
|
|
564
|
+
*/
|
|
565
|
+
export declare function toolbar(items: readonly ToolbarItem[], meta?: FieldMeta & {
|
|
566
|
+
readonly labels?: boolean;
|
|
567
|
+
}): ToolbarField;
|
|
508
568
|
type ReadoutOptions = Omit<ReadoutMeta, "shortcut" | "random" | "readOnly">;
|
|
509
569
|
/** A live value: `watch({ label: "FPS", decimals: 0 })`, then `controls.watch("fps", 59.9)`. */
|
|
510
570
|
export declare function watch(options?: ReadoutOptions): WatchField;
|
|
@@ -243,6 +243,13 @@ export interface MountPanelOptions extends LabelOptions {
|
|
|
243
243
|
readonly density?: DensityMode;
|
|
244
244
|
readonly renderer?: RendererMode;
|
|
245
245
|
readonly variants?: boolean;
|
|
246
|
+
/**
|
|
247
|
+
* The presets as chips in a tray (`"chips"`) or as rows like a menu's (`"list"`). `"auto"` (the
|
|
248
|
+
* default) shows chips while they fit on two lines and the list once they'd take more.
|
|
249
|
+
*/
|
|
250
|
+
readonly presetDisplay?: "auto" | "list" | "chips";
|
|
251
|
+
/** Shown in the panel's quiet style while it has no rows (`"This story has no controls"`). */
|
|
252
|
+
readonly empty?: string;
|
|
246
253
|
readonly history?: boolean;
|
|
247
254
|
readonly footer?: boolean;
|
|
248
255
|
readonly actions?: readonly ControlAction<any>[];
|
|
@@ -348,6 +355,12 @@ export interface ControlsOptions<S extends Schema> extends LabelOptions {
|
|
|
348
355
|
/** An undo stack the host owns; undo and redo call it (see `HostHistory`). */
|
|
349
356
|
history?: HostHistory;
|
|
350
357
|
presets?: Readonly<Record<string, Partial<Values<S>>>>;
|
|
358
|
+
/**
|
|
359
|
+
* Groups pinned to the top of a panel, under the header and above the tab strip, on every tab
|
|
360
|
+
* (as fields' `pinned: true` pins theirs). Presets and saved variants leave them alone: applying
|
|
361
|
+
* one keeps their values, saving one keeps them out, and they're modified against their defaults.
|
|
362
|
+
*/
|
|
363
|
+
pinned?: readonly string[];
|
|
351
364
|
/**
|
|
352
365
|
* Numbered presets per group, shown as 1, 2, 3… on the group's card in a page. A group's
|
|
353
366
|
* presets cover every key any of them names; keys a preset leaves out are at their defaults,
|
|
@@ -426,6 +439,8 @@ export interface Controls<S extends Schema = Schema> {
|
|
|
426
439
|
readonly sectionFolded: SectionFolded | undefined;
|
|
427
440
|
/** A preset's display label (`presetLabels`, `defaultLabel`), else its id. */
|
|
428
441
|
presetLabel(id: string): string;
|
|
442
|
+
/** Whether `key` is pinned (its group is, see `ControlsOptions.pinned`), outside the presets. */
|
|
443
|
+
isPinned(key: string): boolean;
|
|
429
444
|
readonly error: string | null;
|
|
430
445
|
get(): Values<S>;
|
|
431
446
|
getServerSnapshot(): Values<S>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { knob, choice, toggle, spring, color, point, easing, action, flatSpring, vec2, vec3, range, angle, seed, keybind, gradient, curve, toolbar, toolbarShortcut, actionShortcut, watch, sparkline, graph, meter, table, log, hasValue, isReadout, wrapAngle, SPRING, type FieldMeta, type ActionField, type SpringPart, type ValueField, type Knob, type Choice, type Toggle, type SpringField, type ColorField, type PointField, type EasingField, type VecField, type Vec3, type RangeField, type AngleField, type SeedField, type KeybindField, type GradientField, type CurveField, type ToolbarField, type ToolbarItem, type ToolbarShortcut, type WatchField, type SparklineField, type GraphField, type GraphSeries, type GraphThreshold, type MeterField, type TableField, type LogField, type ReadoutField, type ReadoutKeys, type ReadoutInput, type Point, type Field, type Schema, type Values, type Value, type State, } from "./core/schema.js";
|
|
1
|
+
export { knob, choice, toggle, spring, color, point, easing, action, flatSpring, vec2, vec3, range, angle, seed, keybind, text, gradient, curve, toolbar, toolbarShortcut, actionShortcut, watch, sparkline, graph, meter, table, log, hasValue, isReadout, wrapAngle, SPRING, type FieldMeta, type ActionField, type SpringPart, type ValueField, type Knob, type Choice, type Toggle, type SpringField, type ColorField, type PointField, type EasingField, type VecField, type Vec3, type RangeField, type AngleField, type SeedField, type KeybindField, type TextField, type GradientField, type CurveField, type ToolbarField, type ToolbarItem, type ToolbarShortcut, type WatchField, type SparklineField, type GraphField, type GraphSeries, type GraphThreshold, type MeterField, type TableField, type LogField, type ReadoutField, type ReadoutKeys, type ReadoutInput, type Point, type Field, type Schema, type Values, type Value, type State, } from "./core/schema.js";
|
|
2
2
|
export { springCss, springMotion, type SpringValue } from "./core/spring.js";
|
|
3
3
|
export { reducedSpring, SPRINGS, type SpringName } from "./core/motion.js";
|
|
4
4
|
export { colorHex, parseColor, type Oklch } from "./core/color.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The header of the knobs/ui split pane `host` sits in, when the pane has one (a title) and no
|
|
3
|
+
* panel's buttons have joined it yet. A panel there with no title of its own puts its buttons in
|
|
4
|
+
* it (after the pane's actions, before its collapse button) rather than drawing a header row
|
|
5
|
+
* that would hold only them: one header per surface.
|
|
6
|
+
*/
|
|
7
|
+
export declare function paneHeadOf(host: HTMLElement): HTMLElement | undefined;
|
|
8
|
+
/** Moves `actions` into the pane header `head`; the returned function takes them back out. */
|
|
9
|
+
export declare function dock(actions: HTMLElement, head: HTMLElement): () => void;
|
|
@@ -333,6 +333,16 @@ export interface GpuKeybindRow {
|
|
|
333
333
|
readonly wave: RowWave;
|
|
334
334
|
isModified(): boolean;
|
|
335
335
|
}
|
|
336
|
+
/** A text row: the canvas draws its label; the field itself is the DOM's, above the canvas. */
|
|
337
|
+
export interface GpuTextRow {
|
|
338
|
+
readonly kind: "text";
|
|
339
|
+
readonly key: string;
|
|
340
|
+
readonly display: string;
|
|
341
|
+
readonly row: HTMLElement;
|
|
342
|
+
readonly labelEl: HTMLElement;
|
|
343
|
+
readonly wave: RowWave;
|
|
344
|
+
isModified(): boolean;
|
|
345
|
+
}
|
|
336
346
|
export interface GpuToolbarRow {
|
|
337
347
|
readonly kind: "toolbar";
|
|
338
348
|
readonly key: string;
|
|
@@ -370,7 +380,7 @@ export interface GpuSubheadRow {
|
|
|
370
380
|
readonly wave: RowWave;
|
|
371
381
|
isModified(): boolean;
|
|
372
382
|
}
|
|
373
|
-
export type GpuRowBinding = GpuReadoutRow | GpuVecRow | GpuRangeRow | GpuAngleRow | GpuSeedRow | GpuKeybindRow | GpuToolbarRow | GpuSubheadRow | GpuActionRow | GpuKnobRow | GpuChoiceRow | GpuToggleRow | GpuSpringRow | GpuColorRow | GpuPointRow | GpuEasingRow | GpuGradientRow | GpuCurveRow;
|
|
383
|
+
export type GpuRowBinding = GpuReadoutRow | GpuVecRow | GpuRangeRow | GpuAngleRow | GpuSeedRow | GpuKeybindRow | GpuTextRow | GpuToolbarRow | GpuSubheadRow | GpuActionRow | GpuKnobRow | GpuChoiceRow | GpuToggleRow | GpuSpringRow | GpuColorRow | GpuPointRow | GpuEasingRow | GpuGradientRow | GpuCurveRow;
|
|
374
384
|
export interface GpuGroupBinding {
|
|
375
385
|
readonly element: HTMLElement;
|
|
376
386
|
readonly labelEl: HTMLElement;
|
|
@@ -407,6 +417,8 @@ export interface GpuPanelBinding {
|
|
|
407
417
|
readonly id: string;
|
|
408
418
|
readonly root: HTMLElement;
|
|
409
419
|
readonly body: HTMLElement;
|
|
420
|
+
/** The pinned groups' cards, above the tab strip (empty while none are pinned). */
|
|
421
|
+
readonly pinned?: HTMLElement;
|
|
410
422
|
readonly inline: boolean;
|
|
411
423
|
readonly header: HTMLElement;
|
|
412
424
|
readonly titleEl: HTMLElement;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toolbarShortcut, type AngleField, type KeybindField, type Point, type RangeField, type SeedField, type ToolbarField, type ToolbarItem, type Vec3, type VecField } from "../core/schema.js";
|
|
1
|
+
import { toolbarShortcut, type AngleField, type KeybindField, type Point, type TextField, type RangeField, type SeedField, type ToolbarField, type ToolbarItem, type Vec3, type VecField } from "../core/schema.js";
|
|
2
2
|
import { type EditorRow, type RowContext } from "./editors.js";
|
|
3
3
|
type Vec = Point & {
|
|
4
4
|
readonly z?: number;
|
|
@@ -30,6 +30,12 @@ export declare function keyCap(o: {
|
|
|
30
30
|
wake(): void;
|
|
31
31
|
}): KeyCap;
|
|
32
32
|
export declare function keybindRow(c: RowContext<KeybindField>): EditorRow;
|
|
33
|
+
/**
|
|
34
|
+
* Words in a field: one line beside the label, or (`multiline`) a box under it that grows with
|
|
35
|
+
* its lines. Each keystroke sets the value; one focus's typing is one undo step. Enter (⌘Enter in
|
|
36
|
+
* a box) or Esc lets go of it.
|
|
37
|
+
*/
|
|
38
|
+
export declare function textRow(c: RowContext<TextField>): EditorRow;
|
|
33
39
|
export interface ToolbarRow extends EditorRow {
|
|
34
40
|
readonly field: ToolbarField;
|
|
35
41
|
/** The item a keydown presses, if any (`global` says whether it's a page-wide shortcut). */
|
|
@@ -42,8 +48,10 @@ export interface ToolbarRow extends EditorRow {
|
|
|
42
48
|
type Shortcut = ReturnType<typeof toolbarShortcut>;
|
|
43
49
|
/**
|
|
44
50
|
* A toolbar: its buttons in equal columns, as many as fit and balanced across the rows (see
|
|
45
|
-
* `toolbarColumns`), each with its icon and label at the start and its shortcut at the end.
|
|
46
|
-
*
|
|
51
|
+
* `toolbarColumns`), each with its icon and label at the start and its shortcut at the end. When
|
|
52
|
+
* every button has an icon and the labels don't fit on one row (or `labels: false`), it's one row
|
|
53
|
+
* of icons, each label and shortcut in its tooltip. `keyOf` is each item's shortcut as it is now
|
|
54
|
+
* (the store's rebinding, else its own).
|
|
47
55
|
*/
|
|
48
56
|
export declare function toolbarRow(c: RowContext<ToolbarField>, press: (item: ToolbarItem) => void, keyOf?: (item: ToolbarItem) => Shortcut): ToolbarRow;
|
|
49
57
|
/** The value a vector shows, as `{ x, y }` or `{ x, y, z }`. */
|
|
@@ -28,7 +28,19 @@ export declare function activeEntryId(controls: Controls<any>): string | null;
|
|
|
28
28
|
* cycling never gets stuck when a saved variant has the exact same values as a built-in preset.
|
|
29
29
|
*/
|
|
30
30
|
export declare function resolveActivePresetEntry<V extends Record<string, any>>(entries: readonly PresetEntry<V>[], currentValues: V, lastAppliedId: string | null, baseId?: string | null): ActivePresetState;
|
|
31
|
+
/**
|
|
32
|
+
* How many lines `count` chips take in `avail` px, at least `min` px each with `gap` between (as
|
|
33
|
+
* the strip's auto-fit grid lays them); 1 before there's room to measure.
|
|
34
|
+
*/
|
|
35
|
+
export declare function presetLines(count: number, min: number, avail: number, gap: number): number;
|
|
36
|
+
/** Whether any entry but the defaults gives one of `keys` a value other than its default. */
|
|
37
|
+
export declare function presetsTouch(entries: readonly PresetEntry<any>[], keys: readonly string[], defaults: Readonly<Record<string, unknown>>): boolean;
|
|
31
38
|
export declare function nextPresetIndex(count: number, activeIndex: number, direction: number): number;
|
|
39
|
+
/**
|
|
40
|
+
* How the strip lays out its presets: `"chips"` in a tray of equal columns, `"list"` as rows like a
|
|
41
|
+
* menu's, or `"auto"` (the default): chips while they fit on two lines, else the list.
|
|
42
|
+
*/
|
|
43
|
+
export type PresetDisplay = "auto" | "list" | "chips";
|
|
32
44
|
export interface PresetStrip {
|
|
33
45
|
readonly element: HTMLElement;
|
|
34
46
|
readonly track: HTMLElement;
|
|
@@ -43,6 +55,10 @@ export interface PresetStrip {
|
|
|
43
55
|
getButtons(): readonly HTMLButtonElement[];
|
|
44
56
|
getActiveState(): ActivePresetState;
|
|
45
57
|
cycle(direction: -1 | 1): void;
|
|
58
|
+
/** Applies the nth chip (0-based), as a click on it does. */
|
|
59
|
+
select(index: number): void;
|
|
60
|
+
/** Lays the presets out as chips or a list (see `PresetDisplay`). */
|
|
61
|
+
display(mode: PresetDisplay): void;
|
|
46
62
|
selectById(id: string): void;
|
|
47
63
|
/** Saves the current values as "Variant N" and opens its name to typing; returns a status message. */
|
|
48
64
|
save(): string;
|
|
@@ -52,9 +68,13 @@ export interface PresetStrip {
|
|
|
52
68
|
readonly nameField: () => HTMLInputElement | null;
|
|
53
69
|
/** Deletes the selected chip when it's a saved variant; returns a status message. */
|
|
54
70
|
deleteActive(): string | undefined;
|
|
71
|
+
/** Whether the strip should show: presets to pick (or saving on) that touch what's shown. */
|
|
72
|
+
shows(): boolean;
|
|
55
73
|
update(): void;
|
|
56
74
|
dispose(): void;
|
|
57
75
|
}
|
|
58
76
|
export declare function createPresetStrip<S extends Schema>(doc: Document, controls: Controls<S>, showVariants: boolean, run: (action: () => unknown) => void, onAfterChange?: () => void,
|
|
59
77
|
/** What a preset's chip says (its id stays its identity); `controls.presetLabel` by default. */
|
|
60
|
-
presetLabel?: (id: string) => string
|
|
78
|
+
presetLabel?: (id: string) => string,
|
|
79
|
+
/** Whether these entries matter where the strip is shown (a tab they don't touch hides it). */
|
|
80
|
+
relevant?: (entries: readonly PresetEntry<any>[]) => boolean): PresetStrip;
|
|
@@ -28,11 +28,12 @@ export declare function crumbTrail(path: {
|
|
|
28
28
|
*/
|
|
29
29
|
export declare function escTarget(trail: readonly Crumb[], title: string): string | undefined;
|
|
30
30
|
/**
|
|
31
|
-
* How much of a trail fits `room` px: crumbs from `from` on show
|
|
32
|
-
*
|
|
33
|
-
* `
|
|
31
|
+
* How much of a trail fits `room` px: crumbs from `from` on show, the ones before it dropped, and
|
|
32
|
+
* the Esc hint shows only when it fits too. Without room for the deepest crumb, none show
|
|
33
|
+
* (`from` is the trail's length): the line never runs past the title's room into the buttons.
|
|
34
|
+
* `widths` are the crumbs' own; `sep` a separator's (with its gaps).
|
|
34
35
|
*/
|
|
35
|
-
export declare function fitCrumbs(widths: readonly number[], room: number, sep: number,
|
|
36
|
+
export declare function fitCrumbs(widths: readonly number[], room: number, sep: number, hint?: number): {
|
|
36
37
|
from: number;
|
|
37
38
|
hint: boolean;
|
|
38
39
|
};
|
|
@@ -41,6 +42,16 @@ export declare function fitCrumbs(widths: readonly number[], room: number, sep:
|
|
|
41
42
|
* the first nine, as they read.
|
|
42
43
|
*/
|
|
43
44
|
export declare function digitOption(key: string, count: number): number | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Where a digit goes in a panel: a focused row's options take 1–9 (even past the last), a number
|
|
47
|
+
* row and a page card's numbered presets keep theirs, and anywhere else 1–9 pick the preset
|
|
48
|
+
* strip's chips (the design's variants) while the strip has that many. Undefined leaves the key.
|
|
49
|
+
*/
|
|
50
|
+
export declare function digitRoute(key: string, at: {
|
|
51
|
+
options: boolean;
|
|
52
|
+
keeps: boolean;
|
|
53
|
+
presets: number;
|
|
54
|
+
}): "options" | "presets" | undefined;
|
|
44
55
|
/**
|
|
45
56
|
* The digit hints for a row's options: one per option that shows (up to nine), or, while they
|
|
46
57
|
* don't (a menu, a folded editor), one standing for them all ("1–5").
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const canvasCss = "\n.knobs-ui.knobs-canvas{--_kc-radius:var(--k-ui-canvas-radius,var(--_k-radius-panel));--_kc-fade:var(--k-ui-canvas-fade,16px);\n position:relative;width:100%;height:100%;overflow:hidden;outline:none;background:var(--k-ui-canvas-bg,var(--_k-bg-page));contain:strict;\n touch-action:none;overscroll-behavior:none;user-select:none;-webkit-user-select:none;cursor:default;\n font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}\n:where(.knobs-canvas-stage,.knobs-canvas-stage *,.knobs-canvas-zoom,.knobs-canvas-zoom *){box-sizing:border-box}\n/* No hard edges: what slides out of view fades over the last few px. */\n.knobs-canvas-stage{position:absolute;inset:0;overflow:hidden;\n -webkit-mask-image:linear-gradient(to right,transparent,#000 var(--_kc-fade),#000 calc(100% - var(--_kc-fade)),transparent),linear-gradient(to bottom,transparent,#000 var(--_kc-fade),#000 calc(100% - var(--_kc-fade)),transparent);\n -webkit-mask-composite:source-in;mask-image:linear-gradient(to right,transparent,#000 var(--_kc-fade),#000 calc(100% - var(--_kc-fade)),transparent),linear-gradient(to bottom,transparent,#000 var(--_kc-fade),#000 calc(100% - var(--_kc-fade)),transparent);mask-composite:intersect}\n.knobs-canvas-dots{position:absolute;left:-128px;top:-128px;right:-128px;bottom:-128px;pointer-events:none;will-change:transform;\n background-image:radial-gradient(circle,var(--k-ui-canvas-dot,color-mix(in srgb,var(--_k-fg) 22%,transparent)) 1px,transparent 1.25px)}\n.knobs-canvas-world{position:absolute;left:0;top:0;width:0;height:0;transform-origin:0 0}\n.knobs-canvas[data-moving] .knobs-canvas-world{will-change:transform}\n.knobs-canvas:is([data-panning],[data-space]) .knobs-canvas-world{pointer-events:none}\n.knobs-canvas[data-space]{cursor:grab}\n.knobs-canvas[data-grabbing]{cursor:grabbing}\n.knobs-canvas-tile{position:absolute;left:0;top:0;display:flex;flex-direction:column;width:max-content;transform-origin:50% 50%}\n.knobs-canvas-title{flex:none;contain:inline-size;height:24px;padding:0 2px 8px;overflow:hidden;white-space:nowrap;color:var(--_k-fg-2);\n font-size:var(--_k-type-small,12px);line-height:16px;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 16px),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 16px),transparent)}\n.knobs-canvas-card{position:relative;flex:none;padding:var(--k-ui-canvas-pad,16px);border-radius:var(--_kc-radius);background:var(--k-ui-canvas-card,var(--_k-bg));\n overflow:hidden;user-select:text;-webkit-user-select:text}\n.knobs-canvas-card::after{content:\"\";position:absolute;inset:0;border-radius:inherit;box-shadow:inset 0 0 0 1px var(--_k-border);pointer-events:none}\n/* The readout: how far, and the way back to the whole set or 100%. */\n.knobs-canvas-zoom{position:absolute;right:10px;bottom:10px;z-index:1;display:flex;align-items:center;gap:2px;height:28px;padding:0 4px;border-radius:999px;\n background:var(--_k-bg);box-shadow:inset 0 0 0 1px var(--_k-border);color:var(--_k-fg-2);font-size:var(--_k-type-small,12px);\n font-variant-numeric:tabular-nums;font-feature-settings:\"tnum\";white-space:nowrap}\n:where(.knobs-ui) .knobs-canvas-btn{all:revert;box-sizing:border-box;font:inherit;letter-spacing:inherit;margin:0;border:0;outline:none;box-shadow:none;\n display:inline-flex;align-items:center;justify-content:center;height:20px;min-width:20px;padding:0 6px;border-radius:999px;background:transparent;color:inherit;cursor:default;\n transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}\n:where(.knobs-ui) .knobs-canvas-btn:is(:hover,:focus-visible){background:var(--_k-fill-hover);color:var(--_k-fg);transition-duration:0ms}\n:where(.knobs-ui) .knobs-canvas-btn:focus-visible{box-shadow:0 0 0 1.5px var(--_k-accent)}\n.knobs-canvas-pct{min-width:46px}\n.knobs-canvas-btn svg{width:13px;height:13px}\n";
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/** The camera: a world point `p` is drawn at `p * scale + (x, y)` in the canvas's own px. */
|
|
2
|
+
export interface CanvasView {
|
|
3
|
+
readonly x: number;
|
|
4
|
+
readonly y: number;
|
|
5
|
+
readonly scale: number;
|
|
6
|
+
}
|
|
7
|
+
export interface CanvasRect {
|
|
8
|
+
readonly x: number;
|
|
9
|
+
readonly y: number;
|
|
10
|
+
readonly w: number;
|
|
11
|
+
readonly h: number;
|
|
12
|
+
}
|
|
13
|
+
export interface CanvasPoint {
|
|
14
|
+
readonly x: number;
|
|
15
|
+
readonly y: number;
|
|
16
|
+
}
|
|
17
|
+
export declare const clamp: (v: number, lo: number, hi: number) => number;
|
|
18
|
+
/**
|
|
19
|
+
* Tiles in rows, in order: each row takes tiles until the next would pass `width`, rows sit `gap`
|
|
20
|
+
* apart and tiles top-align in them. Rects land on whole px, so at 100% they're on the pixel grid.
|
|
21
|
+
*/
|
|
22
|
+
export declare function packRows(sizes: readonly {
|
|
23
|
+
readonly w: number;
|
|
24
|
+
readonly h: number;
|
|
25
|
+
}[], opts: {
|
|
26
|
+
readonly gap: number;
|
|
27
|
+
readonly width: number;
|
|
28
|
+
readonly x?: number;
|
|
29
|
+
readonly y?: number;
|
|
30
|
+
}): CanvasRect[];
|
|
31
|
+
/**
|
|
32
|
+
* How wide rows run so the packed set comes out about `aspect` (width / height): a bisection
|
|
33
|
+
* between the widest tile and one long row, on how the packing actually comes out.
|
|
34
|
+
*/
|
|
35
|
+
export declare function rowWidth(sizes: readonly {
|
|
36
|
+
readonly w: number;
|
|
37
|
+
readonly h: number;
|
|
38
|
+
}[], gap: number, aspect: number): number;
|
|
39
|
+
/**
|
|
40
|
+
* Where every item goes: one with an `x` and `y` stays there; the rest pack in rows (`packRows`),
|
|
41
|
+
* under the placed ones if there are any.
|
|
42
|
+
*/
|
|
43
|
+
export declare function layoutCanvas(items: readonly {
|
|
44
|
+
readonly x?: number;
|
|
45
|
+
readonly y?: number;
|
|
46
|
+
readonly w: number;
|
|
47
|
+
readonly h: number;
|
|
48
|
+
}[], opts: {
|
|
49
|
+
readonly gap: number;
|
|
50
|
+
readonly width?: number;
|
|
51
|
+
readonly aspect?: number;
|
|
52
|
+
}): {
|
|
53
|
+
rects: CanvasRect[];
|
|
54
|
+
width: number;
|
|
55
|
+
};
|
|
56
|
+
/** The rect around `rects`, or null for none. */
|
|
57
|
+
export declare function boundsOf(rects: readonly CanvasRect[]): CanvasRect | null;
|
|
58
|
+
/** Screen (canvas px) to world, and back. */
|
|
59
|
+
export declare const toWorld: (v: CanvasView, p: CanvasPoint) => CanvasPoint;
|
|
60
|
+
export declare const toScreen: (v: CanvasView, p: CanvasPoint) => CanvasPoint;
|
|
61
|
+
/** The view at `scale` (clamped to `min`…`max`), with the world point under `at` still under it. */
|
|
62
|
+
export declare function zoomAround(v: CanvasView, scale: number, at: CanvasPoint, min?: number, max?: number): CanvasView;
|
|
63
|
+
/**
|
|
64
|
+
* Two fingers moved: the world point under their old midpoint `from` lands under the new one
|
|
65
|
+
* `to`, with the zoom times `ratio` (zoom around `from`, then follow the midpoint).
|
|
66
|
+
*/
|
|
67
|
+
export declare function pinchView(v: CanvasView, from: CanvasPoint, to: CanvasPoint, ratio: number, min?: number, max?: number): CanvasView;
|
|
68
|
+
/** The most one wheel event zooms (a mouse wheel's notch under ⌘ or ctrl). */
|
|
69
|
+
export declare const WHEEL_ZOOM_MAX = 1.25;
|
|
70
|
+
/**
|
|
71
|
+
* One ⌘/ctrl-wheel event's zoom factor, applied directly: a trackpad pinch (ctrl) at 1% per px,
|
|
72
|
+
* ⌘-scroll at .25%, and never more than `WHEEL_ZOOM_MAX` either way per event, so a mouse
|
|
73
|
+
* wheel's big notches step evenly without springing.
|
|
74
|
+
*/
|
|
75
|
+
export declare function wheelZoom(deltaY: number, pinch: boolean): number;
|
|
76
|
+
/** The view that shows `r` whole and centred in a `w` × `h` canvas, `pad` px in from its edges. */
|
|
77
|
+
export declare function fitView(r: CanvasRect, w: number, h: number, opts?: {
|
|
78
|
+
readonly pad?: number;
|
|
79
|
+
readonly min?: number;
|
|
80
|
+
readonly max?: number;
|
|
81
|
+
}): CanvasView;
|
|
82
|
+
/**
|
|
83
|
+
* A path between two views, `t` 0 → 1. It zooms around the one canvas point both views agree on,
|
|
84
|
+
* at an even rate in log scale, so every point on screen travels one straight line in one
|
|
85
|
+
* direction (no curve from springing x, y and scale apart). Views at the same scale pan straight.
|
|
86
|
+
*/
|
|
87
|
+
export declare function viewPath(from: CanvasView, to: CanvasView): (t: number) => CanvasView;
|
|
88
|
+
/** The translate on the device-pixel grid, so a resting view draws crisp. */
|
|
89
|
+
export declare const snapView: (v: CanvasView, dpr: number) => CanvasView;
|
|
90
|
+
/**
|
|
91
|
+
* The background's dots: `step` world px apart, drawn `size` canvas px apart, and how strongly
|
|
92
|
+
* (0…1): they fade out as they crowd together zooming out and as they spread apart zooming in.
|
|
93
|
+
*/
|
|
94
|
+
export declare function dotGrid(scale: number, step?: number): {
|
|
95
|
+
size: number;
|
|
96
|
+
alpha: number;
|
|
97
|
+
};
|
|
98
|
+
/** What a key does to the view: 0 fits, 1 is 100%, = and - zoom in and out. */
|
|
99
|
+
export type CanvasKey = "fit" | "actual" | "in" | "out";
|
|
100
|
+
export declare function keyAction(e: {
|
|
101
|
+
readonly key: string;
|
|
102
|
+
readonly altKey?: boolean;
|
|
103
|
+
}): CanvasKey | null;
|
|
104
|
+
/** How far each zoom key steps. */
|
|
105
|
+
export declare const KEY_ZOOM = 1.5;
|
|
106
|
+
/**
|
|
107
|
+
* The hand's velocity at release (px/s) from recent samples `{ x, y, t (ms) }`: the travel over
|
|
108
|
+
* the last `window` ms. Zero if the hand had already stopped.
|
|
109
|
+
*/
|
|
110
|
+
export declare function releaseVelocity(samples: readonly {
|
|
111
|
+
readonly x: number;
|
|
112
|
+
readonly y: number;
|
|
113
|
+
readonly t: number;
|
|
114
|
+
}[], now: number, window?: number): CanvasPoint;
|
|
115
|
+
/** Momentum's time constant (s): the coast slows as iOS's scroll does (0.998 per ms). */
|
|
116
|
+
export declare const COAST_TAU: number;
|