tweakit 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -5
- package/README.md +3 -3
- package/dist/tweaks/auto.js +1 -1
- package/dist/tweaks/auto.js.map +1 -1
- package/dist/tweaks/bezier-S2BOXQBM.js +2 -0
- package/dist/tweaks/bezier-S2BOXQBM.js.map +6 -0
- package/dist/tweaks/chunk-3MZAAMC2.js +2 -0
- package/dist/tweaks/chunk-3MZAAMC2.js.map +6 -0
- package/dist/tweaks/chunk-ETB35KFV.js +2 -0
- package/dist/tweaks/chunk-ETB35KFV.js.map +6 -0
- package/dist/tweaks/chunk-YB5GKWZI.js +2 -0
- package/dist/tweaks/chunk-YB5GKWZI.js.map +6 -0
- package/dist/tweaks/clip-A3VI53BK.js +2 -0
- package/dist/tweaks/clip-A3VI53BK.js.map +6 -0
- package/dist/tweaks/color-UGYMAF3W.js +2 -0
- package/dist/tweaks/core.js +1 -1
- package/dist/tweaks/core.js.map +3 -3
- package/dist/tweaks/fold-GY47HIQA.js +2 -0
- package/dist/tweaks/fold-GY47HIQA.js.map +6 -0
- package/dist/tweaks/gradient-BTX25WSH.js +2 -0
- package/dist/tweaks/gradient-BTX25WSH.js.map +6 -0
- package/dist/tweaks/{heavy-CLD7UAVI.js → heavy-NPFIGHRM.js} +2 -2
- package/dist/tweaks/image-HTQQ4WX6.js +4 -0
- package/dist/tweaks/image-HTQQ4WX6.js.map +6 -0
- package/dist/tweaks/{image-store-WRKY2NPU.js → image-store-QD5SMZXG.js} +1 -1
- package/dist/tweaks/image-store-QD5SMZXG.js.map +6 -0
- package/dist/tweaks/interval-BHPI4LRN.js +2 -0
- package/dist/tweaks/interval-BHPI4LRN.js.map +6 -0
- package/dist/tweaks/markup-QM53TQJZ.js +2 -0
- package/dist/tweaks/markup-QM53TQJZ.js.map +6 -0
- package/dist/tweaks/monitor-722WIYGW.js +3 -0
- package/dist/tweaks/monitor-722WIYGW.js.map +6 -0
- package/dist/tweaks/motion-HMCZK6HJ.js +2 -0
- package/dist/tweaks/motion-HMCZK6HJ.js.map +6 -0
- package/dist/tweaks/plot-D3NQ6XNN.js +3 -0
- package/dist/tweaks/plot-D3NQ6XNN.js.map +6 -0
- package/dist/tweaks/point-XDXKMDXH.js +2 -0
- package/dist/tweaks/point-XDXKMDXH.js.map +6 -0
- package/dist/tweaks/presets-EUWNSCUW.js +2 -0
- package/dist/tweaks/presets-EUWNSCUW.js.map +6 -0
- package/dist/tweaks/shadow-AM7VSRNS.js +2 -0
- package/dist/tweaks/shadow-AM7VSRNS.js.map +6 -0
- package/dist/tweaks/shadow-root-RYUV3AGS.js +3 -0
- package/dist/tweaks/shadow-root-RYUV3AGS.js.map +6 -0
- package/dist/tweaks/sides-JWIURDA4.js +2 -0
- package/dist/tweaks/sides-JWIURDA4.js.map +6 -0
- package/dist/tweaks/spring-BF5GBTPM.js +2 -0
- package/dist/tweaks/spring-BF5GBTPM.js.map +6 -0
- package/dist/tweaks/tabs-PRVUUZXZ.js +2 -0
- package/dist/tweaks/tabs-PRVUUZXZ.js.map +6 -0
- package/dist/tweaks.css +1 -1
- package/dist/tweaks.js +5 -4
- package/dist/tweaks.js.map +3 -3
- package/dist/types/tweaks/enhance.d.ts +3 -2
- package/dist/types/tweaks/types.d.ts +278 -72
- package/package.json +5 -2
- package/src/tweaks/auto.ts +9 -5
- package/src/tweaks/clip.ts +84 -22
- package/src/tweaks/controls/basic.ts +463 -139
- package/src/tweaks/controls/bezier.ts +152 -47
- package/src/tweaks/controls/color.ts +610 -161
- package/src/tweaks/controls/gradient.ts +256 -77
- package/src/tweaks/controls/image.ts +388 -132
- package/src/tweaks/controls/interval.ts +126 -41
- package/src/tweaks/controls/monitor.ts +142 -36
- package/src/tweaks/controls/motion.ts +231 -56
- package/src/tweaks/controls/plot.ts +321 -101
- package/src/tweaks/controls/point.ts +134 -45
- package/src/tweaks/controls/shadow.ts +103 -30
- package/src/tweaks/controls/sides.ts +150 -40
- package/src/tweaks/controls/spring.ts +220 -60
- package/src/tweaks/controls/tabs.ts +39 -18
- package/src/tweaks/core.ts +1 -1
- package/src/tweaks/easing.ts +45 -20
- package/src/tweaks/enhance.ts +21 -6
- package/src/tweaks/feedback.ts +133 -34
- package/src/tweaks/fold.ts +22 -10
- package/src/tweaks/heavy.ts +461 -137
- package/src/tweaks/icons.ts +12 -5
- package/src/tweaks/image-store.ts +131 -43
- package/src/tweaks/lazy.ts +36 -19
- package/src/tweaks/markup.ts +255 -92
- package/src/tweaks/panel.ts +1262 -438
- package/src/tweaks/presets.ts +85 -0
- package/src/tweaks/schema.ts +213 -51
- package/src/tweaks/shadow-root.ts +178 -0
- package/src/tweaks/shared.ts +802 -190
- package/src/tweaks/standalone.ts +120 -30
- package/src/tweaks/types.ts +644 -143
- package/src/tweaks/undo.ts +104 -0
- package/src/tweaks.css +556 -550
- package/src/wide-gamut.ts +473 -125
- package/dist/tweaks/bezier-JFEZMCLP.js +0 -2
- package/dist/tweaks/bezier-JFEZMCLP.js.map +0 -6
- package/dist/tweaks/chunk-6UO4DHBN.js +0 -2
- package/dist/tweaks/chunk-6UO4DHBN.js.map +0 -6
- package/dist/tweaks/chunk-DIQMLBAS.js +0 -2
- package/dist/tweaks/chunk-DIQMLBAS.js.map +0 -6
- package/dist/tweaks/chunk-XE6OUJBG.js +0 -2
- package/dist/tweaks/chunk-XE6OUJBG.js.map +0 -6
- package/dist/tweaks/clip-WW7JYO4X.js +0 -2
- package/dist/tweaks/clip-WW7JYO4X.js.map +0 -6
- package/dist/tweaks/color-YMMFURP6.js +0 -2
- package/dist/tweaks/fold-U6YRZLCJ.js +0 -2
- package/dist/tweaks/fold-U6YRZLCJ.js.map +0 -6
- package/dist/tweaks/gradient-JER7PW4D.js +0 -2
- package/dist/tweaks/gradient-JER7PW4D.js.map +0 -6
- package/dist/tweaks/image-H6QSLGO3.js +0 -4
- package/dist/tweaks/image-H6QSLGO3.js.map +0 -6
- package/dist/tweaks/image-store-WRKY2NPU.js.map +0 -6
- package/dist/tweaks/interval-TEUHQGZN.js +0 -2
- package/dist/tweaks/interval-TEUHQGZN.js.map +0 -6
- package/dist/tweaks/markup-NIBJD6E2.js +0 -2
- package/dist/tweaks/markup-NIBJD6E2.js.map +0 -6
- package/dist/tweaks/monitor-3BY7YCGX.js +0 -3
- package/dist/tweaks/monitor-3BY7YCGX.js.map +0 -6
- package/dist/tweaks/motion-FQPUBKKM.js +0 -2
- package/dist/tweaks/motion-FQPUBKKM.js.map +0 -6
- package/dist/tweaks/plot-2HBNBZSG.js +0 -3
- package/dist/tweaks/plot-2HBNBZSG.js.map +0 -6
- package/dist/tweaks/point-VV6XV3I6.js +0 -2
- package/dist/tweaks/point-VV6XV3I6.js.map +0 -6
- package/dist/tweaks/shadow-5E4E55I2.js +0 -2
- package/dist/tweaks/shadow-5E4E55I2.js.map +0 -6
- package/dist/tweaks/sides-P3STEVYY.js +0 -2
- package/dist/tweaks/sides-P3STEVYY.js.map +0 -6
- package/dist/tweaks/spring-HUVSXNVN.js +0 -2
- package/dist/tweaks/spring-HUVSXNVN.js.map +0 -6
- package/dist/tweaks/tabs-76FW7LQ7.js +0 -2
- package/dist/tweaks/tabs-76FW7LQ7.js.map +0 -6
- /package/dist/tweaks/{color-YMMFURP6.js.map → color-UGYMAF3W.js.map} +0 -0
- /package/dist/tweaks/{heavy-CLD7UAVI.js.map → heavy-NPFIGHRM.js.map} +0 -0
package/src/tweaks/types.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* panel you get back. These are what ship as .d.ts, so consumers get autocomplete on
|
|
3
3
|
* the schema shorthands + the verbose control forms + the panel methods. */
|
|
4
4
|
|
|
5
|
-
/** An option for a select / radio grid: a bare string or number, or `{ value, label }`; the param is the value as
|
|
6
|
-
|
|
5
|
+
/** An option for a select / radio grid: a bare string or number, or `{ value, label }`; the param is the value as
|
|
6
|
+
* written. */
|
|
7
|
+
// color: any CSS color, shown as a swatch beside a dropdown option (and the chosen one on the row)
|
|
8
|
+
export type Option = string | number | { value: string | number; label?: string; color?: string };
|
|
7
9
|
|
|
8
10
|
/** A color stop in a gradient. */
|
|
9
11
|
export type GradientStop = { color: string; pos: number };
|
|
@@ -11,7 +13,8 @@ export type GradientStop = { color: string; pos: number };
|
|
|
11
13
|
/** The CSS `<color-interpolation-method>` a gradient blends through — the space of the
|
|
12
14
|
* mode the stops were authored in. Template it as `linear-gradient(in <interpolation> …)`
|
|
13
15
|
* so your CSS ramp matches the editor's preview exactly. */
|
|
14
|
-
export type GradientInterpolation =
|
|
16
|
+
export type GradientInterpolation =
|
|
17
|
+
"srgb" | "hsl" | "hwb" | "oklch" | "oklab" | "lch" | "lab" | "display-p3" | "rec2020";
|
|
15
18
|
|
|
16
19
|
/** What a sides control accepts: one number for all four; `[vertical, horizontal]`,
|
|
17
20
|
* `[top, horizontal, bottom]` or `[top, right, bottom, left]` (CSS order); a CSS shorthand
|
|
@@ -19,29 +22,69 @@ export type GradientInterpolation = "srgb" | "hsl" | "hwb" | "oklch" | "oklab" |
|
|
|
19
22
|
* units warn and read as px); or any subset of
|
|
20
23
|
* the four as an object. With `corners: true` the four are the corners from top-left round,
|
|
21
24
|
* the same CSS order, under the same keys, and they floor at 0 unless `min` says otherwise. */
|
|
22
|
-
export type SidesInput =
|
|
25
|
+
export type SidesInput =
|
|
26
|
+
number | readonly number[] | string | { top?: number; right?: number; bottom?: number; left?: number };
|
|
23
27
|
/** A sides control's value: the four numbers in CSS order plus the shorthand they collapse
|
|
24
28
|
* to (`css`, in the control's unit, `0` bare), so `el.style.padding = css`. */
|
|
25
|
-
export interface SidesValue {
|
|
29
|
+
export interface SidesValue {
|
|
30
|
+
top: number;
|
|
31
|
+
right: number;
|
|
32
|
+
bottom: number;
|
|
33
|
+
left: number;
|
|
34
|
+
css: string;
|
|
35
|
+
}
|
|
26
36
|
|
|
27
37
|
/** What a shadow control accepts — any subset of its fields (px, `color` any color the kit
|
|
28
38
|
* parses); or, as `value`, a `box-shadow` string: optional `inset`, two to four px lengths,
|
|
29
39
|
* one color, one layer. */
|
|
30
|
-
export interface ShadowInput {
|
|
40
|
+
export interface ShadowInput {
|
|
41
|
+
inset?: boolean;
|
|
42
|
+
x?: number;
|
|
43
|
+
y?: number;
|
|
44
|
+
blur?: number;
|
|
45
|
+
spread?: number;
|
|
46
|
+
color?: string;
|
|
47
|
+
}
|
|
31
48
|
/** A shadow control's value — the fields, plus the `box-shadow` CSS they resolve to (`css`),
|
|
32
49
|
* built from the numbers and the picker's own color notation, so `el.style.boxShadow = css`. */
|
|
33
|
-
export interface ShadowValue {
|
|
50
|
+
export interface ShadowValue {
|
|
51
|
+
inset: boolean;
|
|
52
|
+
x: number;
|
|
53
|
+
y: number;
|
|
54
|
+
blur: number;
|
|
55
|
+
spread: number;
|
|
56
|
+
color: string;
|
|
57
|
+
css: string;
|
|
58
|
+
}
|
|
34
59
|
/** What a motion control accepts — any subset of either mode's fields. A `curve` may be
|
|
35
60
|
* the four cubic-bezier numbers or a CSS keyword (`ease`, `ease-out`, …); `duration` is
|
|
36
61
|
* the easing's, in ms. Spring keys select the spring mode unless `mode` says otherwise. */
|
|
37
|
-
export interface MotionInput {
|
|
62
|
+
export interface MotionInput {
|
|
63
|
+
mode?: "easing" | "spring";
|
|
64
|
+
curve?: readonly [number, number, number, number] | string;
|
|
65
|
+
duration?: number;
|
|
66
|
+
stiffness?: number;
|
|
67
|
+
damping?: number;
|
|
68
|
+
mass?: number;
|
|
69
|
+
visualDuration?: number;
|
|
70
|
+
bounce?: number;
|
|
71
|
+
}
|
|
38
72
|
/** A motion control's value: an easing curve with its duration, or a spring — each also
|
|
39
73
|
* carrying the CSS it resolves to, `duration` in ms and an `easing` string (`cubic-bezier(…)`,
|
|
40
74
|
* or a sampled `linear(…)` for the spring), so a host can write
|
|
41
75
|
* `transition: transform ${duration}ms ${easing}` in either mode. */
|
|
42
76
|
export type MotionValue =
|
|
43
77
|
| { mode: "easing"; curve: [number, number, number, number]; duration: number; easing: string }
|
|
44
|
-
| {
|
|
78
|
+
| {
|
|
79
|
+
mode: "spring";
|
|
80
|
+
stiffness: number;
|
|
81
|
+
damping: number;
|
|
82
|
+
mass: number;
|
|
83
|
+
visualDuration?: number;
|
|
84
|
+
bounce?: number;
|
|
85
|
+
duration: number;
|
|
86
|
+
easing: string;
|
|
87
|
+
};
|
|
45
88
|
|
|
46
89
|
/** How a gradient blends between neighboring stops: `"linear"` (CSS's own straight blend,
|
|
47
90
|
* the default) or a CSS easing — `ease`, `ease-in`, `ease-out`, `ease-in-out`, or any
|
|
@@ -52,7 +95,11 @@ export type GradientEasing = "linear" | "ease" | "ease-in" | "ease-out" | "ease-
|
|
|
52
95
|
/** A gradient control's value — the stops, the blend space, and the easing between stops.
|
|
53
96
|
* Hand it to `gradientCss(value, angle?)` for the CSS, or `gradientStops(value)` for just
|
|
54
97
|
* the stop list (a conic / radial gradient). */
|
|
55
|
-
export interface GradientValue {
|
|
98
|
+
export interface GradientValue {
|
|
99
|
+
stops: GradientStop[];
|
|
100
|
+
interpolation?: GradientInterpolation;
|
|
101
|
+
easing?: GradientEasing;
|
|
102
|
+
}
|
|
56
103
|
|
|
57
104
|
/** Reader passed to a conditional `render` / `disabled` predicate — `get("siblingKey")`. */
|
|
58
105
|
export type Get = (key: string) => unknown;
|
|
@@ -76,14 +123,22 @@ export interface ControlOptions {
|
|
|
76
123
|
|
|
77
124
|
/** The verbose object forms a schema value can take: `{ type: "…", … }`. */
|
|
78
125
|
export type SchemaObject =
|
|
79
|
-
| {
|
|
126
|
+
| {
|
|
127
|
+
type: "slider";
|
|
128
|
+
value?: number;
|
|
129
|
+
min?: number;
|
|
130
|
+
max?: number;
|
|
131
|
+
step?: number;
|
|
132
|
+
soft?: boolean;
|
|
133
|
+
unit?: string;
|
|
80
134
|
/** Draws the readout from the value ("7:30 AM" for minutes of the day), in place of its
|
|
81
135
|
* digits; `unit` still follows it. Display only, like `unit`: the param stays the number. */
|
|
82
136
|
format?: (value: number) => string;
|
|
83
137
|
/** Reads a typed readout back into a value, so the inline editor shows and takes the
|
|
84
138
|
* formatted text (without one, it edits the digits). Text it can't read (it throws, or
|
|
85
139
|
* returns anything but a finite number) leaves the value where it was. */
|
|
86
|
-
parse?: (text: string) => number
|
|
140
|
+
parse?: (text: string) => number;
|
|
141
|
+
}
|
|
87
142
|
| { type: "number"; value?: number; min?: number; max?: number; step?: number; soft?: boolean; unit?: string }
|
|
88
143
|
| { type: "checkbox"; value?: boolean }
|
|
89
144
|
| { type: "list"; options: readonly Option[]; value?: string | number }
|
|
@@ -91,37 +146,121 @@ export type SchemaObject =
|
|
|
91
146
|
| { type: "color"; value?: string }
|
|
92
147
|
| { type: "text"; value?: string; rows?: number; placeholder?: string }
|
|
93
148
|
| { type: "interval"; value?: readonly [number, number]; min?: number; max?: number; step?: number }
|
|
94
|
-
| {
|
|
149
|
+
| {
|
|
150
|
+
type: "spring";
|
|
151
|
+
mode?: "time" | "physics";
|
|
152
|
+
visualDuration?: number;
|
|
153
|
+
bounce?: number;
|
|
154
|
+
stiffness?: number;
|
|
155
|
+
damping?: number;
|
|
156
|
+
mass?: number;
|
|
157
|
+
value?: {
|
|
158
|
+
mode?: "time" | "physics";
|
|
159
|
+
visualDuration?: number;
|
|
160
|
+
bounce?: number;
|
|
161
|
+
stiffness?: number;
|
|
162
|
+
damping?: number;
|
|
163
|
+
mass?: number;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
95
166
|
| { type: "cubicbezier"; value?: readonly [number, number, number, number] }
|
|
96
167
|
| ({ type: "shadow"; value?: ShadowInput | string } & ShadowInput)
|
|
97
|
-
| {
|
|
168
|
+
| {
|
|
169
|
+
type: "sides";
|
|
170
|
+
value?: SidesInput;
|
|
171
|
+
corners?: boolean;
|
|
172
|
+
unit?: string;
|
|
173
|
+
step?: number;
|
|
174
|
+
min?: number;
|
|
175
|
+
max?: number;
|
|
176
|
+
top?: number;
|
|
177
|
+
right?: number;
|
|
178
|
+
bottom?: number;
|
|
179
|
+
left?: number;
|
|
180
|
+
}
|
|
98
181
|
| ({ type: "motion"; value?: MotionInput } & MotionInput)
|
|
99
|
-
| {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
182
|
+
| {
|
|
183
|
+
type: "point";
|
|
184
|
+
components: ReadonlyArray<{
|
|
185
|
+
key: string;
|
|
186
|
+
label?: string;
|
|
187
|
+
value?: number;
|
|
188
|
+
min?: number;
|
|
189
|
+
max?: number;
|
|
190
|
+
step?: number;
|
|
191
|
+
}>;
|
|
192
|
+
value?: { readonly [key: string]: number };
|
|
193
|
+
pad?: boolean;
|
|
194
|
+
invertY?: boolean;
|
|
195
|
+
}
|
|
196
|
+
// the value names its components
|
|
197
|
+
| {
|
|
198
|
+
type: "point";
|
|
199
|
+
value: { readonly [key: string]: number };
|
|
200
|
+
components?: undefined;
|
|
201
|
+
pad?: boolean;
|
|
202
|
+
invertY?: boolean;
|
|
203
|
+
}
|
|
204
|
+
| {
|
|
205
|
+
type: "gradient";
|
|
206
|
+
value?: GradientValue | ReadonlyArray<GradientStop | readonly [string, number]>;
|
|
207
|
+
stops?: ReadonlyArray<GradientStop | readonly [string, number]>;
|
|
208
|
+
interpolation?: GradientInterpolation;
|
|
209
|
+
easing?: GradientEasing;
|
|
210
|
+
}
|
|
211
|
+
| {
|
|
212
|
+
type: "plot";
|
|
213
|
+
expr?: string;
|
|
214
|
+
fn?: (x: number) => number;
|
|
215
|
+
xMin?: number;
|
|
216
|
+
xMax?: number;
|
|
217
|
+
yMin?: number;
|
|
218
|
+
yMax?: number;
|
|
219
|
+
samples?: number;
|
|
220
|
+
editable?: boolean;
|
|
221
|
+
min?: number;
|
|
222
|
+
max?: number;
|
|
223
|
+
}
|
|
103
224
|
| { type: "fpsgraph" }
|
|
104
|
-
| {
|
|
105
|
-
|
|
225
|
+
| {
|
|
226
|
+
type: "monitor";
|
|
227
|
+
get?: () => number | string;
|
|
228
|
+
value?: number | string;
|
|
229
|
+
graph?: boolean;
|
|
230
|
+
view?: "graph" | "text";
|
|
231
|
+
min?: number;
|
|
232
|
+
max?: number;
|
|
233
|
+
interval?: number;
|
|
234
|
+
rows?: number;
|
|
235
|
+
decimals?: number;
|
|
236
|
+
}
|
|
237
|
+
| {
|
|
238
|
+
type: "image";
|
|
239
|
+
value?: string;
|
|
106
240
|
/** Supplied images to pick from, as URLs or `{ value, label }`. The control becomes a row naming the chosen
|
|
107
|
-
* image that opens a pane of tiles with the upload below them, and the param is the chosen URL or the upload's
|
|
241
|
+
* image that opens a pane of tiles with the upload below them, and the param is the chosen URL or the upload's
|
|
242
|
+
* data URL. */
|
|
108
243
|
options?: ReadonlyArray<string | { value: string; label?: string }>;
|
|
109
|
-
/** Tiles per row in the gallery, 1 to 6 (by default one per option for two or three, 2 for one or four, then
|
|
244
|
+
/** Tiles per row in the gallery, 1 to 6 (by default one per option for two or three, 2 for one or four, then
|
|
245
|
+
* 3). */
|
|
110
246
|
cols?: number;
|
|
111
247
|
/** The largest upload taken, in bytes (10 MB unless set); a bigger file is refused with a note. */
|
|
112
248
|
maxSize?: number;
|
|
113
249
|
/** An upload longer than this on its long side, in px, is drawn down to it before it becomes the param, and one
|
|
114
|
-
* heavier than 1.5 MB is redrawn at its size, so a phone photo can't fill storage (2560 unless set; `false`
|
|
115
|
-
* uploads as they are). A JPEG comes out as a JPEG at
|
|
116
|
-
* `quality`; anything else as a PNG when that is 1.5 MB or less (a screenshot, not a photo), else as a JPEG, or
|
|
117
|
-
* when it has transparency. A GIF, an SVG, an animated WebP or APNG, a light picture within `fit`, or one
|
|
250
|
+
* heavier than 1.5 MB is redrawn at its size, so a phone photo can't fill storage (2560 unless set; `false`
|
|
251
|
+
* takes uploads as they are). A JPEG comes out as a JPEG at
|
|
252
|
+
* `quality`; anything else as a PNG when that is 1.5 MB or less (a screenshot, not a photo), else as a JPEG, or
|
|
253
|
+
* a WebP when it has transparency. A GIF, an SVG, an animated WebP or APNG, a light picture within `fit`, or one
|
|
254
|
+
* the redraw wouldn't make smaller is kept as it is. */
|
|
118
255
|
fit?: number | false;
|
|
119
256
|
/** The JPEG or WebP quality of a shrunk upload, 0 to 1 (0.85 unless set). */
|
|
120
257
|
quality?: number;
|
|
121
258
|
/** `false` leaves the upload button out of a gallery: the options are the whole choice. */
|
|
122
259
|
upload?: boolean;
|
|
123
|
-
/** `false` leaves Clear out of a gallery, and Delete no longer empties it: for a choice that must keep a
|
|
124
|
-
|
|
260
|
+
/** `false` leaves Clear out of a gallery, and Delete no longer empties it: for a choice that must keep a
|
|
261
|
+
* value. */
|
|
262
|
+
clear?: boolean;
|
|
263
|
+
}
|
|
125
264
|
| { type: "button"; action: () => void; label?: string }
|
|
126
265
|
| { type: "buttongroup"; buttons: Record<string, () => void> | ReadonlyArray<{ label: string; action: () => void }> }
|
|
127
266
|
| { type: "separator" }
|
|
@@ -129,30 +268,44 @@ export type SchemaObject =
|
|
|
129
268
|
|
|
130
269
|
/** A schema value — a shorthand, a verbose `{ type }` object, or a nested folder. */
|
|
131
270
|
export type SchemaValue = SchemaValueOf<SchemaNest>;
|
|
132
|
-
// `F` is the nested-folder branch: strict for a schema annotated `Schema`, loose for what tweaks() infers from
|
|
271
|
+
// `F` is the nested-folder branch: strict for a schema annotated `Schema`, loose for what tweaks() infers from
|
|
272
|
+
// (HeldSchema).
|
|
133
273
|
type SchemaValueOf<F> =
|
|
134
|
-
|
|
135
|
-
|
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
|
|
139
|
-
| readonly
|
|
140
|
-
| readonly
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
144
|
-
|
|
|
145
|
-
|
|
|
274
|
+
// bare number → slider (0–1 for a value ≤ 1, else 0–3×value; a negative spans that either side of zero)
|
|
275
|
+
| number
|
|
276
|
+
| boolean // → checkbox
|
|
277
|
+
| string // → text input; a hex or color-function string → color picker
|
|
278
|
+
// [value, min?, max?, step?] → slider
|
|
279
|
+
| readonly [number]
|
|
280
|
+
| readonly [number, number]
|
|
281
|
+
| readonly [number, number, number]
|
|
282
|
+
| readonly [number, number, number, number]
|
|
283
|
+
// [[lo,hi], min, max, step?] → interval
|
|
284
|
+
| readonly [readonly [number, number], number, number]
|
|
285
|
+
| readonly [readonly [number, number], number, number, number]
|
|
286
|
+
// either of the above, widened by a schema held in a variable
|
|
287
|
+
| readonly number[]
|
|
288
|
+
| readonly (number | readonly number[])[]
|
|
289
|
+
| readonly Option[] // a list of options → list (dropdown)
|
|
290
|
+
| { options: readonly Option[]; value?: string | number } // a list with a chosen default
|
|
291
|
+
| { action: () => void; label?: string } // → button
|
|
292
|
+
| (SchemaObject & ControlOptions) // verbose form (+ render/disabled/hint)
|
|
293
|
+
| F // nested object → folder
|
|
294
|
+
| SchemaFolder; // a nested folder with its own render / disabled predicate
|
|
146
295
|
|
|
147
296
|
/** The object you hand to `tweaks(name, schema, opts?)`. No value is a function, so a verbose
|
|
148
297
|
* form's callbacks (`format`, `fn`) take their parameter types from that form alone. */
|
|
149
|
-
export interface Schema {
|
|
150
|
-
|
|
151
|
-
|
|
298
|
+
export interface Schema {
|
|
299
|
+
readonly [key: string]: SchemaValue;
|
|
300
|
+
}
|
|
301
|
+
// A folder's string `type` names a control (a known name with none of its fields is still a folder), so a name no
|
|
302
|
+
// control has can't pass as a folder: `const s: Schema = { a: { type: "slidr", value: 1 } }` compiled that way.
|
|
152
303
|
type SchemaNest = Schema & { readonly type?: SchemaObject["type"] | Exclude<SchemaValue, string> };
|
|
153
304
|
/** What tweaks() and mountControl() infer from: `Schema` with a folder's `type` left open, since a schema held in a
|
|
154
305
|
* variable widens a verbose form's `type` to `string`; their own check then rules on the names it can still see. */
|
|
155
|
-
export interface HeldSchema {
|
|
306
|
+
export interface HeldSchema {
|
|
307
|
+
readonly [key: string]: HeldValue;
|
|
308
|
+
}
|
|
156
309
|
/** A single value as mountControl() infers it; see `HeldSchema`. */
|
|
157
310
|
export type HeldValue = SchemaValueOf<HeldSchema>;
|
|
158
311
|
/** A nested folder that carries its own `render` / `disabled` predicate or `hint`: chrome, not
|
|
@@ -171,94 +324,254 @@ export interface SchemaFolder {
|
|
|
171
324
|
// these types read the control it builds the way schema.ts does, in the same order.
|
|
172
325
|
|
|
173
326
|
/** A spring control's value: the physics, plus the time pair and mode when tuned by feel. */
|
|
174
|
-
export interface SpringValue {
|
|
327
|
+
export interface SpringValue {
|
|
328
|
+
stiffness: number;
|
|
329
|
+
damping: number;
|
|
330
|
+
mass: number;
|
|
331
|
+
visualDuration?: number;
|
|
332
|
+
bounce?: number;
|
|
333
|
+
mode?: "time" | "physics";
|
|
334
|
+
}
|
|
175
335
|
|
|
176
|
-
type OptionValue<O> = O extends string | number
|
|
336
|
+
type OptionValue<O> = O extends string | number
|
|
337
|
+
? O
|
|
338
|
+
: O extends { readonly value: infer V extends string | number }
|
|
339
|
+
? V
|
|
340
|
+
: never;
|
|
177
341
|
// A list's param: one of its options, or a `value` off the list, which the control keeps as given until a pick.
|
|
178
|
-
type ChoiceValue<V> =
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
342
|
+
type ChoiceValue<V> =
|
|
343
|
+
| (V extends { readonly options: readonly (infer O)[] } ? OptionValue<O> : never)
|
|
344
|
+
| (V extends { readonly value: infer D extends string | number } ? D : never);
|
|
345
|
+
type Alnum =
|
|
346
|
+
| "a"
|
|
347
|
+
| "b"
|
|
348
|
+
| "c"
|
|
349
|
+
| "d"
|
|
350
|
+
| "e"
|
|
351
|
+
| "f"
|
|
352
|
+
| "g"
|
|
353
|
+
| "h"
|
|
354
|
+
| "i"
|
|
355
|
+
| "j"
|
|
356
|
+
| "k"
|
|
357
|
+
| "l"
|
|
358
|
+
| "m"
|
|
359
|
+
| "n"
|
|
360
|
+
| "o"
|
|
361
|
+
| "p"
|
|
362
|
+
| "q"
|
|
363
|
+
| "r"
|
|
364
|
+
| "s"
|
|
365
|
+
| "t"
|
|
366
|
+
| "u"
|
|
367
|
+
| "v"
|
|
368
|
+
| "w"
|
|
369
|
+
| "x"
|
|
370
|
+
| "y"
|
|
371
|
+
| "z"
|
|
372
|
+
| "0"
|
|
373
|
+
| "1"
|
|
374
|
+
| "2"
|
|
375
|
+
| "3"
|
|
376
|
+
| "4"
|
|
377
|
+
| "5"
|
|
378
|
+
| "6"
|
|
379
|
+
| "7"
|
|
380
|
+
| "8"
|
|
381
|
+
| "9";
|
|
382
|
+
type Dashed<S extends string, Acc extends string = "", Run = false> = S extends `${infer C}${infer R}`
|
|
383
|
+
? C extends Alnum
|
|
384
|
+
? Dashed<R, `${Acc}${C}`, false>
|
|
385
|
+
: Run extends true
|
|
386
|
+
? Dashed<R, Acc, true>
|
|
387
|
+
: Dashed<R, `${Acc}-`, true>
|
|
388
|
+
: Acc;
|
|
182
389
|
type TrimDash<S extends string> = S extends `-${infer R}` ? TrimDash<R> : S extends `${infer R}-` ? TrimDash<R> : S;
|
|
183
390
|
/** A tab title's key under `params`: lower-case, each run of other characters a `-`, trimmed
|
|
184
391
|
* (`"Layout & Grid"` → `"layout-grid"`). Two titles that slug alike are told apart at runtime only. */
|
|
185
|
-
export type TabKey<T extends string> = string extends T
|
|
392
|
+
export type TabKey<T extends string> = string extends T
|
|
393
|
+
? string
|
|
394
|
+
: TrimDash<Dashed<Lowercase<T>>> extends infer K extends string
|
|
395
|
+
? K extends ""
|
|
396
|
+
? "tab"
|
|
397
|
+
: K
|
|
398
|
+
: never;
|
|
186
399
|
// True when two page titles slug to one key: the runtime suffixes the later one (`a`, `a-2`) in an
|
|
187
400
|
// order a type can't follow, so those pages read as the loose Params rather than merge into one.
|
|
188
|
-
type TabClash<P> = true extends {
|
|
401
|
+
type TabClash<P> = true extends {
|
|
402
|
+
[T in keyof P & string]: TabKey<T> extends { [U in Exclude<keyof P & string, T>]: TabKey<U> }[Exclude<
|
|
403
|
+
keyof P & string,
|
|
404
|
+
T
|
|
405
|
+
>]
|
|
406
|
+
? true
|
|
407
|
+
: false;
|
|
408
|
+
}[keyof P & string]
|
|
409
|
+
? true
|
|
410
|
+
: false;
|
|
189
411
|
|
|
190
412
|
type Valueless = "button" | "buttongroup" | "separator" | "fpsgraph" | "monitor";
|
|
191
|
-
// The field each falsy-capable form hinges on (schema.ts REQUIRED)
|
|
413
|
+
// The field each falsy-capable form hinges on (schema.ts REQUIRED), or a `value`: without either the object isn't
|
|
192
414
|
// that control but a shorthand or a folder holding a child named `type`.
|
|
193
|
-
type Hinge = {
|
|
415
|
+
type Hinge = {
|
|
416
|
+
list: "options" | "value";
|
|
417
|
+
radiogrid: "options" | "value";
|
|
418
|
+
segmented: "options" | "value";
|
|
419
|
+
point: "components" | "value";
|
|
420
|
+
tabs: "pages" | "value";
|
|
421
|
+
};
|
|
194
422
|
// A hinge field there but unusable skips the control at runtime, so it puts nothing on params.
|
|
195
|
-
type TypedValue<V> =
|
|
196
|
-
|
|
197
|
-
: V extends { readonly type: "checkbox" }
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
423
|
+
type TypedValue<V> = V extends { readonly type: "slider" | "number" }
|
|
424
|
+
? number
|
|
425
|
+
: V extends { readonly type: "checkbox" }
|
|
426
|
+
? boolean
|
|
427
|
+
: V extends { readonly type: "list" | "radiogrid" | "segmented" }
|
|
428
|
+
? V extends { readonly options: readonly unknown[] }
|
|
429
|
+
? ChoiceValue<V>
|
|
430
|
+
: never
|
|
431
|
+
: V extends { readonly type: "color" | "text" | "image" | "plot" }
|
|
432
|
+
? string
|
|
433
|
+
: V extends { readonly type: "interval" }
|
|
434
|
+
? [number, number]
|
|
435
|
+
: V extends { readonly type: "cubicbezier" }
|
|
436
|
+
? [number, number, number, number]
|
|
437
|
+
: V extends { readonly type: "spring" }
|
|
438
|
+
? SpringValue
|
|
439
|
+
: V extends { readonly type: "motion" }
|
|
440
|
+
? MotionValue
|
|
441
|
+
: V extends { readonly type: "shadow" }
|
|
442
|
+
? ShadowValue
|
|
443
|
+
: V extends { readonly type: "sides" }
|
|
444
|
+
? SidesValue
|
|
445
|
+
: V extends { readonly type: "gradient" }
|
|
446
|
+
? GradientValue
|
|
447
|
+
: V extends { readonly type: "point" }
|
|
448
|
+
? V extends { readonly components: readonly [] }
|
|
449
|
+
? never
|
|
450
|
+
: V extends { readonly components: readonly (infer C)[] }
|
|
451
|
+
? { [K in C extends { readonly key: infer Key extends string } ? Key : never]: number }
|
|
452
|
+
: V extends { readonly value: infer P extends object }
|
|
453
|
+
? keyof P extends never
|
|
454
|
+
? never
|
|
455
|
+
: { -readonly [K in keyof P & string]: number }
|
|
456
|
+
: never
|
|
457
|
+
: V extends { readonly type: "tabs" }
|
|
458
|
+
? V extends { readonly pages: infer P extends object }
|
|
459
|
+
? keyof P extends never
|
|
460
|
+
? never
|
|
461
|
+
: TabClash<P> extends true
|
|
462
|
+
? Record<string, Params>
|
|
463
|
+
: { [T in keyof P & string as TabKey<T>]: ParamsOf<P[T]> }
|
|
464
|
+
: never
|
|
465
|
+
: unknown;
|
|
211
466
|
/** The value a schema entry puts on `params`. */
|
|
212
|
-
export type ValueOf<V> =
|
|
213
|
-
|
|
214
|
-
string extends T
|
|
215
|
-
|
|
216
|
-
:
|
|
467
|
+
export type ValueOf<V> = V extends { readonly type: infer T }
|
|
468
|
+
? // a `type` widened by a schema held in a variable: the control can't be known
|
|
469
|
+
string extends T
|
|
470
|
+
? any
|
|
471
|
+
: T extends SchemaObject["type"]
|
|
472
|
+
? T extends keyof Hinge
|
|
473
|
+
? [Extract<Hinge[T], keyof V>] extends [never]
|
|
474
|
+
? ShorthandValue<V>
|
|
475
|
+
: VerboseValue<V, T>
|
|
476
|
+
: VerboseValue<V, T>
|
|
477
|
+
: // A name no control has: with a field only a control reads, the runtime skips it; without one, it's a folder.
|
|
478
|
+
T extends string
|
|
479
|
+
? [Extract<"value" | "options" | "action", keyof V>] extends [never]
|
|
480
|
+
? ShorthandValue<V>
|
|
481
|
+
: never
|
|
482
|
+
: ShorthandValue<V>
|
|
217
483
|
: ShorthandValue<V>;
|
|
218
484
|
type VerboseValue<V, T> = T extends Valueless ? never : TypedValue<V>;
|
|
219
|
-
type ShorthandValue<V> =
|
|
220
|
-
|
|
221
|
-
: V extends
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
485
|
+
type ShorthandValue<V> = V extends readonly [readonly [number, number], ...unknown[]]
|
|
486
|
+
? [number, number]
|
|
487
|
+
: V extends
|
|
488
|
+
| readonly [number]
|
|
489
|
+
| readonly [number, number]
|
|
490
|
+
| readonly [number, number, number]
|
|
491
|
+
| readonly [number, number, number, number]
|
|
492
|
+
? number
|
|
493
|
+
: V extends readonly [number, ...number[]]
|
|
494
|
+
? V[number] // five numbers or more are a list of them
|
|
495
|
+
: V extends readonly []
|
|
496
|
+
? undefined // an empty list, with no choice to hold
|
|
497
|
+
: // a tuple widened by a schema held in a variable: a slider, or a list of numbers
|
|
498
|
+
V extends readonly number[]
|
|
499
|
+
? number
|
|
500
|
+
: V extends readonly (number | readonly number[])[]
|
|
501
|
+
? [number, number]
|
|
502
|
+
: V extends number
|
|
503
|
+
? number
|
|
504
|
+
: V extends boolean
|
|
505
|
+
? boolean
|
|
506
|
+
: V extends string
|
|
507
|
+
? string
|
|
508
|
+
: V extends readonly (infer O)[]
|
|
509
|
+
? OptionValue<O>
|
|
510
|
+
: V extends { readonly action: (...a: any[]) => any }
|
|
511
|
+
? never
|
|
512
|
+
: V extends { readonly options: readonly unknown[] }
|
|
513
|
+
? ChoiceValue<V>
|
|
514
|
+
: V extends object
|
|
515
|
+
? Entries<V, true>
|
|
516
|
+
: unknown;
|
|
234
517
|
// Keys the runtime skips at every level, since they would write through to Object.prototype.
|
|
235
518
|
type Reserved = "__proto__" | "constructor" | "prototype";
|
|
236
519
|
// A nested folder's render / disabled / hint are its chrome, not children; at the root and on a
|
|
237
520
|
// tab page they are controls like any other key.
|
|
238
|
-
type FolderChrome<V, K> = K extends "disabled" | "hint"
|
|
239
|
-
|
|
240
|
-
:
|
|
521
|
+
type FolderChrome<V, K> = K extends "disabled" | "hint"
|
|
522
|
+
? true
|
|
523
|
+
: K extends "render"
|
|
524
|
+
? V extends (...a: any[]) => any
|
|
525
|
+
? true
|
|
526
|
+
: false
|
|
527
|
+
: false;
|
|
528
|
+
type Entries<S, Nested extends boolean> = string extends keyof S
|
|
529
|
+
? Params
|
|
530
|
+
: {
|
|
531
|
+
-readonly [
|
|
532
|
+
K in keyof S & string as K extends Reserved
|
|
533
|
+
? never
|
|
534
|
+
: ValueOf<S[K]> extends never
|
|
535
|
+
? never
|
|
536
|
+
: Nested extends true
|
|
537
|
+
? FolderChrome<S[K], K> extends true
|
|
538
|
+
? never
|
|
539
|
+
: K
|
|
540
|
+
: K
|
|
541
|
+
]: ValueOf<S[K]>;
|
|
542
|
+
};
|
|
241
543
|
/** The live values a schema builds, nested like the schema; a plain `Schema` gives the loose `Params`. */
|
|
242
544
|
export type ParamsOf<S> = Entries<S, false>;
|
|
243
545
|
|
|
244
546
|
// ── Type names checked at the call ──
|
|
245
|
-
// Schema's folder branch takes any object, so `{ type: "slidr", value: 1 }` fit it and compiled. tweaks() and
|
|
246
|
-
// intersect their argument with these, which keep a known name, a widened `string` (a schema held in a
|
|
247
|
-
// runtime decides), and every shorthand as they are, and turn an unknown name into the list of known
|
|
248
|
-
// right where it's written. A known name without its hinge field is a folder at runtime and isn't
|
|
249
|
-
// or for a name with no hinge, the runtime builds that control, so its fields must fit the
|
|
250
|
-
// options: [1] }` read as a folder of a text and a slider otherwise, and compiled.
|
|
251
|
-
type CheckValue<V> =
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
547
|
+
// Schema's folder branch takes any object, so `{ type: "slidr", value: 1 }` fit it and compiled. tweaks() and
|
|
548
|
+
// mountControl() intersect their argument with these, which keep a known name, a widened `string` (a schema held in a
|
|
549
|
+
// variable: the runtime decides), and every shorthand as they are, and turn an unknown name into the list of known
|
|
550
|
+
// ones, so it fails right where it's written. A known name without its hinge field is a folder at runtime and isn't
|
|
551
|
+
// checked further; with it, or for a name with no hinge, the runtime builds that control, so its fields must fit the
|
|
552
|
+
// form: `{ type: "image", options: [1] }` read as a folder of a text and a slider otherwise, and compiled.
|
|
553
|
+
type CheckValue<V> = V extends { readonly type: infer T }
|
|
554
|
+
? string extends T
|
|
555
|
+
? V
|
|
556
|
+
: T extends SchemaObject["type"]
|
|
557
|
+
? V extends { readonly type: "tabs"; readonly pages: infer P }
|
|
558
|
+
? { readonly pages: { [K in keyof P]: KnownTypes<P[K]> } }
|
|
559
|
+
: T extends keyof Hinge
|
|
560
|
+
? [Extract<Hinge[T], keyof V>] extends [never]
|
|
561
|
+
? unknown
|
|
562
|
+
: FitsForm<V, T>
|
|
563
|
+
: FitsForm<V, T>
|
|
564
|
+
: { readonly type: NotAControlType }
|
|
565
|
+
: V extends readonly unknown[] | ((...a: any[]) => any)
|
|
566
|
+
? unknown
|
|
567
|
+
: V extends object
|
|
568
|
+
? KnownTypes<V>
|
|
569
|
+
: unknown;
|
|
257
570
|
// The verbose form a known name builds (an alias finds the member that lists it), and V held to it when it doesn't fit.
|
|
258
571
|
type FormOf<O, T> = O extends { readonly type: infer N } ? (T extends N ? O & ControlOptions : never) : never;
|
|
259
572
|
type FitsForm<V, T> = V extends FormOf<SchemaObject, T> ? unknown : FormOf<SchemaObject, T>;
|
|
260
|
-
// A brand, not the list of names: a literal intersected with the list collapses the whole entry to `never`, and the
|
|
261
|
-
// read "not assignable to type 'never'" on every key. The brand keeps the entry and puts its text in the error.
|
|
573
|
+
// A brand, not the list of names: a literal intersected with the list collapses the whole entry to `never`, and the
|
|
574
|
+
// error read "not assignable to type 'never'" on every key. The brand keeps the entry and puts its text in the error.
|
|
262
575
|
type NotAControlType = { readonly "names no control: see the type list in AGENTS.md": true };
|
|
263
576
|
/** A schema whose every `type` names a control (`tweaks()` checks it; `{ type: "slidr" }` is an error). */
|
|
264
577
|
export type KnownTypes<S> = { [K in keyof S]: CheckValue<S[K]> };
|
|
@@ -267,37 +580,53 @@ export type KnownType<V> = CheckValue<V>;
|
|
|
267
580
|
|
|
268
581
|
/** A theme — token overrides applied over the default monochrome look. Every key is
|
|
269
582
|
* optional, so a partial theme only moves what it names; unset tokens keep their default.
|
|
270
|
-
* Keys are the `--
|
|
583
|
+
* Keys are the `--twk-*` custom properties themselves. Apply at build via
|
|
271
584
|
* `tweaks(name, schema, { theme })`, or live via `panel.setTheme(theme)`. The kit's whole
|
|
272
585
|
* appearance runs on these custom properties — this is the public theming surface. */
|
|
273
586
|
export interface Theme {
|
|
274
587
|
/** Brand accent — slider fills, focus rings, active highlights (default: neutral, no accent). */
|
|
275
|
-
"--
|
|
276
|
-
/** Text drawn on the accent (active segment / radio label); the primary text tone unless set, so set it with a light
|
|
277
|
-
|
|
588
|
+
"--twk-accent"?: string;
|
|
589
|
+
/** Text drawn on the accent (active segment / radio label); the primary text tone unless set, so set it with a light
|
|
590
|
+
* accent. */
|
|
591
|
+
"--twk-on-accent"?: string;
|
|
278
592
|
/** Panel background, reused for recessed wells. */
|
|
279
|
-
"--
|
|
593
|
+
"--twk-base"?: string;
|
|
280
594
|
/** Popover / dropdown background. */
|
|
281
|
-
"--
|
|
595
|
+
"--twk-dropdown-bg"?: string;
|
|
282
596
|
/** Control surface and its hover / active steps. */
|
|
283
|
-
"--
|
|
597
|
+
"--twk-surface"?: string;
|
|
598
|
+
"--twk-surface-hover"?: string;
|
|
599
|
+
"--twk-surface-active"?: string;
|
|
284
600
|
/** Hairline border and its hover step. */
|
|
285
|
-
"--
|
|
601
|
+
"--twk-border"?: string;
|
|
602
|
+
"--twk-border-hover"?: string;
|
|
286
603
|
/** Text-selection highlight wash. */
|
|
287
|
-
"--
|
|
288
|
-
/** Text tones — panel title, section heading, primary value text, control label, and the muted / faint / focus
|
|
289
|
-
|
|
290
|
-
"--
|
|
604
|
+
"--twk-selection"?: string;
|
|
605
|
+
/** Text tones — panel title, section heading, primary value text, control label, and the muted / faint / focus
|
|
606
|
+
* tones. */
|
|
607
|
+
"--twk-text-root"?: string;
|
|
608
|
+
"--twk-text-section"?: string;
|
|
609
|
+
"--twk-text-primary"?: string;
|
|
610
|
+
"--twk-text-label"?: string;
|
|
611
|
+
"--twk-text-secondary"?: string;
|
|
612
|
+
"--twk-text-tertiary"?: string;
|
|
613
|
+
"--twk-text-focus"?: string;
|
|
291
614
|
/** Copy-confirmation accent, and the invalid-state accent (the plot's bad-expression cue). */
|
|
292
|
-
"--
|
|
615
|
+
"--twk-success"?: string;
|
|
616
|
+
"--twk-danger"?: string;
|
|
293
617
|
/** Popover shadow, the panel's container elevation, and its lifted (floating / dragging) variant. */
|
|
294
|
-
"--
|
|
618
|
+
"--twk-shadow-dropdown"?: string;
|
|
619
|
+
"--twk-shadow-panel"?: string;
|
|
620
|
+
"--twk-shadow-panel-lifted"?: string;
|
|
295
621
|
/** Font stack, and the monospace stack (the plot's expression field). */
|
|
296
|
-
"--
|
|
622
|
+
"--twk-font-sans"?: string;
|
|
623
|
+
"--twk-font-mono"?: string;
|
|
297
624
|
/** Control corner radius, row height and the panel's width (256px by default), as CSS lengths. */
|
|
298
|
-
"--
|
|
625
|
+
"--twk-radius"?: string;
|
|
626
|
+
"--twk-row-height"?: string;
|
|
627
|
+
"--twk-width"?: string;
|
|
299
628
|
/** Any other token. */
|
|
300
|
-
[token:
|
|
629
|
+
[token: `--twk-${string}`]: string | undefined;
|
|
301
630
|
}
|
|
302
631
|
|
|
303
632
|
/** Options for a programmatic write — `set()`, `setMany()`, `fromJSON()`. */
|
|
@@ -310,12 +639,143 @@ export interface SetOptions {
|
|
|
310
639
|
|
|
311
640
|
/** Where a floating panel starts: `x` / `y` are its insets in px (16 by default) from the
|
|
312
641
|
* viewport's top-left. The far edge keeps a gap of `y` when the height caps; CSS
|
|
313
|
-
* `--
|
|
314
|
-
export interface FloatingPlacement {
|
|
642
|
+
* `--twk-float-gap` overrides it. */
|
|
643
|
+
export interface FloatingPlacement {
|
|
644
|
+
x?: number;
|
|
645
|
+
y?: number;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/** The words a panel shows and says, by key, for the `strings` option (each line notes the English default). A key
|
|
649
|
+
* with a `{placeholder}` takes a string that uses it, or a function given the values, for a language whose plurals
|
|
650
|
+
* need more than one form. Units (px, ms, %), axis letters and color notations stay as they are. */
|
|
651
|
+
export type Word<V = never> = [V] extends [never] ? string : string | ((v: V) => string);
|
|
652
|
+
export interface Strings {
|
|
653
|
+
// The panel and its toolbar
|
|
654
|
+
/** "Tweaks" — the panel's landmark name when its own is blank */ panel?: string;
|
|
655
|
+
/** "Presets" — the toolbar button */ presets?: string;
|
|
656
|
+
/** "Copy values (⇧: changed only)" — the toolbar button */ copy?: string;
|
|
657
|
+
/** "Reset" — the toolbar button */ reset?: string;
|
|
658
|
+
/** "Double-click or hold to reset" — a row label's tooltip */ resetHint?: string;
|
|
659
|
+
/** "Values copied" */ copied?: string;
|
|
660
|
+
/** "{n} changed values copied" — 1 changed value copied for one */ copiedChanged?: Word<{ n: number }>;
|
|
661
|
+
/** "Nothing has changed" */ nothingChanged?: string;
|
|
662
|
+
/** "Copy failed" */ copyFailed?: string;
|
|
663
|
+
/** "{label} copied" — ⌘C on a row */ rowCopied?: Word<{ label: string }>;
|
|
664
|
+
/** "Not saved" — a value storage refused */ notSaved?: string;
|
|
665
|
+
/** "{n} values pasted" — 1 value pasted for one */ pasted?: Word<{ n: number }>;
|
|
666
|
+
/** "Nothing to paste" */ nothingToPaste?: string;
|
|
667
|
+
/** "Couldn't read the clipboard" */ clipboardUnread?: string;
|
|
668
|
+
// The presets menu
|
|
669
|
+
/** "Preset name…" — the field's placeholder */ presetName?: string;
|
|
670
|
+
/** "New preset name" — the field's label */ newPreset?: string;
|
|
671
|
+
/** "Save" */ save?: string;
|
|
672
|
+
/** "No presets yet" */ noPresets?: string;
|
|
673
|
+
/** "Loaded preset {name}" */ presetLoaded?: Word<{ name: string }>;
|
|
674
|
+
/** "Saved preset {name}" */ presetSaved?: Word<{ name: string }>;
|
|
675
|
+
/** "Preset {name} not saved: storage is full" */ presetFull?: Word<{ name: string }>;
|
|
676
|
+
/** "Delete preset {name}" — a row's delete button */ presetDelete?: Word<{ name: string }>;
|
|
677
|
+
// Controls
|
|
678
|
+
/** "Off" — a checkbox */ off?: string;
|
|
679
|
+
/** "On" — a checkbox */ on?: string;
|
|
680
|
+
/** "Button" — a group button with no label */ button?: string;
|
|
681
|
+
/** "Tab" — a tab page whose title is blank */ tab?: string;
|
|
682
|
+
/** "Folder" — a markup folder with no data-label */ folder?: string;
|
|
683
|
+
/** "Drag to adjust" — a number field's grip */ dragToAdjust?: string;
|
|
684
|
+
/** "{label} minimum" */ intervalMin?: Word<{ label: string }>;
|
|
685
|
+
/** "{label} maximum" */ intervalMax?: Word<{ label: string }>;
|
|
686
|
+
/** "Monitor" — a monitor with no label */ monitor?: string;
|
|
687
|
+
/** "FPS" — an fpsgraph with no label */ fps?: string;
|
|
688
|
+
/** "Plot" — a plot with no label */ plot?: string;
|
|
689
|
+
/** "{label} — expression in x" — a plot's field */ expression?: Word<{ label: string }>;
|
|
690
|
+
/** "Point" — a point with no label */ point?: string;
|
|
691
|
+
/** "{label} pad: {x} and {y}" */ pointPad?: Word<{ label: string; x: string | number; y: string | number }>;
|
|
692
|
+
/** "Control point {n}" — a cubic bézier handle */ controlPoint?: Word<{ n: number }>;
|
|
693
|
+
/** "Control point {n}, x {x}, y {y}" */ controlPointAt?: Word<{ n: number; x: string | number; y: string | number }>;
|
|
694
|
+
// Color
|
|
695
|
+
/** "Color" — a color row with no label, and the shadow's color row */ color?: string;
|
|
696
|
+
/** "Color plane: chroma across, lightness up" */ colorPlane?: string;
|
|
697
|
+
/** "Color mode" */ colorMode?: string;
|
|
698
|
+
/** "Hex color" */ hex?: string;
|
|
699
|
+
/** "Hue" */ hue?: string;
|
|
700
|
+
/** "Alpha" */ alpha?: string;
|
|
701
|
+
/** "lightness {l}%, chroma {c}" — the color plane's description, said after a run of keys */ colorReadout?: Word<{
|
|
702
|
+
l: number;
|
|
703
|
+
c: number;
|
|
704
|
+
}>;
|
|
705
|
+
/** "wide" — the gamut badge past P3 (sRGB and P3 are names) */ wide?: string;
|
|
706
|
+
// Gradient
|
|
707
|
+
/** "Gradient" — a gradient with no label */ gradient?: string;
|
|
708
|
+
/** "{n} stops" — 1 stop for one */ stops?: Word<{ n: number }>;
|
|
709
|
+
/** "Add a stop after the selected one" — the add button's tooltip */ addStop?: string;
|
|
710
|
+
/** "Add a color stop after the selected one" — the add button's label */ addStopAria?: string;
|
|
711
|
+
/** "Color stop at {pos}%" */ stopAt?: Word<{ pos: number }>;
|
|
712
|
+
/** "Color stop at {pos}%, selected" */ stopAtSelected?: Word<{ pos: number }>;
|
|
713
|
+
/** "Easing between stops" — the easing menu's label */ stopEasing?: string;
|
|
714
|
+
/** "How the ramp blends between stops" — its tooltip */ stopEasingHint?: string;
|
|
715
|
+
/** "Linear" */ easeLinear?: string;
|
|
716
|
+
/** "Ease" */ ease?: string;
|
|
717
|
+
/** "Ease in" */ easeIn?: string;
|
|
718
|
+
/** "Ease out" */ easeOut?: string;
|
|
719
|
+
/** "Ease in-out" */ easeInOut?: string;
|
|
720
|
+
/** "Custom" — an easing the menu doesn't list */ custom?: string;
|
|
721
|
+
// Image
|
|
722
|
+
/** "Upload image" — the gallery's upload button */ upload?: string;
|
|
723
|
+
/** "{label}: upload an image" */ uploadImage?: Word<{ label: string }>;
|
|
724
|
+
/** "{label}: choose an image" */ chooseImage?: Word<{ label: string }>;
|
|
725
|
+
/** "Clear" */ clear?: string;
|
|
726
|
+
/** "{label}: clear the image" */ clearImage?: Word<{ label: string }>;
|
|
727
|
+
/** "Replace" */ replace?: string;
|
|
728
|
+
/** "Drop or choose" */ dropOrChoose?: string;
|
|
729
|
+
/** "None" — no image chosen */ none?: string;
|
|
730
|
+
/** "Image {n}" — a gallery option with no label */ imageN?: Word<{ n: number }>;
|
|
731
|
+
/** "Uploaded image" */ uploadedImage?: string;
|
|
732
|
+
/** "Custom image" — a value off the gallery's list */ customImage?: string;
|
|
733
|
+
/** "Not an image" */ notAnImage?: string;
|
|
734
|
+
/** "Couldn't read the file" */ unreadable?: string;
|
|
735
|
+
/** "{size}, max {max}" — a file over maxSize */ tooLarge?: Word<{ size: string; max: string }>;
|
|
736
|
+
// Motion and spring
|
|
737
|
+
/** "Motion" — a motion row with no label */ motion?: string;
|
|
738
|
+
/** "Motion type" */ motionType?: string;
|
|
739
|
+
/** "Type" — the motion pane's row label beside that switch */ type?: string;
|
|
740
|
+
/** "Easing" */ easing?: string;
|
|
741
|
+
/** "Spring" */ spring?: string;
|
|
742
|
+
/** "Duration" */ duration?: string;
|
|
743
|
+
/** "Replay the motion" */ replay?: string;
|
|
744
|
+
/** "Easing · {ms} ms" */ easingSummary?: Word<{ ms: number }>;
|
|
745
|
+
/** "Spring · {ms} ms" */ springSummary?: Word<{ ms: number }>;
|
|
746
|
+
/** "Spring mode" */ springMode?: string;
|
|
747
|
+
/** "Mode" */ mode?: string;
|
|
748
|
+
/** "Time" */ time?: string;
|
|
749
|
+
/** "Physics" */ physics?: string;
|
|
750
|
+
/** "Stiffness" */ stiffness?: string;
|
|
751
|
+
/** "Damping" */ damping?: string;
|
|
752
|
+
/** "Mass" */ mass?: string;
|
|
753
|
+
/** "Bounce" */ bounce?: string;
|
|
754
|
+
// Shadow
|
|
755
|
+
/** "Shadow" — a shadow with no label */ shadow?: string;
|
|
756
|
+
/** "Shadow position" */ shadowPosition?: string;
|
|
757
|
+
/** "in" — an inset shadow's summary, before its numbers */ inset?: string;
|
|
758
|
+
/** "Position" */ position?: string;
|
|
759
|
+
/** "Outer" */ outer?: string;
|
|
760
|
+
/** "Inner" */ inner?: string;
|
|
761
|
+
/** "Blur" */ blur?: string;
|
|
762
|
+
/** "Spread" */ spread?: string;
|
|
763
|
+
// Sides and corners
|
|
764
|
+
/** "Top" */ top?: string;
|
|
765
|
+
/** "Right" */ right?: string;
|
|
766
|
+
/** "Bottom" */ bottom?: string;
|
|
767
|
+
/** "Left" */ left?: string;
|
|
768
|
+
/** "Top-left" */ topLeft?: string;
|
|
769
|
+
/** "Top-right" */ topRight?: string;
|
|
770
|
+
/** "Bottom-right" */ bottomRight?: string;
|
|
771
|
+
/** "Bottom-left" */ bottomLeft?: string;
|
|
772
|
+
/** "Independent sides" */ splitSides?: string;
|
|
773
|
+
/** "Independent corners" */ splitCorners?: string;
|
|
774
|
+
}
|
|
315
775
|
|
|
316
776
|
/** Panel options (third arg to `tweaks()`). */
|
|
317
777
|
export interface TweaksOptions {
|
|
318
|
-
/** Theme overrides as `--
|
|
778
|
+
/** Theme overrides as `--twk-*` tokens; `null` / omitted = default monochrome. */
|
|
319
779
|
theme?: Theme | null;
|
|
320
780
|
/** Persist values to localStorage + enable presets — a key, or `true` to key by name. Uploaded images go to
|
|
321
781
|
* IndexedDB under the same key, and come back by `ready`. */
|
|
@@ -335,6 +795,19 @@ export interface TweaksOptions {
|
|
|
335
795
|
undo?: boolean;
|
|
336
796
|
/** Custom reset handler (e.g. restore real app defaults + rebuild). */
|
|
337
797
|
onReset?: () => void;
|
|
798
|
+
/** Mount the panel in a shadow root, so page CSS can't restyle it (`#app button { … }`, a `.prose` wrapper). `el` is
|
|
799
|
+
* then its host, a `<twk-host>`: theme it with `--twk-*` on the host itself (`.twk-host { … }`), or with `theme`.
|
|
800
|
+
* The styles come from the page's `tweakit/css` when that's loaded, else from `href` (by default `tweaks.css` beside
|
|
801
|
+
* the module). */
|
|
802
|
+
shadow?: boolean | { href?: string };
|
|
803
|
+
/** The words the panel and its controls show and say, over the English defaults: translate them, or rename the chrome
|
|
804
|
+
* (`{ presets: "Looks" }`). A key left out keeps its default. */
|
|
805
|
+
strings?: Strings;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/** Options for `enhance()`: the words its markup controls show and say. */
|
|
809
|
+
export interface EnhanceOptions {
|
|
810
|
+
strings?: Strings;
|
|
338
811
|
}
|
|
339
812
|
|
|
340
813
|
/** The live values bag, loosely typed — `params[key]` for each control. A schema literal passed
|
|
@@ -342,11 +815,22 @@ export interface TweaksOptions {
|
|
|
342
815
|
export type Params = Record<string, any>;
|
|
343
816
|
/** The keys `set()` and `setMany()` take: a typed panel's controls by dotted path (`size`, `card.size`), so a typo is a
|
|
344
817
|
* compile error; any string for a panel built from a plain `Schema`. An object-valued control's own fields read as
|
|
345
|
-
* paths too (`drop.blur`): the types can't tell its value from a folder, so they allow what the runtime refuses. Past
|
|
346
|
-
* folders deep the rest of a path is any string, since spelling every one out overran tsc's depth limit. */
|
|
818
|
+
* paths too (`drop.blur`): the types can't tell its value from a folder, so they allow what the runtime refuses. Past
|
|
819
|
+
* ten folders deep the rest of a path is any string, since spelling every one out overran tsc's depth limit. */
|
|
347
820
|
export type PanelPath<P> = PathsOf<P, []>;
|
|
348
|
-
type PathsOf<P, D extends unknown[]> = string extends keyof P
|
|
349
|
-
|
|
821
|
+
type PathsOf<P, D extends unknown[]> = string extends keyof P
|
|
822
|
+
? string
|
|
823
|
+
: D["length"] extends 10
|
|
824
|
+
? string
|
|
825
|
+
: {
|
|
826
|
+
[K in keyof P & string]:
|
|
827
|
+
| K
|
|
828
|
+
| (P[K] extends readonly unknown[]
|
|
829
|
+
? never
|
|
830
|
+
: P[K] extends object
|
|
831
|
+
? `${K}.${PathsOf<P[K], [...D, 0]>}`
|
|
832
|
+
: never);
|
|
833
|
+
}[keyof P & string];
|
|
350
834
|
|
|
351
835
|
/** The whole-panel state `toJSON()` returns / `fromJSON()` accepts: every control value
|
|
352
836
|
* (nested to mirror folders/tabs) plus UI state — which folders are collapsed and which
|
|
@@ -373,14 +857,15 @@ export interface PanelState {
|
|
|
373
857
|
/** The panel object `tweaks()` returns; `P` is its values' shape. A bare `Panel` takes any panel, typed or not (its
|
|
374
858
|
* default is `any`, not `Params`: `set()`'s typed paths would make a typed panel unassignable to `Panel<Params>`). */
|
|
375
859
|
export interface Panel<P = any> {
|
|
376
|
-
/** The panel root element — append it to the DOM. */
|
|
860
|
+
/** The panel root element — append it to the DOM. With `shadow`, the host the panel lives in. */
|
|
377
861
|
el: HTMLElement;
|
|
378
862
|
/** Live values, updated in place as controls change. */
|
|
379
863
|
params: P;
|
|
380
864
|
/** Resolves once any lazily-loaded controls have hydrated and, for a panel that persists an image control, its
|
|
381
865
|
* stored images have loaded (resolves immediately when there's neither). */
|
|
382
866
|
ready: Promise<Panel<P>>;
|
|
383
|
-
/** Subscribe to changes; `last` is the changed control's dotted path (`size`, `card.size`), as `set()` and
|
|
867
|
+
/** Subscribe to changes; `last` is the changed control's dotted path (`size`, `card.size`), as `set()` and
|
|
868
|
+
* `changes()`
|
|
384
869
|
* name it, or undefined when many changed at once (a restore, `reset()`, `fromJSON`, `setMany`). */
|
|
385
870
|
on(fn: (params: P, last?: string) => void): () => void;
|
|
386
871
|
/** Set a control's value programmatically. Nested controls take a dotted path
|
|
@@ -418,7 +903,8 @@ export interface Panel<P = any> {
|
|
|
418
903
|
* with stays its storage key (`persist: true`), so saved values and presets keep their
|
|
419
904
|
* home across a rename. */
|
|
420
905
|
setName(name: string): void;
|
|
421
|
-
/** Save the current values as a named preset (needs `opts.persist`). False when there is nowhere to save it, or
|
|
906
|
+
/** Save the current values as a named preset (needs `opts.persist`). False when there is nowhere to save it, or
|
|
907
|
+
* storage refused it. */
|
|
422
908
|
savePreset(name: string): boolean;
|
|
423
909
|
/** Load a named preset. */
|
|
424
910
|
loadPreset(name: string): boolean;
|
|
@@ -429,7 +915,8 @@ export interface Panel<P = any> {
|
|
|
429
915
|
/** Undo / redo (need `opts.undo`). */
|
|
430
916
|
undo(): void;
|
|
431
917
|
redo(): void;
|
|
432
|
-
/** Tear the panel down — remove it from the DOM, release every global listener, close any open popover; the API goes
|
|
918
|
+
/** Tear the panel down — remove it from the DOM, release every global listener, close any open popover; the API goes
|
|
919
|
+
* inert. */
|
|
433
920
|
destroy(): void;
|
|
434
921
|
}
|
|
435
922
|
|
|
@@ -458,13 +945,18 @@ export interface MountOptions {
|
|
|
458
945
|
label?: string;
|
|
459
946
|
/** Fired with the new value whenever the control changes — by hand or through `set()`. */
|
|
460
947
|
onChange?: (value: unknown, key: string) => void;
|
|
948
|
+
/** Mount it in a shadow root, as `tweaks()`'s `shadow` does a panel; `el` is then the host. */
|
|
949
|
+
shadow?: boolean | { href?: string };
|
|
950
|
+
/** Its words, as `tweaks()`'s `strings`. */
|
|
951
|
+
strings?: Strings;
|
|
461
952
|
}
|
|
462
953
|
|
|
463
954
|
/** The handle `mountControl()` returns. */
|
|
464
955
|
export interface MountedControl {
|
|
465
|
-
/** The wrapper (`.
|
|
956
|
+
/** The wrapper (`.twk-control`) the control lives in, already appended to the host and
|
|
466
957
|
* stable from creation — it carries the kit's tokens, so the control renders outside a
|
|
467
|
-
* panel. On the code-split build a lazily-loaded control fills it in once `ready` resolves.
|
|
958
|
+
* panel. On the code-split build a lazily-loaded control fills it in once `ready` resolves. With `shadow`, the host
|
|
959
|
+
* the wrapper lives in. */
|
|
468
960
|
el: HTMLElement;
|
|
469
961
|
/** Apply a value — the control updates AND `onChange` fires (when the value actually
|
|
470
962
|
* changed). Before `ready`, the value is queued and applied once the control builds. */
|
|
@@ -490,14 +982,18 @@ export interface ColorPickerOptions {
|
|
|
490
982
|
mode?: ColorMode;
|
|
491
983
|
/** Fired with the new color string whenever it changes — by hand or through `set()`. */
|
|
492
984
|
onChange?: (value: string) => void;
|
|
985
|
+
/** Build it in a shadow root, as `tweaks()`'s `shadow` does a panel; `el` is then the host. */
|
|
986
|
+
shadow?: boolean | { href?: string };
|
|
987
|
+
/** Its words, as `tweaks()`'s `strings`. */
|
|
988
|
+
strings?: Strings;
|
|
493
989
|
}
|
|
494
990
|
|
|
495
991
|
/** The handle `createColorPicker()` returns. Bring your own trigger: append `el` to a popover
|
|
496
992
|
* or a panel of your own; the kit owns the editor surface and the gamut math. */
|
|
497
993
|
export interface ColorPicker {
|
|
498
|
-
/** The picker's root (`.
|
|
994
|
+
/** The picker's root (`.twk-color-picker`), stable from creation; it carries the kit's tokens,
|
|
499
995
|
* so the picker renders anywhere. On the code-split build the editor fills it in once the
|
|
500
|
-
* color chunk has loaded. */
|
|
996
|
+
* color chunk has loaded. With `shadow`, the host the root lives in. */
|
|
501
997
|
el: HTMLElement;
|
|
502
998
|
/** Apply a color — the picker updates AND `onChange` fires (when the color actually changed). */
|
|
503
999
|
set(value: string): void;
|
|
@@ -516,11 +1012,16 @@ export interface ColorPicker {
|
|
|
516
1012
|
ready: Promise<ColorPicker>;
|
|
517
1013
|
}
|
|
518
1014
|
|
|
519
|
-
/** `detail` of the `
|
|
1015
|
+
/** `detail` of the `twk:change` event a `[data-twk]` host dispatches (bubbling) whenever its
|
|
520
1016
|
* control changes — `key` is the host's `data-key` (or its label), `value` the new value,
|
|
521
1017
|
* also written to the host's `data-value`. */
|
|
522
|
-
export interface TwChangeDetail {
|
|
1018
|
+
export interface TwChangeDetail {
|
|
1019
|
+
key: string;
|
|
1020
|
+
value: unknown;
|
|
1021
|
+
}
|
|
523
1022
|
|
|
524
1023
|
declare global {
|
|
525
|
-
interface HTMLElementEventMap {
|
|
1024
|
+
interface HTMLElementEventMap {
|
|
1025
|
+
"twk:change": CustomEvent<TwChangeDetail>;
|
|
1026
|
+
}
|
|
526
1027
|
}
|