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.
- package/AGENTS.md +32 -30
- package/README.md +55 -29
- package/THIRD-PARTY-NOTICES.md +0 -1
- package/dist/tweaks/auto.js +2 -0
- package/dist/tweaks/auto.js.map +6 -0
- package/dist/tweaks/bezier-S2BOXQBM.js +2 -0
- package/dist/tweaks/bezier-S2BOXQBM.js.map +6 -0
- package/dist/tweaks/chunk-3MZAAMC2.js +2 -0
- package/dist/tweaks/chunk-3MZAAMC2.js.map +6 -0
- package/dist/tweaks/chunk-ETB35KFV.js +2 -0
- package/dist/tweaks/chunk-ETB35KFV.js.map +6 -0
- package/dist/tweaks/chunk-YB5GKWZI.js +2 -0
- package/dist/tweaks/chunk-YB5GKWZI.js.map +6 -0
- package/dist/tweaks/clip-A3VI53BK.js +2 -0
- package/dist/tweaks/clip-A3VI53BK.js.map +6 -0
- package/dist/tweaks/color-UGYMAF3W.js +2 -0
- package/dist/tweaks/color-UGYMAF3W.js.map +6 -0
- package/dist/tweaks/core.js +2 -1
- package/dist/tweaks/core.js.map +6 -0
- package/dist/tweaks/fold-GY47HIQA.js +2 -0
- package/dist/tweaks/fold-GY47HIQA.js.map +6 -0
- package/dist/tweaks/gradient-BTX25WSH.js +2 -0
- package/dist/tweaks/gradient-BTX25WSH.js.map +6 -0
- package/dist/tweaks/heavy-NPFIGHRM.js +2 -0
- package/dist/tweaks/heavy-NPFIGHRM.js.map +6 -0
- package/dist/tweaks/image-HTQQ4WX6.js +4 -0
- package/dist/tweaks/image-HTQQ4WX6.js.map +6 -0
- package/dist/tweaks/image-store-QD5SMZXG.js +2 -0
- package/dist/tweaks/image-store-QD5SMZXG.js.map +6 -0
- package/dist/tweaks/interval-BHPI4LRN.js +2 -0
- package/dist/tweaks/interval-BHPI4LRN.js.map +6 -0
- package/dist/tweaks/markup-QM53TQJZ.js +2 -0
- package/dist/tweaks/markup-QM53TQJZ.js.map +6 -0
- package/dist/tweaks/monitor-722WIYGW.js +3 -0
- package/dist/tweaks/monitor-722WIYGW.js.map +6 -0
- package/dist/tweaks/motion-HMCZK6HJ.js +2 -0
- package/dist/tweaks/motion-HMCZK6HJ.js.map +6 -0
- package/dist/tweaks/plot-D3NQ6XNN.js +3 -0
- package/dist/tweaks/plot-D3NQ6XNN.js.map +6 -0
- package/dist/tweaks/point-XDXKMDXH.js +2 -0
- package/dist/tweaks/point-XDXKMDXH.js.map +6 -0
- package/dist/tweaks/presets-EUWNSCUW.js +2 -0
- package/dist/tweaks/presets-EUWNSCUW.js.map +6 -0
- package/dist/tweaks/shadow-AM7VSRNS.js +2 -0
- package/dist/tweaks/shadow-AM7VSRNS.js.map +6 -0
- package/dist/tweaks/shadow-root-RYUV3AGS.js +3 -0
- package/dist/tweaks/shadow-root-RYUV3AGS.js.map +6 -0
- package/dist/tweaks/sides-JWIURDA4.js +2 -0
- package/dist/tweaks/sides-JWIURDA4.js.map +6 -0
- package/dist/tweaks/spring-BF5GBTPM.js +2 -0
- package/dist/tweaks/spring-BF5GBTPM.js.map +6 -0
- package/dist/tweaks/tabs-PRVUUZXZ.js +2 -0
- package/dist/tweaks/tabs-PRVUUZXZ.js.map +6 -0
- package/dist/tweaks.css +1 -1
- package/dist/tweaks.js +7 -3
- package/dist/tweaks.js.map +6 -0
- package/dist/types/tweaks/easing.d.ts +8 -11
- package/dist/types/tweaks/enhance.d.ts +3 -2
- package/dist/types/tweaks/panel.d.ts +6 -4
- package/dist/types/tweaks/schema.d.ts +4 -8
- package/dist/types/tweaks/standalone.d.ts +3 -3
- package/dist/types/tweaks/types.d.ts +499 -116
- package/package.json +30 -5
- package/src/tweaks/auto.ts +16 -0
- package/src/tweaks/clip.ts +156 -0
- package/src/tweaks/controls/basic.ts +727 -0
- package/src/tweaks/controls/bezier.ts +204 -0
- package/src/tweaks/controls/color.ts +861 -0
- package/src/tweaks/controls/gradient.ts +419 -0
- package/src/tweaks/controls/image.ts +555 -0
- package/src/tweaks/controls/interval.ts +189 -0
- package/src/tweaks/controls/monitor.ts +211 -0
- package/src/tweaks/controls/motion.ts +316 -0
- package/src/tweaks/controls/plot.ts +440 -0
- package/src/tweaks/controls/point.ts +208 -0
- package/src/tweaks/controls/shadow.ts +156 -0
- package/src/tweaks/controls/sides.ts +196 -0
- package/src/tweaks/controls/spring.ts +308 -0
- package/src/tweaks/controls/tabs.ts +68 -0
- package/src/tweaks/core.ts +9 -0
- package/src/tweaks/easing.ts +118 -0
- package/src/tweaks/enhance.ts +35 -0
- package/src/tweaks/feedback.ts +218 -0
- package/src/tweaks/fold.ts +33 -0
- package/src/tweaks/heavy.ts +626 -0
- package/src/tweaks/icons.ts +26 -0
- package/src/tweaks/image-store.ts +189 -0
- package/src/tweaks/lazy.ts +63 -0
- package/src/tweaks/markup.ts +317 -0
- package/src/tweaks/panel.ts +1693 -0
- package/src/tweaks/presets.ts +85 -0
- package/src/tweaks/schema.ts +351 -0
- package/src/tweaks/shadow-root.ts +178 -0
- package/src/tweaks/shared.ts +1137 -0
- package/src/tweaks/single.ts +22 -0
- package/src/tweaks/standalone.ts +179 -0
- package/src/tweaks/types.ts +1027 -0
- package/src/tweaks/undo.ts +104 -0
- package/src/tweaks.css +965 -0
- package/src/wide-gamut.ts +658 -0
- package/dist/tweaks/bezier-2RABC6LD.js +0 -1
- package/dist/tweaks/chunk-4ATI34PB.js +0 -1
- package/dist/tweaks/chunk-7OSCMZ2J.js +0 -1
- package/dist/tweaks/chunk-W3JNIJ2F.js +0 -1
- package/dist/tweaks/clip-ECGCECEC.js +0 -1
- package/dist/tweaks/colour-2RE3FQ5E.js +0 -1
- package/dist/tweaks/fold-UGN7TXGX.js +0 -1
- package/dist/tweaks/gradient-DB6XTDHO.js +0 -1
- package/dist/tweaks/heavy-PKOMHB3C.js +0 -1
- package/dist/tweaks/image-7MCLBOZ3.js +0 -1
- package/dist/tweaks/interval-L2E4L3AZ.js +0 -1
- package/dist/tweaks/markup-ZJJ6CCWM.js +0 -1
- package/dist/tweaks/monitor-IEK742PG.js +0 -2
- package/dist/tweaks/motion-N5UNVR55.js +0 -1
- package/dist/tweaks/plot-VVTOY3RA.js +0 -2
- package/dist/tweaks/point-YMMHSLO7.js +0 -1
- package/dist/tweaks/shadow-IKYOV3VN.js +0 -1
- package/dist/tweaks/sides-FH227Z54.js +0 -1
- package/dist/tweaks/spring-3PKAFIH7.js +0 -1
- 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);
|