@kitlangton/knobs 0.4.0-next.11 → 0.4.0-next.12
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 +88 -12
- package/dist/dev/{chunk-mjdgzad5.js → chunk-7kns3qe6.js} +29 -25
- package/dist/dev/{chunk-bnhzf1tj.js → chunk-9qtvxa6k.js} +1378 -1066
- package/dist/dev/{chunk-75gbzbyd.js → chunk-ab7vfghg.js} +2 -5
- package/dist/dev/{chunk-j57rhjtm.js → chunk-nwfm1bfj.js} +89 -37
- package/dist/dev/index.js +41 -18
- package/dist/index.js +64 -15
- package/dist/panel.js +2788 -2294
- 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 +8 -2
- package/dist/types/core/schema.d.ts +52 -4
- package/dist/types/core/store.d.ts +7 -0
- 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 +14 -0
- 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-react.js
CHANGED
|
@@ -2965,13 +2965,17 @@ var css2 = `
|
|
|
2965
2965
|
.knobs-tree-row[aria-expanded="true"]>.knobs-tree-caret{transform:rotate(90deg)}
|
|
2966
2966
|
.knobs-tree-row:not([aria-expanded])>.knobs-tree-caret{visibility:hidden}
|
|
2967
2967
|
.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)}
|
|
2968
|
-
|
|
2968
|
+
/* The label wins the room: it keeps its width while the detail (which only takes what's left)
|
|
2969
|
+
fades out at its end, and gives way only once the detail is gone. Each fades where it's cut. */
|
|
2970
|
+
.knobs-tree-label{position:relative;flex:0 1 auto;min-width:0;overflow:hidden;font-weight:var(--_k-weight)}
|
|
2971
|
+
.knobs-tree-row[data-editing]>.knobs-tree-label{flex-grow:1}
|
|
2969
2972
|
.knobs-tree-row[data-ancestor] .knobs-tree-label{color:var(--_k-fg-2)}
|
|
2970
2973
|
.knobs-tree-label .knobs-tree-mark{color:var(--_k-accent)}
|
|
2971
2974
|
.knobs-tree-row[data-off~="visible"] :is(.knobs-tree-label,.knobs-tree-icon){opacity:.45}
|
|
2972
2975
|
.knobs-tree-row[data-off~="locked"] .knobs-tree-label{color:var(--_k-fg-2)}
|
|
2973
|
-
.knobs-tree-detail{flex:
|
|
2974
|
-
.knobs-tree-
|
|
2976
|
+
.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}
|
|
2977
|
+
.knobs-tree-row[data-editing]>.knobs-tree-detail{flex-grow:0;visibility:hidden}
|
|
2978
|
+
.knobs-tree-tools{flex:none;display:flex;align-items:center;gap:1px;margin-left:auto;padding-left:4px}
|
|
2975
2979
|
.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}
|
|
2976
2980
|
.knobs-ui .knobs-tree-toggle svg{width:12px;height:12px}
|
|
2977
2981
|
.knobs-tree-row:is(:hover,[data-active],[aria-selected="true"]) .knobs-tree-toggle,.knobs-ui .knobs-tree-toggle[aria-pressed="false"]{opacity:1}
|
|
@@ -4576,13 +4580,13 @@ function orderHintGroups(groups) {
|
|
|
4576
4580
|
}
|
|
4577
4581
|
function pageHints(doc = document) {
|
|
4578
4582
|
const holder = doc;
|
|
4579
|
-
const registry = holder[
|
|
4580
|
-
holder[
|
|
4583
|
+
const registry = holder[KEY2] ?? createRegistry(doc);
|
|
4584
|
+
holder[KEY2] = registry;
|
|
4581
4585
|
return registry;
|
|
4582
4586
|
}
|
|
4583
4587
|
function claimsHintKey(doc, key) {
|
|
4584
4588
|
const holder = doc;
|
|
4585
|
-
return key === PAGE_HINT_KEY && Boolean(holder[
|
|
4589
|
+
return key === PAGE_HINT_KEY && Boolean(holder[KEY2]?.listening);
|
|
4586
4590
|
}
|
|
4587
4591
|
function activateElement(el) {
|
|
4588
4592
|
const pressable = el.matches(PRESSABLE) || el.tabIndex < 0 && !el.matches("input,select,textarea");
|
|
@@ -4948,7 +4952,7 @@ function layerPainter(doc, groups) {
|
|
|
4948
4952
|
}
|
|
4949
4953
|
};
|
|
4950
4954
|
}
|
|
4951
|
-
var HINT_LETTERS = "asdfghjkleruiowtyqpcvnmxzb", HOME_ROW = 9, COLUMN = 200, PAGE_HINT_KEY = "s",
|
|
4955
|
+
var HINT_LETTERS = "asdfghjkleruiowtyqpcvnmxzb", HOME_ROW = 9, COLUMN = 200, PAGE_HINT_KEY = "s", KEY2, hints, isTyping, PRESSABLE = "button,a[href],summary,[role=button],[role=tab],[role=radio],[role=switch],[role=checkbox],[role=menuitem],[role=option]", labelOf = (el) => (el.dataset.hint || el.getAttribute("aria-label") || el.textContent || "").trim(), STYLE_ID = "knobs-page-hints-css", CSS = `
|
|
4952
4956
|
.knobs-page-hints{position:fixed;left:0;top:0;width:0;height:0;pointer-events:none;z-index:2147483000}
|
|
4953
4957
|
.knobs-page-hint{position:fixed;transform:translateY(-50%);display:inline-flex;align-items:center;height:15px;padding:0 4px;border-radius:var(--kh-radius);background:var(--kh-accent);color:var(--kh-bg);
|
|
4954
4958
|
box-shadow:0 1px 3px rgba(0,0,0,calc(.35 * var(--kh-lift)));font:700 var(--kh-size)/1 var(--kh-mono);letter-spacing:.04em;text-transform:uppercase;white-space:nowrap;pointer-events:none;
|
|
@@ -4962,7 +4966,7 @@ var HINT_LETTERS = "asdfghjkleruiowtyqpcvnmxzb", HOME_ROW = 9, COLUMN = 200, PAG
|
|
|
4962
4966
|
var init_page_hints = __esm(() => {
|
|
4963
4967
|
init_keys();
|
|
4964
4968
|
init_hints();
|
|
4965
|
-
|
|
4969
|
+
KEY2 = Symbol.for("knobs.pageHints.v1");
|
|
4966
4970
|
hints = {
|
|
4967
4971
|
register(root, targets, options) {
|
|
4968
4972
|
return pageHints(root.ownerDocument).register(root, targets, options);
|
|
@@ -5327,7 +5331,7 @@ function tree(container, options) {
|
|
|
5327
5331
|
text.className = "knobs-tree-text";
|
|
5328
5332
|
label.append(text);
|
|
5329
5333
|
const detail = doc.createElement("span");
|
|
5330
|
-
detail.className = "knobs-tree-detail";
|
|
5334
|
+
detail.className = "knobs-tree-detail knobs-scroll-fade-x";
|
|
5331
5335
|
const tools = doc.createElement("span");
|
|
5332
5336
|
tools.className = "knobs-tree-tools";
|
|
5333
5337
|
el.append(caret, icon, label, detail, tools);
|
|
@@ -7790,13 +7794,13 @@ function place(o) {
|
|
|
7790
7794
|
if (vertical) {
|
|
7791
7795
|
y = side === "bottom" ? a.y + a.h + offset : a.y - offset - size.h;
|
|
7792
7796
|
x = align === "start" ? a.x : align === "end" ? a.x + a.w - size.w : a.x + (a.w - size.w) / 2;
|
|
7793
|
-
x =
|
|
7797
|
+
x = clamp3(x, v.x + margin, v.x + v.w - margin - size.w);
|
|
7794
7798
|
} else {
|
|
7795
7799
|
x = side === "right" ? a.x + a.w + offset : a.x - offset - size.w;
|
|
7796
7800
|
y = align === "start" ? a.y : align === "end" ? a.y + a.h - size.h : a.y + (a.h - size.h) / 2;
|
|
7797
|
-
y =
|
|
7801
|
+
y = clamp3(y, v.y + margin, v.y + v.h - margin - size.h);
|
|
7798
7802
|
}
|
|
7799
|
-
const origin = vertical ? { x:
|
|
7803
|
+
const origin = vertical ? { x: clamp3(a.x + a.w / 2 - x, 0, size.w), y: side === "bottom" ? 0 : size.h } : { x: side === "right" ? 0 : size.w, y: clamp3(a.y + a.h / 2 - y, 0, size.h) };
|
|
7800
7804
|
return {
|
|
7801
7805
|
x: Math.round(x),
|
|
7802
7806
|
y: Math.round(y),
|
|
@@ -7838,7 +7842,7 @@ function safeTriangle(from, sub) {
|
|
|
7838
7842
|
const edge = right ? sub.x : sub.x + sub.w;
|
|
7839
7843
|
return [from, { x: edge, y: sub.y - 4 }, { x: edge, y: sub.y + sub.h + 4 }];
|
|
7840
7844
|
}
|
|
7841
|
-
var opposite,
|
|
7845
|
+
var opposite, clamp3 = (v, lo, hi) => hi < lo ? lo : Math.max(lo, Math.min(hi, v));
|
|
7842
7846
|
var init_overlay_model = __esm(() => {
|
|
7843
7847
|
opposite = { top: "bottom", bottom: "top", left: "right", right: "left" };
|
|
7844
7848
|
});
|
|
@@ -11373,13 +11377,13 @@ function compare(container, options) {
|
|
|
11373
11377
|
let renderer = "dom";
|
|
11374
11378
|
let pending = false;
|
|
11375
11379
|
let gpu = null;
|
|
11376
|
-
const lay = new Spring(mode === "side" ? 0 : 1,
|
|
11380
|
+
const lay = new Spring(mode === "side" ? 0 : 1, LAYOUT3.omega, LAYOUT3.zeta, 0.0001);
|
|
11377
11381
|
const fade = new Spring(1, FADE6.omega, FADE6.zeta, 0.001);
|
|
11378
11382
|
let fromMode = mode;
|
|
11379
11383
|
let toMode = mode;
|
|
11380
11384
|
const wipeLine = new Spring(wipe, WIPE_CATCH.omega, WIPE_CATCH.zeta, 0.0001);
|
|
11381
11385
|
let wipeCatching = false;
|
|
11382
|
-
const gripAt = new Spring(0.5,
|
|
11386
|
+
const gripAt = new Spring(0.5, LAYOUT3.omega, LAYOUT3.zeta, 0.0001);
|
|
11383
11387
|
let loupeHeld = false;
|
|
11384
11388
|
let pointer = null;
|
|
11385
11389
|
let drag = null;
|
|
@@ -12686,7 +12690,7 @@ function compare(container, options) {
|
|
|
12686
12690
|
wake();
|
|
12687
12691
|
return api;
|
|
12688
12692
|
}
|
|
12689
|
-
var
|
|
12693
|
+
var LAYOUT3, FADE6, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, TAP_SLOP = 4, INSET = 8, GRIP, MARK, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
|
|
12690
12694
|
var init_compare = __esm(() => {
|
|
12691
12695
|
init_motion();
|
|
12692
12696
|
init_page_hints();
|
|
@@ -12700,7 +12704,7 @@ var init_compare = __esm(() => {
|
|
|
12700
12704
|
init_selection_pill();
|
|
12701
12705
|
init_timeline();
|
|
12702
12706
|
init_timeline_model();
|
|
12703
|
-
|
|
12707
|
+
LAYOUT3 = springMotion(SPRINGS.default);
|
|
12704
12708
|
FADE6 = springMotion(SPRINGS.snappy);
|
|
12705
12709
|
FADE_REDUCED = springMotion(FADE);
|
|
12706
12710
|
ZOOM = springMotion({ duration: 0.35, bounce: 0 });
|
|
@@ -15744,6 +15748,977 @@ function bento(container, options) {
|
|
|
15744
15748
|
};
|
|
15745
15749
|
}
|
|
15746
15750
|
|
|
15751
|
+
// src/ui/canvas.ts
|
|
15752
|
+
init_glide();
|
|
15753
|
+
init_motion();
|
|
15754
|
+
|
|
15755
|
+
// src/ui/canvas-css.ts
|
|
15756
|
+
var canvasCss = `
|
|
15757
|
+
.knobs-ui.knobs-canvas{--_kc-radius:var(--k-ui-canvas-radius,var(--_k-radius-panel));--_kc-fade:var(--k-ui-canvas-fade,16px);
|
|
15758
|
+
position:relative;width:100%;height:100%;overflow:hidden;outline:none;background:var(--k-ui-canvas-bg,var(--_k-bg-page));contain:strict;
|
|
15759
|
+
touch-action:none;overscroll-behavior:none;user-select:none;-webkit-user-select:none;cursor:default;
|
|
15760
|
+
font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
|
|
15761
|
+
:where(.knobs-canvas-stage,.knobs-canvas-stage *,.knobs-canvas-zoom,.knobs-canvas-zoom *){box-sizing:border-box}
|
|
15762
|
+
/* No hard edges: what slides out of view fades over the last few px. */
|
|
15763
|
+
.knobs-canvas-stage{position:absolute;inset:0;overflow:hidden;
|
|
15764
|
+
-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);
|
|
15765
|
+
-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}
|
|
15766
|
+
.knobs-canvas-dots{position:absolute;left:-128px;top:-128px;right:-128px;bottom:-128px;pointer-events:none;will-change:transform;
|
|
15767
|
+
background-image:radial-gradient(circle,var(--k-ui-canvas-dot,color-mix(in srgb,var(--_k-fg) 22%,transparent)) 1px,transparent 1.25px)}
|
|
15768
|
+
.knobs-canvas-world{position:absolute;left:0;top:0;width:0;height:0;transform-origin:0 0}
|
|
15769
|
+
.knobs-canvas[data-moving] .knobs-canvas-world{will-change:transform}
|
|
15770
|
+
.knobs-canvas:is([data-panning],[data-space]) .knobs-canvas-world{pointer-events:none}
|
|
15771
|
+
.knobs-canvas[data-space]{cursor:grab}
|
|
15772
|
+
.knobs-canvas[data-grabbing]{cursor:grabbing}
|
|
15773
|
+
.knobs-canvas-tile{position:absolute;left:0;top:0;display:flex;flex-direction:column;width:max-content;transform-origin:50% 50%}
|
|
15774
|
+
.knobs-canvas-title{flex:none;contain:inline-size;height:24px;padding:0 2px 8px;overflow:hidden;white-space:nowrap;color:var(--_k-fg-2);
|
|
15775
|
+
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)}
|
|
15776
|
+
.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));
|
|
15777
|
+
overflow:hidden;user-select:text;-webkit-user-select:text}
|
|
15778
|
+
.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}
|
|
15779
|
+
/* The readout: how far, and the way back to the whole set or 100%. */
|
|
15780
|
+
.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;
|
|
15781
|
+
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);
|
|
15782
|
+
font-variant-numeric:tabular-nums;font-feature-settings:"tnum";white-space:nowrap}
|
|
15783
|
+
: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;
|
|
15784
|
+
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;
|
|
15785
|
+
transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
15786
|
+
:where(.knobs-ui) .knobs-canvas-btn:is(:hover,:focus-visible){background:var(--_k-fill-hover);color:var(--_k-fg);transition-duration:0ms}
|
|
15787
|
+
:where(.knobs-ui) .knobs-canvas-btn:focus-visible{box-shadow:0 0 0 1.5px var(--_k-accent)}
|
|
15788
|
+
.knobs-canvas-pct{min-width:46px}
|
|
15789
|
+
.knobs-canvas-btn svg{width:13px;height:13px}
|
|
15790
|
+
`;
|
|
15791
|
+
|
|
15792
|
+
// src/ui/canvas-model.ts
|
|
15793
|
+
var clamp2 = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
15794
|
+
function packRows(sizes, opts) {
|
|
15795
|
+
const { gap, width } = opts;
|
|
15796
|
+
const x0 = opts.x ?? 0;
|
|
15797
|
+
let x = 0;
|
|
15798
|
+
let y = opts.y ?? 0;
|
|
15799
|
+
let rowH = 0;
|
|
15800
|
+
return sizes.map((s) => {
|
|
15801
|
+
if (x > 0 && x + s.w > width) {
|
|
15802
|
+
x = 0;
|
|
15803
|
+
y += rowH + gap;
|
|
15804
|
+
rowH = 0;
|
|
15805
|
+
}
|
|
15806
|
+
const r = { x: Math.round(x0 + x), y: Math.round(y), w: s.w, h: s.h };
|
|
15807
|
+
x += s.w + gap;
|
|
15808
|
+
rowH = Math.max(rowH, s.h);
|
|
15809
|
+
return r;
|
|
15810
|
+
});
|
|
15811
|
+
}
|
|
15812
|
+
function rowWidth(sizes, gap, aspect) {
|
|
15813
|
+
let widest = 0;
|
|
15814
|
+
let total = 0;
|
|
15815
|
+
for (const s of sizes) {
|
|
15816
|
+
widest = Math.max(widest, s.w);
|
|
15817
|
+
total += s.w + gap;
|
|
15818
|
+
}
|
|
15819
|
+
if (!sizes.length)
|
|
15820
|
+
return 0;
|
|
15821
|
+
const a = Number.isFinite(aspect) && aspect > 0 ? aspect : 16 / 10;
|
|
15822
|
+
const shape = (width) => {
|
|
15823
|
+
const b = boundsOf(packRows(sizes, { gap, width }));
|
|
15824
|
+
return b.w / Math.max(1, b.h);
|
|
15825
|
+
};
|
|
15826
|
+
let lo = widest;
|
|
15827
|
+
let hi = Math.max(widest, total);
|
|
15828
|
+
for (let i = 0;i < 24 && hi - lo > 1; i++) {
|
|
15829
|
+
const mid = (lo + hi) / 2;
|
|
15830
|
+
if (shape(mid) < a)
|
|
15831
|
+
lo = mid;
|
|
15832
|
+
else
|
|
15833
|
+
hi = mid;
|
|
15834
|
+
}
|
|
15835
|
+
return Math.abs(Math.log(shape(lo) / a)) <= Math.abs(Math.log(shape(hi) / a)) ? lo : hi;
|
|
15836
|
+
}
|
|
15837
|
+
function layoutCanvas(items, opts) {
|
|
15838
|
+
const placed = items.flatMap((it) => it.x !== undefined && it.y !== undefined ? [{ x: it.x, y: it.y, w: it.w, h: it.h }] : []);
|
|
15839
|
+
const free = items.filter((it) => it.x === undefined || it.y === undefined);
|
|
15840
|
+
const width = opts.width ?? rowWidth(free, opts.gap, opts.aspect ?? 16 / 10);
|
|
15841
|
+
const under = boundsOf(placed);
|
|
15842
|
+
const packed = packRows(free, {
|
|
15843
|
+
gap: opts.gap,
|
|
15844
|
+
width,
|
|
15845
|
+
x: under ? under.x : 0,
|
|
15846
|
+
y: under ? under.y + under.h + opts.gap * 2 : 0
|
|
15847
|
+
});
|
|
15848
|
+
let i = 0;
|
|
15849
|
+
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++]);
|
|
15850
|
+
return { rects, width };
|
|
15851
|
+
}
|
|
15852
|
+
function boundsOf(rects) {
|
|
15853
|
+
if (!rects.length)
|
|
15854
|
+
return null;
|
|
15855
|
+
let x0 = Number.POSITIVE_INFINITY;
|
|
15856
|
+
let y0 = Number.POSITIVE_INFINITY;
|
|
15857
|
+
let x1 = Number.NEGATIVE_INFINITY;
|
|
15858
|
+
let y1 = Number.NEGATIVE_INFINITY;
|
|
15859
|
+
for (const r of rects) {
|
|
15860
|
+
x0 = Math.min(x0, r.x);
|
|
15861
|
+
y0 = Math.min(y0, r.y);
|
|
15862
|
+
x1 = Math.max(x1, r.x + r.w);
|
|
15863
|
+
y1 = Math.max(y1, r.y + r.h);
|
|
15864
|
+
}
|
|
15865
|
+
return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
|
|
15866
|
+
}
|
|
15867
|
+
function zoomAround(v, scale, at, min = 0, max = Number.POSITIVE_INFINITY) {
|
|
15868
|
+
const s = clamp2(scale, min, max);
|
|
15869
|
+
const k = s / v.scale;
|
|
15870
|
+
return { x: at.x - (at.x - v.x) * k, y: at.y - (at.y - v.y) * k, scale: s };
|
|
15871
|
+
}
|
|
15872
|
+
function pinchView(v, from, to, ratio, min = 0, max = Number.POSITIVE_INFINITY) {
|
|
15873
|
+
const z = zoomAround(v, v.scale * ratio, from, min, max);
|
|
15874
|
+
return { x: z.x + to.x - from.x, y: z.y + to.y - from.y, scale: z.scale };
|
|
15875
|
+
}
|
|
15876
|
+
var WHEEL_ZOOM_MAX = 1.25;
|
|
15877
|
+
function wheelZoom(deltaY, pinch) {
|
|
15878
|
+
const f = Math.exp(-deltaY * (pinch ? 0.01 : 0.0025));
|
|
15879
|
+
return clamp2(f, 1 / WHEEL_ZOOM_MAX, WHEEL_ZOOM_MAX);
|
|
15880
|
+
}
|
|
15881
|
+
function fitView(r, w, h, opts = {}) {
|
|
15882
|
+
const pad = opts.pad ?? 0;
|
|
15883
|
+
const room = (n) => Math.max(1, n - pad * 2);
|
|
15884
|
+
const raw = Math.min(room(w) / Math.max(1, r.w), room(h) / Math.max(1, r.h));
|
|
15885
|
+
const scale = clamp2(raw, opts.min ?? 0, opts.max ?? Number.POSITIVE_INFINITY);
|
|
15886
|
+
return {
|
|
15887
|
+
x: w / 2 - (r.x + r.w / 2) * scale,
|
|
15888
|
+
y: h / 2 - (r.y + r.h / 2) * scale,
|
|
15889
|
+
scale
|
|
15890
|
+
};
|
|
15891
|
+
}
|
|
15892
|
+
function viewPath(from, to) {
|
|
15893
|
+
const k = to.scale / from.scale;
|
|
15894
|
+
if (!Number.isFinite(k) || Math.abs(Math.log(k)) < 0.0001)
|
|
15895
|
+
return (t) => ({
|
|
15896
|
+
x: from.x + (to.x - from.x) * t,
|
|
15897
|
+
y: from.y + (to.y - from.y) * t,
|
|
15898
|
+
scale: from.scale + (to.scale - from.scale) * t
|
|
15899
|
+
});
|
|
15900
|
+
const fx = (to.x - k * from.x) / (1 - k);
|
|
15901
|
+
const fy = (to.y - k * from.y) / (1 - k);
|
|
15902
|
+
const wx = (fx - from.x) / from.scale;
|
|
15903
|
+
const wy = (fy - from.y) / from.scale;
|
|
15904
|
+
return (t) => {
|
|
15905
|
+
if (t >= 1)
|
|
15906
|
+
return to;
|
|
15907
|
+
const scale = from.scale * k ** t;
|
|
15908
|
+
return { x: fx - wx * scale, y: fy - wy * scale, scale };
|
|
15909
|
+
};
|
|
15910
|
+
}
|
|
15911
|
+
var snapView = (v, dpr) => {
|
|
15912
|
+
const d = dpr > 0 ? dpr : 1;
|
|
15913
|
+
return { x: Math.round(v.x * d) / d, y: Math.round(v.y * d) / d, scale: v.scale };
|
|
15914
|
+
};
|
|
15915
|
+
var smooth = (a, b, x) => {
|
|
15916
|
+
const t = clamp2((x - a) / (b - a), 0, 1);
|
|
15917
|
+
return t * t * (3 - 2 * t);
|
|
15918
|
+
};
|
|
15919
|
+
function dotGrid(scale, step = 24) {
|
|
15920
|
+
const size = step * scale;
|
|
15921
|
+
const alpha = smooth(7, 14, size) * (1 - smooth(72, 128, size));
|
|
15922
|
+
return { size, alpha };
|
|
15923
|
+
}
|
|
15924
|
+
function keyAction(e) {
|
|
15925
|
+
if (e.altKey)
|
|
15926
|
+
return null;
|
|
15927
|
+
switch (e.key) {
|
|
15928
|
+
case "0":
|
|
15929
|
+
return "fit";
|
|
15930
|
+
case "1":
|
|
15931
|
+
return "actual";
|
|
15932
|
+
case "=":
|
|
15933
|
+
case "+":
|
|
15934
|
+
return "in";
|
|
15935
|
+
case "-":
|
|
15936
|
+
case "_":
|
|
15937
|
+
return "out";
|
|
15938
|
+
default:
|
|
15939
|
+
return null;
|
|
15940
|
+
}
|
|
15941
|
+
}
|
|
15942
|
+
var KEY_ZOOM = 1.5;
|
|
15943
|
+
function releaseVelocity(samples, now, window2 = 80) {
|
|
15944
|
+
const last = samples.at(-1);
|
|
15945
|
+
if (!last || now - last.t > 60)
|
|
15946
|
+
return { x: 0, y: 0 };
|
|
15947
|
+
let first = last;
|
|
15948
|
+
for (let i = samples.length - 1;i >= 0; i--) {
|
|
15949
|
+
if (last.t - samples[i].t > window2)
|
|
15950
|
+
break;
|
|
15951
|
+
first = samples[i];
|
|
15952
|
+
}
|
|
15953
|
+
const dt = (last.t - first.t) / 1000;
|
|
15954
|
+
if (dt <= 0)
|
|
15955
|
+
return { x: 0, y: 0 };
|
|
15956
|
+
return { x: (last.x - first.x) / dt, y: (last.y - first.y) / dt };
|
|
15957
|
+
}
|
|
15958
|
+
var COAST_TAU = -1 / (1000 * Math.log(0.998));
|
|
15959
|
+
|
|
15960
|
+
// src/ui/canvas.ts
|
|
15961
|
+
init_look();
|
|
15962
|
+
var FIT = springMotion(SPRINGS.smooth);
|
|
15963
|
+
var KEY = springMotion(SPRINGS.default);
|
|
15964
|
+
var LAYOUT2 = springMotion(SPRINGS.calm);
|
|
15965
|
+
var FOCUS_MAX = 2;
|
|
15966
|
+
var MAX_FLING = 8000;
|
|
15967
|
+
var SETTLE_MS = 200;
|
|
15968
|
+
var INTERACTIVE = "a,button,input,select,textarea,label,summary,[contenteditable],[role=button],[role=slider],[role=switch],[role=checkbox],[role=tab],[role=option],[role=textbox]";
|
|
15969
|
+
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"/>');
|
|
15970
|
+
var injectCanvasStyles = (doc) => {
|
|
15971
|
+
if (doc.querySelector("style[data-knobs-canvas]"))
|
|
15972
|
+
return;
|
|
15973
|
+
const style = doc.createElement("style");
|
|
15974
|
+
style.dataset.knobsCanvas = "";
|
|
15975
|
+
style.textContent = aliased(canvasCss);
|
|
15976
|
+
doc.head.append(style);
|
|
15977
|
+
};
|
|
15978
|
+
function canvas(container, options) {
|
|
15979
|
+
const doc = container.ownerDocument;
|
|
15980
|
+
const win = doc.defaultView;
|
|
15981
|
+
const gap = options.gap ?? 48;
|
|
15982
|
+
const pad = options.padding ?? 48;
|
|
15983
|
+
const minZoom = options.minZoom ?? 0.05;
|
|
15984
|
+
const maxZoom = options.maxZoom ?? 8;
|
|
15985
|
+
let items = options.items;
|
|
15986
|
+
const root = doc.createElement("div");
|
|
15987
|
+
root.className = "knobs-canvas";
|
|
15988
|
+
root.tabIndex = 0;
|
|
15989
|
+
root.setAttribute("role", "region");
|
|
15990
|
+
root.setAttribute("aria-roledescription", "canvas");
|
|
15991
|
+
root.setAttribute("aria-label", options.label ?? "Canvas");
|
|
15992
|
+
applyLook2(root, options);
|
|
15993
|
+
injectCanvasStyles(doc);
|
|
15994
|
+
const stage = doc.createElement("div");
|
|
15995
|
+
stage.className = "knobs-canvas-stage";
|
|
15996
|
+
const dots = doc.createElement("div");
|
|
15997
|
+
dots.className = "knobs-canvas-dots";
|
|
15998
|
+
const world = doc.createElement("div");
|
|
15999
|
+
world.className = "knobs-canvas-world";
|
|
16000
|
+
stage.append(dots, world);
|
|
16001
|
+
root.append(stage);
|
|
16002
|
+
let pct = null;
|
|
16003
|
+
if (options.readout !== false) {
|
|
16004
|
+
const bar = doc.createElement("div");
|
|
16005
|
+
bar.className = "knobs-canvas-zoom";
|
|
16006
|
+
const fitBtn = doc.createElement("button");
|
|
16007
|
+
fitBtn.type = "button";
|
|
16008
|
+
fitBtn.className = "knobs-canvas-btn";
|
|
16009
|
+
fitBtn.innerHTML = ICON_FIT;
|
|
16010
|
+
fitBtn.title = "Fit (0)";
|
|
16011
|
+
fitBtn.setAttribute("aria-label", "Fit");
|
|
16012
|
+
fitBtn.addEventListener("click", () => api.fit());
|
|
16013
|
+
pct = doc.createElement("button");
|
|
16014
|
+
pct.type = "button";
|
|
16015
|
+
pct.className = "knobs-canvas-btn knobs-canvas-pct";
|
|
16016
|
+
pct.title = "100% (1)";
|
|
16017
|
+
pct.setAttribute("aria-label", "Zoom to 100%");
|
|
16018
|
+
pct.addEventListener("click", () => api.zoomTo(1));
|
|
16019
|
+
bar.append(fitBtn, pct);
|
|
16020
|
+
bar.addEventListener("dblclick", (e) => e.stopPropagation());
|
|
16021
|
+
root.append(bar);
|
|
16022
|
+
}
|
|
16023
|
+
container.append(root);
|
|
16024
|
+
const motionQuery = win.matchMedia?.("(prefers-reduced-motion: reduce)");
|
|
16025
|
+
const reduced = () => motionQuery?.matches ?? reducedMotion(win);
|
|
16026
|
+
let view = { x: 0, y: 0, scale: 1 };
|
|
16027
|
+
let vw = 0;
|
|
16028
|
+
let vh = 0;
|
|
16029
|
+
let pristine = true;
|
|
16030
|
+
let rowW;
|
|
16031
|
+
let rects = new Map;
|
|
16032
|
+
let jump = null;
|
|
16033
|
+
let coast = null;
|
|
16034
|
+
let frames = 0;
|
|
16035
|
+
const times = [];
|
|
16036
|
+
const tiles = new Map;
|
|
16037
|
+
let drawnView = "";
|
|
16038
|
+
let drawnDots = "";
|
|
16039
|
+
let drawnPct = "";
|
|
16040
|
+
const drawView = () => {
|
|
16041
|
+
const v = view;
|
|
16042
|
+
const key = `${v.x}|${v.y}|${v.scale}`;
|
|
16043
|
+
if (key === drawnView)
|
|
16044
|
+
return;
|
|
16045
|
+
drawnView = key;
|
|
16046
|
+
world.style.transform = `translate(${v.x}px,${v.y}px) scale(${v.scale})`;
|
|
16047
|
+
const g = dotGrid(v.scale);
|
|
16048
|
+
if (g.alpha <= 0.001) {
|
|
16049
|
+
if (drawnDots !== "off")
|
|
16050
|
+
dots.style.visibility = "hidden";
|
|
16051
|
+
drawnDots = "off";
|
|
16052
|
+
} else {
|
|
16053
|
+
const ox = ((v.x + 128 - g.size / 2) % g.size + g.size) % g.size;
|
|
16054
|
+
const oy = ((v.y + 128 - g.size / 2) % g.size + g.size) % g.size;
|
|
16055
|
+
const size = `${g.size}px ${g.size}px`;
|
|
16056
|
+
const a = String(Math.round(g.alpha * 1000) / 1000);
|
|
16057
|
+
if (drawnDots === "off")
|
|
16058
|
+
dots.style.visibility = "";
|
|
16059
|
+
if (dots.style.backgroundSize !== size)
|
|
16060
|
+
dots.style.backgroundSize = size;
|
|
16061
|
+
if (dots.style.opacity !== a)
|
|
16062
|
+
dots.style.opacity = a;
|
|
16063
|
+
dots.style.transform = `translate(${ox}px,${oy}px)`;
|
|
16064
|
+
drawnDots = "on";
|
|
16065
|
+
}
|
|
16066
|
+
if (pct) {
|
|
16067
|
+
const label = `${Math.round(v.scale * 100)}%`;
|
|
16068
|
+
if (label !== drawnPct)
|
|
16069
|
+
pct.textContent = drawnPct = label;
|
|
16070
|
+
}
|
|
16071
|
+
options.onView?.(v);
|
|
16072
|
+
};
|
|
16073
|
+
let settleTimer = 0;
|
|
16074
|
+
let held = false;
|
|
16075
|
+
let layerScale = 0;
|
|
16076
|
+
const settle = () => {
|
|
16077
|
+
settleTimer = 0;
|
|
16078
|
+
if (held)
|
|
16079
|
+
return;
|
|
16080
|
+
if (loop.running) {
|
|
16081
|
+
settleTimer = win.setTimeout(settle, SETTLE_MS);
|
|
16082
|
+
return;
|
|
16083
|
+
}
|
|
16084
|
+
view = snapView(view, win.devicePixelRatio || 1);
|
|
16085
|
+
if (Math.abs(view.scale / layerScale - 1) > 0.000001) {
|
|
16086
|
+
delete root.dataset.moving;
|
|
16087
|
+
layerScale = 0;
|
|
16088
|
+
}
|
|
16089
|
+
drawView();
|
|
16090
|
+
};
|
|
16091
|
+
const moved = () => {
|
|
16092
|
+
if (!layerScale) {
|
|
16093
|
+
root.dataset.moving = "";
|
|
16094
|
+
layerScale = view.scale;
|
|
16095
|
+
}
|
|
16096
|
+
win.clearTimeout(settleTimer);
|
|
16097
|
+
settleTimer = win.setTimeout(settle, SETTLE_MS);
|
|
16098
|
+
};
|
|
16099
|
+
const setView = (v) => {
|
|
16100
|
+
view = v;
|
|
16101
|
+
moved();
|
|
16102
|
+
drawView();
|
|
16103
|
+
};
|
|
16104
|
+
const drawTile = (t) => {
|
|
16105
|
+
const key = `${t.x.x}|${t.y.x}`;
|
|
16106
|
+
if (key === t.drawn)
|
|
16107
|
+
return;
|
|
16108
|
+
t.drawn = key;
|
|
16109
|
+
t.el.style.translate = `${t.x.x}px ${t.y.x}px`;
|
|
16110
|
+
};
|
|
16111
|
+
const sizeCard = (t) => {
|
|
16112
|
+
const { w, h } = t.item;
|
|
16113
|
+
t.card.style.width = w === undefined ? "" : `${w}px`;
|
|
16114
|
+
t.card.style.height = h === undefined ? "" : `${h}px`;
|
|
16115
|
+
};
|
|
16116
|
+
const setTitle = (t) => {
|
|
16117
|
+
const text = t.item.title;
|
|
16118
|
+
if (text && !t.title) {
|
|
16119
|
+
t.title = doc.createElement("div");
|
|
16120
|
+
t.title.className = "knobs-canvas-title";
|
|
16121
|
+
t.el.prepend(t.title);
|
|
16122
|
+
}
|
|
16123
|
+
if (t.title) {
|
|
16124
|
+
if (text)
|
|
16125
|
+
t.title.textContent = text;
|
|
16126
|
+
else {
|
|
16127
|
+
t.title.remove();
|
|
16128
|
+
t.title = null;
|
|
16129
|
+
}
|
|
16130
|
+
}
|
|
16131
|
+
};
|
|
16132
|
+
const mount = (it, enter) => {
|
|
16133
|
+
const el = doc.createElement("div");
|
|
16134
|
+
el.className = "knobs-canvas-tile";
|
|
16135
|
+
el.dataset.id = it.id;
|
|
16136
|
+
const card = doc.createElement("div");
|
|
16137
|
+
card.className = "knobs-canvas-card";
|
|
16138
|
+
el.append(card);
|
|
16139
|
+
const t = {
|
|
16140
|
+
id: it.id,
|
|
16141
|
+
item: it,
|
|
16142
|
+
el,
|
|
16143
|
+
title: null,
|
|
16144
|
+
card,
|
|
16145
|
+
cleanup: undefined,
|
|
16146
|
+
x: new Spring(0, LAYOUT2.omega, LAYOUT2.zeta, 0.05),
|
|
16147
|
+
y: new Spring(0, LAYOUT2.omega, LAYOUT2.zeta, 0.05),
|
|
16148
|
+
w: 0,
|
|
16149
|
+
h: 0,
|
|
16150
|
+
placed: false,
|
|
16151
|
+
leaving: false,
|
|
16152
|
+
gone: false,
|
|
16153
|
+
drawn: ""
|
|
16154
|
+
};
|
|
16155
|
+
setTitle(t);
|
|
16156
|
+
sizeCard(t);
|
|
16157
|
+
world.append(el);
|
|
16158
|
+
t.cleanup = it.render(card) || undefined;
|
|
16159
|
+
tiles.set(it.id, t);
|
|
16160
|
+
ro.observe(el);
|
|
16161
|
+
if (enter) {
|
|
16162
|
+
const r = reduced();
|
|
16163
|
+
el.animate(r ? [{ opacity: 0 }, { opacity: 1 }] : [
|
|
16164
|
+
{ opacity: 0, scale: 0.96, filter: "blur(3px)" },
|
|
16165
|
+
{ opacity: 1, scale: 1, filter: "blur(0px)" }
|
|
16166
|
+
], { duration: r ? 160 : 260, easing: "cubic-bezier(.2,.8,.2,1)" });
|
|
16167
|
+
}
|
|
16168
|
+
return t;
|
|
16169
|
+
};
|
|
16170
|
+
const unmount = (t) => {
|
|
16171
|
+
if (t.gone)
|
|
16172
|
+
return;
|
|
16173
|
+
t.gone = true;
|
|
16174
|
+
ro.unobserve(t.el);
|
|
16175
|
+
t.cleanup?.();
|
|
16176
|
+
t.el.remove();
|
|
16177
|
+
if (tiles.get(t.id) === t)
|
|
16178
|
+
tiles.delete(t.id);
|
|
16179
|
+
};
|
|
16180
|
+
const leave = (t) => {
|
|
16181
|
+
t.leaving = true;
|
|
16182
|
+
tiles.delete(t.id);
|
|
16183
|
+
t.el.style.pointerEvents = "none";
|
|
16184
|
+
const r = reduced();
|
|
16185
|
+
const a = t.el.animate(r ? [{ opacity: 1 }, { opacity: 0 }] : [
|
|
16186
|
+
{ opacity: 1, scale: 1, filter: "blur(0px)" },
|
|
16187
|
+
{ opacity: 0, scale: 0.96, filter: "blur(3px)" }
|
|
16188
|
+
], { duration: r ? 160 : 200, easing: "cubic-bezier(.4,0,1,1)", fill: "forwards" });
|
|
16189
|
+
a.onfinish = () => {
|
|
16190
|
+
leaving.delete(t);
|
|
16191
|
+
unmount(t);
|
|
16192
|
+
};
|
|
16193
|
+
leaving.add(t);
|
|
16194
|
+
};
|
|
16195
|
+
const leaving = new Set;
|
|
16196
|
+
const measure = () => {
|
|
16197
|
+
for (const t of tiles.values()) {
|
|
16198
|
+
t.w = t.el.offsetWidth;
|
|
16199
|
+
t.h = t.el.offsetHeight;
|
|
16200
|
+
}
|
|
16201
|
+
};
|
|
16202
|
+
const fitTarget = () => {
|
|
16203
|
+
const b = boundsOf([...rects.values()]);
|
|
16204
|
+
if (!b || !vw || !vh)
|
|
16205
|
+
return null;
|
|
16206
|
+
return fitView(b, vw, vh, { pad, min: minZoom, max: Math.min(maxZoom, 1) });
|
|
16207
|
+
};
|
|
16208
|
+
const relayout = (glide) => {
|
|
16209
|
+
const live = items.map((it) => tiles.get(it.id)).filter(Boolean);
|
|
16210
|
+
const sizes = live.map((t) => ({ x: t.item.x, y: t.item.y, w: t.w, h: t.h }));
|
|
16211
|
+
if (pristine)
|
|
16212
|
+
rowW = options.width;
|
|
16213
|
+
const out = layoutCanvas(sizes, {
|
|
16214
|
+
gap,
|
|
16215
|
+
width: rowW,
|
|
16216
|
+
aspect: vw && vh ? vw / vh : undefined
|
|
16217
|
+
});
|
|
16218
|
+
rowW = out.width;
|
|
16219
|
+
const next = new Map;
|
|
16220
|
+
const animate = glide && !pristine && !reduced();
|
|
16221
|
+
let moving = false;
|
|
16222
|
+
live.forEach((t, i) => {
|
|
16223
|
+
const r = out.rects[i];
|
|
16224
|
+
next.set(t.id, r);
|
|
16225
|
+
if (!animate || !t.placed) {
|
|
16226
|
+
t.x.snap(r.x);
|
|
16227
|
+
t.y.snap(r.y);
|
|
16228
|
+
} else {
|
|
16229
|
+
tune(t.x, LAYOUT2).set(r.x);
|
|
16230
|
+
tune(t.y, LAYOUT2).set(r.y);
|
|
16231
|
+
moving ||= !t.x.resting || !t.y.resting;
|
|
16232
|
+
}
|
|
16233
|
+
t.placed = true;
|
|
16234
|
+
drawTile(t);
|
|
16235
|
+
});
|
|
16236
|
+
rects = next;
|
|
16237
|
+
if (pristine) {
|
|
16238
|
+
const f = fitTarget();
|
|
16239
|
+
if (f) {
|
|
16240
|
+
view = snapView(f, win.devicePixelRatio || 1);
|
|
16241
|
+
drawView();
|
|
16242
|
+
}
|
|
16243
|
+
}
|
|
16244
|
+
if (moving)
|
|
16245
|
+
loop.wake();
|
|
16246
|
+
};
|
|
16247
|
+
let relayoutQueued = 0;
|
|
16248
|
+
const ro = new win.ResizeObserver((entries) => {
|
|
16249
|
+
let changed = false;
|
|
16250
|
+
for (const e of entries) {
|
|
16251
|
+
const t = tiles.get(e.target.dataset.id ?? "");
|
|
16252
|
+
if (!t || t.el !== e.target)
|
|
16253
|
+
continue;
|
|
16254
|
+
const w = t.el.offsetWidth;
|
|
16255
|
+
const h = t.el.offsetHeight;
|
|
16256
|
+
if (w !== t.w || h !== t.h) {
|
|
16257
|
+
t.w = w;
|
|
16258
|
+
t.h = h;
|
|
16259
|
+
changed = true;
|
|
16260
|
+
}
|
|
16261
|
+
}
|
|
16262
|
+
if (changed && !relayoutQueued)
|
|
16263
|
+
relayoutQueued = win.requestAnimationFrame(() => {
|
|
16264
|
+
relayoutQueued = 0;
|
|
16265
|
+
relayout(true);
|
|
16266
|
+
});
|
|
16267
|
+
});
|
|
16268
|
+
const rootRo = new win.ResizeObserver(() => {
|
|
16269
|
+
const w = root.clientWidth;
|
|
16270
|
+
const h = root.clientHeight;
|
|
16271
|
+
if (w === vw && h === vh)
|
|
16272
|
+
return;
|
|
16273
|
+
const was = { w: vw, h: vh };
|
|
16274
|
+
vw = w;
|
|
16275
|
+
vh = h;
|
|
16276
|
+
if (pristine)
|
|
16277
|
+
return relayout(false);
|
|
16278
|
+
if (was.w && was.h) {
|
|
16279
|
+
const dx = (w - was.w) / 2;
|
|
16280
|
+
const dy = (h - was.h) / 2;
|
|
16281
|
+
if (jump)
|
|
16282
|
+
startJump({ x: jump.to.x + dx, y: jump.to.y + dy, scale: jump.to.scale }, jump.t.omega === FIT.omega ? FIT : KEY);
|
|
16283
|
+
view = { x: view.x + dx, y: view.y + dy, scale: view.scale };
|
|
16284
|
+
drawView();
|
|
16285
|
+
}
|
|
16286
|
+
});
|
|
16287
|
+
const frame = (dt) => {
|
|
16288
|
+
const t0 = performance.now();
|
|
16289
|
+
frames++;
|
|
16290
|
+
let moving = false;
|
|
16291
|
+
if (jump) {
|
|
16292
|
+
jump.t.step(dt);
|
|
16293
|
+
if (jump.t.resting) {
|
|
16294
|
+
view = jump.to;
|
|
16295
|
+
jump = null;
|
|
16296
|
+
} else {
|
|
16297
|
+
view = jump.path(Math.min(1, jump.t.x));
|
|
16298
|
+
moving = true;
|
|
16299
|
+
}
|
|
16300
|
+
moved();
|
|
16301
|
+
}
|
|
16302
|
+
if (coast) {
|
|
16303
|
+
const decay = Math.exp(-dt / COAST_TAU);
|
|
16304
|
+
const travel = COAST_TAU * (1 - decay);
|
|
16305
|
+
let v = {
|
|
16306
|
+
x: view.x + coast.vx * travel,
|
|
16307
|
+
y: view.y + coast.vy * travel,
|
|
16308
|
+
scale: view.scale
|
|
16309
|
+
};
|
|
16310
|
+
if (coast.vs)
|
|
16311
|
+
v = zoomAround(v, v.scale * Math.exp(coast.vs * travel), coast.at, minZoom, maxZoom);
|
|
16312
|
+
view = v;
|
|
16313
|
+
coast.vx *= decay;
|
|
16314
|
+
coast.vy *= decay;
|
|
16315
|
+
coast.vs *= decay;
|
|
16316
|
+
if (Math.hypot(coast.vx, coast.vy) < 10 && Math.abs(coast.vs) < 0.02)
|
|
16317
|
+
coast = null;
|
|
16318
|
+
else
|
|
16319
|
+
moving = true;
|
|
16320
|
+
moved();
|
|
16321
|
+
}
|
|
16322
|
+
drawView();
|
|
16323
|
+
for (const t of tiles.values())
|
|
16324
|
+
if (!t.x.resting || !t.y.resting) {
|
|
16325
|
+
t.x.step(dt);
|
|
16326
|
+
t.y.step(dt);
|
|
16327
|
+
drawTile(t);
|
|
16328
|
+
moving = true;
|
|
16329
|
+
}
|
|
16330
|
+
times.push(performance.now() - t0);
|
|
16331
|
+
if (times.length > 600)
|
|
16332
|
+
times.splice(0, times.length - 600);
|
|
16333
|
+
return moving;
|
|
16334
|
+
};
|
|
16335
|
+
const loop = frameLoop(win, frame);
|
|
16336
|
+
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);
|
|
16337
|
+
const startJump = (to, m) => {
|
|
16338
|
+
pristine = false;
|
|
16339
|
+
coast = null;
|
|
16340
|
+
if (reduced()) {
|
|
16341
|
+
jump = null;
|
|
16342
|
+
setView(to);
|
|
16343
|
+
return;
|
|
16344
|
+
}
|
|
16345
|
+
let v = 0;
|
|
16346
|
+
if (jump) {
|
|
16347
|
+
const len = pathLength(view, to);
|
|
16348
|
+
if (len > 0.000001)
|
|
16349
|
+
v = clamp2(jump.t.v * pathLength(jump.from, jump.to) / len, 0, 40);
|
|
16350
|
+
}
|
|
16351
|
+
const t = new Spring(0, m.omega, m.zeta, 0.0005).set(1);
|
|
16352
|
+
t.v = v;
|
|
16353
|
+
jump = { from: view, to, path: viewPath(view, to), t };
|
|
16354
|
+
moved();
|
|
16355
|
+
loop.wake();
|
|
16356
|
+
};
|
|
16357
|
+
const target = () => jump?.to ?? view;
|
|
16358
|
+
const centre = () => ({ x: vw / 2, y: vh / 2 });
|
|
16359
|
+
const grab = () => {
|
|
16360
|
+
pristine = false;
|
|
16361
|
+
jump = null;
|
|
16362
|
+
coast = null;
|
|
16363
|
+
};
|
|
16364
|
+
const local = (e) => {
|
|
16365
|
+
const r = root.getBoundingClientRect();
|
|
16366
|
+
return { x: e.clientX - r.left, y: e.clientY - r.top };
|
|
16367
|
+
};
|
|
16368
|
+
let pointer = null;
|
|
16369
|
+
let pinching = false;
|
|
16370
|
+
const onWheel = (e) => {
|
|
16371
|
+
e.preventDefault();
|
|
16372
|
+
const k = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? vh : 1;
|
|
16373
|
+
if (e.ctrlKey || e.metaKey) {
|
|
16374
|
+
if (pinching)
|
|
16375
|
+
return;
|
|
16376
|
+
const dy = e.deltaY * k;
|
|
16377
|
+
const at = local(e);
|
|
16378
|
+
grab();
|
|
16379
|
+
setView(zoomAround(view, view.scale * wheelZoom(dy, e.ctrlKey), at, minZoom, maxZoom));
|
|
16380
|
+
return;
|
|
16381
|
+
}
|
|
16382
|
+
let dx = e.deltaX * k;
|
|
16383
|
+
let dy = e.deltaY * k;
|
|
16384
|
+
if (e.shiftKey && !dx) {
|
|
16385
|
+
dx = dy;
|
|
16386
|
+
dy = 0;
|
|
16387
|
+
}
|
|
16388
|
+
grab();
|
|
16389
|
+
setView({ x: view.x - dx, y: view.y - dy, scale: view.scale });
|
|
16390
|
+
};
|
|
16391
|
+
let gestureScale = 1;
|
|
16392
|
+
const onGesture = (e) => {
|
|
16393
|
+
e.preventDefault();
|
|
16394
|
+
const g = e;
|
|
16395
|
+
if (e.type === "gesturestart") {
|
|
16396
|
+
gestureScale = 1;
|
|
16397
|
+
pinching = true;
|
|
16398
|
+
grab();
|
|
16399
|
+
return;
|
|
16400
|
+
}
|
|
16401
|
+
if (e.type === "gestureend") {
|
|
16402
|
+
pinching = false;
|
|
16403
|
+
return;
|
|
16404
|
+
}
|
|
16405
|
+
const at = g.clientX !== undefined ? local(g) : centre();
|
|
16406
|
+
setView(zoomAround(view, view.scale * g.scale / gestureScale, at, minZoom, maxZoom));
|
|
16407
|
+
gestureScale = g.scale;
|
|
16408
|
+
};
|
|
16409
|
+
let drag = null;
|
|
16410
|
+
const touches = new Map;
|
|
16411
|
+
let pinch = null;
|
|
16412
|
+
let space = false;
|
|
16413
|
+
let spaceDragged = false;
|
|
16414
|
+
let hovering = false;
|
|
16415
|
+
let pressed = null;
|
|
16416
|
+
const capped = (v) => {
|
|
16417
|
+
const speed = Math.hypot(v.x, v.y);
|
|
16418
|
+
return speed > MAX_FLING ? { x: v.x / speed * MAX_FLING, y: v.y / speed * MAX_FLING } : v;
|
|
16419
|
+
};
|
|
16420
|
+
const onBackground = (target) => {
|
|
16421
|
+
const el = target;
|
|
16422
|
+
return !el?.closest?.(".knobs-canvas-card,.knobs-canvas-zoom");
|
|
16423
|
+
};
|
|
16424
|
+
const beginDrag = (e, grabbing) => {
|
|
16425
|
+
grab();
|
|
16426
|
+
held = true;
|
|
16427
|
+
drag = { id: e.pointerId, last: local(e), samples: [] };
|
|
16428
|
+
root.dataset.panning = "";
|
|
16429
|
+
if (space)
|
|
16430
|
+
spaceDragged = true;
|
|
16431
|
+
if (grabbing)
|
|
16432
|
+
root.dataset.grabbing = "";
|
|
16433
|
+
try {
|
|
16434
|
+
root.setPointerCapture(e.pointerId);
|
|
16435
|
+
} catch {}
|
|
16436
|
+
moved();
|
|
16437
|
+
};
|
|
16438
|
+
const endDrag = (coastOn) => {
|
|
16439
|
+
const d = drag;
|
|
16440
|
+
drag = null;
|
|
16441
|
+
held = false;
|
|
16442
|
+
delete root.dataset.panning;
|
|
16443
|
+
delete root.dataset.grabbing;
|
|
16444
|
+
if (!d)
|
|
16445
|
+
return;
|
|
16446
|
+
const v = coastOn ? capped(releaseVelocity(d.samples, performance.now())) : { x: 0, y: 0 };
|
|
16447
|
+
if (Math.hypot(v.x, v.y) > 60) {
|
|
16448
|
+
coast = { vx: v.x, vy: v.y, vs: 0, at: centre() };
|
|
16449
|
+
loop.wake();
|
|
16450
|
+
}
|
|
16451
|
+
moved();
|
|
16452
|
+
};
|
|
16453
|
+
const spread = () => {
|
|
16454
|
+
const [a, b] = [...touches.values()];
|
|
16455
|
+
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;
|
|
16456
|
+
};
|
|
16457
|
+
const onPointerDown = (e) => {
|
|
16458
|
+
pristine = false;
|
|
16459
|
+
if (e.isPrimary)
|
|
16460
|
+
pressed = e.target;
|
|
16461
|
+
if (e.pointerType === "touch") {
|
|
16462
|
+
touches.set(e.pointerId, local(e));
|
|
16463
|
+
if (touches.size === 2) {
|
|
16464
|
+
if (drag)
|
|
16465
|
+
endDrag(false);
|
|
16466
|
+
const s = spread();
|
|
16467
|
+
grab();
|
|
16468
|
+
held = true;
|
|
16469
|
+
pinch = { d: s.d, mid: s.mid, samples: [] };
|
|
16470
|
+
root.dataset.panning = "";
|
|
16471
|
+
for (const id of touches.keys())
|
|
16472
|
+
try {
|
|
16473
|
+
root.setPointerCapture(id);
|
|
16474
|
+
} catch {}
|
|
16475
|
+
e.preventDefault();
|
|
16476
|
+
e.stopPropagation();
|
|
16477
|
+
return;
|
|
16478
|
+
}
|
|
16479
|
+
if (touches.size === 1 && onBackground(e.target))
|
|
16480
|
+
beginDrag(e, false);
|
|
16481
|
+
return;
|
|
16482
|
+
}
|
|
16483
|
+
const middle = e.button === 1;
|
|
16484
|
+
if (!middle && e.button !== 0)
|
|
16485
|
+
return;
|
|
16486
|
+
if (!middle && !space && !onBackground(e.target))
|
|
16487
|
+
return;
|
|
16488
|
+
e.preventDefault();
|
|
16489
|
+
if (!middle && onBackground(e.target))
|
|
16490
|
+
root.focus({ preventScroll: true });
|
|
16491
|
+
beginDrag(e, middle || space);
|
|
16492
|
+
};
|
|
16493
|
+
const onPointerMove = (e) => {
|
|
16494
|
+
if (e.pointerType !== "touch")
|
|
16495
|
+
pointer = local(e);
|
|
16496
|
+
if (touches.has(e.pointerId))
|
|
16497
|
+
touches.set(e.pointerId, local(e));
|
|
16498
|
+
if (pinch) {
|
|
16499
|
+
const s = spread();
|
|
16500
|
+
if (!s)
|
|
16501
|
+
return;
|
|
16502
|
+
const v = pinchView(view, pinch.mid, s.mid, s.d / Math.max(1, pinch.d), minZoom, maxZoom);
|
|
16503
|
+
pinch.d = s.d;
|
|
16504
|
+
pinch.mid = s.mid;
|
|
16505
|
+
pinch.samples.push({ x: s.mid.x, y: s.mid.y, t: performance.now(), s: Math.log(v.scale) });
|
|
16506
|
+
if (pinch.samples.length > 12)
|
|
16507
|
+
pinch.samples.shift();
|
|
16508
|
+
setView(v);
|
|
16509
|
+
return;
|
|
16510
|
+
}
|
|
16511
|
+
if (!drag || drag.id !== e.pointerId)
|
|
16512
|
+
return;
|
|
16513
|
+
const p = local(e);
|
|
16514
|
+
const dx = p.x - drag.last.x;
|
|
16515
|
+
const dy = p.y - drag.last.y;
|
|
16516
|
+
drag.last = p;
|
|
16517
|
+
drag.samples.push({ x: p.x, y: p.y, t: performance.now() });
|
|
16518
|
+
if (drag.samples.length > 12)
|
|
16519
|
+
drag.samples.shift();
|
|
16520
|
+
setView({ x: view.x + dx, y: view.y + dy, scale: view.scale });
|
|
16521
|
+
};
|
|
16522
|
+
const onPointerUp = (e) => {
|
|
16523
|
+
if (touches.delete(e.pointerId) && pinch) {
|
|
16524
|
+
if (touches.size < 2) {
|
|
16525
|
+
const p = pinch;
|
|
16526
|
+
pinch = null;
|
|
16527
|
+
held = false;
|
|
16528
|
+
delete root.dataset.panning;
|
|
16529
|
+
const now = performance.now();
|
|
16530
|
+
const v = capped(releaseVelocity(p.samples, now));
|
|
16531
|
+
const first = p.samples.find((s) => now - s.t <= 80) ?? p.samples[0];
|
|
16532
|
+
const last = p.samples.at(-1);
|
|
16533
|
+
const dt = first && last ? (last.t - first.t) / 1000 : 0;
|
|
16534
|
+
const vs = dt > 0 && now - last.t < 60 ? clamp2((last.s - first.s) / dt, -4, 4) : 0;
|
|
16535
|
+
if (Math.hypot(v.x, v.y) > 60 || Math.abs(vs) > 0.3) {
|
|
16536
|
+
coast = { vx: v.x, vy: v.y, vs, at: p.mid };
|
|
16537
|
+
loop.wake();
|
|
16538
|
+
}
|
|
16539
|
+
moved();
|
|
16540
|
+
}
|
|
16541
|
+
return;
|
|
16542
|
+
}
|
|
16543
|
+
if (drag && drag.id === e.pointerId)
|
|
16544
|
+
endDrag(e.type === "pointerup");
|
|
16545
|
+
};
|
|
16546
|
+
const onDblClick = (e) => {
|
|
16547
|
+
if (space)
|
|
16548
|
+
return;
|
|
16549
|
+
const el = e.target === root && pressed && root.contains(pressed) ? pressed : e.target;
|
|
16550
|
+
const tileEl = el.closest?.(".knobs-canvas-tile");
|
|
16551
|
+
if (tileEl) {
|
|
16552
|
+
const card = el.closest(".knobs-canvas-card");
|
|
16553
|
+
if (card && (el.closest(INTERACTIVE) || isTypingTarget(el)))
|
|
16554
|
+
return;
|
|
16555
|
+
const id = tileEl.dataset.id;
|
|
16556
|
+
const f = focusTarget(id);
|
|
16557
|
+
if (!f)
|
|
16558
|
+
return;
|
|
16559
|
+
const t = target();
|
|
16560
|
+
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;
|
|
16561
|
+
if (there)
|
|
16562
|
+
api.fit();
|
|
16563
|
+
else
|
|
16564
|
+
startJump(f, FIT);
|
|
16565
|
+
return;
|
|
16566
|
+
}
|
|
16567
|
+
if (onBackground(el))
|
|
16568
|
+
api.fit();
|
|
16569
|
+
};
|
|
16570
|
+
const keyAnchor = () => hovering && pointer ? pointer : centre();
|
|
16571
|
+
const onKey = (e) => {
|
|
16572
|
+
if (isTypingTarget(e.target))
|
|
16573
|
+
return;
|
|
16574
|
+
const a = keyAction(e);
|
|
16575
|
+
if (!a || a === "actual" && (e.metaKey || e.ctrlKey))
|
|
16576
|
+
return;
|
|
16577
|
+
e.preventDefault();
|
|
16578
|
+
e.stopPropagation();
|
|
16579
|
+
if (a === "fit")
|
|
16580
|
+
return api.fit();
|
|
16581
|
+
const from = target();
|
|
16582
|
+
const scale = a === "actual" ? 1 : a === "in" ? from.scale * KEY_ZOOM : from.scale / KEY_ZOOM;
|
|
16583
|
+
startJump(zoomAround(from, scale, keyAnchor(), minZoom, maxZoom), KEY);
|
|
16584
|
+
};
|
|
16585
|
+
const spaceOwner = (e) => e.key === " " && !e.metaKey && !e.ctrlKey && !e.altKey && !isTypingTarget(e.target) && (hovering || root.contains(doc.activeElement));
|
|
16586
|
+
const onControl = () => {
|
|
16587
|
+
const el = doc.activeElement;
|
|
16588
|
+
return !!el && el !== root && root.contains(el) && !!el.closest(INTERACTIVE);
|
|
16589
|
+
};
|
|
16590
|
+
const onDocKeyDown = (e) => {
|
|
16591
|
+
if (!spaceOwner(e))
|
|
16592
|
+
return;
|
|
16593
|
+
if (!onControl())
|
|
16594
|
+
e.preventDefault();
|
|
16595
|
+
if (space)
|
|
16596
|
+
return;
|
|
16597
|
+
space = true;
|
|
16598
|
+
spaceDragged = false;
|
|
16599
|
+
root.dataset.space = "";
|
|
16600
|
+
};
|
|
16601
|
+
const endSpace = () => {
|
|
16602
|
+
space = false;
|
|
16603
|
+
delete root.dataset.space;
|
|
16604
|
+
if (!drag)
|
|
16605
|
+
delete root.dataset.grabbing;
|
|
16606
|
+
};
|
|
16607
|
+
const onDocKeyUp = (e) => {
|
|
16608
|
+
if (e.key !== " " || !space)
|
|
16609
|
+
return;
|
|
16610
|
+
if (spaceDragged || !onControl())
|
|
16611
|
+
e.preventDefault();
|
|
16612
|
+
endSpace();
|
|
16613
|
+
};
|
|
16614
|
+
root.addEventListener("wheel", onWheel, { passive: false });
|
|
16615
|
+
root.addEventListener("gesturestart", onGesture);
|
|
16616
|
+
root.addEventListener("gesturechange", onGesture);
|
|
16617
|
+
root.addEventListener("gestureend", onGesture);
|
|
16618
|
+
root.addEventListener("pointerdown", onPointerDown, { capture: true });
|
|
16619
|
+
root.addEventListener("pointermove", onPointerMove);
|
|
16620
|
+
root.addEventListener("pointerup", onPointerUp);
|
|
16621
|
+
root.addEventListener("pointercancel", onPointerUp);
|
|
16622
|
+
root.addEventListener("pointerenter", () => hovering = true);
|
|
16623
|
+
root.addEventListener("pointerleave", () => {
|
|
16624
|
+
hovering = false;
|
|
16625
|
+
});
|
|
16626
|
+
root.addEventListener("auxclick", (e) => e.button === 1 && e.preventDefault());
|
|
16627
|
+
root.addEventListener("dblclick", onDblClick);
|
|
16628
|
+
root.addEventListener("keydown", onKey);
|
|
16629
|
+
doc.addEventListener("keydown", onDocKeyDown, { capture: true });
|
|
16630
|
+
doc.addEventListener("keyup", onDocKeyUp, { capture: true });
|
|
16631
|
+
win.addEventListener("blur", endSpace);
|
|
16632
|
+
root.addEventListener("knobs-restyle", () => {
|
|
16633
|
+
measure();
|
|
16634
|
+
relayout(true);
|
|
16635
|
+
});
|
|
16636
|
+
const focusTarget = (id) => {
|
|
16637
|
+
const r = rects.get(id);
|
|
16638
|
+
if (!r || !vw || !vh)
|
|
16639
|
+
return null;
|
|
16640
|
+
return fitView(r, vw, vh, { pad, min: minZoom, max: Math.min(maxZoom, FOCUS_MAX) });
|
|
16641
|
+
};
|
|
16642
|
+
for (const it of items)
|
|
16643
|
+
mount(it, false);
|
|
16644
|
+
vw = root.clientWidth;
|
|
16645
|
+
vh = root.clientHeight;
|
|
16646
|
+
measure();
|
|
16647
|
+
relayout(false);
|
|
16648
|
+
rootRo.observe(root);
|
|
16649
|
+
drawView();
|
|
16650
|
+
const api = {
|
|
16651
|
+
element: root,
|
|
16652
|
+
get view() {
|
|
16653
|
+
return view;
|
|
16654
|
+
},
|
|
16655
|
+
get rects() {
|
|
16656
|
+
return rects;
|
|
16657
|
+
},
|
|
16658
|
+
fit() {
|
|
16659
|
+
const f = fitTarget();
|
|
16660
|
+
if (f)
|
|
16661
|
+
startJump(f, FIT);
|
|
16662
|
+
},
|
|
16663
|
+
focus(id) {
|
|
16664
|
+
const f = focusTarget(id);
|
|
16665
|
+
if (f)
|
|
16666
|
+
startJump(f, FIT);
|
|
16667
|
+
},
|
|
16668
|
+
zoomTo(scale, at) {
|
|
16669
|
+
const from = target();
|
|
16670
|
+
startJump(zoomAround(from, scale, at ?? centre(), minZoom, maxZoom), KEY);
|
|
16671
|
+
},
|
|
16672
|
+
update(next) {
|
|
16673
|
+
const ids = new Set(next.map((it) => it.id));
|
|
16674
|
+
for (const t of [...tiles.values()])
|
|
16675
|
+
if (!ids.has(t.id))
|
|
16676
|
+
leave(t);
|
|
16677
|
+
for (const it of next) {
|
|
16678
|
+
const t = tiles.get(it.id);
|
|
16679
|
+
if (!t) {
|
|
16680
|
+
mount(it, true);
|
|
16681
|
+
continue;
|
|
16682
|
+
}
|
|
16683
|
+
if (t.item.render !== it.render) {
|
|
16684
|
+
t.cleanup?.();
|
|
16685
|
+
t.card.replaceChildren();
|
|
16686
|
+
t.cleanup = it.render(t.card) || undefined;
|
|
16687
|
+
}
|
|
16688
|
+
t.item = it;
|
|
16689
|
+
setTitle(t);
|
|
16690
|
+
sizeCard(t);
|
|
16691
|
+
}
|
|
16692
|
+
items = next;
|
|
16693
|
+
measure();
|
|
16694
|
+
relayout(true);
|
|
16695
|
+
},
|
|
16696
|
+
get moving() {
|
|
16697
|
+
return loop.running;
|
|
16698
|
+
},
|
|
16699
|
+
stats() {
|
|
16700
|
+
const s = [...times].sort((a, b) => a - b);
|
|
16701
|
+
const q = (p) => Math.round((s[Math.floor(p * (s.length - 1))] ?? 0) * 1000) / 1000;
|
|
16702
|
+
return { frames, p50: q(0.5), p95: q(0.95), max: q(1) };
|
|
16703
|
+
},
|
|
16704
|
+
dispose() {
|
|
16705
|
+
loop.stop();
|
|
16706
|
+
ro.disconnect();
|
|
16707
|
+
rootRo.disconnect();
|
|
16708
|
+
win.clearTimeout(settleTimer);
|
|
16709
|
+
if (relayoutQueued)
|
|
16710
|
+
win.cancelAnimationFrame(relayoutQueued);
|
|
16711
|
+
doc.removeEventListener("keydown", onDocKeyDown, { capture: true });
|
|
16712
|
+
doc.removeEventListener("keyup", onDocKeyUp, { capture: true });
|
|
16713
|
+
win.removeEventListener("blur", endSpace);
|
|
16714
|
+
for (const t of [...tiles.values(), ...leaving])
|
|
16715
|
+
unmount(t);
|
|
16716
|
+
root.remove();
|
|
16717
|
+
}
|
|
16718
|
+
};
|
|
16719
|
+
return api;
|
|
16720
|
+
}
|
|
16721
|
+
|
|
15747
16722
|
// src/ui/react.tsx
|
|
15748
16723
|
init_diff();
|
|
15749
16724
|
|
|
@@ -23150,6 +24125,33 @@ function Bento(props) {
|
|
|
23150
24125
|
style: { height: "100%", ...props.style }
|
|
23151
24126
|
});
|
|
23152
24127
|
}
|
|
24128
|
+
function Canvas(props) {
|
|
24129
|
+
const ref = useRef(null);
|
|
24130
|
+
const handle = useRef(null);
|
|
24131
|
+
const latest = useRef(props);
|
|
24132
|
+
latest.current = props;
|
|
24133
|
+
useLayoutEffect(() => {
|
|
24134
|
+
const c = canvas(ref.current, {
|
|
24135
|
+
...latest.current,
|
|
24136
|
+
onView: (v) => latest.current.onView?.(v)
|
|
24137
|
+
});
|
|
24138
|
+
handle.current = c;
|
|
24139
|
+
latest.current.onReady?.(c);
|
|
24140
|
+
return () => {
|
|
24141
|
+
c.dispose();
|
|
24142
|
+
handle.current = null;
|
|
24143
|
+
};
|
|
24144
|
+
}, []);
|
|
24145
|
+
useFollow({ items: props.items }, (c) => {
|
|
24146
|
+
if (c.items)
|
|
24147
|
+
handle.current?.update(c.items);
|
|
24148
|
+
});
|
|
24149
|
+
return /* @__PURE__ */ jsx("div", {
|
|
24150
|
+
ref,
|
|
24151
|
+
className: props.className,
|
|
24152
|
+
style: { height: "100%", ...props.style }
|
|
24153
|
+
});
|
|
24154
|
+
}
|
|
23153
24155
|
function Activity(props) {
|
|
23154
24156
|
const ref = useRef(null);
|
|
23155
24157
|
const handle = useRef(null);
|
|
@@ -23299,6 +24301,7 @@ function MediaSlot(props) {
|
|
|
23299
24301
|
export {
|
|
23300
24302
|
Activity,
|
|
23301
24303
|
Bento,
|
|
24304
|
+
Canvas,
|
|
23302
24305
|
CodeDiff,
|
|
23303
24306
|
Compare,
|
|
23304
24307
|
CompareStrip,
|