tweakit 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/AGENTS.md +31 -29
  2. package/README.md +55 -29
  3. package/THIRD-PARTY-NOTICES.md +0 -1
  4. package/dist/tweaks/auto.js +2 -0
  5. package/dist/tweaks/auto.js.map +6 -0
  6. package/dist/tweaks/bezier-JFEZMCLP.js +2 -0
  7. package/dist/tweaks/bezier-JFEZMCLP.js.map +6 -0
  8. package/dist/tweaks/chunk-6UO4DHBN.js +2 -0
  9. package/dist/tweaks/chunk-6UO4DHBN.js.map +6 -0
  10. package/dist/tweaks/chunk-DIQMLBAS.js +2 -0
  11. package/dist/tweaks/chunk-DIQMLBAS.js.map +6 -0
  12. package/dist/tweaks/chunk-XE6OUJBG.js +2 -0
  13. package/dist/tweaks/chunk-XE6OUJBG.js.map +6 -0
  14. package/dist/tweaks/clip-WW7JYO4X.js +2 -0
  15. package/dist/tweaks/clip-WW7JYO4X.js.map +6 -0
  16. package/dist/tweaks/color-YMMFURP6.js +2 -0
  17. package/dist/tweaks/color-YMMFURP6.js.map +6 -0
  18. package/dist/tweaks/core.js +2 -1
  19. package/dist/tweaks/core.js.map +6 -0
  20. package/dist/tweaks/{fold-UGN7TXGX.js → fold-U6YRZLCJ.js} +2 -1
  21. package/dist/tweaks/fold-U6YRZLCJ.js.map +6 -0
  22. package/dist/tweaks/gradient-JER7PW4D.js +2 -0
  23. package/dist/tweaks/gradient-JER7PW4D.js.map +6 -0
  24. package/dist/tweaks/heavy-CLD7UAVI.js +2 -0
  25. package/dist/tweaks/heavy-CLD7UAVI.js.map +6 -0
  26. package/dist/tweaks/image-H6QSLGO3.js +4 -0
  27. package/dist/tweaks/image-H6QSLGO3.js.map +6 -0
  28. package/dist/tweaks/image-store-WRKY2NPU.js +2 -0
  29. package/dist/tweaks/image-store-WRKY2NPU.js.map +6 -0
  30. package/dist/tweaks/interval-TEUHQGZN.js +2 -0
  31. package/dist/tweaks/interval-TEUHQGZN.js.map +6 -0
  32. package/dist/tweaks/markup-NIBJD6E2.js +2 -0
  33. package/dist/tweaks/markup-NIBJD6E2.js.map +6 -0
  34. package/dist/tweaks/monitor-3BY7YCGX.js +3 -0
  35. package/dist/tweaks/monitor-3BY7YCGX.js.map +6 -0
  36. package/dist/tweaks/motion-FQPUBKKM.js +2 -0
  37. package/dist/tweaks/motion-FQPUBKKM.js.map +6 -0
  38. package/dist/tweaks/plot-2HBNBZSG.js +3 -0
  39. package/dist/tweaks/plot-2HBNBZSG.js.map +6 -0
  40. package/dist/tweaks/point-VV6XV3I6.js +2 -0
  41. package/dist/tweaks/point-VV6XV3I6.js.map +6 -0
  42. package/dist/tweaks/shadow-5E4E55I2.js +2 -0
  43. package/dist/tweaks/shadow-5E4E55I2.js.map +6 -0
  44. package/dist/tweaks/sides-P3STEVYY.js +2 -0
  45. package/dist/tweaks/sides-P3STEVYY.js.map +6 -0
  46. package/dist/tweaks/spring-HUVSXNVN.js +2 -0
  47. package/dist/tweaks/spring-HUVSXNVN.js.map +6 -0
  48. package/dist/tweaks/tabs-76FW7LQ7.js +2 -0
  49. package/dist/tweaks/tabs-76FW7LQ7.js.map +6 -0
  50. package/dist/tweaks.css +1 -1
  51. package/dist/tweaks.js +6 -3
  52. package/dist/tweaks.js.map +6 -0
  53. package/dist/types/tweaks/easing.d.ts +8 -11
  54. package/dist/types/tweaks/panel.d.ts +6 -4
  55. package/dist/types/tweaks/schema.d.ts +4 -8
  56. package/dist/types/tweaks/standalone.d.ts +3 -3
  57. package/dist/types/tweaks/types.d.ts +278 -101
  58. package/package.json +27 -5
  59. package/src/tweaks/auto.ts +12 -0
  60. package/src/tweaks/clip.ts +94 -0
  61. package/src/tweaks/controls/basic.ts +403 -0
  62. package/src/tweaks/controls/bezier.ts +99 -0
  63. package/src/tweaks/controls/color.ts +412 -0
  64. package/src/tweaks/controls/gradient.ts +240 -0
  65. package/src/tweaks/controls/image.ts +299 -0
  66. package/src/tweaks/controls/interval.ts +104 -0
  67. package/src/tweaks/controls/monitor.ts +105 -0
  68. package/src/tweaks/controls/motion.ts +141 -0
  69. package/src/tweaks/controls/plot.ts +220 -0
  70. package/src/tweaks/controls/point.ts +119 -0
  71. package/src/tweaks/controls/shadow.ts +83 -0
  72. package/src/tweaks/controls/sides.ts +86 -0
  73. package/src/tweaks/controls/spring.ts +148 -0
  74. package/src/tweaks/controls/tabs.ts +47 -0
  75. package/src/tweaks/core.ts +9 -0
  76. package/src/tweaks/easing.ts +93 -0
  77. package/src/tweaks/enhance.ts +20 -0
  78. package/src/tweaks/feedback.ts +119 -0
  79. package/src/tweaks/fold.ts +21 -0
  80. package/src/tweaks/heavy.ts +302 -0
  81. package/src/tweaks/icons.ts +19 -0
  82. package/src/tweaks/image-store.ts +101 -0
  83. package/src/tweaks/lazy.ts +46 -0
  84. package/src/tweaks/markup.ts +154 -0
  85. package/src/tweaks/panel.ts +869 -0
  86. package/src/tweaks/schema.ts +189 -0
  87. package/src/tweaks/shared.ts +525 -0
  88. package/src/tweaks/single.ts +22 -0
  89. package/src/tweaks/standalone.ts +89 -0
  90. package/src/tweaks/types.ts +526 -0
  91. package/src/tweaks.css +959 -0
  92. package/src/wide-gamut.ts +310 -0
  93. package/dist/tweaks/bezier-2RABC6LD.js +0 -1
  94. package/dist/tweaks/chunk-4ATI34PB.js +0 -1
  95. package/dist/tweaks/chunk-7OSCMZ2J.js +0 -1
  96. package/dist/tweaks/chunk-W3JNIJ2F.js +0 -1
  97. package/dist/tweaks/clip-ECGCECEC.js +0 -1
  98. package/dist/tweaks/colour-2RE3FQ5E.js +0 -1
  99. package/dist/tweaks/gradient-DB6XTDHO.js +0 -1
  100. package/dist/tweaks/heavy-PKOMHB3C.js +0 -1
  101. package/dist/tweaks/image-7MCLBOZ3.js +0 -1
  102. package/dist/tweaks/interval-L2E4L3AZ.js +0 -1
  103. package/dist/tweaks/markup-ZJJ6CCWM.js +0 -1
  104. package/dist/tweaks/monitor-IEK742PG.js +0 -2
  105. package/dist/tweaks/motion-N5UNVR55.js +0 -1
  106. package/dist/tweaks/plot-VVTOY3RA.js +0 -2
  107. package/dist/tweaks/point-YMMHSLO7.js +0 -1
  108. package/dist/tweaks/shadow-IKYOV3VN.js +0 -1
  109. package/dist/tweaks/sides-FH227Z54.js +0 -1
  110. package/dist/tweaks/spring-3PKAFIH7.js +0 -1
  111. package/dist/tweaks/tabs-BKK5NIIY.js +0 -1
