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
@@ -0,0 +1,204 @@
1
+ // ── Cubic bezier — interactive easing-curve editor. Lazy.
2
+ import { el, btn, txt, clamp, onReady, onLive, registerControl, words } from "../shared.js";
3
+ import { dragGesture, numField, svgEl, arrowStep } from "../heavy.js";
4
+ import { parseEasing } from "../easing.js";
5
+ import type { OnChange, Built } from "../shared.js";
6
+ import type { Meta } from "../schema.js";
7
+ import type { Control } from "../types.js";
8
+
9
+ function createBezier(meta: Meta, onChange: OnChange): Control {
10
+ const tr = words();
11
+ const DEF = [0.25, 0.1, 0.25, 1];
12
+ // x must stay 0–1 for a valid cubic-bezier(); y may be anything in CSS, and −1–2 holds the overshoot curves people
13
+ // reach for (easeOutBack's 1.56, easeInOutBack's −0.6 and 1.6), which a −0.25–1.25 clamp flattened without a word.
14
+ const YMIN = -1,
15
+ YMAX = 2,
16
+ PAD = 8;
17
+ // Fit on build as well as in set(), so an authored [1.5, 0, -0.2, 1] can't reach get() as an invalid cubic-bezier().
18
+ const fit = (m: number[]) => m.map((n, i) => clamp(n, i % 2 ? YMIN : 0, i % 2 ? YMAX : 1));
19
+ // The drawn y range: −0.25–1.25, grown to fit the handles. A drag holds it still, so the curve doesn't slide under
20
+ // the pointer, and the graph fits again when the drag ends.
21
+ let view = [-0.25, 1.25],
22
+ held = false;
23
+ const fitView = () => {
24
+ if (!held) view = [Math.min(-0.25, v[1] - 0.1, v[3] - 0.1), Math.max(1.25, v[1] + 0.1, v[3] + 0.1)];
25
+ };
26
+ // Four numbers, or what CSS writes: a keyword (`ease-in-out`) or `cubic-bezier(…)`. Anything else opens on `ease`.
27
+ // parseEasing reads linear as null
28
+ const read = (x: unknown) =>
29
+ Array.isArray(x) && x.length === 4 ? x.map(Number) : x === "linear" ? [0, 0, 1, 1] : parseEasing(x)?.slice();
30
+ let v: number[] = read(meta.value) || DEF.slice();
31
+ if (v.some((n) => !Number.isFinite(n))) v = DEF.slice(); // a non-finite init would show NaN and a blank curve
32
+ v = fit(v);
33
+ const def = v.slice(); // fitted default; Home on a handle returns to it
34
+
35
+ const root = el("div", "twk-bezier");
36
+ if (meta.label) root.append(txt("div", "twk-bezier-label", meta.label)); // a caption; an explicit "" skips it
37
+ const graph = el("div", "twk-bezier-graph");
38
+ const svg = svgEl("svg", "twk-bezier-svg");
39
+ svg.setAttribute("preserveAspectRatio", "none");
40
+ const unitBot = svgEl("line", "twk-bezier-guide"),
41
+ unitTop = svgEl("line", "twk-bezier-guide");
42
+ const diag = svgEl("line", "twk-bezier-diag"),
43
+ line1 = svgEl("line", "twk-bezier-ctl"),
44
+ line2 = svgEl("line", "twk-bezier-ctl");
45
+ const curve = svgEl("path", "twk-bezier-curve");
46
+ svg.append(unitBot, unitTop, diag, line1, line2, curve);
47
+ const h1 = btn("twk-bezier-handle");
48
+ h1.setAttribute("aria-label", tr("controlPoint", "Control point {n}", { n: 1 }));
49
+ const h2 = btn("twk-bezier-handle");
50
+ h2.setAttribute("aria-label", tr("controlPoint", "Control point {n}", { n: 2 }));
51
+ graph.append(svg, h1, h2);
52
+ const fields = el("div", "twk-fields twk-bezier-fields");
53
+ root.append(graph, fields);
54
+
55
+ let W = 0,
56
+ H = 0;
57
+ const xPx = (x: number) => PAD + x * (W - 2 * PAD);
58
+ const yPx = (y: number) => H - PAD - ((y - view[0]) / (view[1] - view[0])) * (H - 2 * PAD);
59
+ const setLine = (ln: Built<SVGLineElement>, ax: number, ay: number, bx: number, by: number) => {
60
+ ln.setAttribute("x1", ax);
61
+ ln.setAttribute("y1", ay);
62
+ ln.setAttribute("x2", bx);
63
+ ln.setAttribute("y2", by);
64
+ };
65
+ // The curve and handles only; the fields update separately, so a field edit doesn't re-set itself.
66
+ const drawGraph = () => {
67
+ // No ARIA role fits a 2D point, so the labels carry the coordinates; set before the size guard for hidden tab
68
+ // pages.
69
+ h1.setAttribute("aria-label", tr("controlPointAt", "Control point {n}, x {x}, y {y}", { n: 1, x: v[0], y: v[1] }));
70
+ h2.setAttribute("aria-label", tr("controlPointAt", "Control point {n}, x {x}, y {y}", { n: 2, x: v[2], y: v[3] }));
71
+ // Layout px, not getBoundingClientRect: under an ancestor CSS scale the rect sat the handles off the curve.
72
+ W = graph.offsetWidth;
73
+ H = graph.offsetHeight;
74
+ if (W < 2) return;
75
+ fitView();
76
+ svg.setAttribute("viewBox", `0 0 ${W} ${H}`);
77
+ const [x1, y1, x2, y2] = v;
78
+ const ax = xPx(0),
79
+ ay = yPx(0),
80
+ bx = xPx(1),
81
+ by = yPx(1),
82
+ p1x = xPx(x1),
83
+ p1y = yPx(y1),
84
+ p2x = xPx(x2),
85
+ p2y = yPx(y2);
86
+ setLine(unitBot, PAD, ay, W - PAD, ay);
87
+ setLine(unitTop, PAD, by, W - PAD, by);
88
+ setLine(diag, ax, ay, bx, by);
89
+ setLine(line1, ax, ay, p1x, p1y);
90
+ setLine(line2, bx, by, p2x, p2y);
91
+ curve.setAttribute("d", `M${ax},${ay} C${p1x},${p1y} ${p2x},${p2y} ${bx},${by}`);
92
+ h1.style.left = p1x + "px";
93
+ h1.style.top = p1y + "px";
94
+ h2.style.left = p2x + "px";
95
+ h2.style.top = p2y + "px";
96
+ };
97
+
98
+ const SPECS = [
99
+ { label: "X1", i: 0, lo: 0, hi: 1 },
100
+ { label: "Y1", i: 1, lo: YMIN, hi: YMAX },
101
+ { label: "X2", i: 2, lo: 0, hi: 1 },
102
+ { label: "Y2", i: 3, lo: YMIN, hi: YMAX },
103
+ ];
104
+ const flds = SPECS.map((sp) => {
105
+ const fld = numField({ label: sp.label, value: v[sp.i], step: 0.01, min: sp.lo, max: sp.hi }, (val) => {
106
+ v[sp.i] = val;
107
+ drawGraph();
108
+ onChange(v.slice());
109
+ });
110
+ fields.append(fld.el);
111
+ return fld;
112
+ });
113
+ const syncFields = () => flds.forEach((fld, k) => fld.set(v[SPECS[k].i]));
114
+
115
+ const drag = (handle: HTMLButtonElement, idx: number) => {
116
+ let rect: DOMRect | null = null,
117
+ gw = 0,
118
+ gh = 0,
119
+ scale = 1;
120
+ dragGesture(handle, {
121
+ // Divide out any ancestor CSS transform (rect is visual px, offsetWidth layout px); preventDefault killed
122
+ // click-to-focus.
123
+ onDown: (e) => {
124
+ e.preventDefault();
125
+ rect = graph.getBoundingClientRect();
126
+ gw = graph.offsetWidth || rect.width;
127
+ gh = graph.offsetHeight || rect.height;
128
+ scale = rect.width / gw;
129
+ handle.classList.add("is-dragging");
130
+ handle.focus({ focusVisible: false });
131
+ held = true;
132
+ },
133
+ onMove: (e) => {
134
+ const gx = (e.clientX - rect.left) / scale,
135
+ gy = (e.clientY - rect.top) / scale;
136
+ const x = clamp((gx - PAD) / (gw - 2 * PAD), 0, 1);
137
+ const y = clamp(view[0] + ((gh - PAD - gy) / (gh - 2 * PAD)) * (view[1] - view[0]), YMIN, YMAX);
138
+ v[idx * 2] = +x.toFixed(2);
139
+ v[idx * 2 + 1] = +y.toFixed(2);
140
+ drawGraph();
141
+ syncFields();
142
+ onChange(v.slice());
143
+ },
144
+ onEnd: () => {
145
+ rect = null;
146
+ handle.classList.remove("is-dragging");
147
+ held = false;
148
+ drawGraph();
149
+ },
150
+ });
151
+ // Keyboard: arrows nudge by 0.01 (⇧ = 0.1) on the drag's clamps and grid, Home restores the default, Escape drops
152
+ // focus.
153
+ handle.addEventListener("keydown", (e) => {
154
+ if (e.key === "Escape") return handle.blur();
155
+ if (e.key === "Home") {
156
+ e.preventDefault();
157
+ v = def.slice();
158
+ drawGraph();
159
+ syncFields();
160
+ onChange(v.slice());
161
+ return;
162
+ }
163
+ const [dx, dy] = arrowStep(e);
164
+ if (!dx && !dy) return;
165
+ e.preventDefault();
166
+ const k = e.shiftKey ? 0.1 : 0.01,
167
+ i = idx * 2;
168
+ if (dx) v[i] = +clamp(v[i] + dx * k, 0, 1).toFixed(2);
169
+ if (dy) v[i + 1] = +clamp(v[i + 1] + dy * k, YMIN, YMAX).toFixed(2);
170
+ drawGraph();
171
+ syncFields();
172
+ onChange(v.slice());
173
+ });
174
+ };
175
+ drag(h1, 0);
176
+ drag(h2, 1);
177
+
178
+ onReady(drawGraph);
179
+ // twk-reflow: a tab page revealing this re-measures it
180
+ onLive(
181
+ root,
182
+ [
183
+ [window, "resize"],
184
+ [window, "twk-reflow"],
185
+ ],
186
+ drawGraph,
187
+ );
188
+ // the build's clamp, so get() agrees with the handles
189
+ return {
190
+ el: root,
191
+ set: (nv: unknown) => {
192
+ const m = read(nv);
193
+ if (m) {
194
+ if (m.some((n) => !Number.isFinite(n))) return;
195
+ v = fit(m);
196
+ drawGraph();
197
+ syncFields();
198
+ }
199
+ },
200
+ get: () => v.slice(),
201
+ };
202
+ }
203
+
204
+ registerControl("cubicbezier", createBezier);