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.
Files changed (132) hide show
  1. package/AGENTS.md +5 -5
  2. package/README.md +3 -3
  3. package/dist/tweaks/auto.js +1 -1
  4. package/dist/tweaks/auto.js.map +1 -1
  5. package/dist/tweaks/bezier-S2BOXQBM.js +2 -0
  6. package/dist/tweaks/bezier-S2BOXQBM.js.map +6 -0
  7. package/dist/tweaks/chunk-3MZAAMC2.js +2 -0
  8. package/dist/tweaks/chunk-3MZAAMC2.js.map +6 -0
  9. package/dist/tweaks/chunk-ETB35KFV.js +2 -0
  10. package/dist/tweaks/chunk-ETB35KFV.js.map +6 -0
  11. package/dist/tweaks/chunk-YB5GKWZI.js +2 -0
  12. package/dist/tweaks/chunk-YB5GKWZI.js.map +6 -0
  13. package/dist/tweaks/clip-A3VI53BK.js +2 -0
  14. package/dist/tweaks/clip-A3VI53BK.js.map +6 -0
  15. package/dist/tweaks/color-UGYMAF3W.js +2 -0
  16. package/dist/tweaks/core.js +1 -1
  17. package/dist/tweaks/core.js.map +3 -3
  18. package/dist/tweaks/fold-GY47HIQA.js +2 -0
  19. package/dist/tweaks/fold-GY47HIQA.js.map +6 -0
  20. package/dist/tweaks/gradient-BTX25WSH.js +2 -0
  21. package/dist/tweaks/gradient-BTX25WSH.js.map +6 -0
  22. package/dist/tweaks/{heavy-CLD7UAVI.js → heavy-NPFIGHRM.js} +2 -2
  23. package/dist/tweaks/image-HTQQ4WX6.js +4 -0
  24. package/dist/tweaks/image-HTQQ4WX6.js.map +6 -0
  25. package/dist/tweaks/{image-store-WRKY2NPU.js → image-store-QD5SMZXG.js} +1 -1
  26. package/dist/tweaks/image-store-QD5SMZXG.js.map +6 -0
  27. package/dist/tweaks/interval-BHPI4LRN.js +2 -0
  28. package/dist/tweaks/interval-BHPI4LRN.js.map +6 -0
  29. package/dist/tweaks/markup-QM53TQJZ.js +2 -0
  30. package/dist/tweaks/markup-QM53TQJZ.js.map +6 -0
  31. package/dist/tweaks/monitor-722WIYGW.js +3 -0
  32. package/dist/tweaks/monitor-722WIYGW.js.map +6 -0
  33. package/dist/tweaks/motion-HMCZK6HJ.js +2 -0
  34. package/dist/tweaks/motion-HMCZK6HJ.js.map +6 -0
  35. package/dist/tweaks/plot-D3NQ6XNN.js +3 -0
  36. package/dist/tweaks/plot-D3NQ6XNN.js.map +6 -0
  37. package/dist/tweaks/point-XDXKMDXH.js +2 -0
  38. package/dist/tweaks/point-XDXKMDXH.js.map +6 -0
  39. package/dist/tweaks/presets-EUWNSCUW.js +2 -0
  40. package/dist/tweaks/presets-EUWNSCUW.js.map +6 -0
  41. package/dist/tweaks/shadow-AM7VSRNS.js +2 -0
  42. package/dist/tweaks/shadow-AM7VSRNS.js.map +6 -0
  43. package/dist/tweaks/shadow-root-RYUV3AGS.js +3 -0
  44. package/dist/tweaks/shadow-root-RYUV3AGS.js.map +6 -0
  45. package/dist/tweaks/sides-JWIURDA4.js +2 -0
  46. package/dist/tweaks/sides-JWIURDA4.js.map +6 -0
  47. package/dist/tweaks/spring-BF5GBTPM.js +2 -0
  48. package/dist/tweaks/spring-BF5GBTPM.js.map +6 -0
  49. package/dist/tweaks/tabs-PRVUUZXZ.js +2 -0
  50. package/dist/tweaks/tabs-PRVUUZXZ.js.map +6 -0
  51. package/dist/tweaks.css +1 -1
  52. package/dist/tweaks.js +5 -4
  53. package/dist/tweaks.js.map +3 -3
  54. package/dist/types/tweaks/enhance.d.ts +3 -2
  55. package/dist/types/tweaks/types.d.ts +278 -72
  56. package/package.json +5 -2
  57. package/src/tweaks/auto.ts +9 -5
  58. package/src/tweaks/clip.ts +84 -22
  59. package/src/tweaks/controls/basic.ts +463 -139
  60. package/src/tweaks/controls/bezier.ts +152 -47
  61. package/src/tweaks/controls/color.ts +610 -161
  62. package/src/tweaks/controls/gradient.ts +256 -77
  63. package/src/tweaks/controls/image.ts +388 -132
  64. package/src/tweaks/controls/interval.ts +126 -41
  65. package/src/tweaks/controls/monitor.ts +142 -36
  66. package/src/tweaks/controls/motion.ts +231 -56
  67. package/src/tweaks/controls/plot.ts +321 -101
  68. package/src/tweaks/controls/point.ts +134 -45
  69. package/src/tweaks/controls/shadow.ts +103 -30
  70. package/src/tweaks/controls/sides.ts +150 -40
  71. package/src/tweaks/controls/spring.ts +220 -60
  72. package/src/tweaks/controls/tabs.ts +39 -18
  73. package/src/tweaks/core.ts +1 -1
  74. package/src/tweaks/easing.ts +45 -20
  75. package/src/tweaks/enhance.ts +21 -6
  76. package/src/tweaks/feedback.ts +133 -34
  77. package/src/tweaks/fold.ts +22 -10
  78. package/src/tweaks/heavy.ts +461 -137
  79. package/src/tweaks/icons.ts +12 -5
  80. package/src/tweaks/image-store.ts +131 -43
  81. package/src/tweaks/lazy.ts +36 -19
  82. package/src/tweaks/markup.ts +255 -92
  83. package/src/tweaks/panel.ts +1262 -438
  84. package/src/tweaks/presets.ts +85 -0
  85. package/src/tweaks/schema.ts +213 -51
  86. package/src/tweaks/shadow-root.ts +178 -0
  87. package/src/tweaks/shared.ts +802 -190
  88. package/src/tweaks/standalone.ts +120 -30
  89. package/src/tweaks/types.ts +644 -143
  90. package/src/tweaks/undo.ts +104 -0
  91. package/src/tweaks.css +556 -550
  92. package/src/wide-gamut.ts +473 -125
  93. package/dist/tweaks/bezier-JFEZMCLP.js +0 -2
  94. package/dist/tweaks/bezier-JFEZMCLP.js.map +0 -6
  95. package/dist/tweaks/chunk-6UO4DHBN.js +0 -2
  96. package/dist/tweaks/chunk-6UO4DHBN.js.map +0 -6
  97. package/dist/tweaks/chunk-DIQMLBAS.js +0 -2
  98. package/dist/tweaks/chunk-DIQMLBAS.js.map +0 -6
  99. package/dist/tweaks/chunk-XE6OUJBG.js +0 -2
  100. package/dist/tweaks/chunk-XE6OUJBG.js.map +0 -6
  101. package/dist/tweaks/clip-WW7JYO4X.js +0 -2
  102. package/dist/tweaks/clip-WW7JYO4X.js.map +0 -6
  103. package/dist/tweaks/color-YMMFURP6.js +0 -2
  104. package/dist/tweaks/fold-U6YRZLCJ.js +0 -2
  105. package/dist/tweaks/fold-U6YRZLCJ.js.map +0 -6
  106. package/dist/tweaks/gradient-JER7PW4D.js +0 -2
  107. package/dist/tweaks/gradient-JER7PW4D.js.map +0 -6
  108. package/dist/tweaks/image-H6QSLGO3.js +0 -4
  109. package/dist/tweaks/image-H6QSLGO3.js.map +0 -6
  110. package/dist/tweaks/image-store-WRKY2NPU.js.map +0 -6
  111. package/dist/tweaks/interval-TEUHQGZN.js +0 -2
  112. package/dist/tweaks/interval-TEUHQGZN.js.map +0 -6
  113. package/dist/tweaks/markup-NIBJD6E2.js +0 -2
  114. package/dist/tweaks/markup-NIBJD6E2.js.map +0 -6
  115. package/dist/tweaks/monitor-3BY7YCGX.js +0 -3
  116. package/dist/tweaks/monitor-3BY7YCGX.js.map +0 -6
  117. package/dist/tweaks/motion-FQPUBKKM.js +0 -2
  118. package/dist/tweaks/motion-FQPUBKKM.js.map +0 -6
  119. package/dist/tweaks/plot-2HBNBZSG.js +0 -3
  120. package/dist/tweaks/plot-2HBNBZSG.js.map +0 -6
  121. package/dist/tweaks/point-VV6XV3I6.js +0 -2
  122. package/dist/tweaks/point-VV6XV3I6.js.map +0 -6
  123. package/dist/tweaks/shadow-5E4E55I2.js +0 -2
  124. package/dist/tweaks/shadow-5E4E55I2.js.map +0 -6
  125. package/dist/tweaks/sides-P3STEVYY.js +0 -2
  126. package/dist/tweaks/sides-P3STEVYY.js.map +0 -6
  127. package/dist/tweaks/spring-HUVSXNVN.js +0 -2
  128. package/dist/tweaks/spring-HUVSXNVN.js.map +0 -6
  129. package/dist/tweaks/tabs-76FW7LQ7.js +0 -2
  130. package/dist/tweaks/tabs-76FW7LQ7.js.map +0 -6
  131. /package/dist/tweaks/{color-YMMFURP6.js.map → color-UGYMAF3W.js.map} +0 -0
  132. /package/dist/tweaks/{heavy-CLD7UAVI.js.map → heavy-NPFIGHRM.js.map} +0 -0
