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