tweakit 0.5.2 → 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.
Files changed (120) hide show
  1. package/AGENTS.md +32 -30
  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-S2BOXQBM.js +2 -0
  7. package/dist/tweaks/bezier-S2BOXQBM.js.map +6 -0
  8. package/dist/tweaks/chunk-3MZAAMC2.js +2 -0
  9. package/dist/tweaks/chunk-3MZAAMC2.js.map +6 -0
  10. package/dist/tweaks/chunk-ETB35KFV.js +2 -0
  11. package/dist/tweaks/chunk-ETB35KFV.js.map +6 -0
  12. package/dist/tweaks/chunk-YB5GKWZI.js +2 -0
  13. package/dist/tweaks/chunk-YB5GKWZI.js.map +6 -0
  14. package/dist/tweaks/clip-A3VI53BK.js +2 -0
  15. package/dist/tweaks/clip-A3VI53BK.js.map +6 -0
  16. package/dist/tweaks/color-UGYMAF3W.js +2 -0
  17. package/dist/tweaks/color-UGYMAF3W.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-GY47HIQA.js +2 -0
  21. package/dist/tweaks/fold-GY47HIQA.js.map +6 -0
  22. package/dist/tweaks/gradient-BTX25WSH.js +2 -0
  23. package/dist/tweaks/gradient-BTX25WSH.js.map +6 -0
  24. package/dist/tweaks/heavy-NPFIGHRM.js +2 -0
  25. package/dist/tweaks/heavy-NPFIGHRM.js.map +6 -0
  26. package/dist/tweaks/image-HTQQ4WX6.js +4 -0
  27. package/dist/tweaks/image-HTQQ4WX6.js.map +6 -0
  28. package/dist/tweaks/image-store-QD5SMZXG.js +2 -0
  29. package/dist/tweaks/image-store-QD5SMZXG.js.map +6 -0
  30. package/dist/tweaks/interval-BHPI4LRN.js +2 -0
  31. package/dist/tweaks/interval-BHPI4LRN.js.map +6 -0
  32. package/dist/tweaks/markup-QM53TQJZ.js +2 -0
  33. package/dist/tweaks/markup-QM53TQJZ.js.map +6 -0
  34. package/dist/tweaks/monitor-722WIYGW.js +3 -0
  35. package/dist/tweaks/monitor-722WIYGW.js.map +6 -0
  36. package/dist/tweaks/motion-HMCZK6HJ.js +2 -0
  37. package/dist/tweaks/motion-HMCZK6HJ.js.map +6 -0
  38. package/dist/tweaks/plot-D3NQ6XNN.js +3 -0
  39. package/dist/tweaks/plot-D3NQ6XNN.js.map +6 -0
  40. package/dist/tweaks/point-XDXKMDXH.js +2 -0
  41. package/dist/tweaks/point-XDXKMDXH.js.map +6 -0
  42. package/dist/tweaks/presets-EUWNSCUW.js +2 -0
  43. package/dist/tweaks/presets-EUWNSCUW.js.map +6 -0
  44. package/dist/tweaks/shadow-AM7VSRNS.js +2 -0
  45. package/dist/tweaks/shadow-AM7VSRNS.js.map +6 -0
  46. package/dist/tweaks/shadow-root-RYUV3AGS.js +3 -0
  47. package/dist/tweaks/shadow-root-RYUV3AGS.js.map +6 -0
  48. package/dist/tweaks/sides-JWIURDA4.js +2 -0
  49. package/dist/tweaks/sides-JWIURDA4.js.map +6 -0
  50. package/dist/tweaks/spring-BF5GBTPM.js +2 -0
  51. package/dist/tweaks/spring-BF5GBTPM.js.map +6 -0
  52. package/dist/tweaks/tabs-PRVUUZXZ.js +2 -0
  53. package/dist/tweaks/tabs-PRVUUZXZ.js.map +6 -0
  54. package/dist/tweaks.css +1 -1
  55. package/dist/tweaks.js +7 -3
  56. package/dist/tweaks.js.map +6 -0
  57. package/dist/types/tweaks/easing.d.ts +8 -11
  58. package/dist/types/tweaks/enhance.d.ts +3 -2
  59. package/dist/types/tweaks/panel.d.ts +6 -4
  60. package/dist/types/tweaks/schema.d.ts +4 -8
  61. package/dist/types/tweaks/standalone.d.ts +3 -3
  62. package/dist/types/tweaks/types.d.ts +499 -116
  63. package/package.json +30 -5
  64. package/src/tweaks/auto.ts +16 -0
  65. package/src/tweaks/clip.ts +156 -0
  66. package/src/tweaks/controls/basic.ts +727 -0
  67. package/src/tweaks/controls/bezier.ts +204 -0
  68. package/src/tweaks/controls/color.ts +861 -0
  69. package/src/tweaks/controls/gradient.ts +419 -0
  70. package/src/tweaks/controls/image.ts +555 -0
  71. package/src/tweaks/controls/interval.ts +189 -0
  72. package/src/tweaks/controls/monitor.ts +211 -0
  73. package/src/tweaks/controls/motion.ts +316 -0
  74. package/src/tweaks/controls/plot.ts +440 -0
  75. package/src/tweaks/controls/point.ts +208 -0
  76. package/src/tweaks/controls/shadow.ts +156 -0
  77. package/src/tweaks/controls/sides.ts +196 -0
  78. package/src/tweaks/controls/spring.ts +308 -0
  79. package/src/tweaks/controls/tabs.ts +68 -0
  80. package/src/tweaks/core.ts +9 -0
  81. package/src/tweaks/easing.ts +118 -0
  82. package/src/tweaks/enhance.ts +35 -0
  83. package/src/tweaks/feedback.ts +218 -0
  84. package/src/tweaks/fold.ts +33 -0
  85. package/src/tweaks/heavy.ts +626 -0
  86. package/src/tweaks/icons.ts +26 -0
  87. package/src/tweaks/image-store.ts +189 -0
  88. package/src/tweaks/lazy.ts +63 -0
  89. package/src/tweaks/markup.ts +317 -0
  90. package/src/tweaks/panel.ts +1693 -0
  91. package/src/tweaks/presets.ts +85 -0
  92. package/src/tweaks/schema.ts +351 -0
  93. package/src/tweaks/shadow-root.ts +178 -0
  94. package/src/tweaks/shared.ts +1137 -0
  95. package/src/tweaks/single.ts +22 -0
  96. package/src/tweaks/standalone.ts +179 -0
  97. package/src/tweaks/types.ts +1027 -0
  98. package/src/tweaks/undo.ts +104 -0
  99. package/src/tweaks.css +965 -0
  100. package/src/wide-gamut.ts +658 -0
  101. package/dist/tweaks/bezier-2RABC6LD.js +0 -1
  102. package/dist/tweaks/chunk-4ATI34PB.js +0 -1
  103. package/dist/tweaks/chunk-7OSCMZ2J.js +0 -1
  104. package/dist/tweaks/chunk-W3JNIJ2F.js +0 -1
  105. package/dist/tweaks/clip-ECGCECEC.js +0 -1
  106. package/dist/tweaks/colour-2RE3FQ5E.js +0 -1
  107. package/dist/tweaks/fold-UGN7TXGX.js +0 -1
  108. package/dist/tweaks/gradient-DB6XTDHO.js +0 -1
  109. package/dist/tweaks/heavy-PKOMHB3C.js +0 -1
  110. package/dist/tweaks/image-7MCLBOZ3.js +0 -1
  111. package/dist/tweaks/interval-L2E4L3AZ.js +0 -1
  112. package/dist/tweaks/markup-ZJJ6CCWM.js +0 -1
  113. package/dist/tweaks/monitor-IEK742PG.js +0 -2
  114. package/dist/tweaks/motion-N5UNVR55.js +0 -1
  115. package/dist/tweaks/plot-VVTOY3RA.js +0 -2
  116. package/dist/tweaks/point-YMMHSLO7.js +0 -1
  117. package/dist/tweaks/shadow-IKYOV3VN.js +0 -1
  118. package/dist/tweaks/sides-FH227Z54.js +0 -1
  119. package/dist/tweaks/spring-3PKAFIH7.js +0 -1
  120. package/dist/tweaks/tabs-BKK5NIIY.js +0 -1
