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