@@ -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 written. */
6
- export type Option = string | number | { value: string | number; label?: string; color?: string }; // color: any CSS color, shown as a swatch beside a dropdown option (and the chosen one on the row)
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 = "srgb" | "hsl" | "hwb" | "oklch" | "oklab" | "lch" | "lab" | "display-p3" | "rec2020";
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 = number | readonly number[] | string | { top?: number; right?: number; bottom?: number; left?: number };
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 { top: number; right: number; bottom: number; left: number; css: string }
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 { inset?: boolean; x?: number; y?: number; blur?: number; spread?: number; color?: string }
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 { inset: boolean; x: number; y: number; blur: number; spread: number; color: string; css: string }
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 { mode?: "easing" | "spring"; curve?: readonly [number, number, number, number] | string; duration?: number; stiffness?: number; damping?: number; mass?: number; visualDuration?: number; bounce?: number }
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
- | { mode: "spring"; stiffness: number; damping: number; mass: number; visualDuration?: number; bounce?: number; duration: number; easing: string };
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 { stops: GradientStop[]; interpolation?: GradientInterpolation; easing?: GradientEasing }
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
- | { type: "slider"; value?: number; min?: number; max?: number; step?: number; soft?: boolean; unit?: string;
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
- | { type: "spring"; mode?: "time" | "physics"; visualDuration?: number; bounce?: number; stiffness?: number; damping?: number; mass?: number; value?: { mode?: "time" | "physics"; visualDuration?: number; bounce?: number; stiffness?: number; damping?: number; mass?: number } }
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
- | { type: "sides"; value?: SidesInput; corners?: boolean; unit?: string; step?: number; min?: number; max?: number; top?: number; right?: number; bottom?: number; left?: number }
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
- | { type: "point"; components: ReadonlyArray<{ key: string; label?: string; value?: number; min?: number; max?: number; step?: number }>; value?: { readonly [key: string]: number }; pad?: boolean; invertY?: boolean }
100
- | { type: "point"; value: { readonly [key: string]: number }; components?: undefined; pad?: boolean; invertY?: boolean } // the value names its components
101
- | { type: "gradient"; value?: GradientValue | ReadonlyArray<GradientStop | readonly [string, number]>; stops?: ReadonlyArray<GradientStop | readonly [string, number]>; interpolation?: GradientInterpolation; easing?: GradientEasing }
102
- | { type: "plot"; expr?: string; fn?: (x: number) => number; xMin?: number; xMax?: number; yMin?: number; yMax?: number; samples?: number; editable?: boolean; min?: number; max?: number }
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
- | { type: "monitor"; get?: () => number | string; value?: number | string; graph?: boolean; view?: "graph" | "text"; min?: number; max?: number; interval?: number; rows?: number; decimals?: number }
105
- | { type: "image"; value?: string;
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 data URL. */
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 3). */
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` takes
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 a WebP
117
- * 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. */
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 value. */
124
- clear?: boolean }
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 (HeldSchema).
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
- | number // bare number → slider (0–1 for a value ≤ 1, else 0–3×value; a negative spans that either side of zero)
135
- | boolean // → checkbox
136
- | string // → text input; a hex or color-function string → color picker
137
- | readonly [number] | readonly [number, number] | readonly [number, number, number] | readonly [number, number, number, number] // [value, min?, max?, step?] → slider
138
- | readonly [readonly [number, number], number, number] | readonly [readonly [number, number], number, number, number] // [[lo,hi], min, max, step?] → interval
139
- | readonly number[] | readonly (number | readonly number[])[] // either of the above, widened by a schema held in a variable
140
- | readonly Option[] // a list of options → list (dropdown)
141
- | { options: readonly Option[]; value?: string | number } // a list with a chosen default
142
- | { action: () => void; label?: string } // → button
143
- | (SchemaObject & ControlOptions) // verbose form (+ render/disabled/hint)
144
- | F // nested object → folder
145
- | SchemaFolder; // a nested folder with its own render / disabled predicate
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 { readonly [key: string]: SchemaValue; }
150
- // A folder's string `type` names a control (a known name with none of its fields is still a folder), so a name no control
151
- // has can't pass as a folder: `const s: Schema = { a: { type: "slidr", value: 1 } }` compiled that way.
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 { readonly [key: string]: HeldValue; }
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 { stiffness: number; damping: number; mass: number; visualDuration?: number; bounce?: number; mode?: "time" | "physics" }
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 ? O : O extends { readonly value: infer V extends string | number } ? V : never;
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> = (V extends { readonly options: readonly (infer O)[] } ? OptionValue<O> : never) | (V extends { readonly value: infer D extends string | number } ? D : never);
179
- 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";
180
- type Dashed<S extends string, Acc extends string = "", Run = false> =
181
- 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;
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 ? string : TrimDash<Dashed<Lowercase<T>>> extends infer K extends string ? (K extends "" ? "tab" : K) : never;
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 { [T in keyof P & string]: TabKey<T> extends { [U in Exclude<keyof P & string, T>]: TabKey<U> }[Exclude<keyof P & string, T>] ? true : false }[keyof P & string] ? true : false;
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): without it the object isn't
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 = { list: "options"; radiogrid: "options"; segmented: "options"; point: "components" | "value"; tabs: "pages" };
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
- V extends { readonly type: "slider" | "number" } ? number
197
- : V extends { readonly type: "checkbox" } ? boolean
198
- : V extends { readonly type: "list" | "radiogrid" | "segmented" } ? (V extends { readonly options: readonly unknown[] } ? ChoiceValue<V> : never)
199
- : V extends { readonly type: "color" | "text" | "image" | "plot" } ? string
200
- : V extends { readonly type: "interval" } ? [number, number]
201
- : V extends { readonly type: "cubicbezier" } ? [number, number, number, number]
202
- : V extends { readonly type: "spring" } ? SpringValue
203
- : V extends { readonly type: "motion" } ? MotionValue
204
- : V extends { readonly type: "shadow" } ? ShadowValue
205
- : V extends { readonly type: "sides" } ? SidesValue
206
- : V extends { readonly type: "gradient" } ? GradientValue
207
- : V extends { readonly type: "point" } ? (V extends { readonly components: readonly [] } ? never : V extends { readonly components: readonly (infer C)[] } ? { [K in C extends { readonly key: infer Key extends string } ? Key : never]: number }
208
- : V extends { readonly value: infer P extends object } ? (keyof P extends never ? never : { -readonly [K in keyof P & string]: number }) : never)
209
- : V extends { readonly type: "tabs" } ? (V extends { readonly pages: infer P extends object } ? (keyof P extends never ? never : TabClash<P> extends true ? Record<string, Params> : { [T in keyof P & string as TabKey<T>]: ParamsOf<P[T]> }) : never)
210
- : unknown;
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
- V extends { readonly type: infer T } ? (
214
- string extends T ? any // a `type` widened by a schema held in a variable: the control can't be known
215
- : T extends SchemaObject["type"] ? (T extends keyof Hinge ? ([Extract<Hinge[T], keyof V>] extends [never] ? ShorthandValue<V> : VerboseValue<V, T>) : VerboseValue<V, T>)
216
- : ShorthandValue<V>)
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
- V extends readonly [readonly [number, number], ...unknown[]] ? [number, number]
221
- : V extends readonly [number] | readonly [number, number] | readonly [number, number, number] | readonly [number, number, number, number] ? number
222
- : V extends readonly [number, ...number[]] ? V[number] // five numbers or more are a list of them
223
- : V extends readonly [] ? undefined // an empty list, with no choice to hold
224
- : V extends readonly number[] ? number // a tuple widened by a schema held in a variable: a slider, or a list of numbers
225
- : V extends readonly (number | readonly number[])[] ? [number, number]
226
- : V extends number ? number
227
- : V extends boolean ? boolean
228
- : V extends string ? string
229
- : V extends readonly (infer O)[] ? OptionValue<O>
230
- : V extends { readonly action: (...a: any[]) => any } ? never
231
- : V extends { readonly options: readonly unknown[] } ? ChoiceValue<V>
232
- : V extends object ? Entries<V, true>
233
- : unknown;
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" ? true : K extends "render" ? (V extends (...a: any[]) => any ? true : false) : false;
239
- type Entries<S, Nested extends boolean> = string extends keyof S ? Params
240
- : { -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]> };
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 mountControl()
246
- // intersect their argument with these, which keep a known name, a widened `string` (a schema held in a variable: the
247
- // runtime decides), and every shorthand as they are, and turn an unknown name into the list of known ones, so it fails
248
- // right where it's written. A known name without its hinge field is a folder at runtime and isn't checked further; with it,
249
- // or for a name with no hinge, the runtime builds that control, so its fields must fit the form: `{ type: "image",
250
- // options: [1] }` read as a folder of a text and a slider otherwise, and compiled.
251
- type CheckValue<V> =
252
- V extends { readonly type: infer T } ? (string extends T ? V : T extends SchemaObject["type"] ? (V extends { readonly type: "tabs"; readonly pages: infer P } ? { readonly pages: { [K in keyof P]: KnownTypes<P[K]> } }
253
- : T extends keyof Hinge ? ([Extract<Hinge[T], keyof V>] extends [never] ? unknown : FitsForm<V, T>) : FitsForm<V, T>) : { readonly type: NotAControlType })
254
- : V extends readonly unknown[] | ((...a: any[]) => any) ? unknown
255
- : V extends object ? KnownTypes<V>
256
- : unknown;
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 error
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 `--tw-*` custom properties themselves. Apply at build via
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
- "--tw-accent"?: string;
276
- /** Text drawn on the accent (active segment / radio label); the primary text tone unless set, so set it with a light accent. */
277
- "--tw-on-accent"?: string;
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
- "--tw-base"?: string;
593
+ "--twk-base"?: string;
280
594
  /** Popover / dropdown background. */
281
- "--tw-dropdown-bg"?: string;
595
+ "--twk-dropdown-bg"?: string;
282
596
  /** Control surface and its hover / active steps. */
283
- "--tw-surface"?: string; "--tw-surface-hover"?: string; "--tw-surface-active"?: string;
597
+ "--twk-surface"?: string;
598
+ "--twk-surface-hover"?: string;
599
+ "--twk-surface-active"?: string;
284
600
  /** Hairline border and its hover step. */
285
- "--tw-border"?: string; "--tw-border-hover"?: string;
601
+ "--twk-border"?: string;
602
+ "--twk-border-hover"?: string;
286
603
  /** Text-selection highlight wash. */
287
- "--tw-selection"?: string;
288
- /** Text tones — panel title, section heading, primary value text, control label, and the muted / faint / focus tones. */
289
- "--tw-text-root"?: string; "--tw-text-section"?: string; "--tw-text-primary"?: string; "--tw-text-label"?: string;
290
- "--tw-text-secondary"?: string; "--tw-text-tertiary"?: string; "--tw-text-focus"?: string;
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
- "--tw-success"?: string; "--tw-danger"?: string;
615
+ "--twk-success"?: string;
616
+ "--twk-danger"?: string;
293
617
  /** Popover shadow, the panel's container elevation, and its lifted (floating / dragging) variant. */
294
- "--tw-shadow-dropdown"?: string; "--tw-shadow-panel"?: string; "--tw-shadow-panel-lifted"?: string;
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
- "--tw-font-sans"?: string; "--tw-font-mono"?: string;
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
- "--tw-radius"?: string; "--tw-row-height"?: string; "--tw-width"?: string;
625
+ "--twk-radius"?: string;
626
+ "--twk-row-height"?: string;
627
+ "--twk-width"?: string;
299
628
  /** Any other token. */
300
- [token: `--${string}`]: string | undefined;
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
- * `--tw-float-gap` overrides it. */
314
- export interface FloatingPlacement { x?: number; y?: number }
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 `--tw-*` tokens; `null` / omitted = default monochrome. */
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 ten
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 ? string : D["length"] extends 10 ? string
349
- : { [K in keyof P & string]: K | (P[K] extends readonly unknown[] ? never : P[K] extends object ? `${K}.${PathsOf<P[K], [...D, 0]>}` : never) }[keyof P & string];
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 `changes()`
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 storage refused it. */
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 inert. */
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 (`.tw-control`) the control lives in, already appended to the host and
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 (`.tw-color-picker`), stable from creation; it carries the kit's tokens,
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 `tw:change` event a `[data-tw]` host dispatches (bubbling) whenever its
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 { key: string; value: unknown; }
1018
+ export interface TwChangeDetail {
1019
+ key: string;
1020
+ value: unknown;
1021
+ }
523
1022
 
524
1023
  declare global {
525
- interface HTMLElementEventMap { "tw:change": CustomEvent<TwChangeDetail>; }
1024
+ interface HTMLElementEventMap {
1025
+ "twk:change": CustomEvent<TwChangeDetail>;
1026
+ }
526
1027
  }