@@ -1,10 +1,11 @@
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
2
+ * written. */
3
+ export type Option = string | number | {
4
+ value: string | number;
4
5
  label?: string;
5
6
  color?: string;
6
7
  };
7
- /** A colour stop in a gradient. */
8
+ /** A color stop in a gradient. */
8
9
  export type GradientStop = {
9
10
  color: string;
10
11
  pos: number;
@@ -15,10 +16,11 @@ export type GradientStop = {
15
16
  export type GradientInterpolation = "srgb" | "hsl" | "hwb" | "oklch" | "oklab" | "lch" | "lab" | "display-p3" | "rec2020";
16
17
  /** What a sides control accepts: one number for all four; `[vertical, horizontal]`,
17
18
  * `[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
19
+ * string like `"1rem 2rem"` (its unit becomes the control's when `unit` isn't given; mixed
20
+ * units warn and read as px); or any subset of
19
21
  * the four as an object. With `corners: true` the four are the corners from top-left round,
20
22
  * the same CSS order, under the same keys, and they floor at 0 unless `min` says otherwise. */
21
- export type SidesInput = number | number[] | string | {
23
+ export type SidesInput = number | readonly number[] | string | {
22
24
  top?: number;
23
25
  right?: number;
24
26
  bottom?: number;
@@ -33,9 +35,9 @@ export interface SidesValue {
33
35
  left: number;
34
36
  css: string;
35
37
  }
36
- /** What a shadow control accepts — any subset of its fields (px, `color` any colour the kit
38
+ /** What a shadow control accepts — any subset of its fields (px, `color` any color the kit
37
39
  * parses); or, as `value`, a `box-shadow` string: optional `inset`, two to four px lengths,
38
- * one colour, one layer. */
40
+ * one color, one layer. */
39
41
  export interface ShadowInput {
40
42
  inset?: boolean;
41
43
  x?: number;
@@ -45,7 +47,7 @@ export interface ShadowInput {
45
47
  color?: string;
46
48
  }
47
49
  /** 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`. */
50
+ * built from the numbers and the picker's own color notation, so `el.style.boxShadow = css`. */
49
51
  export interface ShadowValue {
50
52
  inset: boolean;
51
53
  x: number;
@@ -60,7 +62,7 @@ export interface ShadowValue {
60
62
  * the easing's, in ms. Spring keys select the spring mode unless `mode` says otherwise. */
61
63
  export interface MotionInput {
62
64
  mode?: "easing" | "spring";
63
- curve?: [number, number, number, number] | string;
65
+ curve?: readonly [number, number, number, number] | string;
64
66
  duration?: number;
65
67
  stiffness?: number;
66
68
  damping?: number;
@@ -87,7 +89,7 @@ export type MotionValue = {
87
89
  duration: number;
88
90
  easing: string;
89
91
  };
90
- /** How a gradient blends between neighbouring stops: `"linear"` (CSS's own straight blend,
92
+ /** How a gradient blends between neighboring stops: `"linear"` (CSS's own straight blend,
91
93
  * the default) or a CSS easing — `ease`, `ease-in`, `ease-out`, `ease-in-out`, or any
92
94
  * `cubic-bezier(x1, y1, x2, y2)`. No browser eases a gradient natively, so `gradientCss()`
93
95
  * samples the curve into stops; template through it rather than by hand. */
@@ -109,7 +111,7 @@ export interface ControlOptions {
109
111
  label?: string;
110
112
  /** Show/hide this control based on other values. */
111
113
  render?: (get: Get) => boolean;
112
- /** Grey-out + lock this control (static, or derived from other values). */
114
+ /** Gray-out + lock this control (static, or derived from other values). */
113
115
  disabled?: boolean | ((get: Get) => boolean);
114
116
  /** A tooltip revealed by an ⓘ marker beside the label. */
115
117
  hint?: string;
@@ -147,12 +149,12 @@ export type SchemaObject = {
147
149
  value?: boolean;
148
150
  } | {
149
151
  type: "list";
150
- options: Option[];
151
- value?: string;
152
+ options: readonly Option[];
153
+ value?: string | number;
152
154
  } | {
153
155
  type: "radiogrid" | "segmented";
154
- options: Option[];
155
- value?: string;
156
+ options: readonly Option[];
157
+ value?: string | number;
156
158
  cols?: number;
157
159
  } | {
158
160
  type: "color";
@@ -164,7 +166,7 @@ export type SchemaObject = {
164
166
  placeholder?: string;
165
167
  } | {
166
168
  type: "interval";
167
- value?: [number, number];
169
+ value?: readonly [number, number];
168
170
  min?: number;
169
171
  max?: number;
170
172
  step?: number;
@@ -186,7 +188,7 @@ export type SchemaObject = {
186
188
  };
187
189
  } | {
188
190
  type: "cubicbezier";
189
- value?: [number, number, number, number];
191
+ value?: readonly [number, number, number, number];
190
192
  } | ({
191
193
  type: "shadow";
192
194
  value?: ShadowInput | string;
@@ -207,7 +209,7 @@ export type SchemaObject = {
207
209
  value?: MotionInput;
208
210
  } & MotionInput) | {
209
211
  type: "point";
210
- components: Array<{
212
+ components: ReadonlyArray<{
211
213
  key: string;
212
214
  label?: string;
213
215
  value?: number;
@@ -215,12 +217,25 @@ export type SchemaObject = {
215
217
  max?: number;
216
218
  step?: number;
217
219
  }>;
220
+ value?: {
221
+ readonly [key: string]: number;
222
+ };
223
+ pad?: boolean;
224
+ invertY?: boolean;
225
+ } | {
226
+ type: "point";
227
+ value: {
228
+ readonly [key: string]: number;
229
+ };
230
+ components?: undefined;
218
231
  pad?: boolean;
219
232
  invertY?: boolean;
220
233
  } | {
221
234
  type: "gradient";
222
- value?: GradientValue | Array<GradientStop | [string, number]>;
223
- stops?: Array<GradientStop | [string, number]>;
235
+ value?: GradientValue | ReadonlyArray<GradientStop | readonly [string, number]>;
236
+ stops?: ReadonlyArray<GradientStop | readonly [string, number]>;
237
+ interpolation?: GradientInterpolation;
238
+ easing?: GradientEasing;
224
239
  } | {
225
240
  type: "plot";
226
241
  expr?: string;
@@ -250,23 +265,30 @@ export type SchemaObject = {
250
265
  type: "image";
251
266
  value?: string;
252
267
  /** Supplied images to pick from, as URLs or `{ value, label }`. The control becomes a row naming the chosen
253
- * 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[];
255
- /** 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). */
268
+ * image that opens a pane of tiles with the upload below them, and the param is the chosen URL or the upload's
269
+ * data URL. */
270
+ options?: ReadonlyArray<string | {
271
+ value: string;
272
+ label?: string;
273
+ }>;
274
+ /** Tiles per row in the gallery, 1 to 6 (by default one per option for two or three, 2 for one or four, then
275
+ * 3). */
256
276
  cols?: number;
257
277
  /** The largest upload taken, in bytes (10 MB unless set); a bigger file is refused with a note. */
258
278
  maxSize?: number;
259
279
  /** An upload longer than this on its long side, in px, is drawn down to it before it becomes the param, and one
260
- * heavier than 1.5 MB is redrawn at its size, so a phone photo can't fill storage (2560 unless set; `false` takes
261
- * uploads as they are). A JPEG comes out as a JPEG at
262
- * `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
+ * heavier than 1.5 MB is redrawn at its size, so a phone photo can't fill storage (2560 unless set; `false`
281
+ * takes uploads as they are). A JPEG comes out as a JPEG at
282
+ * `quality`; anything else as a PNG when that is 1.5 MB or less (a screenshot, not a photo), else as a JPEG, or
283
+ * a WebP when it has transparency. A GIF, an SVG, an animated WebP or APNG, a light picture within `fit`, or one
284
+ * the redraw wouldn't make smaller is kept as it is. */
264
285
  fit?: number | false;
265
286
  /** The JPEG or WebP quality of a shrunk upload, 0 to 1 (0.85 unless set). */
266
287
  quality?: number;
267
288
  /** `false` leaves the upload button out of a gallery: the options are the whole choice. */
268
289
  upload?: boolean;
269
- /** `false` leaves Clear out of a gallery, and Delete no longer empties it: for a choice that must keep a value. */
290
+ /** `false` leaves Clear out of a gallery, and Delete no longer empties it: for a choice that must keep a
291
+ * value. */
270
292
  clear?: boolean;
271
293
  } | {
272
294
  type: "button";
@@ -274,7 +296,7 @@ export type SchemaObject = {
274
296
  label?: string;
275
297
  } | {
276
298
  type: "buttongroup";
277
- buttons: Record<string, () => void> | Array<{
299
+ buttons: Record<string, () => void> | ReadonlyArray<{
278
300
  label: string;
279
301
  action: () => void;
280
302
  }>;
@@ -282,63 +304,216 @@ export type SchemaObject = {
282
304
  type: "separator";
283
305
  } | {
284
306
  type: "tabs";
285
- pages: Record<string, Schema>;
307
+ pages: {
308
+ readonly [title: string]: Schema;
309
+ };
286
310
  };
287
311
  /** 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?)`. */
312
+ export type SchemaValue = SchemaValueOf<SchemaNest>;
313
+ 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[] | {
314
+ options: readonly Option[];
315
+ value?: string | number;
316
+ } | {
317
+ action: () => void;
318
+ label?: string;
319
+ } | (SchemaObject & ControlOptions) | F | SchemaFolder;
320
+ /** The object you hand to `tweaks(name, schema, opts?)`. No value is a function, so a verbose
321
+ * form's callbacks (`format`, `fn`) take their parameter types from that form alone. */
290
322
  export interface Schema {
291
- [key: string]: SchemaValue;
323
+ readonly [key: string]: SchemaValue;
324
+ }
325
+ type SchemaNest = Schema & {
326
+ readonly type?: SchemaObject["type"] | Exclude<SchemaValue, string>;
327
+ };
328
+ /** What tweaks() and mountControl() infer from: `Schema` with a folder's `type` left open, since a schema held in a
329
+ * variable widens a verbose form's `type` to `string`; their own check then rules on the names it can still see. */
330
+ export interface HeldSchema {
331
+ readonly [key: string]: HeldValue;
292
332
  }
333
+ /** A single value as mountControl() infers it; see `HeldSchema`. */
334
+ export type HeldValue = SchemaValueOf<HeldSchema>;
335
+ /** A nested folder that carries its own `render` / `disabled` predicate or `hint`: chrome, not
336
+ * children. Its index admits the predicate, so it gives up a child named `type`: that sets it
337
+ * apart from a verbose form, whose callbacks would otherwise be typed against the predicate too. */
338
+ export interface SchemaFolder {
339
+ readonly type?: undefined;
340
+ readonly render?: (get: Get) => boolean;
341
+ readonly disabled?: boolean | ((get: Get) => boolean);
342
+ readonly hint?: string;
343
+ readonly [key: string]: SchemaValue | ((get: Get) => boolean) | undefined;
344
+ }
345
+ /** A spring control's value: the physics, plus the time pair and mode when tuned by feel. */
346
+ export interface SpringValue {
347
+ stiffness: number;
348
+ damping: number;
349
+ mass: number;
350
+ visualDuration?: number;
351
+ bounce?: number;
352
+ mode?: "time" | "physics";
353
+ }
354
+ type OptionValue<O> = O extends string | number ? O : O extends {
355
+ readonly value: infer V extends string | number;
356
+ } ? V : never;
357
+ type ChoiceValue<V> = (V extends {
358
+ readonly options: readonly (infer O)[];
359
+ } ? OptionValue<O> : never) | (V extends {
360
+ readonly value: infer D extends string | number;
361
+ } ? D : never);
362
+ 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";
363
+ 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;
364
+ type TrimDash<S extends string> = S extends `-${infer R}` ? TrimDash<R> : S extends `${infer R}-` ? TrimDash<R> : S;
365
+ /** A tab title's key under `params`: lower-case, each run of other characters a `-`, trimmed
366
+ * (`"Layout & Grid"` → `"layout-grid"`). Two titles that slug alike are told apart at runtime only. */
367
+ export type TabKey<T extends string> = string extends T ? string : TrimDash<Dashed<Lowercase<T>>> extends infer K extends string ? K extends "" ? "tab" : K : never;
368
+ type TabClash<P> = true extends {
369
+ [T in keyof P & string]: TabKey<T> extends {
370
+ [U in Exclude<keyof P & string, T>]: TabKey<U>;
371
+ }[Exclude<keyof P & string, T>] ? true : false;
372
+ }[keyof P & string] ? true : false;
373
+ type Valueless = "button" | "buttongroup" | "separator" | "fpsgraph" | "monitor";
374
+ type Hinge = {
375
+ list: "options" | "value";
376
+ radiogrid: "options" | "value";
377
+ segmented: "options" | "value";
378
+ point: "components" | "value";
379
+ tabs: "pages" | "value";
380
+ };
381
+ type TypedValue<V> = V extends {
382
+ readonly type: "slider" | "number";
383
+ } ? number : V extends {
384
+ readonly type: "checkbox";
385
+ } ? boolean : V extends {
386
+ readonly type: "list" | "radiogrid" | "segmented";
387
+ } ? V extends {
388
+ readonly options: readonly unknown[];
389
+ } ? ChoiceValue<V> : never : V extends {
390
+ readonly type: "color" | "text" | "image" | "plot";
391
+ } ? string : V extends {
392
+ readonly type: "interval";
393
+ } ? [number, number] : V extends {
394
+ readonly type: "cubicbezier";
395
+ } ? [number, number, number, number] : V extends {
396
+ readonly type: "spring";
397
+ } ? SpringValue : V extends {
398
+ readonly type: "motion";
399
+ } ? MotionValue : V extends {
400
+ readonly type: "shadow";
401
+ } ? ShadowValue : V extends {
402
+ readonly type: "sides";
403
+ } ? SidesValue : V extends {
404
+ readonly type: "gradient";
405
+ } ? GradientValue : V extends {
406
+ readonly type: "point";
407
+ } ? V extends {
408
+ readonly components: readonly [];
409
+ } ? never : V extends {
410
+ readonly components: readonly (infer C)[];
411
+ } ? {
412
+ [K in C extends {
413
+ readonly key: infer Key extends string;
414
+ } ? Key : never]: number;
415
+ } : V extends {
416
+ readonly value: infer P extends object;
417
+ } ? keyof P extends never ? never : {
418
+ -readonly [K in keyof P & string]: number;
419
+ } : never : V extends {
420
+ readonly type: "tabs";
421
+ } ? V extends {
422
+ readonly pages: infer P extends object;
423
+ } ? keyof P extends never ? never : TabClash<P> extends true ? Record<string, Params> : {
424
+ [T in keyof P & string as TabKey<T>]: ParamsOf<P[T]>;
425
+ } : never : unknown;
426
+ /** The value a schema entry puts on `params`. */
427
+ export type ValueOf<V> = V extends {
428
+ readonly type: infer T;
429
+ } ? 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> : T extends string ? [Extract<"value" | "options" | "action", keyof V>] extends [never] ? ShorthandValue<V> : never : ShorthandValue<V> : ShorthandValue<V>;
430
+ type VerboseValue<V, T> = T extends Valueless ? never : TypedValue<V>;
431
+ 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 {
432
+ readonly action: (...a: any[]) => any;
433
+ } ? never : V extends {
434
+ readonly options: readonly unknown[];
435
+ } ? ChoiceValue<V> : V extends object ? Entries<V, true> : unknown;
436
+ type Reserved = "__proto__" | "constructor" | "prototype";
437
+ type FolderChrome<V, K> = K extends "disabled" | "hint" ? true : K extends "render" ? V extends (...a: any[]) => any ? true : false : false;
438
+ type Entries<S, Nested extends boolean> = string extends keyof S ? Params : {
439
+ -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]>;
440
+ };
441
+ /** The live values a schema builds, nested like the schema; a plain `Schema` gives the loose `Params`. */
442
+ export type ParamsOf<S> = Entries<S, false>;
443
+ type CheckValue<V> = V extends {
444
+ readonly type: infer T;
445
+ } ? string extends T ? V : T extends SchemaObject["type"] ? V extends {
446
+ readonly type: "tabs";
447
+ readonly pages: infer P;
448
+ } ? {
449
+ readonly pages: {
450
+ [K in keyof P]: KnownTypes<P[K]>;
451
+ };
452
+ } : T extends keyof Hinge ? [Extract<Hinge[T], keyof V>] extends [never] ? unknown : FitsForm<V, T> : FitsForm<V, T> : {
453
+ readonly type: NotAControlType;
454
+ } : V extends readonly unknown[] | ((...a: any[]) => any) ? unknown : V extends object ? KnownTypes<V> : unknown;
455
+ type FormOf<O, T> = O extends {
456
+ readonly type: infer N;
457
+ } ? (T extends N ? O & ControlOptions : never) : never;
458
+ type FitsForm<V, T> = V extends FormOf<SchemaObject, T> ? unknown : FormOf<SchemaObject, T>;
459
+ type NotAControlType = {
460
+ readonly "names no control: see the type list in AGENTS.md": true;
461
+ };
462
+ /** A schema whose every `type` names a control (`tweaks()` checks it; `{ type: "slidr" }` is an error). */
463
+ export type KnownTypes<S> = {
464
+ [K in keyof S]: CheckValue<S[K]>;
465
+ };
466
+ /** One schema value whose `type` names a control (`mountControl()` checks it). */
467
+ export type KnownType<V> = CheckValue<V>;
293
468
  /** A theme — token overrides applied over the default monochrome look. Every key is
294
469
  * 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
470
+ * Keys are the `--twk-*` custom properties themselves. Apply at build via
296
471
  * `tweaks(name, schema, { theme })`, or live via `panel.setTheme(theme)`. The kit's whole
297
472
  * appearance runs on these custom properties — this is the public theming surface. */
298
473
  export interface Theme {
299
474
  /** 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;
475
+ "--twk-accent"?: string;
476
+ /** Text drawn on the accent (active segment / radio label); the primary text tone unless set, so set it with a light
477
+ * accent. */
478
+ "--twk-on-accent"?: string;
303
479
  /** Panel background, reused for recessed wells. */
304
- base?: string;
480
+ "--twk-base"?: string;
305
481
  /** Popover / dropdown background. */
306
- dropdownBg?: string;
482
+ "--twk-dropdown-bg"?: string;
307
483
  /** Control surface and its hover / active steps. */
308
- surface?: string;
309
- surfaceHover?: string;
310
- surfaceActive?: string;
484
+ "--twk-surface"?: string;
485
+ "--twk-surface-hover"?: string;
486
+ "--twk-surface-active"?: string;
311
487
  /** Hairline border and its hover step. */
312
- border?: string;
313
- borderHover?: string;
488
+ "--twk-border"?: string;
489
+ "--twk-border-hover"?: string;
314
490
  /** Text-selection highlight wash. */
315
- selection?: string;
316
- /** 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;
491
+ "--twk-selection"?: string;
492
+ /** Text tones — panel title, section heading, primary value text, control label, and the muted / faint / focus
493
+ * tones. */
494
+ "--twk-text-root"?: string;
495
+ "--twk-text-section"?: string;
496
+ "--twk-text-primary"?: string;
497
+ "--twk-text-label"?: string;
498
+ "--twk-text-secondary"?: string;
499
+ "--twk-text-tertiary"?: string;
500
+ "--twk-text-focus"?: string;
501
+ /** Copy-confirmation accent, and the invalid-state accent (the plot's bad-expression cue). */
502
+ "--twk-success"?: string;
503
+ "--twk-danger"?: string;
328
504
  /** Popover shadow, the panel's container elevation, and its lifted (floating / dragging) variant. */
329
- shadow?: string;
330
- shadowPanel?: string;
331
- shadowPanelLifted?: string;
505
+ "--twk-shadow-dropdown"?: string;
506
+ "--twk-shadow-panel"?: string;
507
+ "--twk-shadow-panel-lifted"?: string;
332
508
  /** 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;
509
+ "--twk-font-sans"?: string;
510
+ "--twk-font-mono"?: string;
511
+ /** Control corner radius, row height and the panel's width (256px by default), as CSS lengths. */
512
+ "--twk-radius"?: string;
513
+ "--twk-row-height"?: string;
514
+ "--twk-width"?: string;
515
+ /** Any other token. */
516
+ [token: `--twk-${string}`]: string | undefined;
342
517
  }
343
518
  /** Options for a programmatic write — `set()`, `setMany()`, `fromJSON()`. */
344
519
  export interface SetOptions {
@@ -347,29 +522,194 @@ export interface SetOptions {
347
522
  * controls, persistence and undo still follow it: it's an undo step like any other. */
348
523
  silent?: boolean;
349
524
  }
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. */
525
+ /** Where a floating panel starts: `x` / `y` are its insets in px (16 by default) from the
526
+ * viewport's top-left. The far edge keeps a gap of `y` when the height caps; CSS
527
+ * `--twk-float-gap` overrides it. */
356
528
  export interface FloatingPlacement {
357
529
  x?: number;
358
530
  y?: number;
359
- corner?: FloatingCorner;
360
531
  }
532
+ /** The words a panel shows and says, by key, for the `strings` option (each line notes the English default). A key
533
+ * with a `{placeholder}` takes a string that uses it, or a function given the values, for a language whose plurals
534
+ * need more than one form. Units (px, ms, %), axis letters and color notations stay as they are. */
535
+ export type Word<V = never> = [V] extends [never] ? string : string | ((v: V) => string);
536
+ export interface Strings {
537
+ /** "Tweaks" — the panel's landmark name when its own is blank */ panel?: string;
538
+ /** "Presets" — the toolbar button */ presets?: string;
539
+ /** "Copy values (⇧: changed only)" — the toolbar button */ copy?: string;
540
+ /** "Reset" — the toolbar button */ reset?: string;
541
+ /** "Double-click or hold to reset" — a row label's tooltip */ resetHint?: string;
542
+ /** "Values copied" */ copied?: string;
543
+ /** "{n} changed values copied" — 1 changed value copied for one */ copiedChanged?: Word<{
544
+ n: number;
545
+ }>;
546
+ /** "Nothing has changed" */ nothingChanged?: string;
547
+ /** "Copy failed" */ copyFailed?: string;
548
+ /** "{label} copied" — ⌘C on a row */ rowCopied?: Word<{
549
+ label: string;
550
+ }>;
551
+ /** "Not saved" — a value storage refused */ notSaved?: string;
552
+ /** "{n} values pasted" — 1 value pasted for one */ pasted?: Word<{
553
+ n: number;
554
+ }>;
555
+ /** "Nothing to paste" */ nothingToPaste?: string;
556
+ /** "Couldn't read the clipboard" */ clipboardUnread?: string;
557
+ /** "Preset name…" — the field's placeholder */ presetName?: string;
558
+ /** "New preset name" — the field's label */ newPreset?: string;
559
+ /** "Save" */ save?: string;
560
+ /** "No presets yet" */ noPresets?: string;
561
+ /** "Loaded preset {name}" */ presetLoaded?: Word<{
562
+ name: string;
563
+ }>;
564
+ /** "Saved preset {name}" */ presetSaved?: Word<{
565
+ name: string;
566
+ }>;
567
+ /** "Preset {name} not saved: storage is full" */ presetFull?: Word<{
568
+ name: string;
569
+ }>;
570
+ /** "Delete preset {name}" — a row's delete button */ presetDelete?: Word<{
571
+ name: string;
572
+ }>;
573
+ /** "Off" — a checkbox */ off?: string;
574
+ /** "On" — a checkbox */ on?: string;
575
+ /** "Button" — a group button with no label */ button?: string;
576
+ /** "Tab" — a tab page whose title is blank */ tab?: string;
577
+ /** "Folder" — a markup folder with no data-label */ folder?: string;
578
+ /** "Drag to adjust" — a number field's grip */ dragToAdjust?: string;
579
+ /** "{label} minimum" */ intervalMin?: Word<{
580
+ label: string;
581
+ }>;
582
+ /** "{label} maximum" */ intervalMax?: Word<{
583
+ label: string;
584
+ }>;
585
+ /** "Monitor" — a monitor with no label */ monitor?: string;
586
+ /** "FPS" — an fpsgraph with no label */ fps?: string;
587
+ /** "Plot" — a plot with no label */ plot?: string;
588
+ /** "{label} — expression in x" — a plot's field */ expression?: Word<{
589
+ label: string;
590
+ }>;
591
+ /** "Point" — a point with no label */ point?: string;
592
+ /** "{label} pad: {x} and {y}" */ pointPad?: Word<{
593
+ label: string;
594
+ x: string | number;
595
+ y: string | number;
596
+ }>;
597
+ /** "Control point {n}" — a cubic bézier handle */ controlPoint?: Word<{
598
+ n: number;
599
+ }>;
600
+ /** "Control point {n}, x {x}, y {y}" */ controlPointAt?: Word<{
601
+ n: number;
602
+ x: string | number;
603
+ y: string | number;
604
+ }>;
605
+ /** "Color" — a color row with no label, and the shadow's color row */ color?: string;
606
+ /** "Color plane: chroma across, lightness up" */ colorPlane?: string;
607
+ /** "Color mode" */ colorMode?: string;
608
+ /** "Hex color" */ hex?: string;
609
+ /** "Hue" */ hue?: string;
610
+ /** "Alpha" */ alpha?: string;
611
+ /** "lightness {l}%, chroma {c}" — the color plane's description, said after a run of keys */ colorReadout?: Word<{
612
+ l: number;
613
+ c: number;
614
+ }>;
615
+ /** "wide" — the gamut badge past P3 (sRGB and P3 are names) */ wide?: string;
616
+ /** "Gradient" — a gradient with no label */ gradient?: string;
617
+ /** "{n} stops" — 1 stop for one */ stops?: Word<{
618
+ n: number;
619
+ }>;
620
+ /** "Add a stop after the selected one" — the add button's tooltip */ addStop?: string;
621
+ /** "Add a color stop after the selected one" — the add button's label */ addStopAria?: string;
622
+ /** "Color stop at {pos}%" */ stopAt?: Word<{
623
+ pos: number;
624
+ }>;
625
+ /** "Color stop at {pos}%, selected" */ stopAtSelected?: Word<{
626
+ pos: number;
627
+ }>;
628
+ /** "Easing between stops" — the easing menu's label */ stopEasing?: string;
629
+ /** "How the ramp blends between stops" — its tooltip */ stopEasingHint?: string;
630
+ /** "Linear" */ easeLinear?: string;
631
+ /** "Ease" */ ease?: string;
632
+ /** "Ease in" */ easeIn?: string;
633
+ /** "Ease out" */ easeOut?: string;
634
+ /** "Ease in-out" */ easeInOut?: string;
635
+ /** "Custom" — an easing the menu doesn't list */ custom?: string;
636
+ /** "Upload image" — the gallery's upload button */ upload?: string;
637
+ /** "{label}: upload an image" */ uploadImage?: Word<{
638
+ label: string;
639
+ }>;
640
+ /** "{label}: choose an image" */ chooseImage?: Word<{
641
+ label: string;
642
+ }>;
643
+ /** "Clear" */ clear?: string;
644
+ /** "{label}: clear the image" */ clearImage?: Word<{
645
+ label: string;
646
+ }>;
647
+ /** "Replace" */ replace?: string;
648
+ /** "Drop or choose" */ dropOrChoose?: string;
649
+ /** "None" — no image chosen */ none?: string;
650
+ /** "Image {n}" — a gallery option with no label */ imageN?: Word<{
651
+ n: number;
652
+ }>;
653
+ /** "Uploaded image" */ uploadedImage?: string;
654
+ /** "Custom image" — a value off the gallery's list */ customImage?: string;
655
+ /** "Not an image" */ notAnImage?: string;
656
+ /** "Couldn't read the file" */ unreadable?: string;
657
+ /** "{size}, max {max}" — a file over maxSize */ tooLarge?: Word<{
658
+ size: string;
659
+ max: string;
660
+ }>;
661
+ /** "Motion" — a motion row with no label */ motion?: string;
662
+ /** "Motion type" */ motionType?: string;
663
+ /** "Type" — the motion pane's row label beside that switch */ type?: string;
664
+ /** "Easing" */ easing?: string;
665
+ /** "Spring" */ spring?: string;
666
+ /** "Duration" */ duration?: string;
667
+ /** "Replay the motion" */ replay?: string;
668
+ /** "Easing · {ms} ms" */ easingSummary?: Word<{
669
+ ms: number;
670
+ }>;
671
+ /** "Spring · {ms} ms" */ springSummary?: Word<{
672
+ ms: number;
673
+ }>;
674
+ /** "Spring mode" */ springMode?: string;
675
+ /** "Mode" */ mode?: string;
676
+ /** "Time" */ time?: string;
677
+ /** "Physics" */ physics?: string;
678
+ /** "Stiffness" */ stiffness?: string;
679
+ /** "Damping" */ damping?: string;
680
+ /** "Mass" */ mass?: string;
681
+ /** "Bounce" */ bounce?: string;
682
+ /** "Shadow" — a shadow with no label */ shadow?: string;
683
+ /** "Shadow position" */ shadowPosition?: string;
684
+ /** "in" — an inset shadow's summary, before its numbers */ inset?: string;
685
+ /** "Position" */ position?: string;
686
+ /** "Outer" */ outer?: string;
687
+ /** "Inner" */ inner?: string;
688
+ /** "Blur" */ blur?: string;
689
+ /** "Spread" */ spread?: string;
690
+ /** "Top" */ top?: string;
691
+ /** "Right" */ right?: string;
692
+ /** "Bottom" */ bottom?: string;
693
+ /** "Left" */ left?: string;
694
+ /** "Top-left" */ topLeft?: string;
695
+ /** "Top-right" */ topRight?: string;
696
+ /** "Bottom-right" */ bottomRight?: string;
697
+ /** "Bottom-left" */ bottomLeft?: string;
698
+ /** "Independent sides" */ splitSides?: string;
699
+ /** "Independent corners" */ splitCorners?: string;
700
+ }
701
+ /** Panel options (third arg to `tweaks()`). */
361
702
  export interface TweaksOptions {
362
- /** Theme overrides (friendly names or raw `--tw-*`); `null` / omitted = default monochrome. */
703
+ /** Theme overrides as `--twk-*` tokens; `null` / omitted = default monochrome. */
363
704
  theme?: Theme | null;
364
- /** Persist values to localStorage + enable presets — a key, or `true` to key by name. */
705
+ /** Persist values to localStorage + enable presets — a key, or `true` to key by name. Uploaded images go to
706
+ * IndexedDB under the same key, and come back by `ready`. */
365
707
  persist?: string | boolean;
366
708
  /** With `persist`, `false` keeps saving values but leaves the Presets menu out of the
367
709
  * toolbar (the presets API still works). On by default. */
368
710
  presets?: boolean;
369
- /** Add a filter/search field to the toolbar. */
370
- filter?: boolean;
371
711
  /** 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. */
712
+ * placement `{ x, y }`. Its height caps at the window and it scrolls, header and all. */
373
713
  floating?: boolean | FloatingPlacement;
374
714
  /** Drag the header to reposition the panel — on by default. An inline panel lifts into a
375
715
  * floating layer on first drag; set `false` to pin it in place. */
@@ -380,15 +720,32 @@ export interface TweaksOptions {
380
720
  undo?: boolean;
381
721
  /** Custom reset handler (e.g. restore real app defaults + rebuild). */
382
722
  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;
723
+ /** Mount the panel in a shadow root, so page CSS can't restyle it (`#app button { … }`, a `.prose` wrapper). `el` is
724
+ * then its host, a `<twk-host>`: theme it with `--twk-*` on the host itself (`.twk-host { … }`), or with `theme`.
725
+ * The styles come from the page's `tweakit/css` when that's loaded, else from `href` (by default `tweaks.css` beside
726
+ * the module). */
727
+ shadow?: boolean | {
728
+ href?: string;
729
+ };
730
+ /** The words the panel and its controls show and say, over the English defaults: translate them, or rename the chrome
731
+ * (`{ presets: "Looks" }`). A key left out keeps its default. */
732
+ strings?: Strings;
387
733
  }
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
- };
734
+ /** Options for `enhance()`: the words its markup controls show and say. */
735
+ export interface EnhanceOptions {
736
+ strings?: Strings;
737
+ }
738
+ /** The live values bag, loosely typed — `params[key]` for each control. A schema literal passed
739
+ * to `tweaks()` gets its exact shape instead (`ParamsOf`). */
740
+ export type Params = Record<string, any>;
741
+ /** The keys `set()` and `setMany()` take: a typed panel's controls by dotted path (`size`, `card.size`), so a typo is a
742
+ * compile error; any string for a panel built from a plain `Schema`. An object-valued control's own fields read as
743
+ * paths too (`drop.blur`): the types can't tell its value from a folder, so they allow what the runtime refuses. Past
744
+ * ten folders deep the rest of a path is any string, since spelling every one out overran tsc's depth limit. */
745
+ export type PanelPath<P> = PathsOf<P, []>;
746
+ type PathsOf<P, D extends unknown[]> = string extends keyof P ? string : D["length"] extends 10 ? string : {
747
+ [K in keyof P & string]: K | (P[K] extends readonly unknown[] ? never : P[K] extends object ? `${K}.${PathsOf<P[K], [...D, 0]>}` : never);
748
+ }[keyof P & string];
392
749
  /** The whole-panel state `toJSON()` returns / `fromJSON()` accepts: every control value
393
750
  * (nested to mirror folders/tabs) plus UI state — which folders are collapsed and which
394
751
  * tab is active. JSON-safe; persist it however you like. Both `values` and `ui` are
@@ -410,25 +767,32 @@ export interface PanelState {
410
767
  tabs?: Record<string, string | null>;
411
768
  };
412
769
  }
413
- /** The panel object `tweaks()` returns. */
414
- export interface Panel {
415
- /** The panel root element — append it to the DOM. */
770
+ /** The panel object `tweaks()` returns; `P` is its values' shape. A bare `Panel` takes any panel, typed or not (its
771
+ * default is `any`, not `Params`: `set()`'s typed paths would make a typed panel unassignable to `Panel<Params>`). */
772
+ export interface Panel<P = any> {
773
+ /** The panel root element — append it to the DOM. With `shadow`, the host the panel lives in. */
416
774
  el: HTMLElement;
417
775
  /** 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;
776
+ params: P;
777
+ /** Resolves once any lazily-loaded controls have hydrated and, for a panel that persists an image control, its
778
+ * stored images have loaded (resolves immediately when there's neither). */
779
+ ready: Promise<Panel<P>>;
780
+ /** Subscribe to changes; `last` is the changed control's dotted path (`size`, `card.size`), as `set()` and
781
+ * `changes()`
782
+ * name it, or undefined when many changed at once (a restore, `reset()`, `fromJSON`, `setMany`). */
783
+ on(fn: (params: P, last?: string) => void): () => void;
423
784
  /** 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;
785
+ * ("folder.child", "tabs.page.child"); at runtime a bare key also reaches a nested control
786
+ * when it's unambiguous, though a typed panel's types take the full path. A key matching
787
+ * no control, or a folder or tab, is refused with a console warning. */
788
+ set(key: PanelPath<P>, value: unknown, options?: SetOptions): void;
427
789
  /** Set several controls at once — a flat map of keys (dotted for nested controls), e.g.
428
790
  * `setMany({ "shadow.radius": 28, blur: 48 })`. Resolves each key like `set()`, then fires
429
791
  * listeners + persists ONCE for the whole batch, where N separate `set()` calls would fire
430
792
  * N times. Unknown / ambiguous / reserved keys warn and are skipped. */
431
- setMany(values: Record<string, unknown>, options?: SetOptions): void;
793
+ setMany(values: {
794
+ readonly [K in PanelPath<P>]?: unknown;
795
+ }, options?: SetOptions): void;
432
796
  /** Reset every control to its default (or run `opts.onReset`). */
433
797
  reset(): void;
434
798
  /** Serialize the whole panel — every control value plus UI state (collapsed folders,
@@ -454,7 +818,8 @@ export interface Panel {
454
818
  * with stays its storage key (`persist: true`), so saved values and presets keep their
455
819
  * home across a rename. */
456
820
  setName(name: string): void;
457
- /** Save the current values as a named preset (needs `opts.persist`). False when there is nowhere to save it, or storage refused it. */
821
+ /** Save the current values as a named preset (needs `opts.persist`). False when there is nowhere to save it, or
822
+ * storage refused it. */
458
823
  savePreset(name: string): boolean;
459
824
  /** Load a named preset. */
460
825
  loadPreset(name: string): boolean;
@@ -465,7 +830,8 @@ export interface Panel {
465
830
  /** Undo / redo (need `opts.undo`). */
466
831
  undo(): void;
467
832
  redo(): void;
468
- /** Tear the panel down — remove it from the DOM, release every global listener, close any open popover; the API goes inert. */
833
+ /** Tear the panel down — remove it from the DOM, release every global listener, close any open popover; the API goes
834
+ * inert. */
469
835
  destroy(): void;
470
836
  }
471
837
  /** A built control's handle (internal-ish, but used across modules). */
@@ -479,6 +845,9 @@ export interface Control {
479
845
  /** Show a short message on the control for a moment. Only the image control supplies it; the panel calls it when
480
846
  * storage refuses the control's value. */
481
847
  note?(message: string): void;
848
+ /** The hard range set() fits a number into, as the control resolved it (derived bounds too); a point's per component.
849
+ * Only the numeric controls supply it; the panel's clamp notice reads it. */
850
+ range?: [number, number] | Record<string, [number, number]>;
482
851
  }
483
852
  /** Options for `mountControl(host, value, opts?)` — one control, no panel. */
484
853
  export interface MountOptions {
@@ -489,12 +858,19 @@ export interface MountOptions {
489
858
  label?: string;
490
859
  /** Fired with the new value whenever the control changes — by hand or through `set()`. */
491
860
  onChange?: (value: unknown, key: string) => void;
861
+ /** Mount it in a shadow root, as `tweaks()`'s `shadow` does a panel; `el` is then the host. */
862
+ shadow?: boolean | {
863
+ href?: string;
864
+ };
865
+ /** Its words, as `tweaks()`'s `strings`. */
866
+ strings?: Strings;
492
867
  }
493
868
  /** The handle `mountControl()` returns. */
494
869
  export interface MountedControl {
495
- /** The wrapper (`.tw-control`) the control lives in, already appended to the host and
870
+ /** The wrapper (`.twk-control`) the control lives in, already appended to the host and
496
871
  * stable from creation — it carries the kit's tokens, so the control renders outside a
497
- * panel. On the code-split build a lazily-loaded control fills it in once `ready` resolves. */
872
+ * panel. On the code-split build a lazily-loaded control fills it in once `ready` resolves. With `shadow`, the host
873
+ * the wrapper lives in. */
498
874
  el: HTMLElement;
499
875
  /** Apply a value — the control updates AND `onChange` fires (when the value actually
500
876
  * changed). Before `ready`, the value is queued and applied once the control builds. */
@@ -507,28 +883,34 @@ export interface MountedControl {
507
883
  /** Resolves with this handle once the control has built — immediately on the single-file build. */
508
884
  ready: Promise<MountedControl>;
509
885
  }
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(…)`. */
886
+ /** The notations the color picker edits in (its mode dropdown). The emitted value follows
887
+ * the mode: `hex` gives `#RRGGBB`, the wide-gamut modes give a CSS color like `oklch(…)`. */
512
888
  export type ColorMode = "hex" | "srgb" | "css" | "hsl" | "hwb" | "oklch" | "oklab" | "lch" | "lab" | "p3" | "rec2020";
513
889
  /** Options for `createColorPicker(opts?)` — the picker body alone, no trigger row. */
514
890
  export interface ColorPickerOptions {
515
- /** The colour it opens on — any CSS colour string (default `#7c5cff`). */
891
+ /** The color it opens on — any CSS color string (default `#7c5cff`). */
516
892
  value?: string;
517
893
  /** The notation it opens in (default `oklch`). */
518
894
  mode?: ColorMode;
519
- /** Fired with the new colour string whenever it changes — by hand or through `set()`. */
895
+ /** Fired with the new color string whenever it changes — by hand or through `set()`. */
520
896
  onChange?: (value: string) => void;
897
+ /** Build it in a shadow root, as `tweaks()`'s `shadow` does a panel; `el` is then the host. */
898
+ shadow?: boolean | {
899
+ href?: string;
900
+ };
901
+ /** Its words, as `tweaks()`'s `strings`. */
902
+ strings?: Strings;
521
903
  }
522
904
  /** The handle `createColorPicker()` returns. Bring your own trigger: append `el` to a popover
523
905
  * or a panel of your own; the kit owns the editor surface and the gamut math. */
524
906
  export interface ColorPicker {
525
- /** The picker's root (`.tw-color-picker`), stable from creation; it carries the kit's tokens,
907
+ /** The picker's root (`.twk-color-picker`), stable from creation; it carries the kit's tokens,
526
908
  * so the picker renders anywhere. On the code-split build the editor fills it in once the
527
- * colour chunk has loaded. */
909
+ * color chunk has loaded. With `shadow`, the host the root lives in. */
528
910
  el: HTMLElement;
529
- /** Apply a colour — the picker updates AND `onChange` fires (when the colour actually changed). */
911
+ /** Apply a color — the picker updates AND `onChange` fires (when the color actually changed). */
530
912
  set(value: string): void;
531
- /** The current colour, serialized in the picker's mode. */
913
+ /** The current color, serialized in the picker's mode. */
532
914
  get(): string;
533
915
  /** The current notation. */
534
916
  mode(): ColorMode;
@@ -542,7 +924,7 @@ export interface ColorPicker {
542
924
  /** Resolves with this handle once the editor has built — immediately on the single-file build. */
543
925
  ready: Promise<ColorPicker>;
544
926
  }
545
- /** `detail` of the `tw:change` event a `[data-tw]` host dispatches (bubbling) whenever its
927
+ /** `detail` of the `twk:change` event a `[data-twk]` host dispatches (bubbling) whenever its
546
928
  * control changes — `key` is the host's `data-key` (or its label), `value` the new value,
547
929
  * also written to the host's `data-value`. */
548
930
  export interface TwChangeDetail {
@@ -551,6 +933,7 @@ export interface TwChangeDetail {
551
933
  }
552
934
  declare global {
553
935
  interface HTMLElementEventMap {
554
- "tw:change": CustomEvent<TwChangeDetail>;
936
+ "twk:change": CustomEvent<TwChangeDetail>;
555
937
  }
556
938
  }
939
+ export {};