@@ -1,10 +1,10 @@
1
- /** An option for a select / radio grid: a bare string, or `{ value, label }`. */
2
- export type Option = string | {
3
- value: string;
1
+ /** An option for a select / radio grid: a bare string or number, or `{ value, label }`; the param is the value as written. */
2
+ export type Option = string | number | {
3
+ value: string | number;
4
4
  label?: string;
5
5
  color?: string;
6
6
  };
7
- /** A colour stop in a gradient. */
7
+ /** A color stop in a gradient. */
8
8
  export type GradientStop = {
9
9
  color: string;
10
10
  pos: number;
@@ -15,10 +15,11 @@ export type GradientStop = {
15
15
  export type GradientInterpolation = "srgb" | "hsl" | "hwb" | "oklch" | "oklab" | "lch" | "lab" | "display-p3" | "rec2020";
16
16
  /** What a sides control accepts: one number for all four; `[vertical, horizontal]`,
17
17
  * `[top, horizontal, bottom]` or `[top, right, bottom, left]` (CSS order); a CSS shorthand
18
- * string like `"8px 16px"` (the numbers are read in the control's unit); or any subset of
18
+ * string like `"1rem 2rem"` (its unit becomes the control's when `unit` isn't given; mixed
19
+ * units warn and read as px); or any subset of
19
20
  * the four as an object. With `corners: true` the four are the corners from top-left round,
20
21
  * the same CSS order, under the same keys, and they floor at 0 unless `min` says otherwise. */
21
- export type SidesInput = number | number[] | string | {
22
+ export type SidesInput = number | readonly number[] | string | {
22
23
  top?: number;
23
24
  right?: number;
24
25
  bottom?: number;
@@ -33,9 +34,9 @@ export interface SidesValue {
33
34
  left: number;
34
35
  css: string;
35
36
  }
36
- /** What a shadow control accepts — any subset of its fields (px, `color` any colour the kit
37
+ /** What a shadow control accepts — any subset of its fields (px, `color` any color the kit
37
38
  * parses); or, as `value`, a `box-shadow` string: optional `inset`, two to four px lengths,
38
- * one colour, one layer. */
39
+ * one color, one layer. */
39
40
  export interface ShadowInput {
40
41
  inset?: boolean;
41
42
  x?: number;
@@ -45,7 +46,7 @@ export interface ShadowInput {
45
46
  color?: string;
46
47
  }
47
48
  /** A shadow control's value — the fields, plus the `box-shadow` CSS they resolve to (`css`),
48
- * built from the numbers and the picker's own colour notation, so `el.style.boxShadow = css`. */
49
+ * built from the numbers and the picker's own color notation, so `el.style.boxShadow = css`. */
49
50
  export interface ShadowValue {
50
51
  inset: boolean;
51
52
  x: number;
@@ -60,7 +61,7 @@ export interface ShadowValue {
60
61
  * the easing's, in ms. Spring keys select the spring mode unless `mode` says otherwise. */
61
62
  export interface MotionInput {
62
63
  mode?: "easing" | "spring";
63
- curve?: [number, number, number, number] | string;
64
+ curve?: readonly [number, number, number, number] | string;
64
65
  duration?: number;
65
66
  stiffness?: number;
66
67
  damping?: number;
@@ -87,7 +88,7 @@ export type MotionValue = {
87
88
  duration: number;
88
89
  easing: string;
89
90
  };
90
- /** How a gradient blends between neighbouring stops: `"linear"` (CSS's own straight blend,
91
+ /** How a gradient blends between neighboring stops: `"linear"` (CSS's own straight blend,
91
92
  * the default) or a CSS easing — `ease`, `ease-in`, `ease-out`, `ease-in-out`, or any
92
93
  * `cubic-bezier(x1, y1, x2, y2)`. No browser eases a gradient natively, so `gradientCss()`
93
94
  * samples the curve into stops; template through it rather than by hand. */
@@ -109,7 +110,7 @@ export interface ControlOptions {
109
110
  label?: string;
110
111
  /** Show/hide this control based on other values. */
111
112
  render?: (get: Get) => boolean;
112
- /** Grey-out + lock this control (static, or derived from other values). */
113
+ /** Gray-out + lock this control (static, or derived from other values). */
113
114
  disabled?: boolean | ((get: Get) => boolean);
114
115
  /** A tooltip revealed by an ⓘ marker beside the label. */
115
116
  hint?: string;
@@ -147,12 +148,12 @@ export type SchemaObject = {
147
148
  value?: boolean;
148
149
  } | {
149
150
  type: "list";
150
- options: Option[];
151
- value?: string;
151
+ options: readonly Option[];
152
+ value?: string | number;
152
153
  } | {
153
154
  type: "radiogrid" | "segmented";
154
- options: Option[];
155
- value?: string;
155
+ options: readonly Option[];
156
+ value?: string | number;
156
157
  cols?: number;
157
158
  } | {
158
159
  type: "color";
@@ -164,7 +165,7 @@ export type SchemaObject = {
164
165
  placeholder?: string;
165
166
  } | {
166
167
  type: "interval";
167
- value?: [number, number];
168
+ value?: readonly [number, number];
168
169
  min?: number;
169
170
  max?: number;
170
171
  step?: number;
@@ -186,7 +187,7 @@ export type SchemaObject = {
186
187
  };
187
188
  } | {
188
189
  type: "cubicbezier";
189
- value?: [number, number, number, number];
190
+ value?: readonly [number, number, number, number];
190
191
  } | ({
191
192
  type: "shadow";
192
193
  value?: ShadowInput | string;
@@ -207,7 +208,7 @@ export type SchemaObject = {
207
208
  value?: MotionInput;
208
209
  } & MotionInput) | {
209
210
  type: "point";
210
- components: Array<{
211
+ components: ReadonlyArray<{
211
212
  key: string;
212
213
  label?: string;
213
214
  value?: number;
@@ -215,12 +216,25 @@ export type SchemaObject = {
215
216
  max?: number;
216
217
  step?: number;
217
218
  }>;
219
+ value?: {
220
+ readonly [key: string]: number;
221
+ };
222
+ pad?: boolean;
223
+ invertY?: boolean;
224
+ } | {
225
+ type: "point";
226
+ value: {
227
+ readonly [key: string]: number;
228
+ };
229
+ components?: undefined;
218
230
  pad?: boolean;
219
231
  invertY?: boolean;
220
232
  } | {
221
233
  type: "gradient";
222
- value?: GradientValue | Array<GradientStop | [string, number]>;
223
- stops?: Array<GradientStop | [string, number]>;
234
+ value?: GradientValue | ReadonlyArray<GradientStop | readonly [string, number]>;
235
+ stops?: ReadonlyArray<GradientStop | readonly [string, number]>;
236
+ interpolation?: GradientInterpolation;
237
+ easing?: GradientEasing;
224
238
  } | {
225
239
  type: "plot";
226
240
  expr?: string;
@@ -251,7 +265,10 @@ export type SchemaObject = {
251
265
  value?: string;
252
266
  /** Supplied images to pick from, as URLs or `{ value, label }`. The control becomes a row naming the chosen
253
267
  * image that opens a pane of tiles with the upload below them, and the param is the chosen URL or the upload's data URL. */
254
- options?: Option[];
268
+ options?: ReadonlyArray<string | {
269
+ value: string;
270
+ label?: string;
271
+ }>;
255
272
  /** Tiles per row in the gallery, 1 to 6 (by default one per option for two or three, 2 for one or four, then 3). */
256
273
  cols?: number;
257
274
  /** The largest upload taken, in bytes (10 MB unless set); a bigger file is refused with a note. */
@@ -260,7 +277,7 @@ export type SchemaObject = {
260
277
  * heavier than 1.5 MB is redrawn at its size, so a phone photo can't fill storage (2560 unless set; `false` takes
261
278
  * uploads as they are). A JPEG comes out as a JPEG at
262
279
  * `quality`; anything else as a PNG when that is 1.5 MB or less (a screenshot, not a photo), else as a JPEG, or a WebP
263
- * when it has transparency. A GIF, an SVG, a light picture within `fit` or one the redraw wouldn't make smaller is kept as it is. */
280
+ * when it has transparency. A GIF, an SVG, an animated WebP or APNG, a light picture within `fit`, or one the redraw wouldn't make smaller is kept as it is. */
264
281
  fit?: number | false;
265
282
  /** The JPEG or WebP quality of a shrunk upload, 0 to 1 (0.85 unless set). */
266
283
  quality?: number;
@@ -274,7 +291,7 @@ export type SchemaObject = {
274
291
  label?: string;
275
292
  } | {
276
293
  type: "buttongroup";
277
- buttons: Record<string, () => void> | Array<{
294
+ buttons: Record<string, () => void> | ReadonlyArray<{
278
295
  label: string;
279
296
  action: () => void;
280
297
  }>;
@@ -282,63 +299,214 @@ export type SchemaObject = {
282
299
  type: "separator";
283
300
  } | {
284
301
  type: "tabs";
285
- pages: Record<string, Schema>;
302
+ pages: {
303
+ readonly [title: string]: Schema;
304
+ };
286
305
  };
287
306
  /** A schema value — a shorthand, a verbose `{ type }` object, or a nested folder. */
288
- export type SchemaValue = number | boolean | string | [number] | [number, number] | [number, number, number] | [number, number, number, number] | [[number, number], number, number] | [[number, number], number, number, number] | Option[] | (SchemaObject & ControlOptions) | Schema;
289
- /** The object you hand to `tweaks(name, schema, opts?)`. */
307
+ export type SchemaValue = SchemaValueOf<SchemaNest>;
308
+ type SchemaValueOf<F> = number | boolean | string | readonly [number] | readonly [number, number] | readonly [number, number, number] | readonly [number, number, number, number] | readonly [readonly [number, number], number, number] | readonly [readonly [number, number], number, number, number] | readonly number[] | readonly (number | readonly number[])[] | readonly Option[] | {
309
+ options: readonly Option[];
310
+ value?: string | number;
311
+ } | {
312
+ action: () => void;
313
+ label?: string;
314
+ } | (SchemaObject & ControlOptions) | F | SchemaFolder;
315
+ /** The object you hand to `tweaks(name, schema, opts?)`. No value is a function, so a verbose
316
+ * form's callbacks (`format`, `fn`) take their parameter types from that form alone. */
290
317
  export interface Schema {
291
- [key: string]: SchemaValue;
318
+ readonly [key: string]: SchemaValue;
319
+ }
320
+ type SchemaNest = Schema & {
321
+ readonly type?: SchemaObject["type"] | Exclude<SchemaValue, string>;
322
+ };
323
+ /** What tweaks() and mountControl() infer from: `Schema` with a folder's `type` left open, since a schema held in a
324
+ * variable widens a verbose form's `type` to `string`; their own check then rules on the names it can still see. */
325
+ export interface HeldSchema {
326
+ readonly [key: string]: HeldValue;
327
+ }
328
+ /** A single value as mountControl() infers it; see `HeldSchema`. */
329
+ export type HeldValue = SchemaValueOf<HeldSchema>;
330
+ /** A nested folder that carries its own `render` / `disabled` predicate or `hint`: chrome, not
331
+ * children. Its index admits the predicate, so it gives up a child named `type`: that sets it
332
+ * apart from a verbose form, whose callbacks would otherwise be typed against the predicate too. */
333
+ export interface SchemaFolder {
334
+ readonly type?: undefined;
335
+ readonly render?: (get: Get) => boolean;
336
+ readonly disabled?: boolean | ((get: Get) => boolean);
337
+ readonly hint?: string;
338
+ readonly [key: string]: SchemaValue | ((get: Get) => boolean) | undefined;
339
+ }
340
+ /** A spring control's value: the physics, plus the time pair and mode when tuned by feel. */
341
+ export interface SpringValue {
342
+ stiffness: number;
343
+ damping: number;
344
+ mass: number;
345
+ visualDuration?: number;
346
+ bounce?: number;
347
+ mode?: "time" | "physics";
292
348
  }
349
+ type OptionValue<O> = O extends string | number ? O : O extends {
350
+ readonly value: infer V extends string | number;
351
+ } ? V : never;
352
+ type ChoiceValue<V> = (V extends {
353
+ readonly options: readonly (infer O)[];
354
+ } ? OptionValue<O> : never) | (V extends {
355
+ readonly value: infer D extends string | number;
356
+ } ? D : never);
357
+ type Alnum = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
358
+ type Dashed<S extends string, Acc extends string = "", Run = false> = S extends `${infer C}${infer R}` ? C extends Alnum ? Dashed<R, `${Acc}${C}`, false> : Run extends true ? Dashed<R, Acc, true> : Dashed<R, `${Acc}-`, true> : Acc;
359
+ type TrimDash<S extends string> = S extends `-${infer R}` ? TrimDash<R> : S extends `${infer R}-` ? TrimDash<R> : S;
360
+ /** A tab title's key under `params`: lower-case, each run of other characters a `-`, trimmed
361
+ * (`"Layout & Grid"` → `"layout-grid"`). Two titles that slug alike are told apart at runtime only. */
362
+ export type TabKey<T extends string> = string extends T ? string : TrimDash<Dashed<Lowercase<T>>> extends infer K extends string ? (K extends "" ? "tab" : K) : never;
363
+ type TabClash<P> = true extends {
364
+ [T in keyof P & string]: TabKey<T> extends {
365
+ [U in Exclude<keyof P & string, T>]: TabKey<U>;
366
+ }[Exclude<keyof P & string, T>] ? true : false;
367
+ }[keyof P & string] ? true : false;
368
+ type Valueless = "button" | "buttongroup" | "separator" | "fpsgraph" | "monitor";
369
+ type Hinge = {
370
+ list: "options";
371
+ radiogrid: "options";
372
+ segmented: "options";
373
+ point: "components" | "value";
374
+ tabs: "pages";
375
+ };
376
+ type TypedValue<V> = V extends {
377
+ readonly type: "slider" | "number";
378
+ } ? number : V extends {
379
+ readonly type: "checkbox";
380
+ } ? boolean : V extends {
381
+ readonly type: "list" | "radiogrid" | "segmented";
382
+ } ? (V extends {
383
+ readonly options: readonly unknown[];
384
+ } ? ChoiceValue<V> : never) : V extends {
385
+ readonly type: "color" | "text" | "image" | "plot";
386
+ } ? string : V extends {
387
+ readonly type: "interval";
388
+ } ? [number, number] : V extends {
389
+ readonly type: "cubicbezier";
390
+ } ? [number, number, number, number] : V extends {
391
+ readonly type: "spring";
392
+ } ? SpringValue : V extends {
393
+ readonly type: "motion";
394
+ } ? MotionValue : V extends {
395
+ readonly type: "shadow";
396
+ } ? ShadowValue : V extends {
397
+ readonly type: "sides";
398
+ } ? SidesValue : V extends {
399
+ readonly type: "gradient";
400
+ } ? GradientValue : V extends {
401
+ readonly type: "point";
402
+ } ? (V extends {
403
+ readonly components: readonly [];
404
+ } ? never : V extends {
405
+ readonly components: readonly (infer C)[];
406
+ } ? {
407
+ [K in C extends {
408
+ readonly key: infer Key extends string;
409
+ } ? Key : never]: number;
410
+ } : V extends {
411
+ readonly value: infer P extends object;
412
+ } ? (keyof P extends never ? never : {
413
+ -readonly [K in keyof P & string]: number;
414
+ }) : never) : V extends {
415
+ readonly type: "tabs";
416
+ } ? (V extends {
417
+ readonly pages: infer P extends object;
418
+ } ? (keyof P extends never ? never : TabClash<P> extends true ? Record<string, Params> : {
419
+ [T in keyof P & string as TabKey<T>]: ParamsOf<P[T]>;
420
+ }) : never) : unknown;
421
+ /** The value a schema entry puts on `params`. */
422
+ export type ValueOf<V> = V extends {
423
+ readonly type: infer T;
424
+ } ? (string extends T ? any : T extends SchemaObject["type"] ? (T extends keyof Hinge ? ([Extract<Hinge[T], keyof V>] extends [never] ? ShorthandValue<V> : VerboseValue<V, T>) : VerboseValue<V, T>) : ShorthandValue<V>) : ShorthandValue<V>;
425
+ type VerboseValue<V, T> = T extends Valueless ? never : TypedValue<V>;
426
+ type ShorthandValue<V> = V extends readonly [readonly [number, number], ...unknown[]] ? [number, number] : V extends readonly [number] | readonly [number, number] | readonly [number, number, number] | readonly [number, number, number, number] ? number : V extends readonly [number, ...number[]] ? V[number] : V extends readonly [] ? undefined : V extends readonly number[] ? number : V extends readonly (number | readonly number[])[] ? [number, number] : V extends number ? number : V extends boolean ? boolean : V extends string ? string : V extends readonly (infer O)[] ? OptionValue<O> : V extends {
427
+ readonly action: (...a: any[]) => any;
428
+ } ? never : V extends {
429
+ readonly options: readonly unknown[];
430
+ } ? ChoiceValue<V> : V extends object ? Entries<V, true> : unknown;
431
+ type Reserved = "__proto__" | "constructor" | "prototype";
432
+ type FolderChrome<V, K> = K extends "disabled" | "hint" ? true : K extends "render" ? (V extends (...a: any[]) => any ? true : false) : false;
433
+ type Entries<S, Nested extends boolean> = string extends keyof S ? Params : {
434
+ -readonly [K in keyof S & string as K extends Reserved ? never : ValueOf<S[K]> extends never ? never : Nested extends true ? (FolderChrome<S[K], K> extends true ? never : K) : K]: ValueOf<S[K]>;
435
+ };
436
+ /** The live values a schema builds, nested like the schema; a plain `Schema` gives the loose `Params`. */
437
+ export type ParamsOf<S> = Entries<S, false>;
438
+ type CheckValue<V> = V extends {
439
+ readonly type: infer T;
440
+ } ? (string extends T ? V : T extends SchemaObject["type"] ? (V extends {
441
+ readonly type: "tabs";
442
+ readonly pages: infer P;
443
+ } ? {
444
+ readonly pages: {
445
+ [K in keyof P]: KnownTypes<P[K]>;
446
+ };
447
+ } : T extends keyof Hinge ? ([Extract<Hinge[T], keyof V>] extends [never] ? unknown : FitsForm<V, T>) : FitsForm<V, T>) : {
448
+ readonly type: NotAControlType;
449
+ }) : V extends readonly unknown[] | ((...a: any[]) => any) ? unknown : V extends object ? KnownTypes<V> : unknown;
450
+ type FormOf<O, T> = O extends {
451
+ readonly type: infer N;
452
+ } ? (T extends N ? O & ControlOptions : never) : never;
453
+ type FitsForm<V, T> = V extends FormOf<SchemaObject, T> ? unknown : FormOf<SchemaObject, T>;
454
+ type NotAControlType = {
455
+ readonly "names no control: see the type list in AGENTS.md": true;
456
+ };
457
+ /** A schema whose every `type` names a control (`tweaks()` checks it; `{ type: "slidr" }` is an error). */
458
+ export type KnownTypes<S> = {
459
+ [K in keyof S]: CheckValue<S[K]>;
460
+ };
461
+ /** One schema value whose `type` names a control (`mountControl()` checks it). */
462
+ export type KnownType<V> = CheckValue<V>;
293
463
  /** A theme — token overrides applied over the default monochrome look. Every key is
294
464
  * optional, so a partial theme only moves what it names; unset tokens keep their default.
295
- * Use the friendly names here, or any raw `--tw-*` custom property. Apply at build via
465
+ * Keys are the `--tw-*` custom properties themselves. Apply at build via
296
466
  * `tweaks(name, schema, { theme })`, or live via `panel.setTheme(theme)`. The kit's whole
297
467
  * appearance runs on these custom properties — this is the public theming surface. */
298
468
  export interface Theme {
299
469
  /** Brand accent — slider fills, focus rings, active highlights (default: neutral, no accent). */
300
- accent?: string;
301
- /** Text drawn on the accent (active segment / radio label). Auto-derived from the accent's luminance if unset. */
302
- onAccent?: string;
470
+ "--tw-accent"?: string;
471
+ /** Text drawn on the accent (active segment / radio label); the primary text tone unless set, so set it with a light accent. */
472
+ "--tw-on-accent"?: string;
303
473
  /** Panel background, reused for recessed wells. */
304
- base?: string;
474
+ "--tw-base"?: string;
305
475
  /** Popover / dropdown background. */
306
- dropdownBg?: string;
476
+ "--tw-dropdown-bg"?: string;
307
477
  /** Control surface and its hover / active steps. */
308
- surface?: string;
309
- surfaceHover?: string;
310
- surfaceActive?: string;
478
+ "--tw-surface"?: string;
479
+ "--tw-surface-hover"?: string;
480
+ "--tw-surface-active"?: string;
311
481
  /** Hairline border and its hover step. */
312
- border?: string;
313
- borderHover?: string;
482
+ "--tw-border"?: string;
483
+ "--tw-border-hover"?: string;
314
484
  /** Text-selection highlight wash. */
315
- selection?: string;
485
+ "--tw-selection"?: string;
316
486
  /** Text tones — panel title, section heading, primary value text, control label, and the muted / faint / focus tones. */
317
- title?: string;
318
- section?: string;
319
- text?: string;
320
- label?: string;
321
- textMuted?: string;
322
- textFaint?: string;
323
- focus?: string;
324
- /** Copy-confirmation accent. */
325
- success?: string;
326
- /** Invalid-state accent (the plot's bad-expression cue). */
327
- danger?: string;
487
+ "--tw-text-root"?: string;
488
+ "--tw-text-section"?: string;
489
+ "--tw-text-primary"?: string;
490
+ "--tw-text-label"?: string;
491
+ "--tw-text-secondary"?: string;
492
+ "--tw-text-tertiary"?: string;
493
+ "--tw-text-focus"?: string;
494
+ /** Copy-confirmation accent, and the invalid-state accent (the plot's bad-expression cue). */
495
+ "--tw-success"?: string;
496
+ "--tw-danger"?: string;
328
497
  /** Popover shadow, the panel's container elevation, and its lifted (floating / dragging) variant. */
329
- shadow?: string;
330
- shadowPanel?: string;
331
- shadowPanelLifted?: string;
498
+ "--tw-shadow-dropdown"?: string;
499
+ "--tw-shadow-panel"?: string;
500
+ "--tw-shadow-panel-lifted"?: string;
332
501
  /** Font stack, and the monospace stack (the plot's expression field). */
333
- font?: string;
334
- fontMono?: string;
335
- /** Control corner radius and row height — a bare number is treated as px. */
336
- radius?: number | string;
337
- density?: number | string;
338
- /** The panel's width (256px by default) — a bare number is treated as px. */
339
- width?: number | string;
340
- /** Escape hatch — any raw token, e.g. `"--tw-accent": "#6c8cff"`. */
341
- [token: string]: string | number | undefined;
502
+ "--tw-font-sans"?: string;
503
+ "--tw-font-mono"?: string;
504
+ /** Control corner radius, row height and the panel's width (256px by default), as CSS lengths. */
505
+ "--tw-radius"?: string;
506
+ "--tw-row-height"?: string;
507
+ "--tw-width"?: string;
508
+ /** Any other token. */
509
+ [token: `--${string}`]: string | undefined;
342
510
  }
343
511
  /** Options for a programmatic write — `set()`, `setMany()`, `fromJSON()`. */
344
512
  export interface SetOptions {
@@ -347,29 +515,25 @@ export interface SetOptions {
347
515
  * controls, persistence and undo still follow it: it's an undo step like any other. */
348
516
  silent?: boolean;
349
517
  }
350
- /** Panel options (third arg to `tweaks()`). */
351
- /** The corner a floating panel anchors to. */
352
- export type FloatingCorner = "top-left" | "top-right" | "bottom-left" | "bottom-right";
353
- /** Where a floating panel starts: `x` / `y` are its insets in px (16 by default) from the two
354
- * edges of `corner` (top-left by default), and it keeps that corner as the window resizes.
355
- * The far edge keeps a gap of `y` when the height caps; CSS `--tw-float-gap` overrides it. */
518
+ /** Where a floating panel starts: `x` / `y` are its insets in px (16 by default) from the
519
+ * viewport's top-left. The far edge keeps a gap of `y` when the height caps; CSS
520
+ * `--tw-float-gap` overrides it. */
356
521
  export interface FloatingPlacement {
357
522
  x?: number;
358
523
  y?: number;
359
- corner?: FloatingCorner;
360
524
  }
525
+ /** Panel options (third arg to `tweaks()`). */
361
526
  export interface TweaksOptions {
362
- /** Theme overrides (friendly names or raw `--tw-*`); `null` / omitted = default monochrome. */
527
+ /** Theme overrides as `--tw-*` tokens; `null` / omitted = default monochrome. */
363
528
  theme?: Theme | null;
364
- /** Persist values to localStorage + enable presets — a key, or `true` to key by name. */
529
+ /** Persist values to localStorage + enable presets — a key, or `true` to key by name. Uploaded images go to
530
+ * IndexedDB under the same key, and come back by `ready`. */
365
531
  persist?: string | boolean;
366
532
  /** With `persist`, `false` keeps saving values but leaves the Presets menu out of the
367
533
  * toolbar (the presets API still works). On by default. */
368
534
  presets?: boolean;
369
- /** Add a filter/search field to the toolbar. */
370
- filter?: boolean;
371
535
  /** Start the panel already floated (fixed-positioned): `true` → 16px from the top-left, or a
372
- * placement `{ x, y, corner }`. Its height caps at the window and it scrolls, header and all. */
536
+ * placement `{ x, y }`. Its height caps at the window and it scrolls, header and all. */
373
537
  floating?: boolean | FloatingPlacement;
374
538
  /** Drag the header to reposition the panel — on by default. An inline panel lifts into a
375
539
  * floating layer on first drag; set `false` to pin it in place. */
@@ -380,15 +544,18 @@ export interface TweaksOptions {
380
544
  undo?: boolean;
381
545
  /** Custom reset handler (e.g. restore real app defaults + rebuild). */
382
546
  onReset?: () => void;
383
- /** Fired when a drag/scrub on any control begins. */
384
- onEditStart?: () => void;
385
- /** Fired when a drag/scrub ends. */
386
- onEditEnd?: () => void;
387
547
  }
388
- /** The live values bag — `params[key]` for each control; `_last` is the last-changed key. */
389
- export type Params = Record<string, any> & {
390
- _last?: string;
391
- };
548
+ /** The live values bag, loosely typed — `params[key]` for each control. A schema literal passed
549
+ * to `tweaks()` gets its exact shape instead (`ParamsOf`). */
550
+ export type Params = Record<string, any>;
551
+ /** The keys `set()` and `setMany()` take: a typed panel's controls by dotted path (`size`, `card.size`), so a typo is a
552
+ * compile error; any string for a panel built from a plain `Schema`. An object-valued control's own fields read as
553
+ * paths too (`drop.blur`): the types can't tell its value from a folder, so they allow what the runtime refuses. Past ten
554
+ * folders deep the rest of a path is any string, since spelling every one out overran tsc's depth limit. */
555
+ export type PanelPath<P> = PathsOf<P, []>;
556
+ type PathsOf<P, D extends unknown[]> = string extends keyof P ? string : D["length"] extends 10 ? string : {
557
+ [K in keyof P & string]: K | (P[K] extends readonly unknown[] ? never : P[K] extends object ? `${K}.${PathsOf<P[K], [...D, 0]>}` : never);
558
+ }[keyof P & string];
392
559
  /** The whole-panel state `toJSON()` returns / `fromJSON()` accepts: every control value
393
560
  * (nested to mirror folders/tabs) plus UI state — which folders are collapsed and which
394
561
  * tab is active. JSON-safe; persist it however you like. Both `values` and `ui` are
@@ -410,25 +577,31 @@ export interface PanelState {
410
577
  tabs?: Record<string, string | null>;
411
578
  };
412
579
  }
413
- /** The panel object `tweaks()` returns. */
414
- export interface Panel {
580
+ /** The panel object `tweaks()` returns; `P` is its values' shape. A bare `Panel` takes any panel, typed or not (its
581
+ * default is `any`, not `Params`: `set()`'s typed paths would make a typed panel unassignable to `Panel<Params>`). */
582
+ export interface Panel<P = any> {
415
583
  /** The panel root element — append it to the DOM. */
416
584
  el: HTMLElement;
417
585
  /** Live values, updated in place as controls change. */
418
- params: Params;
419
- /** Resolves once any lazily-loaded controls have hydrated (resolves immediately when none). */
420
- ready: Promise<Panel>;
421
- /** Subscribe to changes; returns an unsubscribe fn. */
422
- on(fn: (params: Params, last?: string) => void): () => void;
586
+ params: P;
587
+ /** Resolves once any lazily-loaded controls have hydrated and, for a panel that persists an image control, its
588
+ * stored images have loaded (resolves immediately when there's neither). */
589
+ ready: Promise<Panel<P>>;
590
+ /** Subscribe to changes; `last` is the changed control's dotted path (`size`, `card.size`), as `set()` and `changes()`
591
+ * name it, or undefined when many changed at once (a restore, `reset()`, `fromJSON`, `setMany`). */
592
+ on(fn: (params: P, last?: string) => void): () => void;
423
593
  /** Set a control's value programmatically. Nested controls take a dotted path
424
- * ("folder.child", "tabs.page.child"); a bare key also reaches a nested control
425
- * when it's unambiguous. A key matching no control is stored on `params` as-is. */
426
- set(key: string, value: unknown, options?: SetOptions): void;
594
+ * ("folder.child", "tabs.page.child"); at runtime a bare key also reaches a nested control
595
+ * when it's unambiguous, though a typed panel's types take the full path. A key matching
596
+ * no control, or a folder or tab, is refused with a console warning. */
597
+ set(key: PanelPath<P>, value: unknown, options?: SetOptions): void;
427
598
  /** Set several controls at once — a flat map of keys (dotted for nested controls), e.g.
428
599
  * `setMany({ "shadow.radius": 28, blur: 48 })`. Resolves each key like `set()`, then fires
429
600
  * listeners + persists ONCE for the whole batch, where N separate `set()` calls would fire
430
601
  * N times. Unknown / ambiguous / reserved keys warn and are skipped. */
431
- setMany(values: Record<string, unknown>, options?: SetOptions): void;
602
+ setMany(values: {
603
+ readonly [K in PanelPath<P>]?: unknown;
604
+ }, options?: SetOptions): void;
432
605
  /** Reset every control to its default (or run `opts.onReset`). */
433
606
  reset(): void;
434
607
  /** Serialize the whole panel — every control value plus UI state (collapsed folders,
@@ -479,6 +652,9 @@ export interface Control {
479
652
  /** Show a short message on the control for a moment. Only the image control supplies it; the panel calls it when
480
653
  * storage refuses the control's value. */
481
654
  note?(message: string): void;
655
+ /** The hard range set() fits a number into, as the control resolved it (derived bounds too); a point's per component.
656
+ * Only the numeric controls supply it; the panel's clamp notice reads it. */
657
+ range?: [number, number] | Record<string, [number, number]>;
482
658
  }
483
659
  /** Options for `mountControl(host, value, opts?)` — one control, no panel. */
484
660
  export interface MountOptions {
@@ -507,16 +683,16 @@ export interface MountedControl {
507
683
  /** Resolves with this handle once the control has built — immediately on the single-file build. */
508
684
  ready: Promise<MountedControl>;
509
685
  }
510
- /** The notations the colour picker edits in (its mode dropdown). The emitted value follows
511
- * the mode: `hex` gives `#rrggbb`, the wide-gamut modes give a CSS colour like `oklch(…)`. */
686
+ /** The notations the color picker edits in (its mode dropdown). The emitted value follows
687
+ * the mode: `hex` gives `#RRGGBB`, the wide-gamut modes give a CSS color like `oklch(…)`. */
512
688
  export type ColorMode = "hex" | "srgb" | "css" | "hsl" | "hwb" | "oklch" | "oklab" | "lch" | "lab" | "p3" | "rec2020";
513
689
  /** Options for `createColorPicker(opts?)` — the picker body alone, no trigger row. */
514
690
  export interface ColorPickerOptions {
515
- /** The colour it opens on — any CSS colour string (default `#7c5cff`). */
691
+ /** The color it opens on — any CSS color string (default `#7c5cff`). */
516
692
  value?: string;
517
693
  /** The notation it opens in (default `oklch`). */
518
694
  mode?: ColorMode;
519
- /** Fired with the new colour string whenever it changes — by hand or through `set()`. */
695
+ /** Fired with the new color string whenever it changes — by hand or through `set()`. */
520
696
  onChange?: (value: string) => void;
521
697
  }
522
698
  /** The handle `createColorPicker()` returns. Bring your own trigger: append `el` to a popover
@@ -524,11 +700,11 @@ export interface ColorPickerOptions {
524
700
  export interface ColorPicker {
525
701
  /** The picker's root (`.tw-color-picker`), stable from creation; it carries the kit's tokens,
526
702
  * so the picker renders anywhere. On the code-split build the editor fills it in once the
527
- * colour chunk has loaded. */
703
+ * color chunk has loaded. */
528
704
  el: HTMLElement;
529
- /** Apply a colour — the picker updates AND `onChange` fires (when the colour actually changed). */
705
+ /** Apply a color — the picker updates AND `onChange` fires (when the color actually changed). */
530
706
  set(value: string): void;
531
- /** The current colour, serialized in the picker's mode. */
707
+ /** The current color, serialized in the picker's mode. */
532
708
  get(): string;
533
709
  /** The current notation. */
534
710
  mode(): ColorMode;
@@ -554,3 +730,4 @@ declare global {
554
730
  "tw:change": CustomEvent<TwChangeDetail>;
555
731
  }
556
732
  }
733
+ export {};