@kitlangton/knobs 0.4.0-next.11 → 0.4.0-next.13
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/README.md +117 -21
- package/dist/dev/{chunk-75gbzbyd.js → chunk-44r02asc.js} +2 -5
- package/dist/dev/{chunk-mjdgzad5.js → chunk-7kns3qe6.js} +29 -25
- package/dist/dev/{chunk-bnhzf1tj.js → chunk-mbhy4jg5.js} +1453 -1097
- package/dist/dev/{chunk-j57rhjtm.js → chunk-pdf3zy95.js} +95 -42
- package/dist/dev/index.js +188 -63
- package/dist/index.js +211 -60
- package/dist/panel.js +2699 -2149
- package/dist/review-react.js +8 -4
- package/dist/review-solid.js +10 -5
- package/dist/review.js +8 -4
- package/dist/types/core/remote.d.ts +10 -2
- package/dist/types/core/schema.d.ts +52 -4
- package/dist/types/core/store.d.ts +45 -8
- package/dist/types/core/variants.d.ts +3 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/panel/dock.d.ts +9 -0
- package/dist/types/panel/gpu/renderer.d.ts +11 -1
- package/dist/types/panel/inspector.d.ts +11 -3
- package/dist/types/panel/presets.d.ts +26 -4
- package/dist/types/panel/scope.d.ts +15 -4
- package/dist/types/ui/canvas-css.d.ts +1 -0
- package/dist/types/ui/canvas-model.d.ts +116 -0
- package/dist/types/ui/canvas.d.ts +66 -0
- package/dist/types/ui/index.d.ts +2 -0
- package/dist/types/ui/react.d.ts +12 -0
- package/dist/types/ui/solid.d.ts +9 -0
- package/dist/ui-react.js +1020 -17
- package/dist/ui-solid.js +1032 -25
- package/dist/ui.js +986 -4
- package/package.json +1 -1
package/dist/ui.js
CHANGED
|
@@ -3043,13 +3043,17 @@ var css2 = `
|
|
|
3043
3043
|
.knobs-tree-row[aria-expanded="true"]>.knobs-tree-caret{transform:rotate(90deg)}
|
|
3044
3044
|
.knobs-tree-row:not([aria-expanded])>.knobs-tree-caret{visibility:hidden}
|
|
3045
3045
|
.knobs-tree-icon{flex:none;display:flex;align-items:center;justify-content:center;width:14px;margin-right:6px;color:var(--_k-fg-3);font-size:calc(var(--_k-font-size) - 1px)}
|
|
3046
|
-
|
|
3046
|
+
/* The label wins the room: it keeps its width while the detail (which only takes what's left)
|
|
3047
|
+
fades out at its end, and gives way only once the detail is gone. Each fades where it's cut. */
|
|
3048
|
+
.knobs-tree-label{position:relative;flex:0 1 auto;min-width:0;overflow:hidden;font-weight:var(--_k-weight)}
|
|
3049
|
+
.knobs-tree-row[data-editing]>.knobs-tree-label{flex-grow:1}
|
|
3047
3050
|
.knobs-tree-row[data-ancestor] .knobs-tree-label{color:var(--_k-fg-2)}
|
|
3048
3051
|
.knobs-tree-label .knobs-tree-mark{color:var(--_k-accent)}
|
|
3049
3052
|
.knobs-tree-row[data-off~="visible"] :is(.knobs-tree-label,.knobs-tree-icon){opacity:.45}
|
|
3050
3053
|
.knobs-tree-row[data-off~="locked"] .knobs-tree-label{color:var(--_k-fg-2)}
|
|
3051
|
-
.knobs-tree-detail{flex:
|
|
3052
|
-
.knobs-tree-
|
|
3054
|
+
.knobs-tree-detail{flex:1 1 0;min-width:0;overflow:hidden;padding-left:6px;text-align:right;color:var(--_k-fg-3);font-variant-numeric:tabular-nums}
|
|
3055
|
+
.knobs-tree-row[data-editing]>.knobs-tree-detail{flex-grow:0;visibility:hidden}
|
|
3056
|
+
.knobs-tree-tools{flex:none;display:flex;align-items:center;gap:1px;margin-left:auto;padding-left:4px}
|
|
3053
3057
|
.knobs-ui .knobs-tree-toggle{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:calc(var(--_k-radius) * 999);color:var(--_k-fg-3);cursor:pointer;opacity:0;transition:opacity .12s,color .12s,background .12s}
|
|
3054
3058
|
.knobs-ui .knobs-tree-toggle svg{width:12px;height:12px}
|
|
3055
3059
|
.knobs-tree-row:is(:hover,[data-active],[aria-selected="true"]) .knobs-tree-toggle,.knobs-ui .knobs-tree-toggle[aria-pressed="false"]{opacity:1}
|
|
@@ -10047,7 +10051,7 @@ function tree(container, options) {
|
|
|
10047
10051
|
text.className = "knobs-tree-text";
|
|
10048
10052
|
label.append(text);
|
|
10049
10053
|
const detail = doc.createElement("span");
|
|
10050
|
-
detail.className = "knobs-tree-detail";
|
|
10054
|
+
detail.className = "knobs-tree-detail knobs-scroll-fade-x";
|
|
10051
10055
|
const tools = doc.createElement("span");
|
|
10052
10056
|
tools.className = "knobs-tree-tools";
|
|
10053
10057
|
el.append(caret, icon, label, detail, tools);
|
|
@@ -19773,6 +19777,976 @@ function bento(container, options) {
|
|
|
19773
19777
|
}
|
|
19774
19778
|
};
|
|
19775
19779
|
}
|
|
19780
|
+
// src/ui/canvas.ts
|
|
19781
|
+
init_glide();
|
|
19782
|
+
init_motion();
|
|
19783
|
+
|
|
19784
|
+
// src/ui/canvas-css.ts
|
|
19785
|
+
var canvasCss = `
|
|
19786
|
+
.knobs-ui.knobs-canvas{--_kc-radius:var(--k-ui-canvas-radius,var(--_k-radius-panel));--_kc-fade:var(--k-ui-canvas-fade,16px);
|
|
19787
|
+
position:relative;width:100%;height:100%;overflow:hidden;outline:none;background:var(--k-ui-canvas-bg,var(--_k-bg-page));contain:strict;
|
|
19788
|
+
touch-action:none;overscroll-behavior:none;user-select:none;-webkit-user-select:none;cursor:default;
|
|
19789
|
+
font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
|
|
19790
|
+
:where(.knobs-canvas-stage,.knobs-canvas-stage *,.knobs-canvas-zoom,.knobs-canvas-zoom *){box-sizing:border-box}
|
|
19791
|
+
/* No hard edges: what slides out of view fades over the last few px. */
|
|
19792
|
+
.knobs-canvas-stage{position:absolute;inset:0;overflow:hidden;
|
|
19793
|
+
-webkit-mask-image:linear-gradient(to right,transparent,#000 var(--_kc-fade),#000 calc(100% - var(--_kc-fade)),transparent),linear-gradient(to bottom,transparent,#000 var(--_kc-fade),#000 calc(100% - var(--_kc-fade)),transparent);
|
|
19794
|
+
-webkit-mask-composite:source-in;mask-image:linear-gradient(to right,transparent,#000 var(--_kc-fade),#000 calc(100% - var(--_kc-fade)),transparent),linear-gradient(to bottom,transparent,#000 var(--_kc-fade),#000 calc(100% - var(--_kc-fade)),transparent);mask-composite:intersect}
|
|
19795
|
+
.knobs-canvas-dots{position:absolute;left:-128px;top:-128px;right:-128px;bottom:-128px;pointer-events:none;will-change:transform;
|
|
19796
|
+
background-image:radial-gradient(circle,var(--k-ui-canvas-dot,color-mix(in srgb,var(--_k-fg) 22%,transparent)) 1px,transparent 1.25px)}
|
|
19797
|
+
.knobs-canvas-world{position:absolute;left:0;top:0;width:0;height:0;transform-origin:0 0}
|
|
19798
|
+
.knobs-canvas[data-moving] .knobs-canvas-world{will-change:transform}
|
|
19799
|
+
.knobs-canvas:is([data-panning],[data-space]) .knobs-canvas-world{pointer-events:none}
|
|
19800
|
+
.knobs-canvas[data-space]{cursor:grab}
|
|
19801
|
+
.knobs-canvas[data-grabbing]{cursor:grabbing}
|
|
19802
|
+
.knobs-canvas-tile{position:absolute;left:0;top:0;display:flex;flex-direction:column;width:max-content;transform-origin:50% 50%}
|
|
19803
|
+
.knobs-canvas-title{flex:none;contain:inline-size;height:24px;padding:0 2px 8px;overflow:hidden;white-space:nowrap;color:var(--_k-fg-2);
|
|
19804
|
+
font-size:var(--_k-type-small,12px);line-height:16px;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 16px),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 16px),transparent)}
|
|
19805
|
+
.knobs-canvas-card{position:relative;flex:none;padding:var(--k-ui-canvas-pad,16px);border-radius:var(--_kc-radius);background:var(--k-ui-canvas-card,var(--_k-bg));
|
|
19806
|
+
overflow:hidden;user-select:text;-webkit-user-select:text}
|
|
19807
|
+
.knobs-canvas-card::after{content:"";position:absolute;inset:0;border-radius:inherit;box-shadow:inset 0 0 0 1px var(--_k-border);pointer-events:none}
|
|
19808
|
+
/* The readout: how far, and the way back to the whole set or 100%. */
|
|
19809
|
+
.knobs-canvas-zoom{position:absolute;right:10px;bottom:10px;z-index:1;display:flex;align-items:center;gap:2px;height:28px;padding:0 4px;border-radius:999px;
|
|
19810
|
+
background:var(--_k-bg);box-shadow:inset 0 0 0 1px var(--_k-border);color:var(--_k-fg-2);font-size:var(--_k-type-small,12px);
|
|
19811
|
+
font-variant-numeric:tabular-nums;font-feature-settings:"tnum";white-space:nowrap}
|
|
19812
|
+
:where(.knobs-ui) .knobs-canvas-btn{all:revert;box-sizing:border-box;font:inherit;letter-spacing:inherit;margin:0;border:0;outline:none;box-shadow:none;
|
|
19813
|
+
display:inline-flex;align-items:center;justify-content:center;height:20px;min-width:20px;padding:0 6px;border-radius:999px;background:transparent;color:inherit;cursor:default;
|
|
19814
|
+
transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
19815
|
+
:where(.knobs-ui) .knobs-canvas-btn:is(:hover,:focus-visible){background:var(--_k-fill-hover);color:var(--_k-fg);transition-duration:0ms}
|
|
19816
|
+
:where(.knobs-ui) .knobs-canvas-btn:focus-visible{box-shadow:0 0 0 1.5px var(--_k-accent)}
|
|
19817
|
+
.knobs-canvas-pct{min-width:46px}
|
|
19818
|
+
.knobs-canvas-btn svg{width:13px;height:13px}
|
|
19819
|
+
`;
|
|
19820
|
+
|
|
19821
|
+
// src/ui/canvas-model.ts
|
|
19822
|
+
var clamp3 = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
19823
|
+
function packRows(sizes, opts) {
|
|
19824
|
+
const { gap, width } = opts;
|
|
19825
|
+
const x0 = opts.x ?? 0;
|
|
19826
|
+
let x = 0;
|
|
19827
|
+
let y = opts.y ?? 0;
|
|
19828
|
+
let rowH = 0;
|
|
19829
|
+
return sizes.map((s) => {
|
|
19830
|
+
if (x > 0 && x + s.w > width) {
|
|
19831
|
+
x = 0;
|
|
19832
|
+
y += rowH + gap;
|
|
19833
|
+
rowH = 0;
|
|
19834
|
+
}
|
|
19835
|
+
const r = { x: Math.round(x0 + x), y: Math.round(y), w: s.w, h: s.h };
|
|
19836
|
+
x += s.w + gap;
|
|
19837
|
+
rowH = Math.max(rowH, s.h);
|
|
19838
|
+
return r;
|
|
19839
|
+
});
|
|
19840
|
+
}
|
|
19841
|
+
function rowWidth(sizes, gap, aspect) {
|
|
19842
|
+
let widest = 0;
|
|
19843
|
+
let total = 0;
|
|
19844
|
+
for (const s of sizes) {
|
|
19845
|
+
widest = Math.max(widest, s.w);
|
|
19846
|
+
total += s.w + gap;
|
|
19847
|
+
}
|
|
19848
|
+
if (!sizes.length)
|
|
19849
|
+
return 0;
|
|
19850
|
+
const a = Number.isFinite(aspect) && aspect > 0 ? aspect : 16 / 10;
|
|
19851
|
+
const shape = (width) => {
|
|
19852
|
+
const b = boundsOf(packRows(sizes, { gap, width }));
|
|
19853
|
+
return b.w / Math.max(1, b.h);
|
|
19854
|
+
};
|
|
19855
|
+
let lo = widest;
|
|
19856
|
+
let hi = Math.max(widest, total);
|
|
19857
|
+
for (let i = 0;i < 24 && hi - lo > 1; i++) {
|
|
19858
|
+
const mid = (lo + hi) / 2;
|
|
19859
|
+
if (shape(mid) < a)
|
|
19860
|
+
lo = mid;
|
|
19861
|
+
else
|
|
19862
|
+
hi = mid;
|
|
19863
|
+
}
|
|
19864
|
+
return Math.abs(Math.log(shape(lo) / a)) <= Math.abs(Math.log(shape(hi) / a)) ? lo : hi;
|
|
19865
|
+
}
|
|
19866
|
+
function layoutCanvas(items, opts) {
|
|
19867
|
+
const placed = items.flatMap((it) => it.x !== undefined && it.y !== undefined ? [{ x: it.x, y: it.y, w: it.w, h: it.h }] : []);
|
|
19868
|
+
const free = items.filter((it) => it.x === undefined || it.y === undefined);
|
|
19869
|
+
const width = opts.width ?? rowWidth(free, opts.gap, opts.aspect ?? 16 / 10);
|
|
19870
|
+
const under = boundsOf(placed);
|
|
19871
|
+
const packed = packRows(free, {
|
|
19872
|
+
gap: opts.gap,
|
|
19873
|
+
width,
|
|
19874
|
+
x: under ? under.x : 0,
|
|
19875
|
+
y: under ? under.y + under.h + opts.gap * 2 : 0
|
|
19876
|
+
});
|
|
19877
|
+
let i = 0;
|
|
19878
|
+
const rects = items.map((it) => it.x !== undefined && it.y !== undefined ? { x: it.x, y: it.y, w: it.w, h: it.h } : packed[i++]);
|
|
19879
|
+
return { rects, width };
|
|
19880
|
+
}
|
|
19881
|
+
function boundsOf(rects) {
|
|
19882
|
+
if (!rects.length)
|
|
19883
|
+
return null;
|
|
19884
|
+
let x0 = Number.POSITIVE_INFINITY;
|
|
19885
|
+
let y0 = Number.POSITIVE_INFINITY;
|
|
19886
|
+
let x1 = Number.NEGATIVE_INFINITY;
|
|
19887
|
+
let y1 = Number.NEGATIVE_INFINITY;
|
|
19888
|
+
for (const r of rects) {
|
|
19889
|
+
x0 = Math.min(x0, r.x);
|
|
19890
|
+
y0 = Math.min(y0, r.y);
|
|
19891
|
+
x1 = Math.max(x1, r.x + r.w);
|
|
19892
|
+
y1 = Math.max(y1, r.y + r.h);
|
|
19893
|
+
}
|
|
19894
|
+
return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
|
|
19895
|
+
}
|
|
19896
|
+
function zoomAround(v, scale, at, min = 0, max = Number.POSITIVE_INFINITY) {
|
|
19897
|
+
const s = clamp3(scale, min, max);
|
|
19898
|
+
const k = s / v.scale;
|
|
19899
|
+
return { x: at.x - (at.x - v.x) * k, y: at.y - (at.y - v.y) * k, scale: s };
|
|
19900
|
+
}
|
|
19901
|
+
function pinchView(v, from, to, ratio, min = 0, max = Number.POSITIVE_INFINITY) {
|
|
19902
|
+
const z = zoomAround(v, v.scale * ratio, from, min, max);
|
|
19903
|
+
return { x: z.x + to.x - from.x, y: z.y + to.y - from.y, scale: z.scale };
|
|
19904
|
+
}
|
|
19905
|
+
var WHEEL_ZOOM_MAX = 1.25;
|
|
19906
|
+
function wheelZoom(deltaY, pinch) {
|
|
19907
|
+
const f = Math.exp(-deltaY * (pinch ? 0.01 : 0.0025));
|
|
19908
|
+
return clamp3(f, 1 / WHEEL_ZOOM_MAX, WHEEL_ZOOM_MAX);
|
|
19909
|
+
}
|
|
19910
|
+
function fitView(r, w, h, opts = {}) {
|
|
19911
|
+
const pad = opts.pad ?? 0;
|
|
19912
|
+
const room = (n) => Math.max(1, n - pad * 2);
|
|
19913
|
+
const raw = Math.min(room(w) / Math.max(1, r.w), room(h) / Math.max(1, r.h));
|
|
19914
|
+
const scale = clamp3(raw, opts.min ?? 0, opts.max ?? Number.POSITIVE_INFINITY);
|
|
19915
|
+
return {
|
|
19916
|
+
x: w / 2 - (r.x + r.w / 2) * scale,
|
|
19917
|
+
y: h / 2 - (r.y + r.h / 2) * scale,
|
|
19918
|
+
scale
|
|
19919
|
+
};
|
|
19920
|
+
}
|
|
19921
|
+
function viewPath(from, to) {
|
|
19922
|
+
const k = to.scale / from.scale;
|
|
19923
|
+
if (!Number.isFinite(k) || Math.abs(Math.log(k)) < 0.0001)
|
|
19924
|
+
return (t) => ({
|
|
19925
|
+
x: from.x + (to.x - from.x) * t,
|
|
19926
|
+
y: from.y + (to.y - from.y) * t,
|
|
19927
|
+
scale: from.scale + (to.scale - from.scale) * t
|
|
19928
|
+
});
|
|
19929
|
+
const fx = (to.x - k * from.x) / (1 - k);
|
|
19930
|
+
const fy = (to.y - k * from.y) / (1 - k);
|
|
19931
|
+
const wx = (fx - from.x) / from.scale;
|
|
19932
|
+
const wy = (fy - from.y) / from.scale;
|
|
19933
|
+
return (t) => {
|
|
19934
|
+
if (t >= 1)
|
|
19935
|
+
return to;
|
|
19936
|
+
const scale = from.scale * k ** t;
|
|
19937
|
+
return { x: fx - wx * scale, y: fy - wy * scale, scale };
|
|
19938
|
+
};
|
|
19939
|
+
}
|
|
19940
|
+
var snapView = (v, dpr) => {
|
|
19941
|
+
const d = dpr > 0 ? dpr : 1;
|
|
19942
|
+
return { x: Math.round(v.x * d) / d, y: Math.round(v.y * d) / d, scale: v.scale };
|
|
19943
|
+
};
|
|
19944
|
+
var smooth = (a, b, x) => {
|
|
19945
|
+
const t = clamp3((x - a) / (b - a), 0, 1);
|
|
19946
|
+
return t * t * (3 - 2 * t);
|
|
19947
|
+
};
|
|
19948
|
+
function dotGrid(scale, step = 24) {
|
|
19949
|
+
const size = step * scale;
|
|
19950
|
+
const alpha = smooth(7, 14, size) * (1 - smooth(72, 128, size));
|
|
19951
|
+
return { size, alpha };
|
|
19952
|
+
}
|
|
19953
|
+
function keyAction(e) {
|
|
19954
|
+
if (e.altKey)
|
|
19955
|
+
return null;
|
|
19956
|
+
switch (e.key) {
|
|
19957
|
+
case "0":
|
|
19958
|
+
return "fit";
|
|
19959
|
+
case "1":
|
|
19960
|
+
return "actual";
|
|
19961
|
+
case "=":
|
|
19962
|
+
case "+":
|
|
19963
|
+
return "in";
|
|
19964
|
+
case "-":
|
|
19965
|
+
case "_":
|
|
19966
|
+
return "out";
|
|
19967
|
+
default:
|
|
19968
|
+
return null;
|
|
19969
|
+
}
|
|
19970
|
+
}
|
|
19971
|
+
var KEY_ZOOM = 1.5;
|
|
19972
|
+
function releaseVelocity(samples, now, window2 = 80) {
|
|
19973
|
+
const last = samples.at(-1);
|
|
19974
|
+
if (!last || now - last.t > 60)
|
|
19975
|
+
return { x: 0, y: 0 };
|
|
19976
|
+
let first = last;
|
|
19977
|
+
for (let i = samples.length - 1;i >= 0; i--) {
|
|
19978
|
+
if (last.t - samples[i].t > window2)
|
|
19979
|
+
break;
|
|
19980
|
+
first = samples[i];
|
|
19981
|
+
}
|
|
19982
|
+
const dt = (last.t - first.t) / 1000;
|
|
19983
|
+
if (dt <= 0)
|
|
19984
|
+
return { x: 0, y: 0 };
|
|
19985
|
+
return { x: (last.x - first.x) / dt, y: (last.y - first.y) / dt };
|
|
19986
|
+
}
|
|
19987
|
+
var COAST_TAU = -1 / (1000 * Math.log(0.998));
|
|
19988
|
+
|
|
19989
|
+
// src/ui/canvas.ts
|
|
19990
|
+
init_look();
|
|
19991
|
+
var FIT = springMotion(SPRINGS.smooth);
|
|
19992
|
+
var KEY2 = springMotion(SPRINGS.default);
|
|
19993
|
+
var LAYOUT3 = springMotion(SPRINGS.calm);
|
|
19994
|
+
var FOCUS_MAX = 2;
|
|
19995
|
+
var MAX_FLING = 8000;
|
|
19996
|
+
var SETTLE_MS = 200;
|
|
19997
|
+
var INTERACTIVE = "a,button,input,select,textarea,label,summary,[contenteditable],[role=button],[role=slider],[role=switch],[role=checkbox],[role=tab],[role=option],[role=textbox]";
|
|
19998
|
+
var ICON_FIT = svg('<path d="M2.5 6V3.5a1 1 0 0 1 1-1H6M10 2.5h2.5a1 1 0 0 1 1 1V6M13.5 10v2.5a1 1 0 0 1-1 1H10M6 13.5H3.5a1 1 0 0 1-1-1V10"/>');
|
|
19999
|
+
var injectCanvasStyles = (doc) => {
|
|
20000
|
+
if (doc.querySelector("style[data-knobs-canvas]"))
|
|
20001
|
+
return;
|
|
20002
|
+
const style = doc.createElement("style");
|
|
20003
|
+
style.dataset.knobsCanvas = "";
|
|
20004
|
+
style.textContent = aliased(canvasCss);
|
|
20005
|
+
doc.head.append(style);
|
|
20006
|
+
};
|
|
20007
|
+
function canvas(container, options) {
|
|
20008
|
+
const doc = container.ownerDocument;
|
|
20009
|
+
const win = doc.defaultView;
|
|
20010
|
+
const gap = options.gap ?? 48;
|
|
20011
|
+
const pad = options.padding ?? 48;
|
|
20012
|
+
const minZoom = options.minZoom ?? 0.05;
|
|
20013
|
+
const maxZoom = options.maxZoom ?? 8;
|
|
20014
|
+
let items = options.items;
|
|
20015
|
+
const root = doc.createElement("div");
|
|
20016
|
+
root.className = "knobs-canvas";
|
|
20017
|
+
root.tabIndex = 0;
|
|
20018
|
+
root.setAttribute("role", "region");
|
|
20019
|
+
root.setAttribute("aria-roledescription", "canvas");
|
|
20020
|
+
root.setAttribute("aria-label", options.label ?? "Canvas");
|
|
20021
|
+
applyLook2(root, options);
|
|
20022
|
+
injectCanvasStyles(doc);
|
|
20023
|
+
const stage = doc.createElement("div");
|
|
20024
|
+
stage.className = "knobs-canvas-stage";
|
|
20025
|
+
const dots = doc.createElement("div");
|
|
20026
|
+
dots.className = "knobs-canvas-dots";
|
|
20027
|
+
const world = doc.createElement("div");
|
|
20028
|
+
world.className = "knobs-canvas-world";
|
|
20029
|
+
stage.append(dots, world);
|
|
20030
|
+
root.append(stage);
|
|
20031
|
+
let pct = null;
|
|
20032
|
+
if (options.readout !== false) {
|
|
20033
|
+
const bar = doc.createElement("div");
|
|
20034
|
+
bar.className = "knobs-canvas-zoom";
|
|
20035
|
+
const fitBtn = doc.createElement("button");
|
|
20036
|
+
fitBtn.type = "button";
|
|
20037
|
+
fitBtn.className = "knobs-canvas-btn";
|
|
20038
|
+
fitBtn.innerHTML = ICON_FIT;
|
|
20039
|
+
fitBtn.title = "Fit (0)";
|
|
20040
|
+
fitBtn.setAttribute("aria-label", "Fit");
|
|
20041
|
+
fitBtn.addEventListener("click", () => api.fit());
|
|
20042
|
+
pct = doc.createElement("button");
|
|
20043
|
+
pct.type = "button";
|
|
20044
|
+
pct.className = "knobs-canvas-btn knobs-canvas-pct";
|
|
20045
|
+
pct.title = "100% (1)";
|
|
20046
|
+
pct.setAttribute("aria-label", "Zoom to 100%");
|
|
20047
|
+
pct.addEventListener("click", () => api.zoomTo(1));
|
|
20048
|
+
bar.append(fitBtn, pct);
|
|
20049
|
+
bar.addEventListener("dblclick", (e) => e.stopPropagation());
|
|
20050
|
+
root.append(bar);
|
|
20051
|
+
}
|
|
20052
|
+
container.append(root);
|
|
20053
|
+
const motionQuery = win.matchMedia?.("(prefers-reduced-motion: reduce)");
|
|
20054
|
+
const reduced = () => motionQuery?.matches ?? reducedMotion(win);
|
|
20055
|
+
let view = { x: 0, y: 0, scale: 1 };
|
|
20056
|
+
let vw = 0;
|
|
20057
|
+
let vh = 0;
|
|
20058
|
+
let pristine = true;
|
|
20059
|
+
let rowW;
|
|
20060
|
+
let rects = new Map;
|
|
20061
|
+
let jump = null;
|
|
20062
|
+
let coast = null;
|
|
20063
|
+
let frames = 0;
|
|
20064
|
+
const times = [];
|
|
20065
|
+
const tiles = new Map;
|
|
20066
|
+
let drawnView = "";
|
|
20067
|
+
let drawnDots = "";
|
|
20068
|
+
let drawnPct = "";
|
|
20069
|
+
const drawView = () => {
|
|
20070
|
+
const v = view;
|
|
20071
|
+
const key = `${v.x}|${v.y}|${v.scale}`;
|
|
20072
|
+
if (key === drawnView)
|
|
20073
|
+
return;
|
|
20074
|
+
drawnView = key;
|
|
20075
|
+
world.style.transform = `translate(${v.x}px,${v.y}px) scale(${v.scale})`;
|
|
20076
|
+
const g = dotGrid(v.scale);
|
|
20077
|
+
if (g.alpha <= 0.001) {
|
|
20078
|
+
if (drawnDots !== "off")
|
|
20079
|
+
dots.style.visibility = "hidden";
|
|
20080
|
+
drawnDots = "off";
|
|
20081
|
+
} else {
|
|
20082
|
+
const ox = ((v.x + 128 - g.size / 2) % g.size + g.size) % g.size;
|
|
20083
|
+
const oy = ((v.y + 128 - g.size / 2) % g.size + g.size) % g.size;
|
|
20084
|
+
const size = `${g.size}px ${g.size}px`;
|
|
20085
|
+
const a = String(Math.round(g.alpha * 1000) / 1000);
|
|
20086
|
+
if (drawnDots === "off")
|
|
20087
|
+
dots.style.visibility = "";
|
|
20088
|
+
if (dots.style.backgroundSize !== size)
|
|
20089
|
+
dots.style.backgroundSize = size;
|
|
20090
|
+
if (dots.style.opacity !== a)
|
|
20091
|
+
dots.style.opacity = a;
|
|
20092
|
+
dots.style.transform = `translate(${ox}px,${oy}px)`;
|
|
20093
|
+
drawnDots = "on";
|
|
20094
|
+
}
|
|
20095
|
+
if (pct) {
|
|
20096
|
+
const label = `${Math.round(v.scale * 100)}%`;
|
|
20097
|
+
if (label !== drawnPct)
|
|
20098
|
+
pct.textContent = drawnPct = label;
|
|
20099
|
+
}
|
|
20100
|
+
options.onView?.(v);
|
|
20101
|
+
};
|
|
20102
|
+
let settleTimer = 0;
|
|
20103
|
+
let held = false;
|
|
20104
|
+
let layerScale = 0;
|
|
20105
|
+
const settle = () => {
|
|
20106
|
+
settleTimer = 0;
|
|
20107
|
+
if (held)
|
|
20108
|
+
return;
|
|
20109
|
+
if (loop.running) {
|
|
20110
|
+
settleTimer = win.setTimeout(settle, SETTLE_MS);
|
|
20111
|
+
return;
|
|
20112
|
+
}
|
|
20113
|
+
view = snapView(view, win.devicePixelRatio || 1);
|
|
20114
|
+
if (Math.abs(view.scale / layerScale - 1) > 0.000001) {
|
|
20115
|
+
delete root.dataset.moving;
|
|
20116
|
+
layerScale = 0;
|
|
20117
|
+
}
|
|
20118
|
+
drawView();
|
|
20119
|
+
};
|
|
20120
|
+
const moved = () => {
|
|
20121
|
+
if (!layerScale) {
|
|
20122
|
+
root.dataset.moving = "";
|
|
20123
|
+
layerScale = view.scale;
|
|
20124
|
+
}
|
|
20125
|
+
win.clearTimeout(settleTimer);
|
|
20126
|
+
settleTimer = win.setTimeout(settle, SETTLE_MS);
|
|
20127
|
+
};
|
|
20128
|
+
const setView = (v) => {
|
|
20129
|
+
view = v;
|
|
20130
|
+
moved();
|
|
20131
|
+
drawView();
|
|
20132
|
+
};
|
|
20133
|
+
const drawTile = (t) => {
|
|
20134
|
+
const key = `${t.x.x}|${t.y.x}`;
|
|
20135
|
+
if (key === t.drawn)
|
|
20136
|
+
return;
|
|
20137
|
+
t.drawn = key;
|
|
20138
|
+
t.el.style.translate = `${t.x.x}px ${t.y.x}px`;
|
|
20139
|
+
};
|
|
20140
|
+
const sizeCard = (t) => {
|
|
20141
|
+
const { w, h } = t.item;
|
|
20142
|
+
t.card.style.width = w === undefined ? "" : `${w}px`;
|
|
20143
|
+
t.card.style.height = h === undefined ? "" : `${h}px`;
|
|
20144
|
+
};
|
|
20145
|
+
const setTitle = (t) => {
|
|
20146
|
+
const text = t.item.title;
|
|
20147
|
+
if (text && !t.title) {
|
|
20148
|
+
t.title = doc.createElement("div");
|
|
20149
|
+
t.title.className = "knobs-canvas-title";
|
|
20150
|
+
t.el.prepend(t.title);
|
|
20151
|
+
}
|
|
20152
|
+
if (t.title) {
|
|
20153
|
+
if (text)
|
|
20154
|
+
t.title.textContent = text;
|
|
20155
|
+
else {
|
|
20156
|
+
t.title.remove();
|
|
20157
|
+
t.title = null;
|
|
20158
|
+
}
|
|
20159
|
+
}
|
|
20160
|
+
};
|
|
20161
|
+
const mount = (it, enter) => {
|
|
20162
|
+
const el = doc.createElement("div");
|
|
20163
|
+
el.className = "knobs-canvas-tile";
|
|
20164
|
+
el.dataset.id = it.id;
|
|
20165
|
+
const card = doc.createElement("div");
|
|
20166
|
+
card.className = "knobs-canvas-card";
|
|
20167
|
+
el.append(card);
|
|
20168
|
+
const t = {
|
|
20169
|
+
id: it.id,
|
|
20170
|
+
item: it,
|
|
20171
|
+
el,
|
|
20172
|
+
title: null,
|
|
20173
|
+
card,
|
|
20174
|
+
cleanup: undefined,
|
|
20175
|
+
x: new Spring(0, LAYOUT3.omega, LAYOUT3.zeta, 0.05),
|
|
20176
|
+
y: new Spring(0, LAYOUT3.omega, LAYOUT3.zeta, 0.05),
|
|
20177
|
+
w: 0,
|
|
20178
|
+
h: 0,
|
|
20179
|
+
placed: false,
|
|
20180
|
+
leaving: false,
|
|
20181
|
+
gone: false,
|
|
20182
|
+
drawn: ""
|
|
20183
|
+
};
|
|
20184
|
+
setTitle(t);
|
|
20185
|
+
sizeCard(t);
|
|
20186
|
+
world.append(el);
|
|
20187
|
+
t.cleanup = it.render(card) || undefined;
|
|
20188
|
+
tiles.set(it.id, t);
|
|
20189
|
+
ro.observe(el);
|
|
20190
|
+
if (enter) {
|
|
20191
|
+
const r = reduced();
|
|
20192
|
+
el.animate(r ? [{ opacity: 0 }, { opacity: 1 }] : [
|
|
20193
|
+
{ opacity: 0, scale: 0.96, filter: "blur(3px)" },
|
|
20194
|
+
{ opacity: 1, scale: 1, filter: "blur(0px)" }
|
|
20195
|
+
], { duration: r ? 160 : 260, easing: "cubic-bezier(.2,.8,.2,1)" });
|
|
20196
|
+
}
|
|
20197
|
+
return t;
|
|
20198
|
+
};
|
|
20199
|
+
const unmount = (t) => {
|
|
20200
|
+
if (t.gone)
|
|
20201
|
+
return;
|
|
20202
|
+
t.gone = true;
|
|
20203
|
+
ro.unobserve(t.el);
|
|
20204
|
+
t.cleanup?.();
|
|
20205
|
+
t.el.remove();
|
|
20206
|
+
if (tiles.get(t.id) === t)
|
|
20207
|
+
tiles.delete(t.id);
|
|
20208
|
+
};
|
|
20209
|
+
const leave = (t) => {
|
|
20210
|
+
t.leaving = true;
|
|
20211
|
+
tiles.delete(t.id);
|
|
20212
|
+
t.el.style.pointerEvents = "none";
|
|
20213
|
+
const r = reduced();
|
|
20214
|
+
const a = t.el.animate(r ? [{ opacity: 1 }, { opacity: 0 }] : [
|
|
20215
|
+
{ opacity: 1, scale: 1, filter: "blur(0px)" },
|
|
20216
|
+
{ opacity: 0, scale: 0.96, filter: "blur(3px)" }
|
|
20217
|
+
], { duration: r ? 160 : 200, easing: "cubic-bezier(.4,0,1,1)", fill: "forwards" });
|
|
20218
|
+
a.onfinish = () => {
|
|
20219
|
+
leaving.delete(t);
|
|
20220
|
+
unmount(t);
|
|
20221
|
+
};
|
|
20222
|
+
leaving.add(t);
|
|
20223
|
+
};
|
|
20224
|
+
const leaving = new Set;
|
|
20225
|
+
const measure = () => {
|
|
20226
|
+
for (const t of tiles.values()) {
|
|
20227
|
+
t.w = t.el.offsetWidth;
|
|
20228
|
+
t.h = t.el.offsetHeight;
|
|
20229
|
+
}
|
|
20230
|
+
};
|
|
20231
|
+
const fitTarget = () => {
|
|
20232
|
+
const b = boundsOf([...rects.values()]);
|
|
20233
|
+
if (!b || !vw || !vh)
|
|
20234
|
+
return null;
|
|
20235
|
+
return fitView(b, vw, vh, { pad, min: minZoom, max: Math.min(maxZoom, 1) });
|
|
20236
|
+
};
|
|
20237
|
+
const relayout = (glide) => {
|
|
20238
|
+
const live = items.map((it) => tiles.get(it.id)).filter(Boolean);
|
|
20239
|
+
const sizes = live.map((t) => ({ x: t.item.x, y: t.item.y, w: t.w, h: t.h }));
|
|
20240
|
+
if (pristine)
|
|
20241
|
+
rowW = options.width;
|
|
20242
|
+
const out = layoutCanvas(sizes, {
|
|
20243
|
+
gap,
|
|
20244
|
+
width: rowW,
|
|
20245
|
+
aspect: vw && vh ? vw / vh : undefined
|
|
20246
|
+
});
|
|
20247
|
+
rowW = out.width;
|
|
20248
|
+
const next = new Map;
|
|
20249
|
+
const animate = glide && !pristine && !reduced();
|
|
20250
|
+
let moving = false;
|
|
20251
|
+
live.forEach((t, i) => {
|
|
20252
|
+
const r = out.rects[i];
|
|
20253
|
+
next.set(t.id, r);
|
|
20254
|
+
if (!animate || !t.placed) {
|
|
20255
|
+
t.x.snap(r.x);
|
|
20256
|
+
t.y.snap(r.y);
|
|
20257
|
+
} else {
|
|
20258
|
+
tune(t.x, LAYOUT3).set(r.x);
|
|
20259
|
+
tune(t.y, LAYOUT3).set(r.y);
|
|
20260
|
+
moving ||= !t.x.resting || !t.y.resting;
|
|
20261
|
+
}
|
|
20262
|
+
t.placed = true;
|
|
20263
|
+
drawTile(t);
|
|
20264
|
+
});
|
|
20265
|
+
rects = next;
|
|
20266
|
+
if (pristine) {
|
|
20267
|
+
const f = fitTarget();
|
|
20268
|
+
if (f) {
|
|
20269
|
+
view = snapView(f, win.devicePixelRatio || 1);
|
|
20270
|
+
drawView();
|
|
20271
|
+
}
|
|
20272
|
+
}
|
|
20273
|
+
if (moving)
|
|
20274
|
+
loop.wake();
|
|
20275
|
+
};
|
|
20276
|
+
let relayoutQueued = 0;
|
|
20277
|
+
const ro = new win.ResizeObserver((entries) => {
|
|
20278
|
+
let changed = false;
|
|
20279
|
+
for (const e of entries) {
|
|
20280
|
+
const t = tiles.get(e.target.dataset.id ?? "");
|
|
20281
|
+
if (!t || t.el !== e.target)
|
|
20282
|
+
continue;
|
|
20283
|
+
const w = t.el.offsetWidth;
|
|
20284
|
+
const h = t.el.offsetHeight;
|
|
20285
|
+
if (w !== t.w || h !== t.h) {
|
|
20286
|
+
t.w = w;
|
|
20287
|
+
t.h = h;
|
|
20288
|
+
changed = true;
|
|
20289
|
+
}
|
|
20290
|
+
}
|
|
20291
|
+
if (changed && !relayoutQueued)
|
|
20292
|
+
relayoutQueued = win.requestAnimationFrame(() => {
|
|
20293
|
+
relayoutQueued = 0;
|
|
20294
|
+
relayout(true);
|
|
20295
|
+
});
|
|
20296
|
+
});
|
|
20297
|
+
const rootRo = new win.ResizeObserver(() => {
|
|
20298
|
+
const w = root.clientWidth;
|
|
20299
|
+
const h = root.clientHeight;
|
|
20300
|
+
if (w === vw && h === vh)
|
|
20301
|
+
return;
|
|
20302
|
+
const was = { w: vw, h: vh };
|
|
20303
|
+
vw = w;
|
|
20304
|
+
vh = h;
|
|
20305
|
+
if (pristine)
|
|
20306
|
+
return relayout(false);
|
|
20307
|
+
if (was.w && was.h) {
|
|
20308
|
+
const dx = (w - was.w) / 2;
|
|
20309
|
+
const dy = (h - was.h) / 2;
|
|
20310
|
+
if (jump)
|
|
20311
|
+
startJump({ x: jump.to.x + dx, y: jump.to.y + dy, scale: jump.to.scale }, jump.t.omega === FIT.omega ? FIT : KEY2);
|
|
20312
|
+
view = { x: view.x + dx, y: view.y + dy, scale: view.scale };
|
|
20313
|
+
drawView();
|
|
20314
|
+
}
|
|
20315
|
+
});
|
|
20316
|
+
const frame = (dt) => {
|
|
20317
|
+
const t0 = performance.now();
|
|
20318
|
+
frames++;
|
|
20319
|
+
let moving = false;
|
|
20320
|
+
if (jump) {
|
|
20321
|
+
jump.t.step(dt);
|
|
20322
|
+
if (jump.t.resting) {
|
|
20323
|
+
view = jump.to;
|
|
20324
|
+
jump = null;
|
|
20325
|
+
} else {
|
|
20326
|
+
view = jump.path(Math.min(1, jump.t.x));
|
|
20327
|
+
moving = true;
|
|
20328
|
+
}
|
|
20329
|
+
moved();
|
|
20330
|
+
}
|
|
20331
|
+
if (coast) {
|
|
20332
|
+
const decay = Math.exp(-dt / COAST_TAU);
|
|
20333
|
+
const travel = COAST_TAU * (1 - decay);
|
|
20334
|
+
let v = {
|
|
20335
|
+
x: view.x + coast.vx * travel,
|
|
20336
|
+
y: view.y + coast.vy * travel,
|
|
20337
|
+
scale: view.scale
|
|
20338
|
+
};
|
|
20339
|
+
if (coast.vs)
|
|
20340
|
+
v = zoomAround(v, v.scale * Math.exp(coast.vs * travel), coast.at, minZoom, maxZoom);
|
|
20341
|
+
view = v;
|
|
20342
|
+
coast.vx *= decay;
|
|
20343
|
+
coast.vy *= decay;
|
|
20344
|
+
coast.vs *= decay;
|
|
20345
|
+
if (Math.hypot(coast.vx, coast.vy) < 10 && Math.abs(coast.vs) < 0.02)
|
|
20346
|
+
coast = null;
|
|
20347
|
+
else
|
|
20348
|
+
moving = true;
|
|
20349
|
+
moved();
|
|
20350
|
+
}
|
|
20351
|
+
drawView();
|
|
20352
|
+
for (const t of tiles.values())
|
|
20353
|
+
if (!t.x.resting || !t.y.resting) {
|
|
20354
|
+
t.x.step(dt);
|
|
20355
|
+
t.y.step(dt);
|
|
20356
|
+
drawTile(t);
|
|
20357
|
+
moving = true;
|
|
20358
|
+
}
|
|
20359
|
+
times.push(performance.now() - t0);
|
|
20360
|
+
if (times.length > 600)
|
|
20361
|
+
times.splice(0, times.length - 600);
|
|
20362
|
+
return moving;
|
|
20363
|
+
};
|
|
20364
|
+
const loop = frameLoop(win, frame);
|
|
20365
|
+
const pathLength = (a, b) => Math.abs(Math.log(b.scale / a.scale)) + Math.hypot(b.x - a.x, b.y - a.y) / Math.max(1, vw, vh);
|
|
20366
|
+
const startJump = (to, m) => {
|
|
20367
|
+
pristine = false;
|
|
20368
|
+
coast = null;
|
|
20369
|
+
if (reduced()) {
|
|
20370
|
+
jump = null;
|
|
20371
|
+
setView(to);
|
|
20372
|
+
return;
|
|
20373
|
+
}
|
|
20374
|
+
let v = 0;
|
|
20375
|
+
if (jump) {
|
|
20376
|
+
const len = pathLength(view, to);
|
|
20377
|
+
if (len > 0.000001)
|
|
20378
|
+
v = clamp3(jump.t.v * pathLength(jump.from, jump.to) / len, 0, 40);
|
|
20379
|
+
}
|
|
20380
|
+
const t = new Spring(0, m.omega, m.zeta, 0.0005).set(1);
|
|
20381
|
+
t.v = v;
|
|
20382
|
+
jump = { from: view, to, path: viewPath(view, to), t };
|
|
20383
|
+
moved();
|
|
20384
|
+
loop.wake();
|
|
20385
|
+
};
|
|
20386
|
+
const target = () => jump?.to ?? view;
|
|
20387
|
+
const centre = () => ({ x: vw / 2, y: vh / 2 });
|
|
20388
|
+
const grab = () => {
|
|
20389
|
+
pristine = false;
|
|
20390
|
+
jump = null;
|
|
20391
|
+
coast = null;
|
|
20392
|
+
};
|
|
20393
|
+
const local = (e) => {
|
|
20394
|
+
const r = root.getBoundingClientRect();
|
|
20395
|
+
return { x: e.clientX - r.left, y: e.clientY - r.top };
|
|
20396
|
+
};
|
|
20397
|
+
let pointer = null;
|
|
20398
|
+
let pinching = false;
|
|
20399
|
+
const onWheel = (e) => {
|
|
20400
|
+
e.preventDefault();
|
|
20401
|
+
const k = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? vh : 1;
|
|
20402
|
+
if (e.ctrlKey || e.metaKey) {
|
|
20403
|
+
if (pinching)
|
|
20404
|
+
return;
|
|
20405
|
+
const dy = e.deltaY * k;
|
|
20406
|
+
const at = local(e);
|
|
20407
|
+
grab();
|
|
20408
|
+
setView(zoomAround(view, view.scale * wheelZoom(dy, e.ctrlKey), at, minZoom, maxZoom));
|
|
20409
|
+
return;
|
|
20410
|
+
}
|
|
20411
|
+
let dx = e.deltaX * k;
|
|
20412
|
+
let dy = e.deltaY * k;
|
|
20413
|
+
if (e.shiftKey && !dx) {
|
|
20414
|
+
dx = dy;
|
|
20415
|
+
dy = 0;
|
|
20416
|
+
}
|
|
20417
|
+
grab();
|
|
20418
|
+
setView({ x: view.x - dx, y: view.y - dy, scale: view.scale });
|
|
20419
|
+
};
|
|
20420
|
+
let gestureScale = 1;
|
|
20421
|
+
const onGesture = (e) => {
|
|
20422
|
+
e.preventDefault();
|
|
20423
|
+
const g = e;
|
|
20424
|
+
if (e.type === "gesturestart") {
|
|
20425
|
+
gestureScale = 1;
|
|
20426
|
+
pinching = true;
|
|
20427
|
+
grab();
|
|
20428
|
+
return;
|
|
20429
|
+
}
|
|
20430
|
+
if (e.type === "gestureend") {
|
|
20431
|
+
pinching = false;
|
|
20432
|
+
return;
|
|
20433
|
+
}
|
|
20434
|
+
const at = g.clientX !== undefined ? local(g) : centre();
|
|
20435
|
+
setView(zoomAround(view, view.scale * g.scale / gestureScale, at, minZoom, maxZoom));
|
|
20436
|
+
gestureScale = g.scale;
|
|
20437
|
+
};
|
|
20438
|
+
let drag = null;
|
|
20439
|
+
const touches = new Map;
|
|
20440
|
+
let pinch = null;
|
|
20441
|
+
let space = false;
|
|
20442
|
+
let spaceDragged = false;
|
|
20443
|
+
let hovering = false;
|
|
20444
|
+
let pressed = null;
|
|
20445
|
+
const capped = (v) => {
|
|
20446
|
+
const speed = Math.hypot(v.x, v.y);
|
|
20447
|
+
return speed > MAX_FLING ? { x: v.x / speed * MAX_FLING, y: v.y / speed * MAX_FLING } : v;
|
|
20448
|
+
};
|
|
20449
|
+
const onBackground = (target) => {
|
|
20450
|
+
const el = target;
|
|
20451
|
+
return !el?.closest?.(".knobs-canvas-card,.knobs-canvas-zoom");
|
|
20452
|
+
};
|
|
20453
|
+
const beginDrag = (e, grabbing) => {
|
|
20454
|
+
grab();
|
|
20455
|
+
held = true;
|
|
20456
|
+
drag = { id: e.pointerId, last: local(e), samples: [] };
|
|
20457
|
+
root.dataset.panning = "";
|
|
20458
|
+
if (space)
|
|
20459
|
+
spaceDragged = true;
|
|
20460
|
+
if (grabbing)
|
|
20461
|
+
root.dataset.grabbing = "";
|
|
20462
|
+
try {
|
|
20463
|
+
root.setPointerCapture(e.pointerId);
|
|
20464
|
+
} catch {}
|
|
20465
|
+
moved();
|
|
20466
|
+
};
|
|
20467
|
+
const endDrag = (coastOn) => {
|
|
20468
|
+
const d = drag;
|
|
20469
|
+
drag = null;
|
|
20470
|
+
held = false;
|
|
20471
|
+
delete root.dataset.panning;
|
|
20472
|
+
delete root.dataset.grabbing;
|
|
20473
|
+
if (!d)
|
|
20474
|
+
return;
|
|
20475
|
+
const v = coastOn ? capped(releaseVelocity(d.samples, performance.now())) : { x: 0, y: 0 };
|
|
20476
|
+
if (Math.hypot(v.x, v.y) > 60) {
|
|
20477
|
+
coast = { vx: v.x, vy: v.y, vs: 0, at: centre() };
|
|
20478
|
+
loop.wake();
|
|
20479
|
+
}
|
|
20480
|
+
moved();
|
|
20481
|
+
};
|
|
20482
|
+
const spread = () => {
|
|
20483
|
+
const [a, b] = [...touches.values()];
|
|
20484
|
+
return a && b ? { d: Math.hypot(a.x - b.x, a.y - b.y), mid: { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 } } : null;
|
|
20485
|
+
};
|
|
20486
|
+
const onPointerDown = (e) => {
|
|
20487
|
+
pristine = false;
|
|
20488
|
+
if (e.isPrimary)
|
|
20489
|
+
pressed = e.target;
|
|
20490
|
+
if (e.pointerType === "touch") {
|
|
20491
|
+
touches.set(e.pointerId, local(e));
|
|
20492
|
+
if (touches.size === 2) {
|
|
20493
|
+
if (drag)
|
|
20494
|
+
endDrag(false);
|
|
20495
|
+
const s = spread();
|
|
20496
|
+
grab();
|
|
20497
|
+
held = true;
|
|
20498
|
+
pinch = { d: s.d, mid: s.mid, samples: [] };
|
|
20499
|
+
root.dataset.panning = "";
|
|
20500
|
+
for (const id of touches.keys())
|
|
20501
|
+
try {
|
|
20502
|
+
root.setPointerCapture(id);
|
|
20503
|
+
} catch {}
|
|
20504
|
+
e.preventDefault();
|
|
20505
|
+
e.stopPropagation();
|
|
20506
|
+
return;
|
|
20507
|
+
}
|
|
20508
|
+
if (touches.size === 1 && onBackground(e.target))
|
|
20509
|
+
beginDrag(e, false);
|
|
20510
|
+
return;
|
|
20511
|
+
}
|
|
20512
|
+
const middle = e.button === 1;
|
|
20513
|
+
if (!middle && e.button !== 0)
|
|
20514
|
+
return;
|
|
20515
|
+
if (!middle && !space && !onBackground(e.target))
|
|
20516
|
+
return;
|
|
20517
|
+
e.preventDefault();
|
|
20518
|
+
if (!middle && onBackground(e.target))
|
|
20519
|
+
root.focus({ preventScroll: true });
|
|
20520
|
+
beginDrag(e, middle || space);
|
|
20521
|
+
};
|
|
20522
|
+
const onPointerMove = (e) => {
|
|
20523
|
+
if (e.pointerType !== "touch")
|
|
20524
|
+
pointer = local(e);
|
|
20525
|
+
if (touches.has(e.pointerId))
|
|
20526
|
+
touches.set(e.pointerId, local(e));
|
|
20527
|
+
if (pinch) {
|
|
20528
|
+
const s = spread();
|
|
20529
|
+
if (!s)
|
|
20530
|
+
return;
|
|
20531
|
+
const v = pinchView(view, pinch.mid, s.mid, s.d / Math.max(1, pinch.d), minZoom, maxZoom);
|
|
20532
|
+
pinch.d = s.d;
|
|
20533
|
+
pinch.mid = s.mid;
|
|
20534
|
+
pinch.samples.push({ x: s.mid.x, y: s.mid.y, t: performance.now(), s: Math.log(v.scale) });
|
|
20535
|
+
if (pinch.samples.length > 12)
|
|
20536
|
+
pinch.samples.shift();
|
|
20537
|
+
setView(v);
|
|
20538
|
+
return;
|
|
20539
|
+
}
|
|
20540
|
+
if (!drag || drag.id !== e.pointerId)
|
|
20541
|
+
return;
|
|
20542
|
+
const p = local(e);
|
|
20543
|
+
const dx = p.x - drag.last.x;
|
|
20544
|
+
const dy = p.y - drag.last.y;
|
|
20545
|
+
drag.last = p;
|
|
20546
|
+
drag.samples.push({ x: p.x, y: p.y, t: performance.now() });
|
|
20547
|
+
if (drag.samples.length > 12)
|
|
20548
|
+
drag.samples.shift();
|
|
20549
|
+
setView({ x: view.x + dx, y: view.y + dy, scale: view.scale });
|
|
20550
|
+
};
|
|
20551
|
+
const onPointerUp = (e) => {
|
|
20552
|
+
if (touches.delete(e.pointerId) && pinch) {
|
|
20553
|
+
if (touches.size < 2) {
|
|
20554
|
+
const p = pinch;
|
|
20555
|
+
pinch = null;
|
|
20556
|
+
held = false;
|
|
20557
|
+
delete root.dataset.panning;
|
|
20558
|
+
const now = performance.now();
|
|
20559
|
+
const v = capped(releaseVelocity(p.samples, now));
|
|
20560
|
+
const first = p.samples.find((s) => now - s.t <= 80) ?? p.samples[0];
|
|
20561
|
+
const last = p.samples.at(-1);
|
|
20562
|
+
const dt = first && last ? (last.t - first.t) / 1000 : 0;
|
|
20563
|
+
const vs = dt > 0 && now - last.t < 60 ? clamp3((last.s - first.s) / dt, -4, 4) : 0;
|
|
20564
|
+
if (Math.hypot(v.x, v.y) > 60 || Math.abs(vs) > 0.3) {
|
|
20565
|
+
coast = { vx: v.x, vy: v.y, vs, at: p.mid };
|
|
20566
|
+
loop.wake();
|
|
20567
|
+
}
|
|
20568
|
+
moved();
|
|
20569
|
+
}
|
|
20570
|
+
return;
|
|
20571
|
+
}
|
|
20572
|
+
if (drag && drag.id === e.pointerId)
|
|
20573
|
+
endDrag(e.type === "pointerup");
|
|
20574
|
+
};
|
|
20575
|
+
const onDblClick = (e) => {
|
|
20576
|
+
if (space)
|
|
20577
|
+
return;
|
|
20578
|
+
const el = e.target === root && pressed && root.contains(pressed) ? pressed : e.target;
|
|
20579
|
+
const tileEl = el.closest?.(".knobs-canvas-tile");
|
|
20580
|
+
if (tileEl) {
|
|
20581
|
+
const card = el.closest(".knobs-canvas-card");
|
|
20582
|
+
if (card && (el.closest(INTERACTIVE) || isTypingTarget(el)))
|
|
20583
|
+
return;
|
|
20584
|
+
const id = tileEl.dataset.id;
|
|
20585
|
+
const f = focusTarget(id);
|
|
20586
|
+
if (!f)
|
|
20587
|
+
return;
|
|
20588
|
+
const t = target();
|
|
20589
|
+
const there = Math.abs(t.scale / f.scale - 1) < 0.02 && Math.abs(t.x - f.x) < 4 && Math.abs(t.y - f.y) < 4;
|
|
20590
|
+
if (there)
|
|
20591
|
+
api.fit();
|
|
20592
|
+
else
|
|
20593
|
+
startJump(f, FIT);
|
|
20594
|
+
return;
|
|
20595
|
+
}
|
|
20596
|
+
if (onBackground(el))
|
|
20597
|
+
api.fit();
|
|
20598
|
+
};
|
|
20599
|
+
const keyAnchor = () => hovering && pointer ? pointer : centre();
|
|
20600
|
+
const onKey = (e) => {
|
|
20601
|
+
if (isTypingTarget(e.target))
|
|
20602
|
+
return;
|
|
20603
|
+
const a = keyAction(e);
|
|
20604
|
+
if (!a || a === "actual" && (e.metaKey || e.ctrlKey))
|
|
20605
|
+
return;
|
|
20606
|
+
e.preventDefault();
|
|
20607
|
+
e.stopPropagation();
|
|
20608
|
+
if (a === "fit")
|
|
20609
|
+
return api.fit();
|
|
20610
|
+
const from = target();
|
|
20611
|
+
const scale = a === "actual" ? 1 : a === "in" ? from.scale * KEY_ZOOM : from.scale / KEY_ZOOM;
|
|
20612
|
+
startJump(zoomAround(from, scale, keyAnchor(), minZoom, maxZoom), KEY2);
|
|
20613
|
+
};
|
|
20614
|
+
const spaceOwner = (e) => e.key === " " && !e.metaKey && !e.ctrlKey && !e.altKey && !isTypingTarget(e.target) && (hovering || root.contains(doc.activeElement));
|
|
20615
|
+
const onControl = () => {
|
|
20616
|
+
const el = doc.activeElement;
|
|
20617
|
+
return !!el && el !== root && root.contains(el) && !!el.closest(INTERACTIVE);
|
|
20618
|
+
};
|
|
20619
|
+
const onDocKeyDown = (e) => {
|
|
20620
|
+
if (!spaceOwner(e))
|
|
20621
|
+
return;
|
|
20622
|
+
if (!onControl())
|
|
20623
|
+
e.preventDefault();
|
|
20624
|
+
if (space)
|
|
20625
|
+
return;
|
|
20626
|
+
space = true;
|
|
20627
|
+
spaceDragged = false;
|
|
20628
|
+
root.dataset.space = "";
|
|
20629
|
+
};
|
|
20630
|
+
const endSpace = () => {
|
|
20631
|
+
space = false;
|
|
20632
|
+
delete root.dataset.space;
|
|
20633
|
+
if (!drag)
|
|
20634
|
+
delete root.dataset.grabbing;
|
|
20635
|
+
};
|
|
20636
|
+
const onDocKeyUp = (e) => {
|
|
20637
|
+
if (e.key !== " " || !space)
|
|
20638
|
+
return;
|
|
20639
|
+
if (spaceDragged || !onControl())
|
|
20640
|
+
e.preventDefault();
|
|
20641
|
+
endSpace();
|
|
20642
|
+
};
|
|
20643
|
+
root.addEventListener("wheel", onWheel, { passive: false });
|
|
20644
|
+
root.addEventListener("gesturestart", onGesture);
|
|
20645
|
+
root.addEventListener("gesturechange", onGesture);
|
|
20646
|
+
root.addEventListener("gestureend", onGesture);
|
|
20647
|
+
root.addEventListener("pointerdown", onPointerDown, { capture: true });
|
|
20648
|
+
root.addEventListener("pointermove", onPointerMove);
|
|
20649
|
+
root.addEventListener("pointerup", onPointerUp);
|
|
20650
|
+
root.addEventListener("pointercancel", onPointerUp);
|
|
20651
|
+
root.addEventListener("pointerenter", () => hovering = true);
|
|
20652
|
+
root.addEventListener("pointerleave", () => {
|
|
20653
|
+
hovering = false;
|
|
20654
|
+
});
|
|
20655
|
+
root.addEventListener("auxclick", (e) => e.button === 1 && e.preventDefault());
|
|
20656
|
+
root.addEventListener("dblclick", onDblClick);
|
|
20657
|
+
root.addEventListener("keydown", onKey);
|
|
20658
|
+
doc.addEventListener("keydown", onDocKeyDown, { capture: true });
|
|
20659
|
+
doc.addEventListener("keyup", onDocKeyUp, { capture: true });
|
|
20660
|
+
win.addEventListener("blur", endSpace);
|
|
20661
|
+
root.addEventListener("knobs-restyle", () => {
|
|
20662
|
+
measure();
|
|
20663
|
+
relayout(true);
|
|
20664
|
+
});
|
|
20665
|
+
const focusTarget = (id) => {
|
|
20666
|
+
const r = rects.get(id);
|
|
20667
|
+
if (!r || !vw || !vh)
|
|
20668
|
+
return null;
|
|
20669
|
+
return fitView(r, vw, vh, { pad, min: minZoom, max: Math.min(maxZoom, FOCUS_MAX) });
|
|
20670
|
+
};
|
|
20671
|
+
for (const it of items)
|
|
20672
|
+
mount(it, false);
|
|
20673
|
+
vw = root.clientWidth;
|
|
20674
|
+
vh = root.clientHeight;
|
|
20675
|
+
measure();
|
|
20676
|
+
relayout(false);
|
|
20677
|
+
rootRo.observe(root);
|
|
20678
|
+
drawView();
|
|
20679
|
+
const api = {
|
|
20680
|
+
element: root,
|
|
20681
|
+
get view() {
|
|
20682
|
+
return view;
|
|
20683
|
+
},
|
|
20684
|
+
get rects() {
|
|
20685
|
+
return rects;
|
|
20686
|
+
},
|
|
20687
|
+
fit() {
|
|
20688
|
+
const f = fitTarget();
|
|
20689
|
+
if (f)
|
|
20690
|
+
startJump(f, FIT);
|
|
20691
|
+
},
|
|
20692
|
+
focus(id) {
|
|
20693
|
+
const f = focusTarget(id);
|
|
20694
|
+
if (f)
|
|
20695
|
+
startJump(f, FIT);
|
|
20696
|
+
},
|
|
20697
|
+
zoomTo(scale, at) {
|
|
20698
|
+
const from = target();
|
|
20699
|
+
startJump(zoomAround(from, scale, at ?? centre(), minZoom, maxZoom), KEY2);
|
|
20700
|
+
},
|
|
20701
|
+
update(next) {
|
|
20702
|
+
const ids = new Set(next.map((it) => it.id));
|
|
20703
|
+
for (const t of [...tiles.values()])
|
|
20704
|
+
if (!ids.has(t.id))
|
|
20705
|
+
leave(t);
|
|
20706
|
+
for (const it of next) {
|
|
20707
|
+
const t = tiles.get(it.id);
|
|
20708
|
+
if (!t) {
|
|
20709
|
+
mount(it, true);
|
|
20710
|
+
continue;
|
|
20711
|
+
}
|
|
20712
|
+
if (t.item.render !== it.render) {
|
|
20713
|
+
t.cleanup?.();
|
|
20714
|
+
t.card.replaceChildren();
|
|
20715
|
+
t.cleanup = it.render(t.card) || undefined;
|
|
20716
|
+
}
|
|
20717
|
+
t.item = it;
|
|
20718
|
+
setTitle(t);
|
|
20719
|
+
sizeCard(t);
|
|
20720
|
+
}
|
|
20721
|
+
items = next;
|
|
20722
|
+
measure();
|
|
20723
|
+
relayout(true);
|
|
20724
|
+
},
|
|
20725
|
+
get moving() {
|
|
20726
|
+
return loop.running;
|
|
20727
|
+
},
|
|
20728
|
+
stats() {
|
|
20729
|
+
const s = [...times].sort((a, b) => a - b);
|
|
20730
|
+
const q = (p) => Math.round((s[Math.floor(p * (s.length - 1))] ?? 0) * 1000) / 1000;
|
|
20731
|
+
return { frames, p50: q(0.5), p95: q(0.95), max: q(1) };
|
|
20732
|
+
},
|
|
20733
|
+
dispose() {
|
|
20734
|
+
loop.stop();
|
|
20735
|
+
ro.disconnect();
|
|
20736
|
+
rootRo.disconnect();
|
|
20737
|
+
win.clearTimeout(settleTimer);
|
|
20738
|
+
if (relayoutQueued)
|
|
20739
|
+
win.cancelAnimationFrame(relayoutQueued);
|
|
20740
|
+
doc.removeEventListener("keydown", onDocKeyDown, { capture: true });
|
|
20741
|
+
doc.removeEventListener("keyup", onDocKeyUp, { capture: true });
|
|
20742
|
+
win.removeEventListener("blur", endSpace);
|
|
20743
|
+
for (const t of [...tiles.values(), ...leaving])
|
|
20744
|
+
unmount(t);
|
|
20745
|
+
root.remove();
|
|
20746
|
+
}
|
|
20747
|
+
};
|
|
20748
|
+
return api;
|
|
20749
|
+
}
|
|
19776
20750
|
// node_modules/@kitlangton/rolling-number/dist/chunk-vm1v7ncb.js
|
|
19777
20751
|
var z = new Map;
|
|
19778
20752
|
var le = /[\u0590-\u08ff\u200e\u200f\u202a-\u202e\u2066-\u2069\ufb1d-\ufeff]/u;
|
|
@@ -25479,6 +26453,8 @@ export {
|
|
|
25479
26453
|
bento,
|
|
25480
26454
|
neighbour as bentoNeighbour,
|
|
25481
26455
|
windowOf as bentoWindow,
|
|
26456
|
+
canvas,
|
|
26457
|
+
boundsOf as canvasBounds,
|
|
25482
26458
|
claimsDigit,
|
|
25483
26459
|
clampView,
|
|
25484
26460
|
codeDiff,
|
|
@@ -25506,6 +26482,7 @@ export {
|
|
|
25506
26482
|
diffStatus,
|
|
25507
26483
|
diffTree,
|
|
25508
26484
|
digitPick,
|
|
26485
|
+
dotGrid,
|
|
25509
26486
|
drag as dragDivider,
|
|
25510
26487
|
dropTarget,
|
|
25511
26488
|
facts,
|
|
@@ -25513,6 +26490,7 @@ export {
|
|
|
25513
26490
|
fileKey,
|
|
25514
26491
|
filesUnder,
|
|
25515
26492
|
fitRect,
|
|
26493
|
+
fitView,
|
|
25516
26494
|
flatten,
|
|
25517
26495
|
flattenActivity,
|
|
25518
26496
|
followView,
|
|
@@ -25533,6 +26511,7 @@ export {
|
|
|
25533
26511
|
keepSelection as keepActivitySelection,
|
|
25534
26512
|
keepFile,
|
|
25535
26513
|
keepPick,
|
|
26514
|
+
layoutCanvas,
|
|
25536
26515
|
letterbox,
|
|
25537
26516
|
magnet,
|
|
25538
26517
|
marks,
|
|
@@ -25552,6 +26531,7 @@ export {
|
|
|
25552
26531
|
normalizeWalkthrough,
|
|
25553
26532
|
optionPicker,
|
|
25554
26533
|
overlayLook,
|
|
26534
|
+
packRows,
|
|
25555
26535
|
pageHints,
|
|
25556
26536
|
parseLayout,
|
|
25557
26537
|
parsePatch,
|
|
@@ -25606,6 +26586,7 @@ export {
|
|
|
25606
26586
|
tree,
|
|
25607
26587
|
typeahead,
|
|
25608
26588
|
validMove,
|
|
26589
|
+
viewPath,
|
|
25609
26590
|
virtualWindow,
|
|
25610
26591
|
walkthrough,
|
|
25611
26592
|
walkthroughProgress,
|
|
@@ -25614,5 +26595,6 @@ export {
|
|
|
25614
26595
|
wipeKey,
|
|
25615
26596
|
wipeX,
|
|
25616
26597
|
zoomAbout,
|
|
26598
|
+
zoomAround,
|
|
25617
26599
|
zoomView
|
|
25618
26600
|
};
|