foss-design 0.9.8 → 0.9.10
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/dist/viewer-lib/index.js
CHANGED
|
@@ -2329,19 +2329,36 @@ var staticSource = {
|
|
|
2329
2329
|
return text;
|
|
2330
2330
|
}
|
|
2331
2331
|
};
|
|
2332
|
-
/**
|
|
2332
|
+
/**
|
|
2333
|
+
* `url` against a unit's base, or '' when it may not be loaded. Docs come from builds anyone who
|
|
2334
|
+
* can push has written, so a URL is only kept when it is http(s) and, for a unit build, inside
|
|
2335
|
+
* that unit's base: a `javascript:` frame source would run with the viewer's own origin.
|
|
2336
|
+
*/
|
|
2333
2337
|
function resolveUrl(url, base) {
|
|
2334
|
-
if (!base) return url;
|
|
2335
2338
|
try {
|
|
2336
|
-
const
|
|
2337
|
-
|
|
2339
|
+
const page = typeof document === "undefined" ? "http://localhost/" : document.baseURI;
|
|
2340
|
+
if (!base) {
|
|
2341
|
+
const own = new URL(url, page);
|
|
2342
|
+
return own.protocol === "http:" || own.protocol === "https:" ? url : "";
|
|
2343
|
+
}
|
|
2344
|
+
const root = new URL(base.endsWith("/") ? base : `${base}/`, page);
|
|
2345
|
+
const resolved = new URL(url, root);
|
|
2346
|
+
return resolved.origin === root.origin && resolved.href.startsWith(root.href) ? resolved.href : "";
|
|
2347
|
+
} catch {
|
|
2348
|
+
return "";
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
/** An external page for a `url` item: http or https only. */
|
|
2352
|
+
function externalUrl(url) {
|
|
2353
|
+
try {
|
|
2354
|
+
const parsed = new URL(url);
|
|
2355
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:" ? parsed.href : "";
|
|
2338
2356
|
} catch {
|
|
2339
|
-
return
|
|
2357
|
+
return "";
|
|
2340
2358
|
}
|
|
2341
2359
|
}
|
|
2342
|
-
/** A canvas with its frame, snapshot and image URLs resolved against its unit's base. */
|
|
2360
|
+
/** A canvas with its frame, snapshot and image URLs resolved against its unit's base, unsafe ones dropped. */
|
|
2343
2361
|
function resolveCanvas(doc, base) {
|
|
2344
|
-
if (!base) return doc;
|
|
2345
2362
|
const at = (url) => resolveUrl(url, base);
|
|
2346
2363
|
return {
|
|
2347
2364
|
...doc,
|
|
@@ -2351,10 +2368,12 @@ function resolveCanvas(doc, base) {
|
|
|
2351
2368
|
...section,
|
|
2352
2369
|
items: section.items.map((item) => {
|
|
2353
2370
|
if (item.kind === "screen") {
|
|
2354
|
-
const each = (set) => set ? Object.fromEntries(Object.entries(set).map(([theme, url]) => [theme, url && at(url)])) : void 0;
|
|
2371
|
+
const each = (set) => set ? Object.fromEntries(Object.entries(set).map(([theme, url]) => [theme, url && at(url)]).filter(([, url]) => url)) : void 0;
|
|
2372
|
+
const url = at(item.url);
|
|
2355
2373
|
return {
|
|
2356
2374
|
...item,
|
|
2357
|
-
url
|
|
2375
|
+
url,
|
|
2376
|
+
missing: item.missing || !url,
|
|
2358
2377
|
snapshots: each(item.snapshots),
|
|
2359
2378
|
thumbs: each(item.thumbs)
|
|
2360
2379
|
};
|
|
@@ -2363,6 +2382,10 @@ function resolveCanvas(doc, base) {
|
|
|
2363
2382
|
...item,
|
|
2364
2383
|
url: at(item.url)
|
|
2365
2384
|
};
|
|
2385
|
+
if (item.kind === "url") return {
|
|
2386
|
+
...item,
|
|
2387
|
+
url: externalUrl(item.url)
|
|
2388
|
+
};
|
|
2366
2389
|
return item;
|
|
2367
2390
|
})
|
|
2368
2391
|
}))
|
|
@@ -2371,7 +2394,6 @@ function resolveCanvas(doc, base) {
|
|
|
2371
2394
|
}
|
|
2372
2395
|
/** A design system with its specimen and asset URLs resolved against the system unit's base. */
|
|
2373
2396
|
function resolveSystem(doc, base) {
|
|
2374
|
-
if (!base) return doc;
|
|
2375
2397
|
const at = (url) => resolveUrl(url, base);
|
|
2376
2398
|
return {
|
|
2377
2399
|
...doc,
|
|
@@ -7794,1926 +7816,52 @@ function linkPath(href, base = "/") {
|
|
|
7794
7816
|
return url.pathname + url.search + url.hash;
|
|
7795
7817
|
}
|
|
7796
7818
|
//#endregion
|
|
7797
|
-
//#region
|
|
7798
|
-
/** Routes runtime messages to whoever listens to the frame that sent them. */
|
|
7799
|
-
var handlers = /* @__PURE__ */ new Map();
|
|
7800
|
-
window.addEventListener("message", (event) => {
|
|
7801
|
-
const data = event.data;
|
|
7802
|
-
if (data?.source !== "design-runtime" || !event.source) return;
|
|
7803
|
-
for (const handler of handlers.get(event.source) ?? []) handler(data);
|
|
7804
|
-
});
|
|
7805
|
-
function listenToFrame(frame, handler) {
|
|
7806
|
-
let win = null;
|
|
7807
|
-
const detach = () => {
|
|
7808
|
-
if (!win) return;
|
|
7809
|
-
const set = handlers.get(win);
|
|
7810
|
-
set?.delete(handler);
|
|
7811
|
-
if (!set?.size) handlers.delete(win);
|
|
7812
|
-
win = null;
|
|
7813
|
-
};
|
|
7814
|
-
const attach = () => {
|
|
7815
|
-
detach();
|
|
7816
|
-
win = frame.contentWindow;
|
|
7817
|
-
if (!win) return;
|
|
7818
|
-
const set = handlers.get(win) ?? /* @__PURE__ */ new Set();
|
|
7819
|
-
set.add(handler);
|
|
7820
|
-
handlers.set(win, set);
|
|
7821
|
-
};
|
|
7822
|
-
attach();
|
|
7823
|
-
frame.addEventListener("load", attach);
|
|
7824
|
-
return () => {
|
|
7825
|
-
frame.removeEventListener("load", attach);
|
|
7826
|
-
detach();
|
|
7827
|
-
};
|
|
7828
|
-
}
|
|
7829
|
-
function sendToFrame(frame, message) {
|
|
7830
|
-
frame?.contentWindow?.postMessage({
|
|
7831
|
-
source: "design-viewer",
|
|
7832
|
-
...message
|
|
7833
|
-
}, "*");
|
|
7834
|
-
}
|
|
7835
|
-
/** Tell a frame it sits on the canvas, so it hands the wheel to the viewer. */
|
|
7836
|
-
function claimWheel(frame) {
|
|
7837
|
-
sendToFrame(frame, { type: "canvas" });
|
|
7838
|
-
}
|
|
7819
|
+
//#region ../../node_modules/.pnpm/marked@18.0.14/node_modules/marked/lib/marked.esm.js
|
|
7839
7820
|
/**
|
|
7840
|
-
*
|
|
7841
|
-
*
|
|
7821
|
+
* marked v18.0.14 - a markdown parser
|
|
7822
|
+
* Copyright (c) 2018-2026, MarkedJS. (MIT License)
|
|
7823
|
+
* Copyright (c) 2011-2018, Christopher Jeffrey. (MIT License)
|
|
7824
|
+
* https://github.com/markedjs/marked
|
|
7842
7825
|
*/
|
|
7843
|
-
function scrollAround(frame, delta) {
|
|
7844
|
-
for (let node = frame.parentElement; node; node = node.parentElement) {
|
|
7845
|
-
const style = getComputedStyle(node);
|
|
7846
|
-
const scrolls = (overflow) => /(auto|scroll|overlay)/.test(overflow);
|
|
7847
|
-
const y = scrolls(style.overflowY) && node.scrollHeight > node.clientHeight;
|
|
7848
|
-
const x = scrolls(style.overflowX) && node.scrollWidth > node.clientWidth;
|
|
7849
|
-
if (y || x) {
|
|
7850
|
-
node.scrollBy({
|
|
7851
|
-
left: x ? delta.deltaX : 0,
|
|
7852
|
-
top: y ? delta.deltaY : 0,
|
|
7853
|
-
behavior: "instant"
|
|
7854
|
-
});
|
|
7855
|
-
return;
|
|
7856
|
-
}
|
|
7857
|
-
}
|
|
7858
|
-
window.scrollBy({
|
|
7859
|
-
left: delta.deltaX,
|
|
7860
|
-
top: delta.deltaY,
|
|
7861
|
-
behavior: "instant"
|
|
7862
|
-
});
|
|
7863
|
-
}
|
|
7864
|
-
function sendTheme(frame, theme) {
|
|
7865
|
-
sendToFrame(frame, {
|
|
7866
|
-
type: "theme",
|
|
7867
|
-
theme
|
|
7868
|
-
});
|
|
7869
|
-
}
|
|
7870
|
-
/** Load the frame again; works whatever origin it is on. */
|
|
7871
|
-
function reloadFrame(frame) {
|
|
7872
|
-
if (!frame) return;
|
|
7873
|
-
frame.src = frame.src;
|
|
7874
|
-
}
|
|
7875
7826
|
/**
|
|
7876
|
-
*
|
|
7877
|
-
*
|
|
7878
|
-
* both reach the same server) run their scripts and animations off the viewer's
|
|
7879
|
-
* thread: a canvas full of live screens still pans smoothly. Any other host,
|
|
7880
|
-
* and static builds, keep frames on the viewer's origin.
|
|
7827
|
+
* DO NOT EDIT THIS FILE
|
|
7828
|
+
* The code in this file is generated from files in ./src/
|
|
7881
7829
|
*/
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
}
|
|
7896
|
-
/** Absolute URL for opening a frame in a new tab. */
|
|
7897
|
-
function absoluteUrl(url) {
|
|
7898
|
-
return new URL(url, document.baseURI).toString();
|
|
7830
|
+
function I() {
|
|
7831
|
+
return {
|
|
7832
|
+
async: !1,
|
|
7833
|
+
breaks: !1,
|
|
7834
|
+
extensions: null,
|
|
7835
|
+
gfm: !0,
|
|
7836
|
+
hooks: null,
|
|
7837
|
+
pedantic: !1,
|
|
7838
|
+
renderer: null,
|
|
7839
|
+
silent: !1,
|
|
7840
|
+
tokenizer: null,
|
|
7841
|
+
walkTokens: null
|
|
7842
|
+
};
|
|
7899
7843
|
}
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
* screen it is in); without routes, a path whose last part names a screen still opens it. A
|
|
7904
|
-
* link to another site opens in a new tab. A push nobody's route matches belongs to the
|
|
7905
|
-
* screen's own router; a link or form that leads nowhere stays put with a note.
|
|
7906
|
-
*/
|
|
7907
|
-
function followLink(message, canvas, go, from) {
|
|
7908
|
-
if (message.form) {
|
|
7909
|
-
toast("Forms are not sent from a screen", void 0, "info");
|
|
7910
|
-
return;
|
|
7911
|
-
}
|
|
7912
|
-
if (message.path === null) {
|
|
7913
|
-
const tab = window.open(message.href, "_blank");
|
|
7914
|
-
if (tab) tab.opener = null;
|
|
7915
|
-
else toast("Link to another site", message.href, "info");
|
|
7916
|
-
return;
|
|
7917
|
-
}
|
|
7918
|
-
const frames = canvas.pages.flatMap((page) => page.sections.flatMap((section) => section.items.filter((item) => item.kind === "screen" || item.kind === "url")));
|
|
7919
|
-
const here = frames.find((item) => item.id === from);
|
|
7920
|
-
const path = message.raw === void 0 ? message.path : linkPath(message.raw, here?.routes?.[0]);
|
|
7921
|
-
if (path === null) return;
|
|
7922
|
-
const routed = matchRoute(frames.map((item) => ({
|
|
7923
|
-
routes: (item.routes ?? []).map(compileRoute),
|
|
7924
|
-
value: item.id
|
|
7925
|
-
})), path);
|
|
7926
|
-
if (routed) {
|
|
7927
|
-
if (routed !== from) go(routed);
|
|
7928
|
-
return;
|
|
7929
|
-
}
|
|
7930
|
-
const bare = path.split(/[?#]/)[0].replace(/\/+$/, "");
|
|
7931
|
-
let name = bare.slice(bare.lastIndexOf("/") + 1).replace(/\.html?$/, "");
|
|
7932
|
-
try {
|
|
7933
|
-
name = decodeURIComponent(name);
|
|
7934
|
-
} catch {}
|
|
7935
|
-
if (name && name !== from && frames.some((item) => item.id === name)) go(name);
|
|
7936
|
-
else if (!message.pushed) toast("No screen for this link", path, "info");
|
|
7844
|
+
var y = I();
|
|
7845
|
+
function W(l) {
|
|
7846
|
+
y = l;
|
|
7937
7847
|
}
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
* The pick outlives `enabled` (so a quick ⌘-hold leaves the panel open) until
|
|
7945
|
-
* it is cleared.
|
|
7946
|
-
*/
|
|
7947
|
-
function useInspection(frames, enabled, options) {
|
|
7948
|
-
const project = useProject().data;
|
|
7949
|
-
const tokens = useSystem(!!project?.system).data?.tokens;
|
|
7950
|
-
const [selected, setSelected] = useState(null);
|
|
7951
|
-
const state = useRef({
|
|
7952
|
-
enabled,
|
|
7953
|
-
tokens
|
|
7954
|
-
});
|
|
7955
|
-
state.current = {
|
|
7956
|
-
enabled,
|
|
7957
|
-
tokens
|
|
7848
|
+
var A = { exec: () => null };
|
|
7849
|
+
function C(l) {
|
|
7850
|
+
let e = [];
|
|
7851
|
+
return (t) => {
|
|
7852
|
+
let n = Math.max(0, Math.min(3, t - 1)), s = e[n];
|
|
7853
|
+
return s || (s = l(n), e[n] = s), s;
|
|
7958
7854
|
};
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
if (!force && last?.on === on && last.tokens === list) return;
|
|
7968
|
-
told.current.set(frame, {
|
|
7969
|
-
on,
|
|
7970
|
-
tokens: list
|
|
7971
|
-
});
|
|
7972
|
-
sendToFrame(frame, {
|
|
7973
|
-
type: "inspect",
|
|
7974
|
-
on,
|
|
7975
|
-
tokens: on ? list ?? [] : void 0
|
|
7976
|
-
});
|
|
7855
|
+
}
|
|
7856
|
+
function h(l, e = "") {
|
|
7857
|
+
let t = typeof l == "string" ? l : l.source, n = {
|
|
7858
|
+
replace: (s, r) => {
|
|
7859
|
+
let o = typeof r == "string" ? r : r.source;
|
|
7860
|
+
return o = o.replace(x.caret, "$1"), t = t.replace(s, o), n;
|
|
7861
|
+
},
|
|
7862
|
+
getRegex: () => new RegExp(t, e)
|
|
7977
7863
|
};
|
|
7978
|
-
|
|
7979
|
-
for (const frame of frames) tell(frame);
|
|
7980
|
-
for (const frame of told.current.keys()) if (!frames.includes(frame)) told.current.delete(frame);
|
|
7981
|
-
}, [
|
|
7982
|
-
frames,
|
|
7983
|
-
enabled,
|
|
7984
|
-
tokens
|
|
7985
|
-
]);
|
|
7986
|
-
useEffect(() => {
|
|
7987
|
-
const offs = frames.map((frame) => listenToFrame(frame, (message) => {
|
|
7988
|
-
if (message.type === "ready") {
|
|
7989
|
-
if (state.current.enabled) tell(frame, true);
|
|
7990
|
-
else told.current.set(frame, { on: false });
|
|
7991
|
-
} else if (message.type === "inspect") {
|
|
7992
|
-
if (message.info) {
|
|
7993
|
-
for (const other of framesRef.current) if (other !== frame) sendToFrame(other, {
|
|
7994
|
-
type: "inspect-select",
|
|
7995
|
-
ref: null
|
|
7996
|
-
});
|
|
7997
|
-
setSelected({
|
|
7998
|
-
info: message.info,
|
|
7999
|
-
frame
|
|
8000
|
-
});
|
|
8001
|
-
} else setSelected((current) => current?.frame === frame ? null : current);
|
|
8002
|
-
} else if (message.type === "inspect-escape") onEscape.current();
|
|
8003
|
-
}));
|
|
8004
|
-
return () => {
|
|
8005
|
-
for (const off of offs) off();
|
|
8006
|
-
};
|
|
8007
|
-
}, [frames]);
|
|
8008
|
-
const frame = selected && frames.includes(selected.frame) ? selected.frame : null;
|
|
8009
|
-
return {
|
|
8010
|
-
info: frame ? selected.info : null,
|
|
8011
|
-
frame,
|
|
8012
|
-
/** Pick another element in the same frame by its ref, or clear the pick. */
|
|
8013
|
-
select(ref) {
|
|
8014
|
-
if (ref === null) {
|
|
8015
|
-
for (const each of framesRef.current) sendToFrame(each, {
|
|
8016
|
-
type: "inspect-select",
|
|
8017
|
-
ref: null
|
|
8018
|
-
});
|
|
8019
|
-
setSelected(null);
|
|
8020
|
-
} else if (selected) sendToFrame(selected.frame, {
|
|
8021
|
-
type: "inspect-select",
|
|
8022
|
-
ref
|
|
8023
|
-
});
|
|
8024
|
-
}
|
|
8025
|
-
};
|
|
8026
|
-
}
|
|
8027
|
-
/**
|
|
8028
|
-
* True while ⌘ or Ctrl is held on its own, in the viewer or inside any of
|
|
8029
|
-
* `frames`. A modifier used for a shortcut (⌘K, ⌘C) does not count.
|
|
8030
|
-
*/
|
|
8031
|
-
function useModifierHold(frames) {
|
|
8032
|
-
const [held, setHeld] = useState(false);
|
|
8033
|
-
useEffect(() => {
|
|
8034
|
-
const isModifier = (key) => key === "Meta" || key === "Control";
|
|
8035
|
-
let down = false;
|
|
8036
|
-
let combo = false;
|
|
8037
|
-
let timer;
|
|
8038
|
-
const release = () => {
|
|
8039
|
-
down = false;
|
|
8040
|
-
clearTimeout(timer);
|
|
8041
|
-
setHeld(false);
|
|
8042
|
-
};
|
|
8043
|
-
const press = (key) => {
|
|
8044
|
-
if (isModifier(key)) {
|
|
8045
|
-
if (down) return;
|
|
8046
|
-
down = true;
|
|
8047
|
-
combo = false;
|
|
8048
|
-
timer = setTimeout(() => {
|
|
8049
|
-
if (down && !combo) setHeld(true);
|
|
8050
|
-
}, 150);
|
|
8051
|
-
} else if (down) {
|
|
8052
|
-
combo = true;
|
|
8053
|
-
clearTimeout(timer);
|
|
8054
|
-
setHeld(false);
|
|
8055
|
-
}
|
|
8056
|
-
};
|
|
8057
|
-
const onDown = (event) => press(event.key);
|
|
8058
|
-
const onUp = (event) => {
|
|
8059
|
-
if (isModifier(event.key)) release();
|
|
8060
|
-
};
|
|
8061
|
-
window.addEventListener("keydown", onDown, true);
|
|
8062
|
-
window.addEventListener("keyup", onUp, true);
|
|
8063
|
-
window.addEventListener("blur", release);
|
|
8064
|
-
const offs = frames.map((frame) => listenToFrame(frame, (message) => {
|
|
8065
|
-
if (message.type === "key") {
|
|
8066
|
-
if (message.down) press(message.name);
|
|
8067
|
-
else if (isModifier(message.name)) release();
|
|
8068
|
-
} else if (message.type === "blur") release();
|
|
8069
|
-
}));
|
|
8070
|
-
return () => {
|
|
8071
|
-
window.removeEventListener("keydown", onDown, true);
|
|
8072
|
-
window.removeEventListener("keyup", onUp, true);
|
|
8073
|
-
window.removeEventListener("blur", release);
|
|
8074
|
-
for (const off of offs) off();
|
|
8075
|
-
clearTimeout(timer);
|
|
8076
|
-
};
|
|
8077
|
-
}, [frames]);
|
|
8078
|
-
return held;
|
|
8079
|
-
}
|
|
8080
|
-
function Section({ title, children, right }) {
|
|
8081
|
-
return /* @__PURE__ */ jsxs("section", {
|
|
8082
|
-
className: "border-t border-rule pb-2 first:border-t-0",
|
|
8083
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
8084
|
-
className: "flex h-9 items-center justify-between px-4 text-[12px] font-medium text-muted",
|
|
8085
|
-
children: [title, right]
|
|
8086
|
-
}), children]
|
|
8087
|
-
});
|
|
8088
|
-
}
|
|
8089
|
-
function Line({ label, children }) {
|
|
8090
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
8091
|
-
className: "grid min-h-7 grid-cols-[92px_minmax(0,1fr)] items-center gap-x-3 px-4 py-0.5 text-[12.5px]",
|
|
8092
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
8093
|
-
className: "text-muted",
|
|
8094
|
-
children: label
|
|
8095
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
8096
|
-
className: "flex min-w-0 items-center gap-1.5 text-ink",
|
|
8097
|
-
children
|
|
8098
|
-
})]
|
|
8099
|
-
});
|
|
8100
|
-
}
|
|
8101
|
-
function TokenChip({ name }) {
|
|
8102
|
-
if (!name) return null;
|
|
8103
|
-
return /* @__PURE__ */ jsx("button", {
|
|
8104
|
-
type: "button",
|
|
8105
|
-
onClick: () => void copyText(`var(${name})`, "Token copied"),
|
|
8106
|
-
title: "Copy var()",
|
|
8107
|
-
className: "shrink-0 cursor-pointer truncate rounded-[4px] border-0 bg-action-soft px-1.5 py-px font-mono text-[11px] text-action",
|
|
8108
|
-
children: name
|
|
8109
|
-
});
|
|
8110
|
-
}
|
|
8111
|
-
function Value$1({ children, title }) {
|
|
8112
|
-
return /* @__PURE__ */ jsx("span", {
|
|
8113
|
-
className: "min-w-0 truncate font-mono text-[12px]",
|
|
8114
|
-
title,
|
|
8115
|
-
children
|
|
8116
|
-
});
|
|
8117
|
-
}
|
|
8118
|
-
function ColorLine({ label, color }) {
|
|
8119
|
-
return /* @__PURE__ */ jsxs(Line, {
|
|
8120
|
-
label,
|
|
8121
|
-
children: [
|
|
8122
|
-
/* @__PURE__ */ jsx("span", {
|
|
8123
|
-
className: "size-3.5 shrink-0 rounded-[3px] shadow-[inset_0_0_0_1px_rgba(0,0,0,0.12)]",
|
|
8124
|
-
style: { background: color.css }
|
|
8125
|
-
}),
|
|
8126
|
-
/* @__PURE__ */ jsx(Value$1, {
|
|
8127
|
-
title: color.css,
|
|
8128
|
-
children: color.value
|
|
8129
|
-
}),
|
|
8130
|
-
/* @__PURE__ */ jsx(TokenChip, { name: color.token })
|
|
8131
|
-
]
|
|
8132
|
-
});
|
|
8133
|
-
}
|
|
8134
|
-
var dash = (value) => value ? String(value) : "–";
|
|
8135
|
-
function Layer({ label, values, className, children }) {
|
|
8136
|
-
const [top, right, bottom, left] = values;
|
|
8137
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
8138
|
-
className: cn("relative grid grid-cols-[28px_minmax(0,1fr)_28px] grid-rows-[20px_auto_20px] rounded-[4px]", className),
|
|
8139
|
-
children: [
|
|
8140
|
-
/* @__PURE__ */ jsx("span", {
|
|
8141
|
-
className: "absolute top-1 left-1.5 text-[9.5px] tracking-wide text-muted uppercase",
|
|
8142
|
-
children: label
|
|
8143
|
-
}),
|
|
8144
|
-
/* @__PURE__ */ jsx("span", {
|
|
8145
|
-
className: "col-start-2 self-center text-center",
|
|
8146
|
-
children: dash(top)
|
|
8147
|
-
}),
|
|
8148
|
-
/* @__PURE__ */ jsx("span", {
|
|
8149
|
-
className: "row-start-2 self-center text-center",
|
|
8150
|
-
children: dash(left)
|
|
8151
|
-
}),
|
|
8152
|
-
/* @__PURE__ */ jsx("div", {
|
|
8153
|
-
className: "row-start-2 col-start-2",
|
|
8154
|
-
children
|
|
8155
|
-
}),
|
|
8156
|
-
/* @__PURE__ */ jsx("span", {
|
|
8157
|
-
className: "row-start-2 col-start-3 self-center text-center",
|
|
8158
|
-
children: dash(right)
|
|
8159
|
-
}),
|
|
8160
|
-
/* @__PURE__ */ jsx("span", {
|
|
8161
|
-
className: "row-start-3 col-start-2 self-center text-center",
|
|
8162
|
-
children: dash(bottom)
|
|
8163
|
-
})
|
|
8164
|
-
]
|
|
8165
|
-
});
|
|
8166
|
-
}
|
|
8167
|
-
function BoxModel({ info }) {
|
|
8168
|
-
const content = {
|
|
8169
|
-
w: Math.max(0, info.rect.w - info.border[1] - info.border[3] - info.padding[1] - info.padding[3]),
|
|
8170
|
-
h: Math.max(0, info.rect.h - info.border[0] - info.border[2] - info.padding[0] - info.padding[2])
|
|
8171
|
-
};
|
|
8172
|
-
return /* @__PURE__ */ jsx("div", {
|
|
8173
|
-
className: "px-4 pt-1 pb-2 font-mono text-[11px] text-ink2",
|
|
8174
|
-
children: /* @__PURE__ */ jsx(Layer, {
|
|
8175
|
-
label: "margin",
|
|
8176
|
-
values: info.margin,
|
|
8177
|
-
className: "border border-dashed border-rule-strong bg-[#f6b26b]/15",
|
|
8178
|
-
children: /* @__PURE__ */ jsx(Layer, {
|
|
8179
|
-
label: "border",
|
|
8180
|
-
values: info.border,
|
|
8181
|
-
className: "border border-rule-strong bg-[#f9e27d]/15",
|
|
8182
|
-
children: /* @__PURE__ */ jsx(Layer, {
|
|
8183
|
-
label: "padding",
|
|
8184
|
-
values: info.padding,
|
|
8185
|
-
className: "border border-rule-strong bg-[#93c47d]/20",
|
|
8186
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
8187
|
-
className: "flex h-8 items-center justify-center rounded-[3px] border border-rule-strong bg-[#6fa8dc]/25 text-ink",
|
|
8188
|
-
children: [
|
|
8189
|
-
Math.round(content.w * 100) / 100,
|
|
8190
|
-
" × ",
|
|
8191
|
-
Math.round(content.h * 100) / 100
|
|
8192
|
-
]
|
|
8193
|
-
})
|
|
8194
|
-
})
|
|
8195
|
-
})
|
|
8196
|
-
})
|
|
8197
|
-
});
|
|
8198
|
-
}
|
|
8199
|
-
function Crumbs({ info, onSelect }) {
|
|
8200
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
8201
|
-
className: "flex flex-wrap items-center gap-1 px-4 pb-2",
|
|
8202
|
-
children: [info.path.slice(-4).map((crumb, index) => /* @__PURE__ */ jsxs("span", {
|
|
8203
|
-
className: "flex items-center gap-1",
|
|
8204
|
-
children: [/* @__PURE__ */ jsx("button", {
|
|
8205
|
-
type: "button",
|
|
8206
|
-
onClick: () => onSelect(crumb.ref),
|
|
8207
|
-
className: "cursor-pointer rounded-[4px] border-0 bg-soft2 px-1.5 py-0.5 font-mono text-[11px] text-ink2 hover:text-ink",
|
|
8208
|
-
children: crumb.label
|
|
8209
|
-
}), /* @__PURE__ */ jsx(Icon, {
|
|
8210
|
-
name: "chevron-right",
|
|
8211
|
-
size: 11,
|
|
8212
|
-
className: "text-muted"
|
|
8213
|
-
})]
|
|
8214
|
-
}, index)), /* @__PURE__ */ jsx("span", {
|
|
8215
|
-
className: "rounded-[4px] bg-action-soft px-1.5 py-0.5 font-mono text-[11px] text-action",
|
|
8216
|
-
children: info.label
|
|
8217
|
-
})]
|
|
8218
|
-
});
|
|
8219
|
-
}
|
|
8220
|
-
/** The right sidebar: what the selected element is and how it is styled. */
|
|
8221
|
-
function InspectorPanel({ info, onSelect, onClose, active }) {
|
|
8222
|
-
return /* @__PURE__ */ jsxs("aside", {
|
|
8223
|
-
"data-ui": true,
|
|
8224
|
-
className: "flex w-[320px] shrink-0 flex-col overflow-hidden border-l border-rule bg-surface",
|
|
8225
|
-
children: [
|
|
8226
|
-
/* @__PURE__ */ jsxs("div", {
|
|
8227
|
-
className: "flex h-[46px] shrink-0 items-center justify-between border-b border-rule pr-2 pl-4",
|
|
8228
|
-
children: [/* @__PURE__ */ jsxs("span", {
|
|
8229
|
-
className: "flex items-center gap-2 text-[14px] font-semibold",
|
|
8230
|
-
children: [/* @__PURE__ */ jsx(Icon, {
|
|
8231
|
-
name: "pointer",
|
|
8232
|
-
size: 15,
|
|
8233
|
-
className: "text-action"
|
|
8234
|
-
}), " Inspect"]
|
|
8235
|
-
}), /* @__PURE__ */ jsx(IconButton, {
|
|
8236
|
-
icon: "x",
|
|
8237
|
-
label: "Close inspector (I)",
|
|
8238
|
-
onClick: onClose
|
|
8239
|
-
})]
|
|
8240
|
-
}),
|
|
8241
|
-
!info ? /* @__PURE__ */ jsxs("div", {
|
|
8242
|
-
className: "flex flex-col items-center gap-2 px-6 py-10 text-center",
|
|
8243
|
-
children: [
|
|
8244
|
-
/* @__PURE__ */ jsx(Icon, {
|
|
8245
|
-
name: "pointer",
|
|
8246
|
-
size: 22,
|
|
8247
|
-
className: "text-muted"
|
|
8248
|
-
}),
|
|
8249
|
-
/* @__PURE__ */ jsx("span", {
|
|
8250
|
-
className: "text-[13.5px] text-ink2",
|
|
8251
|
-
children: active ? "Click an element in any screen." : `Hold ${MOD_KEY} to highlight, click to inspect.`
|
|
8252
|
-
}),
|
|
8253
|
-
/* @__PURE__ */ jsx("span", {
|
|
8254
|
-
className: "text-[12.5px] leading-[1.5] text-muted",
|
|
8255
|
-
children: "Hover shows margin, padding and content. Clicks go to the inspector, not the screen."
|
|
8256
|
-
}),
|
|
8257
|
-
/* @__PURE__ */ jsxs("span", {
|
|
8258
|
-
className: "mt-2 flex items-center gap-1.5 text-[12px] text-muted",
|
|
8259
|
-
children: [
|
|
8260
|
-
/* @__PURE__ */ jsx(Kbd, { children: "I" }),
|
|
8261
|
-
" toggle · hold ",
|
|
8262
|
-
/* @__PURE__ */ jsx(Kbd, { children: MOD_KEY }),
|
|
8263
|
-
" · ",
|
|
8264
|
-
/* @__PURE__ */ jsx(Kbd, { children: "esc" }),
|
|
8265
|
-
" clear"
|
|
8266
|
-
]
|
|
8267
|
-
})
|
|
8268
|
-
]
|
|
8269
|
-
}) : /* @__PURE__ */ jsxs("div", {
|
|
8270
|
-
className: "min-h-0 grow overflow-y-auto",
|
|
8271
|
-
children: [
|
|
8272
|
-
/* @__PURE__ */ jsxs(Section, {
|
|
8273
|
-
title: "Element",
|
|
8274
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
8275
|
-
className: "flex flex-col gap-1 px-4 pb-2",
|
|
8276
|
-
children: [
|
|
8277
|
-
/* @__PURE__ */ jsx("div", {
|
|
8278
|
-
className: "flex min-w-0 items-baseline gap-1.5",
|
|
8279
|
-
children: info.component?.owners[0] ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
|
|
8280
|
-
className: "truncate font-mono text-[14px] font-medium text-ink",
|
|
8281
|
-
children: [
|
|
8282
|
-
"<",
|
|
8283
|
-
info.component.owners[0],
|
|
8284
|
-
">"
|
|
8285
|
-
]
|
|
8286
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
8287
|
-
className: "truncate font-mono text-[12px] text-muted",
|
|
8288
|
-
children: info.tag
|
|
8289
|
-
})] }) : /* @__PURE__ */ jsx("span", {
|
|
8290
|
-
className: "truncate font-mono text-[14px] font-medium text-ink",
|
|
8291
|
-
children: info.label
|
|
8292
|
-
})
|
|
8293
|
-
}),
|
|
8294
|
-
info.component && info.component.owners.length > 1 ? /* @__PURE__ */ jsxs("span", {
|
|
8295
|
-
className: "truncate text-[12px] text-muted",
|
|
8296
|
-
children: ["in ", info.component.owners.slice(1).join(" ← ")]
|
|
8297
|
-
}) : null,
|
|
8298
|
-
info.component?.file ? /* @__PURE__ */ jsx("button", {
|
|
8299
|
-
type: "button",
|
|
8300
|
-
onClick: () => void copyText(info.component.file, "Path copied"),
|
|
8301
|
-
className: "w-fit max-w-full cursor-pointer truncate border-0 bg-transparent p-0 text-left font-mono text-[11.5px] text-muted hover:text-ink2",
|
|
8302
|
-
title: "Copy path",
|
|
8303
|
-
children: info.component.file
|
|
8304
|
-
}) : null,
|
|
8305
|
-
info.text ? /* @__PURE__ */ jsxs("span", {
|
|
8306
|
-
className: "line-clamp-2 text-[12.5px] text-ink2",
|
|
8307
|
-
children: [
|
|
8308
|
-
"“",
|
|
8309
|
-
info.text,
|
|
8310
|
-
"”"
|
|
8311
|
-
]
|
|
8312
|
-
}) : null
|
|
8313
|
-
]
|
|
8314
|
-
}), /* @__PURE__ */ jsx(Crumbs, {
|
|
8315
|
-
info,
|
|
8316
|
-
onSelect
|
|
8317
|
-
})]
|
|
8318
|
-
}),
|
|
8319
|
-
/* @__PURE__ */ jsxs(Section, {
|
|
8320
|
-
title: "Box",
|
|
8321
|
-
children: [
|
|
8322
|
-
/* @__PURE__ */ jsx(BoxModel, { info }),
|
|
8323
|
-
/* @__PURE__ */ jsx(Line, {
|
|
8324
|
-
label: "Size",
|
|
8325
|
-
children: /* @__PURE__ */ jsxs(Value$1, { children: [
|
|
8326
|
-
info.rect.w,
|
|
8327
|
-
" × ",
|
|
8328
|
-
info.rect.h
|
|
8329
|
-
] })
|
|
8330
|
-
}),
|
|
8331
|
-
/* @__PURE__ */ jsx(Line, {
|
|
8332
|
-
label: "Position",
|
|
8333
|
-
children: /* @__PURE__ */ jsxs(Value$1, { children: [
|
|
8334
|
-
"x ",
|
|
8335
|
-
info.rect.x,
|
|
8336
|
-
" · y ",
|
|
8337
|
-
info.rect.y
|
|
8338
|
-
] })
|
|
8339
|
-
})
|
|
8340
|
-
]
|
|
8341
|
-
}),
|
|
8342
|
-
info.layout.length ? /* @__PURE__ */ jsx(Section, {
|
|
8343
|
-
title: "Layout",
|
|
8344
|
-
children: info.layout.map(([label, value]) => /* @__PURE__ */ jsx(Line, {
|
|
8345
|
-
label,
|
|
8346
|
-
children: /* @__PURE__ */ jsx(Value$1, {
|
|
8347
|
-
title: value,
|
|
8348
|
-
children: value
|
|
8349
|
-
})
|
|
8350
|
-
}, label))
|
|
8351
|
-
}) : null,
|
|
8352
|
-
info.typography ? /* @__PURE__ */ jsxs(Section, {
|
|
8353
|
-
title: "Typography",
|
|
8354
|
-
children: [
|
|
8355
|
-
/* @__PURE__ */ jsxs(Line, {
|
|
8356
|
-
label: "Font",
|
|
8357
|
-
children: [/* @__PURE__ */ jsx(Value$1, {
|
|
8358
|
-
title: info.typography.family,
|
|
8359
|
-
children: info.typography.family.split(",")[0]?.replace(/["']/g, "")
|
|
8360
|
-
}), /* @__PURE__ */ jsx(TokenChip, { name: info.typography.familyToken })]
|
|
8361
|
-
}),
|
|
8362
|
-
/* @__PURE__ */ jsxs(Line, {
|
|
8363
|
-
label: "Size",
|
|
8364
|
-
children: [/* @__PURE__ */ jsxs(Value$1, { children: [
|
|
8365
|
-
info.typography.size,
|
|
8366
|
-
" / ",
|
|
8367
|
-
info.typography.lineHeight
|
|
8368
|
-
] }), /* @__PURE__ */ jsx(TokenChip, { name: info.typography.sizeToken })]
|
|
8369
|
-
}),
|
|
8370
|
-
/* @__PURE__ */ jsx(Line, {
|
|
8371
|
-
label: "Weight",
|
|
8372
|
-
children: /* @__PURE__ */ jsx(Value$1, { children: info.typography.weight })
|
|
8373
|
-
}),
|
|
8374
|
-
info.typography.letterSpacing !== "normal" ? /* @__PURE__ */ jsx(Line, {
|
|
8375
|
-
label: "Tracking",
|
|
8376
|
-
children: /* @__PURE__ */ jsx(Value$1, { children: info.typography.letterSpacing })
|
|
8377
|
-
}) : null,
|
|
8378
|
-
/* @__PURE__ */ jsx(ColorLine, {
|
|
8379
|
-
label: "Color",
|
|
8380
|
-
color: info.typography.color
|
|
8381
|
-
}),
|
|
8382
|
-
info.typography.align !== "start" ? /* @__PURE__ */ jsx(Line, {
|
|
8383
|
-
label: "Align",
|
|
8384
|
-
children: /* @__PURE__ */ jsx(Value$1, { children: info.typography.align })
|
|
8385
|
-
}) : null,
|
|
8386
|
-
info.typography.transform ? /* @__PURE__ */ jsx(Line, {
|
|
8387
|
-
label: "Transform",
|
|
8388
|
-
children: /* @__PURE__ */ jsx(Value$1, { children: info.typography.transform })
|
|
8389
|
-
}) : null
|
|
8390
|
-
]
|
|
8391
|
-
}) : null,
|
|
8392
|
-
Object.values(info.appearance).some(Boolean) ? /* @__PURE__ */ jsxs(Section, {
|
|
8393
|
-
title: "Appearance",
|
|
8394
|
-
children: [
|
|
8395
|
-
info.appearance.background ? /* @__PURE__ */ jsx(ColorLine, {
|
|
8396
|
-
label: "Background",
|
|
8397
|
-
color: info.appearance.background
|
|
8398
|
-
}) : null,
|
|
8399
|
-
info.appearance.backgroundImage ? /* @__PURE__ */ jsx(Line, {
|
|
8400
|
-
label: "Image",
|
|
8401
|
-
children: /* @__PURE__ */ jsx(Value$1, {
|
|
8402
|
-
title: info.appearance.backgroundImage,
|
|
8403
|
-
children: info.appearance.backgroundImage
|
|
8404
|
-
})
|
|
8405
|
-
}) : null,
|
|
8406
|
-
info.appearance.border ? /* @__PURE__ */ jsxs(Line, {
|
|
8407
|
-
label: "Border",
|
|
8408
|
-
children: [
|
|
8409
|
-
/* @__PURE__ */ jsx("span", {
|
|
8410
|
-
className: "size-3.5 shrink-0 rounded-[3px] shadow-[inset_0_0_0_1px_rgba(0,0,0,0.12)]",
|
|
8411
|
-
style: { background: info.appearance.border.color.css }
|
|
8412
|
-
}),
|
|
8413
|
-
/* @__PURE__ */ jsxs(Value$1, { children: [
|
|
8414
|
-
info.appearance.border.width,
|
|
8415
|
-
" ",
|
|
8416
|
-
info.appearance.border.style,
|
|
8417
|
-
" ",
|
|
8418
|
-
info.appearance.border.color.value
|
|
8419
|
-
] }),
|
|
8420
|
-
/* @__PURE__ */ jsx(TokenChip, { name: info.appearance.border.color.token })
|
|
8421
|
-
]
|
|
8422
|
-
}) : null,
|
|
8423
|
-
info.appearance.radius ? /* @__PURE__ */ jsxs(Line, {
|
|
8424
|
-
label: "Radius",
|
|
8425
|
-
children: [/* @__PURE__ */ jsx(Value$1, { children: info.appearance.radius.value }), /* @__PURE__ */ jsx(TokenChip, { name: info.appearance.radius.token })]
|
|
8426
|
-
}) : null,
|
|
8427
|
-
info.appearance.shadow ? /* @__PURE__ */ jsxs(Line, {
|
|
8428
|
-
label: "Shadow",
|
|
8429
|
-
children: [/* @__PURE__ */ jsx(Value$1, {
|
|
8430
|
-
title: info.appearance.shadow.value,
|
|
8431
|
-
children: info.appearance.shadow.value
|
|
8432
|
-
}), /* @__PURE__ */ jsx(TokenChip, { name: info.appearance.shadow.token })]
|
|
8433
|
-
}) : null,
|
|
8434
|
-
info.appearance.opacity ? /* @__PURE__ */ jsx(Line, {
|
|
8435
|
-
label: "Opacity",
|
|
8436
|
-
children: /* @__PURE__ */ jsx(Value$1, { children: info.appearance.opacity })
|
|
8437
|
-
}) : null
|
|
8438
|
-
]
|
|
8439
|
-
}) : null,
|
|
8440
|
-
info.classes.length ? /* @__PURE__ */ jsx(Section, {
|
|
8441
|
-
title: "Classes",
|
|
8442
|
-
right: /* @__PURE__ */ jsx("button", {
|
|
8443
|
-
type: "button",
|
|
8444
|
-
onClick: () => void copyText(info.classes.join(" "), "Classes copied"),
|
|
8445
|
-
className: "cursor-pointer border-0 bg-transparent text-[12px] text-muted hover:text-ink2",
|
|
8446
|
-
children: "Copy"
|
|
8447
|
-
}),
|
|
8448
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
8449
|
-
className: "flex flex-wrap gap-1 px-4 pb-1",
|
|
8450
|
-
children: info.classes.map((name) => /* @__PURE__ */ jsx("span", {
|
|
8451
|
-
className: "rounded-[4px] bg-soft2 px-1.5 py-0.5 font-mono text-[11px] text-ink2",
|
|
8452
|
-
children: name
|
|
8453
|
-
}, name))
|
|
8454
|
-
})
|
|
8455
|
-
}) : null,
|
|
8456
|
-
info.attributes.length ? /* @__PURE__ */ jsx(Section, {
|
|
8457
|
-
title: "Attributes",
|
|
8458
|
-
children: info.attributes.map(([name, value]) => /* @__PURE__ */ jsx(Line, {
|
|
8459
|
-
label: name,
|
|
8460
|
-
children: /* @__PURE__ */ jsx(Value$1, {
|
|
8461
|
-
title: value,
|
|
8462
|
-
children: value
|
|
8463
|
-
})
|
|
8464
|
-
}, name))
|
|
8465
|
-
}) : null,
|
|
8466
|
-
info.children.length ? /* @__PURE__ */ jsx(Section, {
|
|
8467
|
-
title: `Children · ${info.children.length}`,
|
|
8468
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
8469
|
-
className: "flex flex-col px-2 pb-1",
|
|
8470
|
-
children: info.children.map((child, index) => /* @__PURE__ */ jsxs("button", {
|
|
8471
|
-
type: "button",
|
|
8472
|
-
onClick: () => onSelect(child.ref),
|
|
8473
|
-
className: "flex h-7 cursor-pointer items-center gap-2 rounded-[6px] border-0 bg-transparent px-2 text-left font-mono text-[12px] text-ink2 hover:bg-soft2",
|
|
8474
|
-
children: [/* @__PURE__ */ jsx(Icon, {
|
|
8475
|
-
name: "chevron-right",
|
|
8476
|
-
size: 12,
|
|
8477
|
-
className: "text-muted"
|
|
8478
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
8479
|
-
className: "truncate",
|
|
8480
|
-
children: child.label
|
|
8481
|
-
})]
|
|
8482
|
-
}, index))
|
|
8483
|
-
})
|
|
8484
|
-
}) : null
|
|
8485
|
-
]
|
|
8486
|
-
}),
|
|
8487
|
-
info ? /* @__PURE__ */ jsx("div", {
|
|
8488
|
-
className: "flex shrink-0 items-center gap-2 border-t border-rule bg-soft px-4 py-2.5",
|
|
8489
|
-
children: /* @__PURE__ */ jsx(Button, {
|
|
8490
|
-
icon: "copy",
|
|
8491
|
-
onClick: () => void copyText(info.css, "CSS copied"),
|
|
8492
|
-
className: "grow",
|
|
8493
|
-
children: "Copy CSS"
|
|
8494
|
-
})
|
|
8495
|
-
}) : null
|
|
8496
|
-
]
|
|
8497
|
-
});
|
|
8498
|
-
}
|
|
8499
|
-
//#endregion
|
|
8500
|
-
//#region viewer/src/ui/badge.tsx
|
|
8501
|
-
var BADGE = {
|
|
8502
|
-
neutral: "bg-soft2 text-ink2",
|
|
8503
|
-
action: "bg-action-soft text-action",
|
|
8504
|
-
ok: "bg-ok-soft text-ok-text",
|
|
8505
|
-
danger: "bg-danger-soft text-danger-text"
|
|
8506
|
-
};
|
|
8507
|
-
function Badge({ tone = "neutral", children, className }) {
|
|
8508
|
-
return /* @__PURE__ */ jsx("span", {
|
|
8509
|
-
className: cn("inline-flex h-[22px] shrink-0 items-center gap-1 whitespace-nowrap rounded-[6px] px-2 text-[12px] font-medium", BADGE[tone], className),
|
|
8510
|
-
children
|
|
8511
|
-
});
|
|
8512
|
-
}
|
|
8513
|
-
function Count({ value, tone = "neutral" }) {
|
|
8514
|
-
return /* @__PURE__ */ jsx("span", {
|
|
8515
|
-
className: cn("inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-[4px] px-[5px] text-[11.5px] font-semibold tabular", tone === "action" ? "bg-action-soft text-action" : tone === "danger" ? "bg-danger-soft text-danger-text" : "bg-soft2 text-ink2"),
|
|
8516
|
-
children: value
|
|
8517
|
-
});
|
|
8518
|
-
}
|
|
8519
|
-
//#endregion
|
|
8520
|
-
//#region viewer/src/ui/panel.tsx
|
|
8521
|
-
function Panel({ children, className, style }) {
|
|
8522
|
-
return /* @__PURE__ */ jsx("section", {
|
|
8523
|
-
className: cn("min-w-0 shrink-0 overflow-hidden rounded-[10px] border border-rule bg-surface", className),
|
|
8524
|
-
style,
|
|
8525
|
-
children
|
|
8526
|
-
});
|
|
8527
|
-
}
|
|
8528
|
-
function PanelHead({ title, count, sub, right, as: Heading = "h2" }) {
|
|
8529
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
8530
|
-
className: "flex min-h-[46px] items-center justify-between gap-3 px-4",
|
|
8531
|
-
children: [/* @__PURE__ */ jsxs(Heading, {
|
|
8532
|
-
className: "m-0 flex min-w-0 items-center gap-2 whitespace-nowrap text-[14px] font-semibold",
|
|
8533
|
-
children: [
|
|
8534
|
-
title,
|
|
8535
|
-
count !== void 0 ? /* @__PURE__ */ jsx(Count, { value: count }) : null,
|
|
8536
|
-
sub ? /* @__PURE__ */ jsx("span", {
|
|
8537
|
-
className: "truncate text-[12.5px] font-normal text-muted",
|
|
8538
|
-
children: sub
|
|
8539
|
-
}) : null
|
|
8540
|
-
]
|
|
8541
|
-
}), right ? /* @__PURE__ */ jsx("div", {
|
|
8542
|
-
className: "flex items-center gap-2",
|
|
8543
|
-
children: right
|
|
8544
|
-
}) : null]
|
|
8545
|
-
});
|
|
8546
|
-
}
|
|
8547
|
-
function PanelTabs({ items, value, onChange, right }) {
|
|
8548
|
-
const indicator = useId();
|
|
8549
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
8550
|
-
className: "flex h-[46px] items-center justify-between gap-3 px-4",
|
|
8551
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
8552
|
-
role: "tablist",
|
|
8553
|
-
className: "flex gap-5",
|
|
8554
|
-
children: items.map((item) => {
|
|
8555
|
-
const on = item.value === value;
|
|
8556
|
-
return /* @__PURE__ */ jsxs("button", {
|
|
8557
|
-
type: "button",
|
|
8558
|
-
role: "tab",
|
|
8559
|
-
"aria-selected": on,
|
|
8560
|
-
onClick: () => onChange(item.value),
|
|
8561
|
-
className: cn("relative flex h-[46px] cursor-pointer items-center gap-1.5 border-0 bg-transparent px-0.5 text-[14px] transition-colors", on ? "font-semibold text-ink" : "font-medium text-ink2 hover:text-ink"),
|
|
8562
|
-
children: [
|
|
8563
|
-
item.label,
|
|
8564
|
-
item.count !== void 0 ? /* @__PURE__ */ jsx(Count, { value: item.count }) : null,
|
|
8565
|
-
on ? /* @__PURE__ */ jsx(motion.span, {
|
|
8566
|
-
layoutId: indicator,
|
|
8567
|
-
className: "absolute inset-x-0 bottom-0 h-[2px] bg-ink",
|
|
8568
|
-
transition: {
|
|
8569
|
-
type: "spring",
|
|
8570
|
-
stiffness: 520,
|
|
8571
|
-
damping: 42
|
|
8572
|
-
}
|
|
8573
|
-
}) : null
|
|
8574
|
-
]
|
|
8575
|
-
}, item.value);
|
|
8576
|
-
})
|
|
8577
|
-
}), right ? /* @__PURE__ */ jsx("div", {
|
|
8578
|
-
className: "flex items-center gap-2",
|
|
8579
|
-
children: right
|
|
8580
|
-
}) : null]
|
|
8581
|
-
});
|
|
8582
|
-
}
|
|
8583
|
-
function Cell({ align, children }) {
|
|
8584
|
-
return /* @__PURE__ */ jsx("div", {
|
|
8585
|
-
className: cn("flex min-w-0 items-center gap-2", align === "r" && "justify-end text-right", align === "c" && "justify-center"),
|
|
8586
|
-
children
|
|
8587
|
-
});
|
|
8588
|
-
}
|
|
8589
|
-
function cells(children, align) {
|
|
8590
|
-
return Children.toArray(children).map((child, index) => /* @__PURE__ */ jsx(Cell, {
|
|
8591
|
-
align: align?.[index] ?? "l",
|
|
8592
|
-
children: child
|
|
8593
|
-
}, index));
|
|
8594
|
-
}
|
|
8595
|
-
function THead({ cols, align, labels }) {
|
|
8596
|
-
return /* @__PURE__ */ jsx("div", {
|
|
8597
|
-
className: "grid h-8 items-center gap-x-4 border-t border-rule bg-soft px-4 text-[12.5px] text-muted",
|
|
8598
|
-
style: { gridTemplateColumns: cols },
|
|
8599
|
-
children: labels.map((label, index) => /* @__PURE__ */ jsx(Cell, {
|
|
8600
|
-
align: align?.[index] ?? "l",
|
|
8601
|
-
children: /* @__PURE__ */ jsx("span", {
|
|
8602
|
-
className: "whitespace-nowrap",
|
|
8603
|
-
children: label
|
|
8604
|
-
})
|
|
8605
|
-
}, index))
|
|
8606
|
-
});
|
|
8607
|
-
}
|
|
8608
|
-
var rowClass = (first, interactive, className) => cn("grid items-center gap-x-4 border-rule", !first && "border-t", interactive && "cursor-pointer transition-colors hover:bg-soft", className);
|
|
8609
|
-
function Row({ cols, align, minH = 44, pad = "10px 16px", children, first, className, onClick, title }) {
|
|
8610
|
-
return /* @__PURE__ */ jsx("div", {
|
|
8611
|
-
className: rowClass(first, !!onClick, className),
|
|
8612
|
-
style: {
|
|
8613
|
-
gridTemplateColumns: cols,
|
|
8614
|
-
minHeight: minH,
|
|
8615
|
-
padding: pad
|
|
8616
|
-
},
|
|
8617
|
-
onClick,
|
|
8618
|
-
title,
|
|
8619
|
-
children: cells(children, align)
|
|
8620
|
-
});
|
|
8621
|
-
}
|
|
8622
|
-
/** A table row that navigates somewhere in the app. */
|
|
8623
|
-
function RowLink({ to, cols, align, minH = 44, pad = "10px 16px", first, className, children }) {
|
|
8624
|
-
return /* @__PURE__ */ jsx(Link, {
|
|
8625
|
-
href: to,
|
|
8626
|
-
className: cn(rowClass(first, true, className), "text-inherit no-underline"),
|
|
8627
|
-
style: {
|
|
8628
|
-
gridTemplateColumns: cols,
|
|
8629
|
-
minHeight: minH,
|
|
8630
|
-
padding: pad
|
|
8631
|
-
},
|
|
8632
|
-
children: cells(children, align)
|
|
8633
|
-
});
|
|
8634
|
-
}
|
|
8635
|
-
function KV({ label, children, labelWidth = 180, minH = 40, end = false }) {
|
|
8636
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
8637
|
-
className: "grid content-center items-baseline gap-x-4 border-t border-rule px-4 py-2",
|
|
8638
|
-
style: {
|
|
8639
|
-
gridTemplateColumns: `min(${labelWidth}px, 42%) minmax(0, 1fr)`,
|
|
8640
|
-
minHeight: minH
|
|
8641
|
-
},
|
|
8642
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
8643
|
-
className: "text-[13px] text-muted",
|
|
8644
|
-
children: label
|
|
8645
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
8646
|
-
className: cn("flex min-w-0 flex-wrap items-center gap-2", end && "justify-end"),
|
|
8647
|
-
children
|
|
8648
|
-
})]
|
|
8649
|
-
});
|
|
8650
|
-
}
|
|
8651
|
-
function PanelBody({ children, pad = "16px", gap = 16, top = true, className }) {
|
|
8652
|
-
return /* @__PURE__ */ jsx("div", {
|
|
8653
|
-
className: cn("flex flex-col", top && "border-t border-rule", className),
|
|
8654
|
-
style: {
|
|
8655
|
-
padding: pad,
|
|
8656
|
-
gap
|
|
8657
|
-
},
|
|
8658
|
-
children
|
|
8659
|
-
});
|
|
8660
|
-
}
|
|
8661
|
-
function PanelFoot({ left, right }) {
|
|
8662
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
8663
|
-
className: "flex items-center justify-between gap-3 border-t border-rule bg-soft px-4 py-3",
|
|
8664
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
8665
|
-
className: "flex min-w-0 items-center gap-2 text-[12.5px] text-muted",
|
|
8666
|
-
children: left
|
|
8667
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
8668
|
-
className: "flex items-center gap-2",
|
|
8669
|
-
children: right
|
|
8670
|
-
})]
|
|
8671
|
-
});
|
|
8672
|
-
}
|
|
8673
|
-
/** Bottom summary strip. */
|
|
8674
|
-
function Strip({ children, className }) {
|
|
8675
|
-
return /* @__PURE__ */ jsx("div", {
|
|
8676
|
-
className: cn("flex min-h-[38px] shrink-0 items-center gap-2 border-t border-rule bg-soft px-4 py-2 text-[12.5px] text-muted", className),
|
|
8677
|
-
children
|
|
8678
|
-
});
|
|
8679
|
-
}
|
|
8680
|
-
/** Group header row inside tables. */
|
|
8681
|
-
function GroupRow({ children, sub }) {
|
|
8682
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
8683
|
-
className: "flex h-9 items-center gap-2.5 border-t border-rule bg-soft px-4 text-[13px] font-medium",
|
|
8684
|
-
children: [children, sub ? /* @__PURE__ */ jsx("span", {
|
|
8685
|
-
className: "text-[12.5px] font-normal text-muted",
|
|
8686
|
-
children: sub
|
|
8687
|
-
}) : null]
|
|
8688
|
-
});
|
|
8689
|
-
}
|
|
8690
|
-
//#endregion
|
|
8691
|
-
//#region viewer/src/components/issues.tsx
|
|
8692
|
-
function IssueList({ issues, className }) {
|
|
8693
|
-
return /* @__PURE__ */ jsx("div", {
|
|
8694
|
-
className: cn("flex flex-col", className),
|
|
8695
|
-
children: issues.map((issue, index) => /* @__PURE__ */ jsxs("div", {
|
|
8696
|
-
className: "flex items-start gap-2.5 border-t border-rule px-4 py-2.5 text-[13px] first:border-t-0",
|
|
8697
|
-
children: [/* @__PURE__ */ jsx(Icon, {
|
|
8698
|
-
name: issue.severity === "error" ? "alert" : "info",
|
|
8699
|
-
size: 15,
|
|
8700
|
-
className: cn("mt-0.5", issue.severity === "error" ? "text-danger-text" : "text-action")
|
|
8701
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
8702
|
-
className: "flex min-w-0 flex-col gap-0.5",
|
|
8703
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
8704
|
-
className: "text-ink",
|
|
8705
|
-
children: issue.message
|
|
8706
|
-
}), /* @__PURE__ */ jsxs("span", {
|
|
8707
|
-
className: "font-mono text-[12px] break-all text-muted",
|
|
8708
|
-
children: [issue.file, issue.at ? ` · ${issue.at}` : ""]
|
|
8709
|
-
})]
|
|
8710
|
-
})]
|
|
8711
|
-
}, index))
|
|
8712
|
-
});
|
|
8713
|
-
}
|
|
8714
|
-
/** Problems found in the project's files, as a panel. */
|
|
8715
|
-
function IssuesPanel({ issues, title = "Problems" }) {
|
|
8716
|
-
if (!issues.length) return null;
|
|
8717
|
-
const errors = issues.filter((issue) => issue.severity === "error").length;
|
|
8718
|
-
return /* @__PURE__ */ jsxs(Panel, {
|
|
8719
|
-
className: errors ? "border-danger/40" : void 0,
|
|
8720
|
-
children: [/* @__PURE__ */ jsx(PanelHead, {
|
|
8721
|
-
title,
|
|
8722
|
-
count: issues.length,
|
|
8723
|
-
sub: errors ? `${errors} blocking` : "warnings"
|
|
8724
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
8725
|
-
className: "border-t border-rule",
|
|
8726
|
-
children: /* @__PURE__ */ jsx(IssueList, { issues })
|
|
8727
|
-
})]
|
|
8728
|
-
});
|
|
8729
|
-
}
|
|
8730
|
-
//#endregion
|
|
8731
|
-
//#region viewer/src/components/slide-panel.tsx
|
|
8732
|
-
var EASE = [
|
|
8733
|
-
.2,
|
|
8734
|
-
.7,
|
|
8735
|
-
.2,
|
|
8736
|
-
1
|
|
8737
|
-
];
|
|
8738
|
-
/**
|
|
8739
|
-
* A side panel that slides open and shut. The wrapper animates its width while
|
|
8740
|
-
* the content keeps its own, so nothing inside reflows mid-animation.
|
|
8741
|
-
*/
|
|
8742
|
-
function SlidePanel({ open, side, width, children }) {
|
|
8743
|
-
return /* @__PURE__ */ jsx(AnimatePresence, {
|
|
8744
|
-
initial: false,
|
|
8745
|
-
children: open ? /* @__PURE__ */ jsx(motion.div, {
|
|
8746
|
-
initial: {
|
|
8747
|
-
width: 0,
|
|
8748
|
-
opacity: .6
|
|
8749
|
-
},
|
|
8750
|
-
animate: {
|
|
8751
|
-
width,
|
|
8752
|
-
opacity: 1
|
|
8753
|
-
},
|
|
8754
|
-
exit: {
|
|
8755
|
-
width: 0,
|
|
8756
|
-
opacity: .6
|
|
8757
|
-
},
|
|
8758
|
-
transition: {
|
|
8759
|
-
duration: .22,
|
|
8760
|
-
ease: EASE
|
|
8761
|
-
},
|
|
8762
|
-
className: cn("flex min-h-0 shrink-0 overflow-hidden", side === "right" ? "justify-start" : "justify-end"),
|
|
8763
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
8764
|
-
className: "flex min-h-0 shrink-0",
|
|
8765
|
-
style: { width },
|
|
8766
|
-
children
|
|
8767
|
-
})
|
|
8768
|
-
}, "panel") : null
|
|
8769
|
-
});
|
|
8770
|
-
}
|
|
8771
|
-
//#endregion
|
|
8772
|
-
//#region viewer/src/ui/page.tsx
|
|
8773
|
-
/** Page body under the top bar. */
|
|
8774
|
-
function Content({ children, className, gap = 20, wide = false }) {
|
|
8775
|
-
return /* @__PURE__ */ jsx("main", {
|
|
8776
|
-
className: cn("mx-auto flex w-full min-h-0 grow flex-col px-8 pt-6 pb-7 animate-[q-fade-in_160ms_ease-out] max-md:px-4 max-md:pt-4", wide ? "max-w-[1440px]" : "max-w-[1200px]", className),
|
|
8777
|
-
style: { gap },
|
|
8778
|
-
children
|
|
8779
|
-
});
|
|
8780
|
-
}
|
|
8781
|
-
function PageHead({ title, sub, right, badge, crumbs }) {
|
|
8782
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
8783
|
-
className: "flex shrink-0 items-end justify-between gap-6 max-md:flex-col max-md:items-stretch max-md:gap-3",
|
|
8784
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
8785
|
-
className: "flex min-w-0 flex-col gap-1",
|
|
8786
|
-
children: [
|
|
8787
|
-
crumbs ? /* @__PURE__ */ jsx("div", {
|
|
8788
|
-
className: "flex items-center gap-1.5 text-[13px] text-muted",
|
|
8789
|
-
children: crumbs
|
|
8790
|
-
}) : null,
|
|
8791
|
-
/* @__PURE__ */ jsxs("div", {
|
|
8792
|
-
className: "flex items-center gap-3",
|
|
8793
|
-
children: [/* @__PURE__ */ jsx("h1", {
|
|
8794
|
-
className: "m-0 text-[22px] font-semibold tracking-[-0.01em]",
|
|
8795
|
-
children: title
|
|
8796
|
-
}), badge]
|
|
8797
|
-
}),
|
|
8798
|
-
sub ? /* @__PURE__ */ jsx("div", {
|
|
8799
|
-
className: "text-[13px] [overflow-wrap:anywhere] text-muted",
|
|
8800
|
-
children: sub
|
|
8801
|
-
}) : null
|
|
8802
|
-
]
|
|
8803
|
-
}), right ? /* @__PURE__ */ jsx("div", {
|
|
8804
|
-
className: "flex flex-wrap items-center gap-2",
|
|
8805
|
-
children: right
|
|
8806
|
-
}) : null]
|
|
8807
|
-
});
|
|
8808
|
-
}
|
|
8809
|
-
function SubNav({ items, label }) {
|
|
8810
|
-
const [location] = useLocation();
|
|
8811
|
-
return /* @__PURE__ */ jsx("nav", {
|
|
8812
|
-
"aria-label": label,
|
|
8813
|
-
className: "flex flex-col gap-0.5",
|
|
8814
|
-
children: items.map((item) => {
|
|
8815
|
-
const active = location === item.to;
|
|
8816
|
-
return /* @__PURE__ */ jsxs(Link, {
|
|
8817
|
-
href: item.to,
|
|
8818
|
-
"aria-current": active ? "page" : void 0,
|
|
8819
|
-
className: cn("relative flex h-8 items-center gap-2 whitespace-nowrap rounded-[6px] px-3 text-[13.5px] no-underline transition-colors focus-visible:outline-offset-[-2px]", active ? "font-medium text-ink" : "text-ink2 hover:bg-soft2"),
|
|
8820
|
-
children: [
|
|
8821
|
-
active ? /* @__PURE__ */ jsx(motion.span, {
|
|
8822
|
-
layoutId: "sub-nav-active",
|
|
8823
|
-
"aria-hidden": true,
|
|
8824
|
-
className: "absolute inset-0 z-0 rounded-[6px] bg-soft2",
|
|
8825
|
-
transition: {
|
|
8826
|
-
type: "spring",
|
|
8827
|
-
stiffness: 520,
|
|
8828
|
-
damping: 42
|
|
8829
|
-
}
|
|
8830
|
-
}) : null,
|
|
8831
|
-
/* @__PURE__ */ jsx("span", {
|
|
8832
|
-
className: "relative z-[1] min-w-0 truncate",
|
|
8833
|
-
children: item.label
|
|
8834
|
-
}),
|
|
8835
|
-
item.hint ? /* @__PURE__ */ jsx("span", {
|
|
8836
|
-
className: "relative z-[1] ml-auto text-[12px] font-normal text-muted",
|
|
8837
|
-
children: item.hint
|
|
8838
|
-
}) : null
|
|
8839
|
-
]
|
|
8840
|
-
}, item.to);
|
|
8841
|
-
})
|
|
8842
|
-
});
|
|
8843
|
-
}
|
|
8844
|
-
function NavLabel({ children }) {
|
|
8845
|
-
return /* @__PURE__ */ jsx("div", {
|
|
8846
|
-
className: "px-3 pt-4 pb-1 text-[12px] text-muted",
|
|
8847
|
-
children
|
|
8848
|
-
});
|
|
8849
|
-
}
|
|
8850
|
-
/** Standalone message card: not found, empty, failed to load. */
|
|
8851
|
-
function NoticeCard({ title, text, action }) {
|
|
8852
|
-
return /* @__PURE__ */ jsxs(Panel, {
|
|
8853
|
-
className: "flex w-[480px] max-w-full flex-col items-center gap-2 px-6 py-8 text-center",
|
|
8854
|
-
children: [
|
|
8855
|
-
/* @__PURE__ */ jsx("h1", {
|
|
8856
|
-
className: "m-0 text-[16px] font-semibold",
|
|
8857
|
-
children: title
|
|
8858
|
-
}),
|
|
8859
|
-
text ? /* @__PURE__ */ jsx("div", {
|
|
8860
|
-
className: "m-0 max-w-[400px] text-[14px] text-ink2",
|
|
8861
|
-
children: text
|
|
8862
|
-
}) : null,
|
|
8863
|
-
action ? /* @__PURE__ */ jsx("div", {
|
|
8864
|
-
className: "mt-3 flex flex-wrap justify-center gap-2",
|
|
8865
|
-
children: action
|
|
8866
|
-
}) : null
|
|
8867
|
-
]
|
|
8868
|
-
});
|
|
8869
|
-
}
|
|
8870
|
-
/** NoticeCard centred in the free space. */
|
|
8871
|
-
function Notice(props) {
|
|
8872
|
-
return /* @__PURE__ */ jsx("div", {
|
|
8873
|
-
className: "flex min-h-[50vh] grow items-center justify-center p-6 max-md:px-4",
|
|
8874
|
-
children: /* @__PURE__ */ jsx(NoticeCard, { ...props })
|
|
8875
|
-
});
|
|
8876
|
-
}
|
|
8877
|
-
//#endregion
|
|
8878
|
-
//#region viewer/src/pages/not-found.tsx
|
|
8879
|
-
function NotFound({ title = "Nothing here", text }) {
|
|
8880
|
-
const scoped = !!useViewer().scope;
|
|
8881
|
-
return /* @__PURE__ */ jsx(Notice, {
|
|
8882
|
-
title,
|
|
8883
|
-
text: text ?? "This address does not match a canvas or a page of the design system.",
|
|
8884
|
-
action: scoped ? void 0 : /* @__PURE__ */ jsx(ButtonLink, {
|
|
8885
|
-
to: "/",
|
|
8886
|
-
children: "All canvases"
|
|
8887
|
-
})
|
|
8888
|
-
});
|
|
8889
|
-
}
|
|
8890
|
-
//#endregion
|
|
8891
|
-
//#region viewer/src/pages/canvas/camera.ts
|
|
8892
|
-
/** The farthest out the camera goes; past it a page is specks, and every one of them still costs. */
|
|
8893
|
-
var MIN_ZOOM = .1;
|
|
8894
|
-
var clampZoom = (z, min = MIN_ZOOM) => Math.min(8, Math.max(min, z));
|
|
8895
|
-
/** The canvas camera. Lives outside React so panning never re-renders the tree. */
|
|
8896
|
-
var CameraStore = class {
|
|
8897
|
-
camera = {
|
|
8898
|
-
x: 0,
|
|
8899
|
-
y: 0,
|
|
8900
|
-
z: 1
|
|
8901
|
-
};
|
|
8902
|
-
/** How far out the camera may go; the viewport sets it from the size of the page. */
|
|
8903
|
-
minZoom = MIN_ZOOM;
|
|
8904
|
-
listeners = /* @__PURE__ */ new Set();
|
|
8905
|
-
animation = 0;
|
|
8906
|
-
subscribe(listener) {
|
|
8907
|
-
this.listeners.add(listener);
|
|
8908
|
-
return () => {
|
|
8909
|
-
this.listeners.delete(listener);
|
|
8910
|
-
};
|
|
8911
|
-
}
|
|
8912
|
-
set(next) {
|
|
8913
|
-
this.camera = {
|
|
8914
|
-
x: next.x,
|
|
8915
|
-
y: next.y,
|
|
8916
|
-
z: clampZoom(next.z, this.minZoom)
|
|
8917
|
-
};
|
|
8918
|
-
for (const listener of this.listeners) listener(this.camera);
|
|
8919
|
-
}
|
|
8920
|
-
/** Stop any animation: the user took over. */
|
|
8921
|
-
interrupt() {
|
|
8922
|
-
cancelAnimationFrame(this.animation);
|
|
8923
|
-
this.animation = 0;
|
|
8924
|
-
}
|
|
8925
|
-
panBy(dx, dy) {
|
|
8926
|
-
this.interrupt();
|
|
8927
|
-
const { x, y, z } = this.camera;
|
|
8928
|
-
this.set({
|
|
8929
|
-
x: x + dx,
|
|
8930
|
-
y: y + dy,
|
|
8931
|
-
z
|
|
8932
|
-
});
|
|
8933
|
-
}
|
|
8934
|
-
/** Zoom by `factor` keeping the screen point (sx, sy) in place. */
|
|
8935
|
-
zoomAt(sx, sy, factor) {
|
|
8936
|
-
this.interrupt();
|
|
8937
|
-
const { x, y, z } = this.camera;
|
|
8938
|
-
const next = clampZoom(z * factor, this.minZoom);
|
|
8939
|
-
const wx = (sx - x) / z;
|
|
8940
|
-
const wy = (sy - y) / z;
|
|
8941
|
-
this.set({
|
|
8942
|
-
x: sx - wx * next,
|
|
8943
|
-
y: sy - wy * next,
|
|
8944
|
-
z: next
|
|
8945
|
-
});
|
|
8946
|
-
}
|
|
8947
|
-
animateTo(target, duration = 320) {
|
|
8948
|
-
this.interrupt();
|
|
8949
|
-
const from = { ...this.camera };
|
|
8950
|
-
const to = {
|
|
8951
|
-
...target,
|
|
8952
|
-
z: clampZoom(target.z, this.minZoom)
|
|
8953
|
-
};
|
|
8954
|
-
if (duration <= 0 || matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
8955
|
-
this.set(to);
|
|
8956
|
-
return;
|
|
8957
|
-
}
|
|
8958
|
-
const start = performance.now();
|
|
8959
|
-
const step = (now) => {
|
|
8960
|
-
const t = Math.min(1, (now - start) / duration);
|
|
8961
|
-
const e = 1 - (1 - t) ** 3;
|
|
8962
|
-
const z = from.z * (to.z / from.z) ** e;
|
|
8963
|
-
const k = from.z === to.z ? e : (z - from.z) / (to.z - from.z);
|
|
8964
|
-
this.set({
|
|
8965
|
-
x: from.x + (to.x - from.x) * k,
|
|
8966
|
-
y: from.y + (to.y - from.y) * k,
|
|
8967
|
-
z
|
|
8968
|
-
});
|
|
8969
|
-
if (t < 1) this.animation = requestAnimationFrame(step);
|
|
8970
|
-
else this.animation = 0;
|
|
8971
|
-
};
|
|
8972
|
-
this.animation = requestAnimationFrame(step);
|
|
8973
|
-
}
|
|
8974
|
-
toWorld(sx, sy) {
|
|
8975
|
-
const { x, y, z } = this.camera;
|
|
8976
|
-
return {
|
|
8977
|
-
x: (sx - x) / z,
|
|
8978
|
-
y: (sy - y) / z
|
|
8979
|
-
};
|
|
8980
|
-
}
|
|
8981
|
-
};
|
|
8982
|
-
/**
|
|
8983
|
-
* The camera that shows `rect` inside a viewport of `w`×`h` with `pad` screen pixels around it.
|
|
8984
|
-
* When `rect` does not fit even at `minZoom`, it lines up with the top (and the left, if it is too wide).
|
|
8985
|
-
*/
|
|
8986
|
-
function fitRect(rect, w, h, pad = 72, maxZoom = 1, minZoom = MIN_ZOOM) {
|
|
8987
|
-
const z = clampZoom(Math.min((w - pad * 2) / Math.max(rect.w, 1), (h - pad * 2) / Math.max(rect.h, 1), maxZoom), minZoom);
|
|
8988
|
-
const place = (start, size, room) => size * z <= room - pad * 2 ? room / 2 - (start + size / 2) * z : pad - start * z;
|
|
8989
|
-
return {
|
|
8990
|
-
z,
|
|
8991
|
-
x: place(rect.x, rect.w, w),
|
|
8992
|
-
y: place(rect.y, rect.h, h)
|
|
8993
|
-
};
|
|
8994
|
-
}
|
|
8995
|
-
function intersects(a, b) {
|
|
8996
|
-
return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
|
|
8997
|
-
}
|
|
8998
|
-
//#endregion
|
|
8999
|
-
//#region viewer/src/ui/choice.tsx
|
|
9000
|
-
function Switch({ checked, onChange, label, disabled }) {
|
|
9001
|
-
return /* @__PURE__ */ jsx("button", {
|
|
9002
|
-
type: "button",
|
|
9003
|
-
role: "switch",
|
|
9004
|
-
"aria-checked": checked,
|
|
9005
|
-
"aria-label": label,
|
|
9006
|
-
disabled,
|
|
9007
|
-
onClick: () => onChange?.(!checked),
|
|
9008
|
-
className: cn("relative h-[18px] w-[32px] shrink-0 cursor-pointer rounded-[9px] border-0 p-0 transition-colors duration-200 disabled:cursor-not-allowed disabled:opacity-50", checked ? "bg-ink" : "bg-rule-strong"),
|
|
9009
|
-
children: /* @__PURE__ */ jsx("span", {
|
|
9010
|
-
className: "absolute top-[2px] left-[2px] size-[14px] rounded-full bg-surface shadow-[0_1px_2px_rgba(0,0,0,0.25)] transition-transform duration-200 ease-[cubic-bezier(0.2,0.7,0.2,1)]",
|
|
9011
|
-
style: { transform: checked ? "translateX(14px)" : "none" }
|
|
9012
|
-
})
|
|
9013
|
-
});
|
|
9014
|
-
}
|
|
9015
|
-
function Segmented({ items, value, onChange, label, stretch, size = "ctrl" }) {
|
|
9016
|
-
const indicator = useId();
|
|
9017
|
-
return /* @__PURE__ */ jsx("div", {
|
|
9018
|
-
role: "group",
|
|
9019
|
-
"aria-label": label,
|
|
9020
|
-
className: cn("shrink-0 items-center gap-0.5 rounded-[6px] bg-track p-[3px]", size === "ctrl" ? "h-ctrl" : "h-7", stretch ? "flex" : "inline-flex"),
|
|
9021
|
-
children: items.map((item) => {
|
|
9022
|
-
const on = item.value === value;
|
|
9023
|
-
return /* @__PURE__ */ jsxs("button", {
|
|
9024
|
-
type: "button",
|
|
9025
|
-
"aria-pressed": on,
|
|
9026
|
-
title: item.title,
|
|
9027
|
-
onClick: () => onChange(item.value),
|
|
9028
|
-
className: cn("relative flex h-full cursor-pointer items-center justify-center whitespace-nowrap rounded-[4px] border-0 bg-transparent font-medium transition-colors", size === "ctrl" ? "px-2.5 text-ctrl" : "px-2 text-[12.5px]", stretch && "flex-1", on ? "text-ink" : "text-muted hover:text-ink2"),
|
|
9029
|
-
children: [on ? /* @__PURE__ */ jsx(motion.span, {
|
|
9030
|
-
layoutId: indicator,
|
|
9031
|
-
className: "absolute inset-0 z-0 rounded-[4px] bg-surface shadow-[0_1px_2px_rgba(0,0,0,0.10)]",
|
|
9032
|
-
transition: {
|
|
9033
|
-
type: "spring",
|
|
9034
|
-
stiffness: 520,
|
|
9035
|
-
damping: 42
|
|
9036
|
-
}
|
|
9037
|
-
}) : null, /* @__PURE__ */ jsx("span", {
|
|
9038
|
-
className: "relative z-[1] flex items-center gap-1.5",
|
|
9039
|
-
children: item.label
|
|
9040
|
-
})]
|
|
9041
|
-
}, item.value);
|
|
9042
|
-
})
|
|
9043
|
-
});
|
|
9044
|
-
}
|
|
9045
|
-
//#endregion
|
|
9046
|
-
//#region viewer/src/pages/canvas/full-page.tsx
|
|
9047
|
-
/** The full-window page of one screen, relative to the viewer's base. */
|
|
9048
|
-
var fullHref = (canvas, item, theme) => `/c/${encodeURIComponent(canvas)}/full/${encodeURIComponent(item)}?theme=${theme}`;
|
|
9049
|
-
/**
|
|
9050
|
-
* "Open in a new tab" for a screen. A sandboxed viewer serves screens to its frames only, so the
|
|
9051
|
-
* tab gets the full-window page of the viewer; otherwise the screen's own URL.
|
|
9052
|
-
*/
|
|
9053
|
-
function useOpenUrl(canvasId, item, theme) {
|
|
9054
|
-
const { frameSandbox } = useViewer();
|
|
9055
|
-
const { base } = useRouter();
|
|
9056
|
-
if (!item) return null;
|
|
9057
|
-
if (item.kind === "url") return item.url;
|
|
9058
|
-
if (!frameSandbox) return absoluteUrl(frameSrc(item.url, item.theme ?? theme));
|
|
9059
|
-
return absoluteUrl(`${base}${fullHref(canvasId, item.id, item.theme ?? theme)}`);
|
|
9060
|
-
}
|
|
9061
|
-
/** `/c/:canvas/full/:item`: one screen filling the window, a way out on hover and Esc. */
|
|
9062
|
-
function FullPage({ canvasId, itemId }) {
|
|
9063
|
-
const { data: canvas, error, isLoading } = useCanvas(canvasId);
|
|
9064
|
-
const [, navigate] = useLocation();
|
|
9065
|
-
const { frameSandbox } = useViewer();
|
|
9066
|
-
const appTheme = useTheme();
|
|
9067
|
-
const frame = useRef(null);
|
|
9068
|
-
const param = new URLSearchParams(window.location.search).get("theme");
|
|
9069
|
-
const items = canvas ? canvas.pages.flatMap((page) => page.sections.flatMap((section) => section.items.filter((item) => item.kind === "screen" || item.kind === "url"))) : [];
|
|
9070
|
-
const item = items.find((entry) => entry.id === itemId);
|
|
9071
|
-
const theme = (item?.kind === "screen" ? item.theme : void 0) ?? (param === "light" || param === "dark" ? param : canvas?.theme ?? appTheme);
|
|
9072
|
-
const url = item ? item.kind === "screen" ? frameSrc(item.url, theme) : item.url : "";
|
|
9073
|
-
const back = `/c/${encodeURIComponent(canvasId)}`;
|
|
9074
|
-
useEffect(() => {
|
|
9075
|
-
if (item) document.title = `${item.title} · ${canvas?.title ?? ""}`;
|
|
9076
|
-
}, [item, canvas?.title]);
|
|
9077
|
-
useEffect(() => {
|
|
9078
|
-
const onKey = (event) => {
|
|
9079
|
-
if (event.key === "Escape") navigate(back);
|
|
9080
|
-
};
|
|
9081
|
-
window.addEventListener("keydown", onKey);
|
|
9082
|
-
return () => window.removeEventListener("keydown", onKey);
|
|
9083
|
-
}, [navigate, back]);
|
|
9084
|
-
useEffect(() => {
|
|
9085
|
-
const el = frame.current;
|
|
9086
|
-
if (!el || item?.kind !== "screen") return;
|
|
9087
|
-
return listenToFrame(el, (message) => {
|
|
9088
|
-
if (message.type === "keydown" && message.code === "Escape") navigate(back);
|
|
9089
|
-
else if (message.type === "go") {
|
|
9090
|
-
const id = message.target.includes("/") ? message.target.split("/")[1] : message.target;
|
|
9091
|
-
if (items.some((entry) => entry.id === id)) navigate(fullHref(canvasId, id, theme), { replace: true });
|
|
9092
|
-
} else if (message.type === "link" && canvas) followLink(message, canvas, (id) => navigate(fullHref(canvasId, id, theme), { replace: true }), itemId);
|
|
9093
|
-
});
|
|
9094
|
-
}, [url, item?.kind]);
|
|
9095
|
-
if (error) return /* @__PURE__ */ jsx(NotFound, {
|
|
9096
|
-
title: "Canvas not found",
|
|
9097
|
-
text: error.message
|
|
9098
|
-
});
|
|
9099
|
-
if (isLoading || !canvas) return /* @__PURE__ */ jsx("div", { className: "h-dvh bg-surface" });
|
|
9100
|
-
if (!item) return /* @__PURE__ */ jsx(NotFound, {
|
|
9101
|
-
title: "Screen not found",
|
|
9102
|
-
text: `No screen "${itemId}" on this canvas.`
|
|
9103
|
-
});
|
|
9104
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
9105
|
-
className: "group fixed inset-0 bg-surface",
|
|
9106
|
-
children: [/* @__PURE__ */ jsx("iframe", {
|
|
9107
|
-
ref: frame,
|
|
9108
|
-
src: url,
|
|
9109
|
-
title: item.title,
|
|
9110
|
-
allow: "clipboard-read; clipboard-write; fullscreen",
|
|
9111
|
-
sandbox: frameSandbox,
|
|
9112
|
-
className: "block h-full w-full border-0",
|
|
9113
|
-
style: { colorScheme: theme },
|
|
9114
|
-
onLoad: () => item.kind === "screen" && sendTheme(frame.current, theme)
|
|
9115
|
-
}, `${item.id}:${item.kind === "screen" ? item.rev : item.url}`), /* @__PURE__ */ jsx("div", {
|
|
9116
|
-
className: "pointer-events-none absolute inset-x-0 top-0 flex justify-end p-3 opacity-0 transition-opacity duration-200 group-hover:opacity-100 focus-within:opacity-100",
|
|
9117
|
-
children: /* @__PURE__ */ jsxs(Link, {
|
|
9118
|
-
href: back,
|
|
9119
|
-
className: "pointer-events-auto flex h-8 items-center gap-2 rounded-[8px] border border-rule bg-surface px-3 text-[12.5px] text-ink2 no-underline shadow-pop hover:text-ink",
|
|
9120
|
-
children: [
|
|
9121
|
-
/* @__PURE__ */ jsx(Icon, {
|
|
9122
|
-
name: "x",
|
|
9123
|
-
size: 14
|
|
9124
|
-
}),
|
|
9125
|
-
"Back to ",
|
|
9126
|
-
canvas.title,
|
|
9127
|
-
/* @__PURE__ */ jsx(Kbd, { children: "esc" })
|
|
9128
|
-
]
|
|
9129
|
-
})
|
|
9130
|
-
})]
|
|
9131
|
-
});
|
|
9132
|
-
}
|
|
9133
|
-
//#endregion
|
|
9134
|
-
//#region viewer/src/pages/canvas/sidebar.tsx
|
|
9135
|
-
function itemIcon(item) {
|
|
9136
|
-
if (item.kind === "note") return "note";
|
|
9137
|
-
if (item.kind === "image") return "image";
|
|
9138
|
-
if (item.kind === "url") return "globe";
|
|
9139
|
-
return item.frame.width < 600 ? "smartphone" : item.frame.width < 1100 ? "tablet" : "monitor";
|
|
9140
|
-
}
|
|
9141
|
-
function pageHref(canvas, page) {
|
|
9142
|
-
return `/c/${encodeURIComponent(canvas.id)}/p/${encodeURIComponent(page.id)}`;
|
|
9143
|
-
}
|
|
9144
|
-
/** Pages of the canvas and the items on the current one. */
|
|
9145
|
-
function Sidebar({ canvas, page, store, onPick }) {
|
|
9146
|
-
const selected = useStore(store, (state) => state.selected);
|
|
9147
|
-
const errors = useStore(store, (state) => state.frames);
|
|
9148
|
-
return /* @__PURE__ */ jsxs("aside", {
|
|
9149
|
-
"data-ui": true,
|
|
9150
|
-
className: "flex w-[264px] shrink-0 flex-col overflow-hidden border-r border-rule bg-surface",
|
|
9151
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
9152
|
-
className: "flex flex-col gap-1 border-b border-rule px-4 py-3.5",
|
|
9153
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
9154
|
-
className: "truncate text-[14px] font-semibold",
|
|
9155
|
-
children: canvas.title
|
|
9156
|
-
}), canvas.description ? /* @__PURE__ */ jsx("div", {
|
|
9157
|
-
className: "line-clamp-3 text-[12.5px] leading-[1.45] text-muted",
|
|
9158
|
-
children: canvas.description
|
|
9159
|
-
}) : null]
|
|
9160
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
9161
|
-
className: "min-h-0 grow overflow-y-auto pb-4",
|
|
9162
|
-
children: [
|
|
9163
|
-
canvas.pages.length > 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
9164
|
-
className: "px-4 pt-3 pb-1 text-[12px] text-muted",
|
|
9165
|
-
children: "Pages"
|
|
9166
|
-
}), /* @__PURE__ */ jsx("nav", {
|
|
9167
|
-
className: "flex flex-col gap-0.5 px-2",
|
|
9168
|
-
children: canvas.pages.map((p) => {
|
|
9169
|
-
const count = p.sections.reduce((sum, s) => sum + s.items.filter((i) => i.kind === "screen" || i.kind === "url").length, 0);
|
|
9170
|
-
return /* @__PURE__ */ jsxs(Link, {
|
|
9171
|
-
href: pageHref(canvas, p),
|
|
9172
|
-
className: cn("flex h-8 items-center gap-2 rounded-[6px] px-2.5 text-[13.5px] no-underline transition-colors focus-visible:outline-offset-[-2px]", p.id === page.id ? "bg-soft2 font-medium text-ink" : "text-ink2 hover:bg-soft2"),
|
|
9173
|
-
children: [
|
|
9174
|
-
/* @__PURE__ */ jsx(Icon, {
|
|
9175
|
-
name: "file",
|
|
9176
|
-
size: 15,
|
|
9177
|
-
className: "text-muted"
|
|
9178
|
-
}),
|
|
9179
|
-
/* @__PURE__ */ jsx("span", {
|
|
9180
|
-
className: "min-w-0 grow truncate",
|
|
9181
|
-
children: p.title
|
|
9182
|
-
}),
|
|
9183
|
-
/* @__PURE__ */ jsx("span", {
|
|
9184
|
-
className: "text-[12px] text-muted tabular",
|
|
9185
|
-
children: count
|
|
9186
|
-
})
|
|
9187
|
-
]
|
|
9188
|
-
}, p.id);
|
|
9189
|
-
})
|
|
9190
|
-
})] }) : null,
|
|
9191
|
-
/* @__PURE__ */ jsx("div", {
|
|
9192
|
-
className: "px-4 pt-3 pb-1 text-[12px] text-muted",
|
|
9193
|
-
children: canvas.pages.length > 1 ? page.title : "Layers"
|
|
9194
|
-
}),
|
|
9195
|
-
/* @__PURE__ */ jsx("div", {
|
|
9196
|
-
className: "flex flex-col px-2",
|
|
9197
|
-
children: page.sections.map((section) => /* @__PURE__ */ jsxs("div", {
|
|
9198
|
-
className: "flex flex-col",
|
|
9199
|
-
children: [section.title ? /* @__PURE__ */ jsxs("div", {
|
|
9200
|
-
className: "flex h-7 items-center gap-2 px-2.5 pt-1 text-[12px] font-medium text-ink2",
|
|
9201
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
9202
|
-
className: "truncate",
|
|
9203
|
-
children: section.title
|
|
9204
|
-
}), /* @__PURE__ */ jsx(Count, { value: section.items.length })]
|
|
9205
|
-
}) : null, section.items.map((item) => {
|
|
9206
|
-
const hasErrors = (errors[item.id]?.errors.length ?? 0) > 0 || item.kind === "screen" && item.missing;
|
|
9207
|
-
return /* @__PURE__ */ jsxs("button", {
|
|
9208
|
-
type: "button",
|
|
9209
|
-
onClick: () => onPick(item.id),
|
|
9210
|
-
className: cn("flex h-8 w-full cursor-pointer items-center gap-2 rounded-[6px] border-0 px-2.5 text-left text-[13px] transition-colors focus-visible:outline-offset-[-2px]", section.title && "pl-4", selected === item.id ? "bg-action-soft text-ink" : "bg-transparent text-ink2 hover:bg-soft2"),
|
|
9211
|
-
children: [
|
|
9212
|
-
/* @__PURE__ */ jsx(Icon, {
|
|
9213
|
-
name: itemIcon(item),
|
|
9214
|
-
size: 15,
|
|
9215
|
-
className: "text-muted"
|
|
9216
|
-
}),
|
|
9217
|
-
/* @__PURE__ */ jsx("span", {
|
|
9218
|
-
className: "min-w-0 grow truncate",
|
|
9219
|
-
children: item.title || (item.kind === "note" ? "Note" : item.id)
|
|
9220
|
-
}),
|
|
9221
|
-
hasErrors ? /* @__PURE__ */ jsx("span", { className: "size-[7px] shrink-0 rounded-full bg-danger" }) : null
|
|
9222
|
-
]
|
|
9223
|
-
}, item.id);
|
|
9224
|
-
})]
|
|
9225
|
-
}, section.id))
|
|
9226
|
-
})
|
|
9227
|
-
]
|
|
9228
|
-
})]
|
|
9229
|
-
});
|
|
9230
|
-
}
|
|
9231
|
-
//#endregion
|
|
9232
|
-
//#region viewer/src/pages/canvas/canvas-bar.tsx
|
|
9233
|
-
/** The canvas page's top bar: the canvas and its pages, screen theme, inspect and play. */
|
|
9234
|
-
function CanvasBar({ canvas, page, theme, onTheme, sidebar, onSidebar, onPlay, canPlay, inspect, onInspect }) {
|
|
9235
|
-
const [, navigate] = useLocation();
|
|
9236
|
-
const { slots, scope } = useViewer();
|
|
9237
|
-
const titleRef = useRef(null);
|
|
9238
|
-
const endRef = useRef(null);
|
|
9239
|
-
const compact = useFitBar(titleRef, endRef, [
|
|
9240
|
-
canvas.title,
|
|
9241
|
-
page?.title,
|
|
9242
|
-
canvas.pages.length
|
|
9243
|
-
]);
|
|
9244
|
-
return /* @__PURE__ */ jsxs(BarCompactProvider, {
|
|
9245
|
-
value: compact,
|
|
9246
|
-
children: [/* @__PURE__ */ jsxs(Bar, { children: [
|
|
9247
|
-
slots.barStart,
|
|
9248
|
-
slots.brand ? slots.brand : scope ? /* @__PURE__ */ jsx(Mark, {}) : /* @__PURE__ */ jsx(Tooltip, {
|
|
9249
|
-
content: "All canvases",
|
|
9250
|
-
children: /* @__PURE__ */ jsx(Link, {
|
|
9251
|
-
href: "/",
|
|
9252
|
-
className: "flex items-center",
|
|
9253
|
-
"aria-label": "All canvases",
|
|
9254
|
-
children: /* @__PURE__ */ jsx(Mark, {})
|
|
9255
|
-
})
|
|
9256
|
-
}),
|
|
9257
|
-
/* @__PURE__ */ jsx(IconButton, {
|
|
9258
|
-
icon: "panel-left",
|
|
9259
|
-
label: sidebar ? "Hide sidebar (⌘\\)" : "Show sidebar (⌘\\)",
|
|
9260
|
-
onClick: () => onSidebar(!sidebar)
|
|
9261
|
-
}),
|
|
9262
|
-
/* @__PURE__ */ jsxs("div", {
|
|
9263
|
-
ref: titleRef,
|
|
9264
|
-
className: "flex min-w-0 items-center gap-1.5 text-[14px]",
|
|
9265
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
9266
|
-
"data-fit": true,
|
|
9267
|
-
className: "truncate font-semibold",
|
|
9268
|
-
children: canvas.title
|
|
9269
|
-
}), page && canvas.pages.length > 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Icon, {
|
|
9270
|
-
name: "chevron-right",
|
|
9271
|
-
size: 14,
|
|
9272
|
-
className: "shrink-0 text-muted"
|
|
9273
|
-
}), /* @__PURE__ */ jsxs(Menu, {
|
|
9274
|
-
width: 240,
|
|
9275
|
-
trigger: /* @__PURE__ */ jsxs("button", {
|
|
9276
|
-
type: "button",
|
|
9277
|
-
className: "flex h-ctrl cursor-pointer items-center gap-1 rounded-[6px] border-0 bg-transparent px-2 text-[14px] text-ink2 hover:bg-soft2",
|
|
9278
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
9279
|
-
"data-fit": true,
|
|
9280
|
-
className: "truncate",
|
|
9281
|
-
children: page.title
|
|
9282
|
-
}), /* @__PURE__ */ jsx(Icon, {
|
|
9283
|
-
name: "chevron-down",
|
|
9284
|
-
size: 14,
|
|
9285
|
-
className: "text-muted"
|
|
9286
|
-
})]
|
|
9287
|
-
}),
|
|
9288
|
-
children: [/* @__PURE__ */ jsx(MenuLabel, { children: "Pages" }), canvas.pages.map((p) => /* @__PURE__ */ jsxs(MenuItem, {
|
|
9289
|
-
active: p.id === page.id,
|
|
9290
|
-
onSelect: () => navigate(pageHref(canvas, p)),
|
|
9291
|
-
children: [/* @__PURE__ */ jsx(Icon, {
|
|
9292
|
-
name: "file",
|
|
9293
|
-
size: 15,
|
|
9294
|
-
className: "text-muted"
|
|
9295
|
-
}), p.title]
|
|
9296
|
-
}, p.id))]
|
|
9297
|
-
})] }) : null]
|
|
9298
|
-
}),
|
|
9299
|
-
/* @__PURE__ */ jsxs("div", {
|
|
9300
|
-
ref: endRef,
|
|
9301
|
-
className: "ml-auto flex shrink-0 items-center gap-2",
|
|
9302
|
-
children: [
|
|
9303
|
-
/* @__PURE__ */ jsx(Segmented, {
|
|
9304
|
-
label: "Screen theme",
|
|
9305
|
-
value: theme,
|
|
9306
|
-
onChange: onTheme,
|
|
9307
|
-
items: [{
|
|
9308
|
-
value: "light",
|
|
9309
|
-
label: /* @__PURE__ */ jsx(Icon, {
|
|
9310
|
-
name: "sun",
|
|
9311
|
-
size: 15
|
|
9312
|
-
}),
|
|
9313
|
-
title: "Light screens"
|
|
9314
|
-
}, {
|
|
9315
|
-
value: "dark",
|
|
9316
|
-
label: /* @__PURE__ */ jsx(Icon, {
|
|
9317
|
-
name: "moon",
|
|
9318
|
-
size: 15
|
|
9319
|
-
}),
|
|
9320
|
-
title: "Dark screens"
|
|
9321
|
-
}]
|
|
9322
|
-
}),
|
|
9323
|
-
/* @__PURE__ */ jsx(InspectToggle, {
|
|
9324
|
-
on: inspect,
|
|
9325
|
-
onChange: onInspect,
|
|
9326
|
-
compact
|
|
9327
|
-
}),
|
|
9328
|
-
/* @__PURE__ */ jsx(Tooltip, {
|
|
9329
|
-
content: /* @__PURE__ */ jsxs("span", {
|
|
9330
|
-
className: "flex items-center gap-1.5",
|
|
9331
|
-
children: ["Play ", /* @__PURE__ */ jsx(Kbd, { children: "P" })]
|
|
9332
|
-
}),
|
|
9333
|
-
children: /* @__PURE__ */ jsx(Button, {
|
|
9334
|
-
kind: "secondary",
|
|
9335
|
-
icon: "play",
|
|
9336
|
-
onClick: onPlay,
|
|
9337
|
-
disabled: !canPlay,
|
|
9338
|
-
"aria-label": "Play",
|
|
9339
|
-
className: compact ? "w-ctrl px-0" : void 0,
|
|
9340
|
-
children: compact ? null : "Play"
|
|
9341
|
-
})
|
|
9342
|
-
}),
|
|
9343
|
-
/* @__PURE__ */ jsx(SearchButton, {
|
|
9344
|
-
width: 180,
|
|
9345
|
-
label: "Search",
|
|
9346
|
-
compact
|
|
9347
|
-
}),
|
|
9348
|
-
/* @__PURE__ */ jsx(ThemeMenu, {}),
|
|
9349
|
-
slots.barEnd
|
|
9350
|
-
]
|
|
9351
|
-
})
|
|
9352
|
-
] }), slots.belowBar ? /* @__PURE__ */ jsx("div", {
|
|
9353
|
-
className: "shrink-0",
|
|
9354
|
-
children: slots.belowBar
|
|
9355
|
-
}) : null]
|
|
9356
|
-
});
|
|
9357
|
-
}
|
|
9358
|
-
/** The selected item's name and actions, floating over the bottom of the canvas. */
|
|
9359
|
-
function SelectionBar({ canvas, store, layoutItems, theme, onPlay, inspect }) {
|
|
9360
|
-
const selected = useStore(store, (state) => state.selected);
|
|
9361
|
-
const active = useStore(store, (state) => state.active);
|
|
9362
|
-
const status = useStore(store, (state) => selected ? state.frames[selected] : void 0);
|
|
9363
|
-
const item = layoutItems.find((i) => i.id === selected);
|
|
9364
|
-
const openUrl = useOpenUrl(canvas.id, item?.kind === "screen" || item?.kind === "url" ? item : void 0, theme);
|
|
9365
|
-
if (!item) return null;
|
|
9366
|
-
const frame = item.kind === "screen" || item.kind === "url";
|
|
9367
|
-
const reload = () => reloadFrame(document.querySelector(`[data-item="${CSS.escape(item.id)}"] iframe`));
|
|
9368
|
-
const errors = status?.errors ?? [];
|
|
9369
|
-
return /* @__PURE__ */ jsx("div", {
|
|
9370
|
-
"data-ui": true,
|
|
9371
|
-
className: "pointer-events-none absolute inset-x-0 bottom-5 z-20 flex justify-center px-4",
|
|
9372
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
9373
|
-
className: "pointer-events-auto flex max-w-full flex-col overflow-hidden rounded-[10px] border border-rule bg-surface shadow-pop animate-[q-pop_120ms_ease-out]",
|
|
9374
|
-
children: [errors.length ? /* @__PURE__ */ jsxs("div", {
|
|
9375
|
-
className: "flex max-h-40 items-start gap-2 overflow-y-auto border-b border-rule bg-danger-soft px-3.5 py-2 font-mono text-[12px] whitespace-pre-wrap text-danger-text",
|
|
9376
|
-
children: [/* @__PURE__ */ jsx(Icon, {
|
|
9377
|
-
name: "alert",
|
|
9378
|
-
size: 14,
|
|
9379
|
-
className: "mt-px"
|
|
9380
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
9381
|
-
className: "min-w-0",
|
|
9382
|
-
children: errors.join("\n")
|
|
9383
|
-
})]
|
|
9384
|
-
}) : null, /* @__PURE__ */ jsxs("div", {
|
|
9385
|
-
className: "flex h-[44px] items-center gap-1 pr-1.5 pl-3.5",
|
|
9386
|
-
children: [
|
|
9387
|
-
/* @__PURE__ */ jsxs("div", {
|
|
9388
|
-
className: "flex min-w-0 items-center gap-2 pr-2",
|
|
9389
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
9390
|
-
className: "truncate text-[13.5px] font-medium",
|
|
9391
|
-
children: item.title || item.id
|
|
9392
|
-
}), frame && active === item.id ? /* @__PURE__ */ jsxs("span", {
|
|
9393
|
-
className: "flex shrink-0 items-center gap-1.5 text-[12px] text-muted",
|
|
9394
|
-
children: [
|
|
9395
|
-
/* @__PURE__ */ jsx(Icon, {
|
|
9396
|
-
name: "pointer",
|
|
9397
|
-
size: 13
|
|
9398
|
-
}),
|
|
9399
|
-
" ",
|
|
9400
|
-
inspect ? "Inspecting" : "Interacting",
|
|
9401
|
-
" ",
|
|
9402
|
-
/* @__PURE__ */ jsx(Kbd, { children: "esc" })
|
|
9403
|
-
]
|
|
9404
|
-
}) : frame ? /* @__PURE__ */ jsx("span", {
|
|
9405
|
-
className: "shrink-0 text-[12px] text-muted",
|
|
9406
|
-
children: inspect ? "Click an element to inspect it" : "Click the screen to interact"
|
|
9407
|
-
}) : null]
|
|
9408
|
-
}),
|
|
9409
|
-
frame ? /* @__PURE__ */ jsx(Tooltip, {
|
|
9410
|
-
content: /* @__PURE__ */ jsxs("span", {
|
|
9411
|
-
className: "flex items-center gap-1.5",
|
|
9412
|
-
children: ["Play ", /* @__PURE__ */ jsx(Kbd, { children: "↵" })]
|
|
9413
|
-
}),
|
|
9414
|
-
children: /* @__PURE__ */ jsx(IconButton, {
|
|
9415
|
-
icon: "play",
|
|
9416
|
-
label: "Play",
|
|
9417
|
-
onClick: () => onPlay(item.id)
|
|
9418
|
-
})
|
|
9419
|
-
}) : null,
|
|
9420
|
-
openUrl ? /* @__PURE__ */ jsx(Tooltip, {
|
|
9421
|
-
content: "Open in a new tab",
|
|
9422
|
-
children: /* @__PURE__ */ jsx("a", {
|
|
9423
|
-
href: openUrl,
|
|
9424
|
-
target: "_blank",
|
|
9425
|
-
rel: "noreferrer",
|
|
9426
|
-
"aria-label": "Open in a new tab",
|
|
9427
|
-
className: "inline-flex h-ctrl w-ctrl items-center justify-center rounded-[6px] text-muted hover:bg-soft2 hover:text-ink2",
|
|
9428
|
-
children: /* @__PURE__ */ jsx(Icon, {
|
|
9429
|
-
name: "external",
|
|
9430
|
-
size: 16
|
|
9431
|
-
})
|
|
9432
|
-
})
|
|
9433
|
-
}) : null,
|
|
9434
|
-
frame ? /* @__PURE__ */ jsx(Tooltip, {
|
|
9435
|
-
content: "Reload",
|
|
9436
|
-
children: /* @__PURE__ */ jsx(IconButton, {
|
|
9437
|
-
icon: "refresh",
|
|
9438
|
-
label: "Reload",
|
|
9439
|
-
onClick: reload
|
|
9440
|
-
})
|
|
9441
|
-
}) : null,
|
|
9442
|
-
item.kind === "screen" ? /* @__PURE__ */ jsx(Tooltip, {
|
|
9443
|
-
content: "Copy source path",
|
|
9444
|
-
children: /* @__PURE__ */ jsx(IconButton, {
|
|
9445
|
-
icon: "copy",
|
|
9446
|
-
label: "Copy source path",
|
|
9447
|
-
onClick: () => void copyText(item.file, "Path copied")
|
|
9448
|
-
})
|
|
9449
|
-
}) : null,
|
|
9450
|
-
/* @__PURE__ */ jsx(Tooltip, {
|
|
9451
|
-
content: /* @__PURE__ */ jsxs("span", {
|
|
9452
|
-
className: "flex items-center gap-1.5",
|
|
9453
|
-
children: ["Zoom to ", /* @__PURE__ */ jsx(Kbd, { children: "⇧2" })]
|
|
9454
|
-
}),
|
|
9455
|
-
children: /* @__PURE__ */ jsx(IconButton, {
|
|
9456
|
-
icon: "fit",
|
|
9457
|
-
label: "Zoom to",
|
|
9458
|
-
onClick: () => window.dispatchEvent(new CustomEvent("design:focus", { detail: {
|
|
9459
|
-
canvas: canvas.id,
|
|
9460
|
-
item: item.id
|
|
9461
|
-
} }))
|
|
9462
|
-
})
|
|
9463
|
-
})
|
|
9464
|
-
]
|
|
9465
|
-
})]
|
|
9466
|
-
})
|
|
9467
|
-
});
|
|
9468
|
-
}
|
|
9469
|
-
/** The inspect mode switch: a button that stays pressed while on. */
|
|
9470
|
-
function InspectToggle({ on, onChange, compact = false }) {
|
|
9471
|
-
return /* @__PURE__ */ jsx(Tooltip, {
|
|
9472
|
-
content: /* @__PURE__ */ jsxs("span", {
|
|
9473
|
-
className: "flex items-center gap-1.5",
|
|
9474
|
-
children: ["Inspect elements ", /* @__PURE__ */ jsx(Kbd, { children: "I" })]
|
|
9475
|
-
}),
|
|
9476
|
-
children: /* @__PURE__ */ jsxs("button", {
|
|
9477
|
-
type: "button",
|
|
9478
|
-
"aria-pressed": on,
|
|
9479
|
-
"aria-label": "Inspect elements",
|
|
9480
|
-
onClick: () => onChange(!on),
|
|
9481
|
-
className: cn("inline-flex h-ctrl shrink-0 cursor-pointer items-center justify-center gap-1.5 rounded-[6px] border text-ctrl font-medium", compact ? "w-ctrl px-0" : "px-ctrl", on ? "border-action-line bg-action-soft text-action" : "border-rule-strong bg-surface text-ink hover:bg-soft"),
|
|
9482
|
-
children: [/* @__PURE__ */ jsx(Icon, {
|
|
9483
|
-
name: "pointer",
|
|
9484
|
-
size: 15
|
|
9485
|
-
}), compact ? null : "Inspect"]
|
|
9486
|
-
})
|
|
9487
|
-
});
|
|
9488
|
-
}
|
|
9489
|
-
//#endregion
|
|
9490
|
-
//#region viewer/src/pages/canvas/layout.ts
|
|
9491
|
-
var SECTION_GAP = 280;
|
|
9492
|
-
var ITEM_GAP = 96;
|
|
9493
|
-
var ROW_GAP = 150;
|
|
9494
|
-
var HEADER_GAP = 110;
|
|
9495
|
-
var HEADER_MIN_WIDTH = 720;
|
|
9496
|
-
var isFrame = (item) => item.kind === "screen" || item.kind === "url";
|
|
9497
|
-
function itemSize(item, sizes) {
|
|
9498
|
-
const measured = sizes[item.id];
|
|
9499
|
-
if (item.kind === "screen" || item.kind === "url") {
|
|
9500
|
-
const w = item.frame.width;
|
|
9501
|
-
if (item.frame.height !== "auto") return {
|
|
9502
|
-
w,
|
|
9503
|
-
h: item.frame.height
|
|
9504
|
-
};
|
|
9505
|
-
const fallback = item.kind === "screen" ? item.measuredHeight : void 0;
|
|
9506
|
-
return {
|
|
9507
|
-
w,
|
|
9508
|
-
h: measured?.h ?? fallback ?? 900
|
|
9509
|
-
};
|
|
9510
|
-
}
|
|
9511
|
-
if (item.kind === "note") return {
|
|
9512
|
-
w: item.width,
|
|
9513
|
-
h: measured?.h ?? Math.max(80, Math.ceil(item.text.length / 38) * 26 + 40)
|
|
9514
|
-
};
|
|
9515
|
-
const w = item.width ?? measured?.w ?? 480;
|
|
9516
|
-
return {
|
|
9517
|
-
w,
|
|
9518
|
-
h: measured?.w ? measured.h / measured.w * w : measured?.h ?? w * .62
|
|
9519
|
-
};
|
|
9520
|
-
}
|
|
9521
|
-
function sectionHeaderHeight(section, sizes) {
|
|
9522
|
-
if (!section.title && !section.description) return 0;
|
|
9523
|
-
return sizes[`section:${section.id}`]?.h ?? (section.title ? 56 : 0) + (section.description ? 60 : 0);
|
|
9524
|
-
}
|
|
9525
|
-
/** Sections stack top to bottom; items run left to right, wrapping after `columns`. */
|
|
9526
|
-
function layoutPage(page, sizes) {
|
|
9527
|
-
const items = [];
|
|
9528
|
-
const sections = [];
|
|
9529
|
-
if (page.layout === "free") {
|
|
9530
|
-
let cursor = 0;
|
|
9531
|
-
for (const section of page.sections) for (const item of section.items) {
|
|
9532
|
-
const { w, h } = itemSize(item, sizes);
|
|
9533
|
-
const x = item.x ?? cursor;
|
|
9534
|
-
const y = item.y ?? 0;
|
|
9535
|
-
cursor = Math.max(cursor, x + w + ITEM_GAP);
|
|
9536
|
-
items.push({
|
|
9537
|
-
item,
|
|
9538
|
-
section,
|
|
9539
|
-
x,
|
|
9540
|
-
y,
|
|
9541
|
-
w,
|
|
9542
|
-
h
|
|
9543
|
-
});
|
|
9544
|
-
}
|
|
9545
|
-
} else {
|
|
9546
|
-
let y = 0;
|
|
9547
|
-
for (const section of page.sections) {
|
|
9548
|
-
const header = sectionHeaderHeight(section, sizes);
|
|
9549
|
-
const top = y;
|
|
9550
|
-
let rowY = y + (header ? header + HEADER_GAP : 0);
|
|
9551
|
-
let x = 0;
|
|
9552
|
-
let rowHeight = 0;
|
|
9553
|
-
let width = 0;
|
|
9554
|
-
let column = 0;
|
|
9555
|
-
for (const item of section.items) {
|
|
9556
|
-
if (section.columns && column === section.columns) {
|
|
9557
|
-
rowY += rowHeight + ROW_GAP;
|
|
9558
|
-
x = 0;
|
|
9559
|
-
rowHeight = 0;
|
|
9560
|
-
column = 0;
|
|
9561
|
-
}
|
|
9562
|
-
const { w, h } = itemSize(item, sizes);
|
|
9563
|
-
items.push({
|
|
9564
|
-
item,
|
|
9565
|
-
section,
|
|
9566
|
-
x,
|
|
9567
|
-
y: rowY,
|
|
9568
|
-
w,
|
|
9569
|
-
h
|
|
9570
|
-
});
|
|
9571
|
-
x += w + ITEM_GAP;
|
|
9572
|
-
width = Math.max(width, x - ITEM_GAP);
|
|
9573
|
-
rowHeight = Math.max(rowHeight, h);
|
|
9574
|
-
column++;
|
|
9575
|
-
}
|
|
9576
|
-
const bottom = rowY + rowHeight;
|
|
9577
|
-
sections.push({
|
|
9578
|
-
section,
|
|
9579
|
-
x: 0,
|
|
9580
|
-
y: top,
|
|
9581
|
-
w: Math.max(width, header ? HEADER_MIN_WIDTH : 0),
|
|
9582
|
-
h: bottom - top,
|
|
9583
|
-
header
|
|
9584
|
-
});
|
|
9585
|
-
y = bottom + SECTION_GAP;
|
|
9586
|
-
}
|
|
9587
|
-
}
|
|
9588
|
-
let minX = 0;
|
|
9589
|
-
let minY = 0;
|
|
9590
|
-
let maxX = 0;
|
|
9591
|
-
let maxY = 0;
|
|
9592
|
-
const all = [...items, ...sections];
|
|
9593
|
-
if (all.length) {
|
|
9594
|
-
minX = Math.min(...all.map((r) => r.x));
|
|
9595
|
-
minY = Math.min(...all.map((r) => r.y));
|
|
9596
|
-
maxX = Math.max(...all.map((r) => r.x + r.w));
|
|
9597
|
-
maxY = Math.max(...all.map((r) => r.y + r.h));
|
|
9598
|
-
}
|
|
9599
|
-
return {
|
|
9600
|
-
items,
|
|
9601
|
-
sections,
|
|
9602
|
-
bounds: {
|
|
9603
|
-
x: minX,
|
|
9604
|
-
y: minY,
|
|
9605
|
-
w: maxX - minX,
|
|
9606
|
-
h: maxY - minY
|
|
9607
|
-
}
|
|
9608
|
-
};
|
|
9609
|
-
}
|
|
9610
|
-
/** Frames in reading order, for arrow-key stepping and play mode. */
|
|
9611
|
-
function frameOrder(layout) {
|
|
9612
|
-
return layout.items.filter((placed) => isFrame(placed.item));
|
|
9613
|
-
}
|
|
9614
|
-
//#endregion
|
|
9615
|
-
//#region viewer/src/pages/canvas/view-state.ts
|
|
9616
|
-
function createViewState() {
|
|
9617
|
-
return new Store({
|
|
9618
|
-
selected: null,
|
|
9619
|
-
active: null,
|
|
9620
|
-
hovered: null,
|
|
9621
|
-
sizes: {},
|
|
9622
|
-
frames: {},
|
|
9623
|
-
live: [],
|
|
9624
|
-
panning: false,
|
|
9625
|
-
zoomTiny: false,
|
|
9626
|
-
frameEls: {},
|
|
9627
|
-
inspecting: false
|
|
9628
|
-
});
|
|
9629
|
-
}
|
|
9630
|
-
function setSize(store, key, size) {
|
|
9631
|
-
store.set((state) => {
|
|
9632
|
-
const prev = state.sizes[key];
|
|
9633
|
-
if (prev && Math.abs(prev.h - size.h) < 1 && prev.w === size.w) return state;
|
|
9634
|
-
return {
|
|
9635
|
-
...state,
|
|
9636
|
-
sizes: {
|
|
9637
|
-
...state.sizes,
|
|
9638
|
-
[key]: size
|
|
9639
|
-
}
|
|
9640
|
-
};
|
|
9641
|
-
});
|
|
9642
|
-
}
|
|
9643
|
-
function setFrame(store, id, patch) {
|
|
9644
|
-
store.set((state) => {
|
|
9645
|
-
const prev = state.frames[id] ?? {
|
|
9646
|
-
ready: false,
|
|
9647
|
-
errors: []
|
|
9648
|
-
};
|
|
9649
|
-
return {
|
|
9650
|
-
...state,
|
|
9651
|
-
frames: {
|
|
9652
|
-
...state.frames,
|
|
9653
|
-
[id]: {
|
|
9654
|
-
...prev,
|
|
9655
|
-
...patch
|
|
9656
|
-
}
|
|
9657
|
-
}
|
|
9658
|
-
};
|
|
9659
|
-
});
|
|
9660
|
-
}
|
|
9661
|
-
function setFrameEl(store, id, el) {
|
|
9662
|
-
store.set((state) => state.frameEls[id] === el ? state : {
|
|
9663
|
-
...state,
|
|
9664
|
-
frameEls: {
|
|
9665
|
-
...state.frameEls,
|
|
9666
|
-
[id]: el
|
|
9667
|
-
}
|
|
9668
|
-
});
|
|
9669
|
-
}
|
|
9670
|
-
//#endregion
|
|
9671
|
-
//#region ../../node_modules/.pnpm/marked@18.0.14/node_modules/marked/lib/marked.esm.js
|
|
9672
|
-
/**
|
|
9673
|
-
* marked v18.0.14 - a markdown parser
|
|
9674
|
-
* Copyright (c) 2018-2026, MarkedJS. (MIT License)
|
|
9675
|
-
* Copyright (c) 2011-2018, Christopher Jeffrey. (MIT License)
|
|
9676
|
-
* https://github.com/markedjs/marked
|
|
9677
|
-
*/
|
|
9678
|
-
/**
|
|
9679
|
-
* DO NOT EDIT THIS FILE
|
|
9680
|
-
* The code in this file is generated from files in ./src/
|
|
9681
|
-
*/
|
|
9682
|
-
function I() {
|
|
9683
|
-
return {
|
|
9684
|
-
async: !1,
|
|
9685
|
-
breaks: !1,
|
|
9686
|
-
extensions: null,
|
|
9687
|
-
gfm: !0,
|
|
9688
|
-
hooks: null,
|
|
9689
|
-
pedantic: !1,
|
|
9690
|
-
renderer: null,
|
|
9691
|
-
silent: !1,
|
|
9692
|
-
tokenizer: null,
|
|
9693
|
-
walkTokens: null
|
|
9694
|
-
};
|
|
9695
|
-
}
|
|
9696
|
-
var y = I();
|
|
9697
|
-
function W(l) {
|
|
9698
|
-
y = l;
|
|
9699
|
-
}
|
|
9700
|
-
var A = { exec: () => null };
|
|
9701
|
-
function C(l) {
|
|
9702
|
-
let e = [];
|
|
9703
|
-
return (t) => {
|
|
9704
|
-
let n = Math.max(0, Math.min(3, t - 1)), s = e[n];
|
|
9705
|
-
return s || (s = l(n), e[n] = s), s;
|
|
9706
|
-
};
|
|
9707
|
-
}
|
|
9708
|
-
function h(l, e = "") {
|
|
9709
|
-
let t = typeof l == "string" ? l : l.source, n = {
|
|
9710
|
-
replace: (s, r) => {
|
|
9711
|
-
let o = typeof r == "string" ? r : r.source;
|
|
9712
|
-
return o = o.replace(x.caret, "$1"), t = t.replace(s, o), n;
|
|
9713
|
-
},
|
|
9714
|
-
getRegex: () => new RegExp(t, e)
|
|
9715
|
-
};
|
|
9716
|
-
return n;
|
|
7864
|
+
return n;
|
|
9717
7865
|
}
|
|
9718
7866
|
var _e = ((l = "") => {
|
|
9719
7867
|
try {
|
|
@@ -11076,333 +9224,2209 @@ var T = class l {
|
|
|
11076
9224
|
continue;
|
|
11077
9225
|
}
|
|
11078
9226
|
}
|
|
11079
|
-
let o = r;
|
|
11080
|
-
switch (o.type) {
|
|
11081
|
-
case "escape":
|
|
11082
|
-
n += t.text(o);
|
|
11083
|
-
break;
|
|
11084
|
-
case "html":
|
|
11085
|
-
n += t.html(o);
|
|
11086
|
-
break;
|
|
11087
|
-
case "link":
|
|
11088
|
-
n += t.link(o);
|
|
11089
|
-
break;
|
|
11090
|
-
case "image":
|
|
11091
|
-
n += t.image(o);
|
|
11092
|
-
break;
|
|
11093
|
-
case "checkbox":
|
|
11094
|
-
n += t.checkbox(o);
|
|
11095
|
-
break;
|
|
11096
|
-
case "strong":
|
|
11097
|
-
n += t.strong(o);
|
|
11098
|
-
break;
|
|
11099
|
-
case "em":
|
|
11100
|
-
n += t.em(o);
|
|
11101
|
-
break;
|
|
11102
|
-
case "codespan":
|
|
11103
|
-
n += t.codespan(o);
|
|
11104
|
-
break;
|
|
11105
|
-
case "br":
|
|
11106
|
-
n += t.br(o);
|
|
11107
|
-
break;
|
|
11108
|
-
case "del":
|
|
11109
|
-
n += t.del(o);
|
|
11110
|
-
break;
|
|
11111
|
-
case "text":
|
|
11112
|
-
n += t.text(o);
|
|
11113
|
-
break;
|
|
11114
|
-
default: {
|
|
11115
|
-
let i = "Token with \"" + o.type + "\" type was not found.";
|
|
11116
|
-
if (this.options.silent) return console.error(i), "";
|
|
11117
|
-
throw new Error(i);
|
|
9227
|
+
let o = r;
|
|
9228
|
+
switch (o.type) {
|
|
9229
|
+
case "escape":
|
|
9230
|
+
n += t.text(o);
|
|
9231
|
+
break;
|
|
9232
|
+
case "html":
|
|
9233
|
+
n += t.html(o);
|
|
9234
|
+
break;
|
|
9235
|
+
case "link":
|
|
9236
|
+
n += t.link(o);
|
|
9237
|
+
break;
|
|
9238
|
+
case "image":
|
|
9239
|
+
n += t.image(o);
|
|
9240
|
+
break;
|
|
9241
|
+
case "checkbox":
|
|
9242
|
+
n += t.checkbox(o);
|
|
9243
|
+
break;
|
|
9244
|
+
case "strong":
|
|
9245
|
+
n += t.strong(o);
|
|
9246
|
+
break;
|
|
9247
|
+
case "em":
|
|
9248
|
+
n += t.em(o);
|
|
9249
|
+
break;
|
|
9250
|
+
case "codespan":
|
|
9251
|
+
n += t.codespan(o);
|
|
9252
|
+
break;
|
|
9253
|
+
case "br":
|
|
9254
|
+
n += t.br(o);
|
|
9255
|
+
break;
|
|
9256
|
+
case "del":
|
|
9257
|
+
n += t.del(o);
|
|
9258
|
+
break;
|
|
9259
|
+
case "text":
|
|
9260
|
+
n += t.text(o);
|
|
9261
|
+
break;
|
|
9262
|
+
default: {
|
|
9263
|
+
let i = "Token with \"" + o.type + "\" type was not found.";
|
|
9264
|
+
if (this.options.silent) return console.error(i), "";
|
|
9265
|
+
throw new Error(i);
|
|
9266
|
+
}
|
|
9267
|
+
}
|
|
9268
|
+
}
|
|
9269
|
+
return n;
|
|
9270
|
+
}
|
|
9271
|
+
};
|
|
9272
|
+
var _ = class {
|
|
9273
|
+
options;
|
|
9274
|
+
block;
|
|
9275
|
+
constructor(e) {
|
|
9276
|
+
this.options = e || y;
|
|
9277
|
+
}
|
|
9278
|
+
static passThroughHooks = /* @__PURE__ */ new Set([
|
|
9279
|
+
"preprocess",
|
|
9280
|
+
"postprocess",
|
|
9281
|
+
"processAllTokens",
|
|
9282
|
+
"emStrongMask"
|
|
9283
|
+
]);
|
|
9284
|
+
static passThroughHooksRespectAsync = /* @__PURE__ */ new Set([
|
|
9285
|
+
"preprocess",
|
|
9286
|
+
"postprocess",
|
|
9287
|
+
"processAllTokens"
|
|
9288
|
+
]);
|
|
9289
|
+
preprocess(e) {
|
|
9290
|
+
return e;
|
|
9291
|
+
}
|
|
9292
|
+
postprocess(e) {
|
|
9293
|
+
return e;
|
|
9294
|
+
}
|
|
9295
|
+
processAllTokens(e) {
|
|
9296
|
+
return e;
|
|
9297
|
+
}
|
|
9298
|
+
emStrongMask(e) {
|
|
9299
|
+
return e;
|
|
9300
|
+
}
|
|
9301
|
+
provideLexer(e = this.block) {
|
|
9302
|
+
return e ? R.lex : R.lexInline;
|
|
9303
|
+
}
|
|
9304
|
+
provideParser(e = this.block) {
|
|
9305
|
+
return e ? T.parse : T.parseInline;
|
|
9306
|
+
}
|
|
9307
|
+
};
|
|
9308
|
+
var F = class {
|
|
9309
|
+
defaults = I();
|
|
9310
|
+
options = this.setOptions;
|
|
9311
|
+
parse = this.parseMarkdown(!0);
|
|
9312
|
+
parseInline = this.parseMarkdown(!1);
|
|
9313
|
+
Parser = T;
|
|
9314
|
+
Renderer = S;
|
|
9315
|
+
TextRenderer = z;
|
|
9316
|
+
Lexer = R;
|
|
9317
|
+
Tokenizer = P;
|
|
9318
|
+
Hooks = _;
|
|
9319
|
+
constructor(...e) {
|
|
9320
|
+
this.use(...e);
|
|
9321
|
+
}
|
|
9322
|
+
walkTokens(e, t) {
|
|
9323
|
+
let n = [];
|
|
9324
|
+
for (let s of e) switch (n = n.concat(t.call(this, s)), s.type) {
|
|
9325
|
+
case "table": {
|
|
9326
|
+
let r = s;
|
|
9327
|
+
for (let o of r.header) n = n.concat(this.walkTokens(o.tokens, t));
|
|
9328
|
+
for (let o of r.rows) for (let i of o) n = n.concat(this.walkTokens(i.tokens, t));
|
|
9329
|
+
break;
|
|
9330
|
+
}
|
|
9331
|
+
case "list": {
|
|
9332
|
+
let r = s;
|
|
9333
|
+
n = n.concat(this.walkTokens(r.items, t));
|
|
9334
|
+
break;
|
|
9335
|
+
}
|
|
9336
|
+
default: {
|
|
9337
|
+
let r = s;
|
|
9338
|
+
this.defaults.extensions?.childTokens?.[r.type] ? this.defaults.extensions.childTokens[r.type].forEach((o) => {
|
|
9339
|
+
let i = r[o].flat(1 / 0);
|
|
9340
|
+
n = n.concat(this.walkTokens(i, t));
|
|
9341
|
+
}) : r.tokens && (n = n.concat(this.walkTokens(r.tokens, t)));
|
|
9342
|
+
}
|
|
9343
|
+
}
|
|
9344
|
+
return n;
|
|
9345
|
+
}
|
|
9346
|
+
use(...e) {
|
|
9347
|
+
let t = this.defaults.extensions || {
|
|
9348
|
+
renderers: {},
|
|
9349
|
+
childTokens: {}
|
|
9350
|
+
};
|
|
9351
|
+
return e.forEach((n) => {
|
|
9352
|
+
let s = { ...n };
|
|
9353
|
+
if (s.async = this.defaults.async || s.async || !1, n.extensions && (n.extensions.forEach((r) => {
|
|
9354
|
+
if (!r.name) throw new Error("extension name required");
|
|
9355
|
+
if ("renderer" in r) {
|
|
9356
|
+
let o = t.renderers[r.name];
|
|
9357
|
+
o ? t.renderers[r.name] = function(...i) {
|
|
9358
|
+
let u = r.renderer.apply(this, i);
|
|
9359
|
+
return u === !1 && (u = o.apply(this, i)), u;
|
|
9360
|
+
} : t.renderers[r.name] = r.renderer;
|
|
9361
|
+
}
|
|
9362
|
+
if ("tokenizer" in r) {
|
|
9363
|
+
if (!r.level || r.level !== "block" && r.level !== "inline") throw new Error("extension level must be 'block' or 'inline'");
|
|
9364
|
+
let o = t[r.level];
|
|
9365
|
+
o ? o.unshift(r.tokenizer) : t[r.level] = [r.tokenizer], r.start && (r.level === "block" ? t.startBlock ? t.startBlock.push(r.start) : t.startBlock = [r.start] : r.level === "inline" && (t.startInline ? t.startInline.push(r.start) : t.startInline = [r.start]));
|
|
9366
|
+
}
|
|
9367
|
+
"childTokens" in r && r.childTokens && (t.childTokens[r.name] = r.childTokens);
|
|
9368
|
+
}), s.extensions = t), n.renderer) {
|
|
9369
|
+
let r = this.defaults.renderer || new S(this.defaults);
|
|
9370
|
+
for (let o in n.renderer) {
|
|
9371
|
+
if (!(o in r)) throw new Error(`renderer '${o}' does not exist`);
|
|
9372
|
+
if (["options", "parser"].includes(o)) continue;
|
|
9373
|
+
let i = o, u = n.renderer[i], a = r[i];
|
|
9374
|
+
r[i] = (...p) => {
|
|
9375
|
+
let c = u.apply(r, p);
|
|
9376
|
+
return c === !1 && (c = a.apply(r, p)), c || "";
|
|
9377
|
+
};
|
|
9378
|
+
}
|
|
9379
|
+
s.renderer = r;
|
|
9380
|
+
}
|
|
9381
|
+
if (n.tokenizer) {
|
|
9382
|
+
let r = this.defaults.tokenizer || new P(this.defaults);
|
|
9383
|
+
for (let o in n.tokenizer) {
|
|
9384
|
+
if (!(o in r)) throw new Error(`tokenizer '${o}' does not exist`);
|
|
9385
|
+
if ([
|
|
9386
|
+
"options",
|
|
9387
|
+
"rules",
|
|
9388
|
+
"lexer"
|
|
9389
|
+
].includes(o)) continue;
|
|
9390
|
+
let i = o, u = n.tokenizer[i], a = r[i];
|
|
9391
|
+
r[i] = (...p) => {
|
|
9392
|
+
let c = u.apply(r, p);
|
|
9393
|
+
return c === !1 && (c = a.apply(r, p)), c;
|
|
9394
|
+
};
|
|
9395
|
+
}
|
|
9396
|
+
s.tokenizer = r;
|
|
9397
|
+
}
|
|
9398
|
+
if (n.hooks) {
|
|
9399
|
+
let r = this.defaults.hooks || new _();
|
|
9400
|
+
for (let o in n.hooks) {
|
|
9401
|
+
if (!(o in r)) throw new Error(`hook '${o}' does not exist`);
|
|
9402
|
+
if (["options", "block"].includes(o)) continue;
|
|
9403
|
+
let i = o, u = n.hooks[i], a = r[i];
|
|
9404
|
+
_.passThroughHooks.has(o) ? r[i] = (p) => {
|
|
9405
|
+
if (this.defaults.async && _.passThroughHooksRespectAsync.has(o)) return (async () => {
|
|
9406
|
+
let d = await u.call(r, p);
|
|
9407
|
+
return a.call(r, d);
|
|
9408
|
+
})();
|
|
9409
|
+
let c = u.call(r, p);
|
|
9410
|
+
return a.call(r, c);
|
|
9411
|
+
} : r[i] = (...p) => {
|
|
9412
|
+
if (this.defaults.async) return (async () => {
|
|
9413
|
+
let d = await u.apply(r, p);
|
|
9414
|
+
return d === !1 && (d = await a.apply(r, p)), d;
|
|
9415
|
+
})();
|
|
9416
|
+
let c = u.apply(r, p);
|
|
9417
|
+
return c === !1 && (c = a.apply(r, p)), c;
|
|
9418
|
+
};
|
|
11118
9419
|
}
|
|
9420
|
+
s.hooks = r;
|
|
11119
9421
|
}
|
|
11120
|
-
|
|
11121
|
-
|
|
9422
|
+
if (n.walkTokens) {
|
|
9423
|
+
let r = this.defaults.walkTokens, o = n.walkTokens;
|
|
9424
|
+
s.walkTokens = function(i) {
|
|
9425
|
+
let u = [];
|
|
9426
|
+
return u.push(o.call(this, i)), r && (u = u.concat(r.call(this, i))), u;
|
|
9427
|
+
};
|
|
9428
|
+
}
|
|
9429
|
+
this.defaults = {
|
|
9430
|
+
...this.defaults,
|
|
9431
|
+
...s
|
|
9432
|
+
};
|
|
9433
|
+
}), this;
|
|
11122
9434
|
}
|
|
11123
|
-
|
|
11124
|
-
|
|
11125
|
-
|
|
11126
|
-
|
|
11127
|
-
|
|
11128
|
-
this.options = e || y;
|
|
9435
|
+
setOptions(e) {
|
|
9436
|
+
return this.defaults = {
|
|
9437
|
+
...this.defaults,
|
|
9438
|
+
...e
|
|
9439
|
+
}, this;
|
|
11129
9440
|
}
|
|
11130
|
-
|
|
11131
|
-
|
|
11132
|
-
"postprocess",
|
|
11133
|
-
"processAllTokens",
|
|
11134
|
-
"emStrongMask"
|
|
11135
|
-
]);
|
|
11136
|
-
static passThroughHooksRespectAsync = /* @__PURE__ */ new Set([
|
|
11137
|
-
"preprocess",
|
|
11138
|
-
"postprocess",
|
|
11139
|
-
"processAllTokens"
|
|
11140
|
-
]);
|
|
11141
|
-
preprocess(e) {
|
|
11142
|
-
return e;
|
|
9441
|
+
lexer(e, t) {
|
|
9442
|
+
return R.lex(e, t ?? this.defaults);
|
|
11143
9443
|
}
|
|
11144
|
-
|
|
11145
|
-
return e;
|
|
9444
|
+
parser(e, t) {
|
|
9445
|
+
return T.parse(e, t ?? this.defaults);
|
|
11146
9446
|
}
|
|
11147
|
-
|
|
11148
|
-
return
|
|
9447
|
+
parseMarkdown(e) {
|
|
9448
|
+
return (n, s) => {
|
|
9449
|
+
let r = { ...s }, o = {
|
|
9450
|
+
...this.defaults,
|
|
9451
|
+
...r
|
|
9452
|
+
}, i = this.onError(!!o.silent, !!o.async);
|
|
9453
|
+
if (this.defaults.async === !0 && r.async === !1) return i(/* @__PURE__ */ new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));
|
|
9454
|
+
if (typeof n > "u" || n === null) return i(/* @__PURE__ */ new Error("marked(): input parameter is undefined or null"));
|
|
9455
|
+
if (typeof n != "string") return i(/* @__PURE__ */ new Error("marked(): input parameter is of type " + Object.prototype.toString.call(n) + ", string expected"));
|
|
9456
|
+
if (o.hooks && (o.hooks.options = o, o.hooks.block = e), o.async) return (async () => {
|
|
9457
|
+
let u = o.hooks ? await o.hooks.preprocess(n) : n, p = await (o.hooks ? await o.hooks.provideLexer(e) : e ? R.lex : R.lexInline)(u, o), c = o.hooks ? await o.hooks.processAllTokens(p) : p;
|
|
9458
|
+
o.walkTokens && await Promise.all(this.walkTokens(c, o.walkTokens));
|
|
9459
|
+
let m = await (o.hooks ? await o.hooks.provideParser(e) : e ? T.parse : T.parseInline)(c, o);
|
|
9460
|
+
return o.hooks ? await o.hooks.postprocess(m) : m;
|
|
9461
|
+
})().catch(i);
|
|
9462
|
+
try {
|
|
9463
|
+
o.hooks && (n = o.hooks.preprocess(n));
|
|
9464
|
+
let a = (o.hooks ? o.hooks.provideLexer(e) : e ? R.lex : R.lexInline)(n, o);
|
|
9465
|
+
o.hooks && (a = o.hooks.processAllTokens(a)), o.walkTokens && this.walkTokens(a, o.walkTokens);
|
|
9466
|
+
let c = (o.hooks ? o.hooks.provideParser(e) : e ? T.parse : T.parseInline)(a, o);
|
|
9467
|
+
return o.hooks && (c = o.hooks.postprocess(c)), c;
|
|
9468
|
+
} catch (u) {
|
|
9469
|
+
return i(u);
|
|
9470
|
+
}
|
|
9471
|
+
};
|
|
9472
|
+
}
|
|
9473
|
+
onError(e, t) {
|
|
9474
|
+
return (n) => {
|
|
9475
|
+
if (n.message += `
|
|
9476
|
+
Please report this to https://github.com/markedjs/marked.`, e) {
|
|
9477
|
+
let s = "<p>An error occurred:</p><pre>" + O(n.message + "", !0) + "</pre>";
|
|
9478
|
+
return t ? Promise.resolve(s) : s;
|
|
9479
|
+
}
|
|
9480
|
+
if (t) return Promise.reject(n);
|
|
9481
|
+
throw n;
|
|
9482
|
+
};
|
|
9483
|
+
}
|
|
9484
|
+
};
|
|
9485
|
+
var E = new F();
|
|
9486
|
+
function k(l, e) {
|
|
9487
|
+
return E.parse(l, e);
|
|
9488
|
+
}
|
|
9489
|
+
k.options = k.setOptions = function(l) {
|
|
9490
|
+
return E.setOptions(l), k.defaults = E.defaults, W(k.defaults), k;
|
|
9491
|
+
};
|
|
9492
|
+
k.getDefaults = I;
|
|
9493
|
+
k.defaults = y;
|
|
9494
|
+
function Pt(...l) {
|
|
9495
|
+
return E.use(...l), k.defaults = E.defaults, W(k.defaults), k;
|
|
9496
|
+
}
|
|
9497
|
+
k.use = Pt;
|
|
9498
|
+
k.walkTokens = function(l, e) {
|
|
9499
|
+
return E.walkTokens(l, e);
|
|
9500
|
+
};
|
|
9501
|
+
k.parseInline = E.parseInline;
|
|
9502
|
+
k.Parser = T;
|
|
9503
|
+
k.parser = T.parse;
|
|
9504
|
+
k.Renderer = S;
|
|
9505
|
+
k.TextRenderer = z;
|
|
9506
|
+
k.Lexer = R;
|
|
9507
|
+
k.lexer = R.lex;
|
|
9508
|
+
k.Tokenizer = P;
|
|
9509
|
+
k.Hooks = _;
|
|
9510
|
+
k.parse = k;
|
|
9511
|
+
k.options;
|
|
9512
|
+
k.setOptions;
|
|
9513
|
+
k.walkTokens;
|
|
9514
|
+
k.parseInline;
|
|
9515
|
+
T.parse;
|
|
9516
|
+
R.lex;
|
|
9517
|
+
//#endregion
|
|
9518
|
+
//#region viewer/src/components/markdown.tsx
|
|
9519
|
+
var SAFE_SCHEMES = /* @__PURE__ */ new Set([
|
|
9520
|
+
"http:",
|
|
9521
|
+
"https:",
|
|
9522
|
+
"mailto:"
|
|
9523
|
+
]);
|
|
9524
|
+
/** http, https, mailto or relative. Browsers drop whitespace and control characters inside a scheme, so do we. */
|
|
9525
|
+
function isSafeHref(href) {
|
|
9526
|
+
const bare = href.replace(/[\u0000- \u007f]/g, "");
|
|
9527
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(bare)?.[1];
|
|
9528
|
+
return !scheme || SAFE_SCHEMES.has(`${scheme.toLowerCase()}:`);
|
|
9529
|
+
}
|
|
9530
|
+
function escapeHtml(text) {
|
|
9531
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
9532
|
+
}
|
|
9533
|
+
var markdown = new F({
|
|
9534
|
+
gfm: true,
|
|
9535
|
+
breaks: false
|
|
9536
|
+
}, { renderer: {
|
|
9537
|
+
html: ({ text }) => escapeHtml(text),
|
|
9538
|
+
link(token) {
|
|
9539
|
+
return isSafeHref(token.href) ? false : this.parser.parseInline(token.tokens);
|
|
9540
|
+
},
|
|
9541
|
+
image(token) {
|
|
9542
|
+
return isSafeHref(token.href) ? false : escapeHtml(token.text);
|
|
9543
|
+
}
|
|
9544
|
+
} });
|
|
9545
|
+
function renderMarkdown(text) {
|
|
9546
|
+
return markdown.parse(text, { async: false }).replace(/<a href=/g, "<a target=\"_blank\" rel=\"noreferrer\" href=");
|
|
9547
|
+
}
|
|
9548
|
+
/** Markdown from the project's own files (guidelines, notes, descriptions). */
|
|
9549
|
+
function Markdown({ text, className }) {
|
|
9550
|
+
const html = useMemo(() => renderMarkdown(text), [text]);
|
|
9551
|
+
return /* @__PURE__ */ jsx("div", {
|
|
9552
|
+
className: cn("prose", className),
|
|
9553
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
9554
|
+
});
|
|
9555
|
+
}
|
|
9556
|
+
//#endregion
|
|
9557
|
+
//#region viewer/src/lib/frames.ts
|
|
9558
|
+
/** Routes runtime messages to whoever listens to the frame that sent them. */
|
|
9559
|
+
var handlers = /* @__PURE__ */ new Map();
|
|
9560
|
+
window.addEventListener("message", (event) => {
|
|
9561
|
+
const data = event.data;
|
|
9562
|
+
if (data?.source !== "design-runtime" || !event.source) return;
|
|
9563
|
+
for (const handler of handlers.get(event.source) ?? []) handler(data);
|
|
9564
|
+
});
|
|
9565
|
+
function listenToFrame(frame, handler) {
|
|
9566
|
+
let win = null;
|
|
9567
|
+
const detach = () => {
|
|
9568
|
+
if (!win) return;
|
|
9569
|
+
const set = handlers.get(win);
|
|
9570
|
+
set?.delete(handler);
|
|
9571
|
+
if (!set?.size) handlers.delete(win);
|
|
9572
|
+
win = null;
|
|
9573
|
+
};
|
|
9574
|
+
const attach = () => {
|
|
9575
|
+
detach();
|
|
9576
|
+
win = frame.contentWindow;
|
|
9577
|
+
if (!win) return;
|
|
9578
|
+
const set = handlers.get(win) ?? /* @__PURE__ */ new Set();
|
|
9579
|
+
set.add(handler);
|
|
9580
|
+
handlers.set(win, set);
|
|
9581
|
+
};
|
|
9582
|
+
attach();
|
|
9583
|
+
frame.addEventListener("load", attach);
|
|
9584
|
+
return () => {
|
|
9585
|
+
frame.removeEventListener("load", attach);
|
|
9586
|
+
detach();
|
|
9587
|
+
};
|
|
9588
|
+
}
|
|
9589
|
+
function sendToFrame(frame, message) {
|
|
9590
|
+
frame?.contentWindow?.postMessage({
|
|
9591
|
+
source: "design-viewer",
|
|
9592
|
+
...message
|
|
9593
|
+
}, "*");
|
|
9594
|
+
}
|
|
9595
|
+
/** Tell a frame it sits on the canvas, so it hands the wheel to the viewer. */
|
|
9596
|
+
function claimWheel(frame) {
|
|
9597
|
+
sendToFrame(frame, { type: "canvas" });
|
|
9598
|
+
}
|
|
9599
|
+
/**
|
|
9600
|
+
* A wheel the frame had no use for (it reached its edge, or holds nothing that scrolls): scroll
|
|
9601
|
+
* whatever scrolls around the frame, as the browser would if the frame's edges passed it on.
|
|
9602
|
+
*/
|
|
9603
|
+
function scrollAround(frame, delta) {
|
|
9604
|
+
for (let node = frame.parentElement; node; node = node.parentElement) {
|
|
9605
|
+
const style = getComputedStyle(node);
|
|
9606
|
+
const scrolls = (overflow) => /(auto|scroll|overlay)/.test(overflow);
|
|
9607
|
+
const y = scrolls(style.overflowY) && node.scrollHeight > node.clientHeight;
|
|
9608
|
+
const x = scrolls(style.overflowX) && node.scrollWidth > node.clientWidth;
|
|
9609
|
+
if (y || x) {
|
|
9610
|
+
node.scrollBy({
|
|
9611
|
+
left: x ? delta.deltaX : 0,
|
|
9612
|
+
top: y ? delta.deltaY : 0,
|
|
9613
|
+
behavior: "instant"
|
|
9614
|
+
});
|
|
9615
|
+
return;
|
|
9616
|
+
}
|
|
11149
9617
|
}
|
|
11150
|
-
|
|
11151
|
-
|
|
9618
|
+
window.scrollBy({
|
|
9619
|
+
left: delta.deltaX,
|
|
9620
|
+
top: delta.deltaY,
|
|
9621
|
+
behavior: "instant"
|
|
9622
|
+
});
|
|
9623
|
+
}
|
|
9624
|
+
function sendTheme(frame, theme) {
|
|
9625
|
+
sendToFrame(frame, {
|
|
9626
|
+
type: "theme",
|
|
9627
|
+
theme
|
|
9628
|
+
});
|
|
9629
|
+
}
|
|
9630
|
+
/** Load the frame again; works whatever origin it is on. */
|
|
9631
|
+
function reloadFrame(frame) {
|
|
9632
|
+
if (!frame) return;
|
|
9633
|
+
frame.src = frame.src;
|
|
9634
|
+
}
|
|
9635
|
+
/**
|
|
9636
|
+
* Where the dev server's frames load from. Browsers give each site its own
|
|
9637
|
+
* process, so frames served from the viewer's twin host (localhost ↔ 127.0.0.1,
|
|
9638
|
+
* both reach the same server) run their scripts and animations off the viewer's
|
|
9639
|
+
* thread: a canvas full of live screens still pans smoothly. Any other host,
|
|
9640
|
+
* and static builds, keep frames on the viewer's origin.
|
|
9641
|
+
*/
|
|
9642
|
+
var FRAME_ORIGIN = (() => {
|
|
9643
|
+
if (STATIC_SITE) return "";
|
|
9644
|
+
const { protocol, hostname, port } = window.location;
|
|
9645
|
+
const twin = hostname === "localhost" ? "127.0.0.1" : hostname === "127.0.0.1" ? "localhost" : null;
|
|
9646
|
+
return twin ? `${protocol}//${twin}${port ? `:${port}` : ""}` : "";
|
|
9647
|
+
})();
|
|
9648
|
+
/** A frame URL with the theme it should open in. */
|
|
9649
|
+
function frameSrc(url, theme, extra) {
|
|
9650
|
+
const [path = "", query = ""] = url.split("?");
|
|
9651
|
+
const params = new URLSearchParams(query);
|
|
9652
|
+
params.set("theme", theme);
|
|
9653
|
+
for (const [key, value] of Object.entries(extra ?? {})) params.set(key, value);
|
|
9654
|
+
return `${path.startsWith("/") ? FRAME_ORIGIN : ""}${path}?${params.toString()}`;
|
|
9655
|
+
}
|
|
9656
|
+
/** Absolute URL for opening a frame in a new tab. */
|
|
9657
|
+
function absoluteUrl(url) {
|
|
9658
|
+
return new URL(url, document.baseURI).toString();
|
|
9659
|
+
}
|
|
9660
|
+
/**
|
|
9661
|
+
* A link, form or history push the runtime kept from leaving its frame. The path goes to the
|
|
9662
|
+
* screen whose `route` matches it best (a relative `href` resolves against the route of the
|
|
9663
|
+
* screen it is in); without routes, a path whose last part names a screen still opens it. A
|
|
9664
|
+
* link to another site opens in a new tab. A push nobody's route matches belongs to the
|
|
9665
|
+
* screen's own router; a link or form that leads nowhere stays put with a note.
|
|
9666
|
+
*/
|
|
9667
|
+
function followLink(message, canvas, go, from) {
|
|
9668
|
+
if (message.form) {
|
|
9669
|
+
toast("Forms are not sent from a screen", void 0, "info");
|
|
9670
|
+
return;
|
|
11152
9671
|
}
|
|
11153
|
-
|
|
11154
|
-
|
|
9672
|
+
if (message.path === null) {
|
|
9673
|
+
if (!isSafeHref(message.href) || !/^(https?|mailto):/i.test(message.href.trim())) {
|
|
9674
|
+
toast("Link not opened", message.href.slice(0, 200), "info");
|
|
9675
|
+
return;
|
|
9676
|
+
}
|
|
9677
|
+
if (!window.open(message.href, "_blank", "noopener,noreferrer")) toast("Link to another site", message.href, "info");
|
|
9678
|
+
return;
|
|
11155
9679
|
}
|
|
11156
|
-
|
|
11157
|
-
|
|
9680
|
+
const frames = canvas.pages.flatMap((page) => page.sections.flatMap((section) => section.items.filter((item) => item.kind === "screen" || item.kind === "url")));
|
|
9681
|
+
const here = frames.find((item) => item.id === from);
|
|
9682
|
+
const path = message.raw === void 0 ? message.path : linkPath(message.raw, here?.routes?.[0]);
|
|
9683
|
+
if (path === null) return;
|
|
9684
|
+
const routed = matchRoute(frames.map((item) => ({
|
|
9685
|
+
routes: (item.routes ?? []).map(compileRoute),
|
|
9686
|
+
value: item.id
|
|
9687
|
+
})), path);
|
|
9688
|
+
if (routed) {
|
|
9689
|
+
if (routed !== from) go(routed);
|
|
9690
|
+
return;
|
|
11158
9691
|
}
|
|
9692
|
+
const bare = path.split(/[?#]/)[0].replace(/\/+$/, "");
|
|
9693
|
+
let name = bare.slice(bare.lastIndexOf("/") + 1).replace(/\.html?$/, "");
|
|
9694
|
+
try {
|
|
9695
|
+
name = decodeURIComponent(name);
|
|
9696
|
+
} catch {}
|
|
9697
|
+
if (name && name !== from && frames.some((item) => item.id === name)) go(name);
|
|
9698
|
+
else if (!message.pushed) toast("No screen for this link", path, "info");
|
|
9699
|
+
}
|
|
9700
|
+
//#endregion
|
|
9701
|
+
//#region viewer/src/components/inspector.tsx
|
|
9702
|
+
/**
|
|
9703
|
+
* Inspect frames: while `enabled`, every frame in `frames` highlights on hover
|
|
9704
|
+
* and picks on click. The inspecting runs inside each frame (see the runtime's
|
|
9705
|
+
* inspect.ts); this side only switches it and shows what the frames report.
|
|
9706
|
+
* The pick outlives `enabled` (so a quick ⌘-hold leaves the panel open) until
|
|
9707
|
+
* it is cleared.
|
|
9708
|
+
*/
|
|
9709
|
+
function useInspection(frames, enabled, options) {
|
|
9710
|
+
const project = useProject().data;
|
|
9711
|
+
const tokens = useSystem(!!project?.system).data?.tokens;
|
|
9712
|
+
const [selected, setSelected] = useState(null);
|
|
9713
|
+
const state = useRef({
|
|
9714
|
+
enabled,
|
|
9715
|
+
tokens
|
|
9716
|
+
});
|
|
9717
|
+
state.current = {
|
|
9718
|
+
enabled,
|
|
9719
|
+
tokens
|
|
9720
|
+
};
|
|
9721
|
+
const onEscape = useRef(options.onEscape);
|
|
9722
|
+
onEscape.current = options.onEscape;
|
|
9723
|
+
const framesRef = useRef(frames);
|
|
9724
|
+
framesRef.current = frames;
|
|
9725
|
+
const told = useRef(/* @__PURE__ */ new Map());
|
|
9726
|
+
const tell = (frame, force = false) => {
|
|
9727
|
+
const { enabled: on, tokens: list } = state.current;
|
|
9728
|
+
const last = told.current.get(frame);
|
|
9729
|
+
if (!force && last?.on === on && last.tokens === list) return;
|
|
9730
|
+
told.current.set(frame, {
|
|
9731
|
+
on,
|
|
9732
|
+
tokens: list
|
|
9733
|
+
});
|
|
9734
|
+
sendToFrame(frame, {
|
|
9735
|
+
type: "inspect",
|
|
9736
|
+
on,
|
|
9737
|
+
tokens: on ? list ?? [] : void 0
|
|
9738
|
+
});
|
|
9739
|
+
};
|
|
9740
|
+
useEffect(() => {
|
|
9741
|
+
for (const frame of frames) tell(frame);
|
|
9742
|
+
for (const frame of told.current.keys()) if (!frames.includes(frame)) told.current.delete(frame);
|
|
9743
|
+
}, [
|
|
9744
|
+
frames,
|
|
9745
|
+
enabled,
|
|
9746
|
+
tokens
|
|
9747
|
+
]);
|
|
9748
|
+
useEffect(() => {
|
|
9749
|
+
const offs = frames.map((frame) => listenToFrame(frame, (message) => {
|
|
9750
|
+
if (message.type === "ready") {
|
|
9751
|
+
if (state.current.enabled) tell(frame, true);
|
|
9752
|
+
else told.current.set(frame, { on: false });
|
|
9753
|
+
} else if (message.type === "inspect") {
|
|
9754
|
+
if (message.info) {
|
|
9755
|
+
for (const other of framesRef.current) if (other !== frame) sendToFrame(other, {
|
|
9756
|
+
type: "inspect-select",
|
|
9757
|
+
ref: null
|
|
9758
|
+
});
|
|
9759
|
+
setSelected({
|
|
9760
|
+
info: message.info,
|
|
9761
|
+
frame
|
|
9762
|
+
});
|
|
9763
|
+
} else setSelected((current) => current?.frame === frame ? null : current);
|
|
9764
|
+
} else if (message.type === "inspect-escape") onEscape.current();
|
|
9765
|
+
}));
|
|
9766
|
+
return () => {
|
|
9767
|
+
for (const off of offs) off();
|
|
9768
|
+
};
|
|
9769
|
+
}, [frames]);
|
|
9770
|
+
const frame = selected && frames.includes(selected.frame) ? selected.frame : null;
|
|
9771
|
+
return {
|
|
9772
|
+
info: frame ? selected.info : null,
|
|
9773
|
+
frame,
|
|
9774
|
+
/** Pick another element in the same frame by its ref, or clear the pick. */
|
|
9775
|
+
select(ref) {
|
|
9776
|
+
if (ref === null) {
|
|
9777
|
+
for (const each of framesRef.current) sendToFrame(each, {
|
|
9778
|
+
type: "inspect-select",
|
|
9779
|
+
ref: null
|
|
9780
|
+
});
|
|
9781
|
+
setSelected(null);
|
|
9782
|
+
} else if (selected) sendToFrame(selected.frame, {
|
|
9783
|
+
type: "inspect-select",
|
|
9784
|
+
ref
|
|
9785
|
+
});
|
|
9786
|
+
}
|
|
9787
|
+
};
|
|
9788
|
+
}
|
|
9789
|
+
/**
|
|
9790
|
+
* True while ⌘ or Ctrl is held on its own, in the viewer or inside any of
|
|
9791
|
+
* `frames`. A modifier used for a shortcut (⌘K, ⌘C) does not count.
|
|
9792
|
+
*/
|
|
9793
|
+
function useModifierHold(frames) {
|
|
9794
|
+
const [held, setHeld] = useState(false);
|
|
9795
|
+
useEffect(() => {
|
|
9796
|
+
const isModifier = (key) => key === "Meta" || key === "Control";
|
|
9797
|
+
let down = false;
|
|
9798
|
+
let combo = false;
|
|
9799
|
+
let timer;
|
|
9800
|
+
const release = () => {
|
|
9801
|
+
down = false;
|
|
9802
|
+
clearTimeout(timer);
|
|
9803
|
+
setHeld(false);
|
|
9804
|
+
};
|
|
9805
|
+
const press = (key) => {
|
|
9806
|
+
if (isModifier(key)) {
|
|
9807
|
+
if (down) return;
|
|
9808
|
+
down = true;
|
|
9809
|
+
combo = false;
|
|
9810
|
+
timer = setTimeout(() => {
|
|
9811
|
+
if (down && !combo) setHeld(true);
|
|
9812
|
+
}, 150);
|
|
9813
|
+
} else if (down) {
|
|
9814
|
+
combo = true;
|
|
9815
|
+
clearTimeout(timer);
|
|
9816
|
+
setHeld(false);
|
|
9817
|
+
}
|
|
9818
|
+
};
|
|
9819
|
+
const onDown = (event) => press(event.key);
|
|
9820
|
+
const onUp = (event) => {
|
|
9821
|
+
if (isModifier(event.key)) release();
|
|
9822
|
+
};
|
|
9823
|
+
window.addEventListener("keydown", onDown, true);
|
|
9824
|
+
window.addEventListener("keyup", onUp, true);
|
|
9825
|
+
window.addEventListener("blur", release);
|
|
9826
|
+
const offs = frames.map((frame) => listenToFrame(frame, (message) => {
|
|
9827
|
+
if (message.type === "key") {
|
|
9828
|
+
if (message.down) press(message.name);
|
|
9829
|
+
else if (isModifier(message.name)) release();
|
|
9830
|
+
} else if (message.type === "blur") release();
|
|
9831
|
+
}));
|
|
9832
|
+
return () => {
|
|
9833
|
+
window.removeEventListener("keydown", onDown, true);
|
|
9834
|
+
window.removeEventListener("keyup", onUp, true);
|
|
9835
|
+
window.removeEventListener("blur", release);
|
|
9836
|
+
for (const off of offs) off();
|
|
9837
|
+
clearTimeout(timer);
|
|
9838
|
+
};
|
|
9839
|
+
}, [frames]);
|
|
9840
|
+
return held;
|
|
9841
|
+
}
|
|
9842
|
+
function Section({ title, children, right }) {
|
|
9843
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
9844
|
+
className: "border-t border-rule pb-2 first:border-t-0",
|
|
9845
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
9846
|
+
className: "flex h-9 items-center justify-between px-4 text-[12px] font-medium text-muted",
|
|
9847
|
+
children: [title, right]
|
|
9848
|
+
}), children]
|
|
9849
|
+
});
|
|
9850
|
+
}
|
|
9851
|
+
function Line({ label, children }) {
|
|
9852
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
9853
|
+
className: "grid min-h-7 grid-cols-[92px_minmax(0,1fr)] items-center gap-x-3 px-4 py-0.5 text-[12.5px]",
|
|
9854
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
9855
|
+
className: "text-muted",
|
|
9856
|
+
children: label
|
|
9857
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
9858
|
+
className: "flex min-w-0 items-center gap-1.5 text-ink",
|
|
9859
|
+
children
|
|
9860
|
+
})]
|
|
9861
|
+
});
|
|
9862
|
+
}
|
|
9863
|
+
function TokenChip({ name }) {
|
|
9864
|
+
if (!name) return null;
|
|
9865
|
+
return /* @__PURE__ */ jsx("button", {
|
|
9866
|
+
type: "button",
|
|
9867
|
+
onClick: () => void copyText(`var(${name})`, "Token copied"),
|
|
9868
|
+
title: "Copy var()",
|
|
9869
|
+
className: "shrink-0 cursor-pointer truncate rounded-[4px] border-0 bg-action-soft px-1.5 py-px font-mono text-[11px] text-action",
|
|
9870
|
+
children: name
|
|
9871
|
+
});
|
|
9872
|
+
}
|
|
9873
|
+
function Value$1({ children, title }) {
|
|
9874
|
+
return /* @__PURE__ */ jsx("span", {
|
|
9875
|
+
className: "min-w-0 truncate font-mono text-[12px]",
|
|
9876
|
+
title,
|
|
9877
|
+
children
|
|
9878
|
+
});
|
|
9879
|
+
}
|
|
9880
|
+
function ColorLine({ label, color }) {
|
|
9881
|
+
return /* @__PURE__ */ jsxs(Line, {
|
|
9882
|
+
label,
|
|
9883
|
+
children: [
|
|
9884
|
+
/* @__PURE__ */ jsx("span", {
|
|
9885
|
+
className: "size-3.5 shrink-0 rounded-[3px] shadow-[inset_0_0_0_1px_rgba(0,0,0,0.12)]",
|
|
9886
|
+
style: { background: color.css }
|
|
9887
|
+
}),
|
|
9888
|
+
/* @__PURE__ */ jsx(Value$1, {
|
|
9889
|
+
title: color.css,
|
|
9890
|
+
children: color.value
|
|
9891
|
+
}),
|
|
9892
|
+
/* @__PURE__ */ jsx(TokenChip, { name: color.token })
|
|
9893
|
+
]
|
|
9894
|
+
});
|
|
9895
|
+
}
|
|
9896
|
+
var dash = (value) => value ? String(value) : "–";
|
|
9897
|
+
function Layer({ label, values, className, children }) {
|
|
9898
|
+
const [top, right, bottom, left] = values;
|
|
9899
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
9900
|
+
className: cn("relative grid grid-cols-[28px_minmax(0,1fr)_28px] grid-rows-[20px_auto_20px] rounded-[4px]", className),
|
|
9901
|
+
children: [
|
|
9902
|
+
/* @__PURE__ */ jsx("span", {
|
|
9903
|
+
className: "absolute top-1 left-1.5 text-[9.5px] tracking-wide text-muted uppercase",
|
|
9904
|
+
children: label
|
|
9905
|
+
}),
|
|
9906
|
+
/* @__PURE__ */ jsx("span", {
|
|
9907
|
+
className: "col-start-2 self-center text-center",
|
|
9908
|
+
children: dash(top)
|
|
9909
|
+
}),
|
|
9910
|
+
/* @__PURE__ */ jsx("span", {
|
|
9911
|
+
className: "row-start-2 self-center text-center",
|
|
9912
|
+
children: dash(left)
|
|
9913
|
+
}),
|
|
9914
|
+
/* @__PURE__ */ jsx("div", {
|
|
9915
|
+
className: "row-start-2 col-start-2",
|
|
9916
|
+
children
|
|
9917
|
+
}),
|
|
9918
|
+
/* @__PURE__ */ jsx("span", {
|
|
9919
|
+
className: "row-start-2 col-start-3 self-center text-center",
|
|
9920
|
+
children: dash(right)
|
|
9921
|
+
}),
|
|
9922
|
+
/* @__PURE__ */ jsx("span", {
|
|
9923
|
+
className: "row-start-3 col-start-2 self-center text-center",
|
|
9924
|
+
children: dash(bottom)
|
|
9925
|
+
})
|
|
9926
|
+
]
|
|
9927
|
+
});
|
|
9928
|
+
}
|
|
9929
|
+
function BoxModel({ info }) {
|
|
9930
|
+
const content = {
|
|
9931
|
+
w: Math.max(0, info.rect.w - info.border[1] - info.border[3] - info.padding[1] - info.padding[3]),
|
|
9932
|
+
h: Math.max(0, info.rect.h - info.border[0] - info.border[2] - info.padding[0] - info.padding[2])
|
|
9933
|
+
};
|
|
9934
|
+
return /* @__PURE__ */ jsx("div", {
|
|
9935
|
+
className: "px-4 pt-1 pb-2 font-mono text-[11px] text-ink2",
|
|
9936
|
+
children: /* @__PURE__ */ jsx(Layer, {
|
|
9937
|
+
label: "margin",
|
|
9938
|
+
values: info.margin,
|
|
9939
|
+
className: "border border-dashed border-rule-strong bg-[#f6b26b]/15",
|
|
9940
|
+
children: /* @__PURE__ */ jsx(Layer, {
|
|
9941
|
+
label: "border",
|
|
9942
|
+
values: info.border,
|
|
9943
|
+
className: "border border-rule-strong bg-[#f9e27d]/15",
|
|
9944
|
+
children: /* @__PURE__ */ jsx(Layer, {
|
|
9945
|
+
label: "padding",
|
|
9946
|
+
values: info.padding,
|
|
9947
|
+
className: "border border-rule-strong bg-[#93c47d]/20",
|
|
9948
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
9949
|
+
className: "flex h-8 items-center justify-center rounded-[3px] border border-rule-strong bg-[#6fa8dc]/25 text-ink",
|
|
9950
|
+
children: [
|
|
9951
|
+
Math.round(content.w * 100) / 100,
|
|
9952
|
+
" × ",
|
|
9953
|
+
Math.round(content.h * 100) / 100
|
|
9954
|
+
]
|
|
9955
|
+
})
|
|
9956
|
+
})
|
|
9957
|
+
})
|
|
9958
|
+
})
|
|
9959
|
+
});
|
|
9960
|
+
}
|
|
9961
|
+
function Crumbs({ info, onSelect }) {
|
|
9962
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
9963
|
+
className: "flex flex-wrap items-center gap-1 px-4 pb-2",
|
|
9964
|
+
children: [info.path.slice(-4).map((crumb, index) => /* @__PURE__ */ jsxs("span", {
|
|
9965
|
+
className: "flex items-center gap-1",
|
|
9966
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
9967
|
+
type: "button",
|
|
9968
|
+
onClick: () => onSelect(crumb.ref),
|
|
9969
|
+
className: "cursor-pointer rounded-[4px] border-0 bg-soft2 px-1.5 py-0.5 font-mono text-[11px] text-ink2 hover:text-ink",
|
|
9970
|
+
children: crumb.label
|
|
9971
|
+
}), /* @__PURE__ */ jsx(Icon, {
|
|
9972
|
+
name: "chevron-right",
|
|
9973
|
+
size: 11,
|
|
9974
|
+
className: "text-muted"
|
|
9975
|
+
})]
|
|
9976
|
+
}, index)), /* @__PURE__ */ jsx("span", {
|
|
9977
|
+
className: "rounded-[4px] bg-action-soft px-1.5 py-0.5 font-mono text-[11px] text-action",
|
|
9978
|
+
children: info.label
|
|
9979
|
+
})]
|
|
9980
|
+
});
|
|
9981
|
+
}
|
|
9982
|
+
/** The right sidebar: what the selected element is and how it is styled. */
|
|
9983
|
+
function InspectorPanel({ info, onSelect, onClose, active }) {
|
|
9984
|
+
return /* @__PURE__ */ jsxs("aside", {
|
|
9985
|
+
"data-ui": true,
|
|
9986
|
+
className: "flex w-[320px] shrink-0 flex-col overflow-hidden border-l border-rule bg-surface",
|
|
9987
|
+
children: [
|
|
9988
|
+
/* @__PURE__ */ jsxs("div", {
|
|
9989
|
+
className: "flex h-[46px] shrink-0 items-center justify-between border-b border-rule pr-2 pl-4",
|
|
9990
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
9991
|
+
className: "flex items-center gap-2 text-[14px] font-semibold",
|
|
9992
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
9993
|
+
name: "pointer",
|
|
9994
|
+
size: 15,
|
|
9995
|
+
className: "text-action"
|
|
9996
|
+
}), " Inspect"]
|
|
9997
|
+
}), /* @__PURE__ */ jsx(IconButton, {
|
|
9998
|
+
icon: "x",
|
|
9999
|
+
label: "Close inspector (I)",
|
|
10000
|
+
onClick: onClose
|
|
10001
|
+
})]
|
|
10002
|
+
}),
|
|
10003
|
+
!info ? /* @__PURE__ */ jsxs("div", {
|
|
10004
|
+
className: "flex flex-col items-center gap-2 px-6 py-10 text-center",
|
|
10005
|
+
children: [
|
|
10006
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
10007
|
+
name: "pointer",
|
|
10008
|
+
size: 22,
|
|
10009
|
+
className: "text-muted"
|
|
10010
|
+
}),
|
|
10011
|
+
/* @__PURE__ */ jsx("span", {
|
|
10012
|
+
className: "text-[13.5px] text-ink2",
|
|
10013
|
+
children: active ? "Click an element in any screen." : `Hold ${MOD_KEY} to highlight, click to inspect.`
|
|
10014
|
+
}),
|
|
10015
|
+
/* @__PURE__ */ jsx("span", {
|
|
10016
|
+
className: "text-[12.5px] leading-[1.5] text-muted",
|
|
10017
|
+
children: "Hover shows margin, padding and content. Clicks go to the inspector, not the screen."
|
|
10018
|
+
}),
|
|
10019
|
+
/* @__PURE__ */ jsxs("span", {
|
|
10020
|
+
className: "mt-2 flex items-center gap-1.5 text-[12px] text-muted",
|
|
10021
|
+
children: [
|
|
10022
|
+
/* @__PURE__ */ jsx(Kbd, { children: "I" }),
|
|
10023
|
+
" toggle · hold ",
|
|
10024
|
+
/* @__PURE__ */ jsx(Kbd, { children: MOD_KEY }),
|
|
10025
|
+
" · ",
|
|
10026
|
+
/* @__PURE__ */ jsx(Kbd, { children: "esc" }),
|
|
10027
|
+
" clear"
|
|
10028
|
+
]
|
|
10029
|
+
})
|
|
10030
|
+
]
|
|
10031
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
10032
|
+
className: "min-h-0 grow overflow-y-auto",
|
|
10033
|
+
children: [
|
|
10034
|
+
/* @__PURE__ */ jsxs(Section, {
|
|
10035
|
+
title: "Element",
|
|
10036
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
10037
|
+
className: "flex flex-col gap-1 px-4 pb-2",
|
|
10038
|
+
children: [
|
|
10039
|
+
/* @__PURE__ */ jsx("div", {
|
|
10040
|
+
className: "flex min-w-0 items-baseline gap-1.5",
|
|
10041
|
+
children: info.component?.owners[0] ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
|
|
10042
|
+
className: "truncate font-mono text-[14px] font-medium text-ink",
|
|
10043
|
+
children: [
|
|
10044
|
+
"<",
|
|
10045
|
+
info.component.owners[0],
|
|
10046
|
+
">"
|
|
10047
|
+
]
|
|
10048
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
10049
|
+
className: "truncate font-mono text-[12px] text-muted",
|
|
10050
|
+
children: info.tag
|
|
10051
|
+
})] }) : /* @__PURE__ */ jsx("span", {
|
|
10052
|
+
className: "truncate font-mono text-[14px] font-medium text-ink",
|
|
10053
|
+
children: info.label
|
|
10054
|
+
})
|
|
10055
|
+
}),
|
|
10056
|
+
info.component && info.component.owners.length > 1 ? /* @__PURE__ */ jsxs("span", {
|
|
10057
|
+
className: "truncate text-[12px] text-muted",
|
|
10058
|
+
children: ["in ", info.component.owners.slice(1).join(" ← ")]
|
|
10059
|
+
}) : null,
|
|
10060
|
+
info.component?.file ? /* @__PURE__ */ jsx("button", {
|
|
10061
|
+
type: "button",
|
|
10062
|
+
onClick: () => void copyText(info.component.file, "Path copied"),
|
|
10063
|
+
className: "w-fit max-w-full cursor-pointer truncate border-0 bg-transparent p-0 text-left font-mono text-[11.5px] text-muted hover:text-ink2",
|
|
10064
|
+
title: "Copy path",
|
|
10065
|
+
children: info.component.file
|
|
10066
|
+
}) : null,
|
|
10067
|
+
info.text ? /* @__PURE__ */ jsxs("span", {
|
|
10068
|
+
className: "line-clamp-2 text-[12.5px] text-ink2",
|
|
10069
|
+
children: [
|
|
10070
|
+
"“",
|
|
10071
|
+
info.text,
|
|
10072
|
+
"”"
|
|
10073
|
+
]
|
|
10074
|
+
}) : null
|
|
10075
|
+
]
|
|
10076
|
+
}), /* @__PURE__ */ jsx(Crumbs, {
|
|
10077
|
+
info,
|
|
10078
|
+
onSelect
|
|
10079
|
+
})]
|
|
10080
|
+
}),
|
|
10081
|
+
/* @__PURE__ */ jsxs(Section, {
|
|
10082
|
+
title: "Box",
|
|
10083
|
+
children: [
|
|
10084
|
+
/* @__PURE__ */ jsx(BoxModel, { info }),
|
|
10085
|
+
/* @__PURE__ */ jsx(Line, {
|
|
10086
|
+
label: "Size",
|
|
10087
|
+
children: /* @__PURE__ */ jsxs(Value$1, { children: [
|
|
10088
|
+
info.rect.w,
|
|
10089
|
+
" × ",
|
|
10090
|
+
info.rect.h
|
|
10091
|
+
] })
|
|
10092
|
+
}),
|
|
10093
|
+
/* @__PURE__ */ jsx(Line, {
|
|
10094
|
+
label: "Position",
|
|
10095
|
+
children: /* @__PURE__ */ jsxs(Value$1, { children: [
|
|
10096
|
+
"x ",
|
|
10097
|
+
info.rect.x,
|
|
10098
|
+
" · y ",
|
|
10099
|
+
info.rect.y
|
|
10100
|
+
] })
|
|
10101
|
+
})
|
|
10102
|
+
]
|
|
10103
|
+
}),
|
|
10104
|
+
info.layout.length ? /* @__PURE__ */ jsx(Section, {
|
|
10105
|
+
title: "Layout",
|
|
10106
|
+
children: info.layout.map(([label, value]) => /* @__PURE__ */ jsx(Line, {
|
|
10107
|
+
label,
|
|
10108
|
+
children: /* @__PURE__ */ jsx(Value$1, {
|
|
10109
|
+
title: value,
|
|
10110
|
+
children: value
|
|
10111
|
+
})
|
|
10112
|
+
}, label))
|
|
10113
|
+
}) : null,
|
|
10114
|
+
info.typography ? /* @__PURE__ */ jsxs(Section, {
|
|
10115
|
+
title: "Typography",
|
|
10116
|
+
children: [
|
|
10117
|
+
/* @__PURE__ */ jsxs(Line, {
|
|
10118
|
+
label: "Font",
|
|
10119
|
+
children: [/* @__PURE__ */ jsx(Value$1, {
|
|
10120
|
+
title: info.typography.family,
|
|
10121
|
+
children: info.typography.family.split(",")[0]?.replace(/["']/g, "")
|
|
10122
|
+
}), /* @__PURE__ */ jsx(TokenChip, { name: info.typography.familyToken })]
|
|
10123
|
+
}),
|
|
10124
|
+
/* @__PURE__ */ jsxs(Line, {
|
|
10125
|
+
label: "Size",
|
|
10126
|
+
children: [/* @__PURE__ */ jsxs(Value$1, { children: [
|
|
10127
|
+
info.typography.size,
|
|
10128
|
+
" / ",
|
|
10129
|
+
info.typography.lineHeight
|
|
10130
|
+
] }), /* @__PURE__ */ jsx(TokenChip, { name: info.typography.sizeToken })]
|
|
10131
|
+
}),
|
|
10132
|
+
/* @__PURE__ */ jsx(Line, {
|
|
10133
|
+
label: "Weight",
|
|
10134
|
+
children: /* @__PURE__ */ jsx(Value$1, { children: info.typography.weight })
|
|
10135
|
+
}),
|
|
10136
|
+
info.typography.letterSpacing !== "normal" ? /* @__PURE__ */ jsx(Line, {
|
|
10137
|
+
label: "Tracking",
|
|
10138
|
+
children: /* @__PURE__ */ jsx(Value$1, { children: info.typography.letterSpacing })
|
|
10139
|
+
}) : null,
|
|
10140
|
+
/* @__PURE__ */ jsx(ColorLine, {
|
|
10141
|
+
label: "Color",
|
|
10142
|
+
color: info.typography.color
|
|
10143
|
+
}),
|
|
10144
|
+
info.typography.align !== "start" ? /* @__PURE__ */ jsx(Line, {
|
|
10145
|
+
label: "Align",
|
|
10146
|
+
children: /* @__PURE__ */ jsx(Value$1, { children: info.typography.align })
|
|
10147
|
+
}) : null,
|
|
10148
|
+
info.typography.transform ? /* @__PURE__ */ jsx(Line, {
|
|
10149
|
+
label: "Transform",
|
|
10150
|
+
children: /* @__PURE__ */ jsx(Value$1, { children: info.typography.transform })
|
|
10151
|
+
}) : null
|
|
10152
|
+
]
|
|
10153
|
+
}) : null,
|
|
10154
|
+
Object.values(info.appearance).some(Boolean) ? /* @__PURE__ */ jsxs(Section, {
|
|
10155
|
+
title: "Appearance",
|
|
10156
|
+
children: [
|
|
10157
|
+
info.appearance.background ? /* @__PURE__ */ jsx(ColorLine, {
|
|
10158
|
+
label: "Background",
|
|
10159
|
+
color: info.appearance.background
|
|
10160
|
+
}) : null,
|
|
10161
|
+
info.appearance.backgroundImage ? /* @__PURE__ */ jsx(Line, {
|
|
10162
|
+
label: "Image",
|
|
10163
|
+
children: /* @__PURE__ */ jsx(Value$1, {
|
|
10164
|
+
title: info.appearance.backgroundImage,
|
|
10165
|
+
children: info.appearance.backgroundImage
|
|
10166
|
+
})
|
|
10167
|
+
}) : null,
|
|
10168
|
+
info.appearance.border ? /* @__PURE__ */ jsxs(Line, {
|
|
10169
|
+
label: "Border",
|
|
10170
|
+
children: [
|
|
10171
|
+
/* @__PURE__ */ jsx("span", {
|
|
10172
|
+
className: "size-3.5 shrink-0 rounded-[3px] shadow-[inset_0_0_0_1px_rgba(0,0,0,0.12)]",
|
|
10173
|
+
style: { background: info.appearance.border.color.css }
|
|
10174
|
+
}),
|
|
10175
|
+
/* @__PURE__ */ jsxs(Value$1, { children: [
|
|
10176
|
+
info.appearance.border.width,
|
|
10177
|
+
" ",
|
|
10178
|
+
info.appearance.border.style,
|
|
10179
|
+
" ",
|
|
10180
|
+
info.appearance.border.color.value
|
|
10181
|
+
] }),
|
|
10182
|
+
/* @__PURE__ */ jsx(TokenChip, { name: info.appearance.border.color.token })
|
|
10183
|
+
]
|
|
10184
|
+
}) : null,
|
|
10185
|
+
info.appearance.radius ? /* @__PURE__ */ jsxs(Line, {
|
|
10186
|
+
label: "Radius",
|
|
10187
|
+
children: [/* @__PURE__ */ jsx(Value$1, { children: info.appearance.radius.value }), /* @__PURE__ */ jsx(TokenChip, { name: info.appearance.radius.token })]
|
|
10188
|
+
}) : null,
|
|
10189
|
+
info.appearance.shadow ? /* @__PURE__ */ jsxs(Line, {
|
|
10190
|
+
label: "Shadow",
|
|
10191
|
+
children: [/* @__PURE__ */ jsx(Value$1, {
|
|
10192
|
+
title: info.appearance.shadow.value,
|
|
10193
|
+
children: info.appearance.shadow.value
|
|
10194
|
+
}), /* @__PURE__ */ jsx(TokenChip, { name: info.appearance.shadow.token })]
|
|
10195
|
+
}) : null,
|
|
10196
|
+
info.appearance.opacity ? /* @__PURE__ */ jsx(Line, {
|
|
10197
|
+
label: "Opacity",
|
|
10198
|
+
children: /* @__PURE__ */ jsx(Value$1, { children: info.appearance.opacity })
|
|
10199
|
+
}) : null
|
|
10200
|
+
]
|
|
10201
|
+
}) : null,
|
|
10202
|
+
info.classes.length ? /* @__PURE__ */ jsx(Section, {
|
|
10203
|
+
title: "Classes",
|
|
10204
|
+
right: /* @__PURE__ */ jsx("button", {
|
|
10205
|
+
type: "button",
|
|
10206
|
+
onClick: () => void copyText(info.classes.join(" "), "Classes copied"),
|
|
10207
|
+
className: "cursor-pointer border-0 bg-transparent text-[12px] text-muted hover:text-ink2",
|
|
10208
|
+
children: "Copy"
|
|
10209
|
+
}),
|
|
10210
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
10211
|
+
className: "flex flex-wrap gap-1 px-4 pb-1",
|
|
10212
|
+
children: info.classes.map((name) => /* @__PURE__ */ jsx("span", {
|
|
10213
|
+
className: "rounded-[4px] bg-soft2 px-1.5 py-0.5 font-mono text-[11px] text-ink2",
|
|
10214
|
+
children: name
|
|
10215
|
+
}, name))
|
|
10216
|
+
})
|
|
10217
|
+
}) : null,
|
|
10218
|
+
info.attributes.length ? /* @__PURE__ */ jsx(Section, {
|
|
10219
|
+
title: "Attributes",
|
|
10220
|
+
children: info.attributes.map(([name, value]) => /* @__PURE__ */ jsx(Line, {
|
|
10221
|
+
label: name,
|
|
10222
|
+
children: /* @__PURE__ */ jsx(Value$1, {
|
|
10223
|
+
title: value,
|
|
10224
|
+
children: value
|
|
10225
|
+
})
|
|
10226
|
+
}, name))
|
|
10227
|
+
}) : null,
|
|
10228
|
+
info.children.length ? /* @__PURE__ */ jsx(Section, {
|
|
10229
|
+
title: `Children · ${info.children.length}`,
|
|
10230
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
10231
|
+
className: "flex flex-col px-2 pb-1",
|
|
10232
|
+
children: info.children.map((child, index) => /* @__PURE__ */ jsxs("button", {
|
|
10233
|
+
type: "button",
|
|
10234
|
+
onClick: () => onSelect(child.ref),
|
|
10235
|
+
className: "flex h-7 cursor-pointer items-center gap-2 rounded-[6px] border-0 bg-transparent px-2 text-left font-mono text-[12px] text-ink2 hover:bg-soft2",
|
|
10236
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
10237
|
+
name: "chevron-right",
|
|
10238
|
+
size: 12,
|
|
10239
|
+
className: "text-muted"
|
|
10240
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
10241
|
+
className: "truncate",
|
|
10242
|
+
children: child.label
|
|
10243
|
+
})]
|
|
10244
|
+
}, index))
|
|
10245
|
+
})
|
|
10246
|
+
}) : null
|
|
10247
|
+
]
|
|
10248
|
+
}),
|
|
10249
|
+
info ? /* @__PURE__ */ jsx("div", {
|
|
10250
|
+
className: "flex shrink-0 items-center gap-2 border-t border-rule bg-soft px-4 py-2.5",
|
|
10251
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
10252
|
+
icon: "copy",
|
|
10253
|
+
onClick: () => void copyText(info.css, "CSS copied"),
|
|
10254
|
+
className: "grow",
|
|
10255
|
+
children: "Copy CSS"
|
|
10256
|
+
})
|
|
10257
|
+
}) : null
|
|
10258
|
+
]
|
|
10259
|
+
});
|
|
10260
|
+
}
|
|
10261
|
+
//#endregion
|
|
10262
|
+
//#region viewer/src/ui/badge.tsx
|
|
10263
|
+
var BADGE = {
|
|
10264
|
+
neutral: "bg-soft2 text-ink2",
|
|
10265
|
+
action: "bg-action-soft text-action",
|
|
10266
|
+
ok: "bg-ok-soft text-ok-text",
|
|
10267
|
+
danger: "bg-danger-soft text-danger-text"
|
|
11159
10268
|
};
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
|
|
11170
|
-
|
|
11171
|
-
|
|
11172
|
-
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
|
|
11186
|
-
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
|
|
11196
|
-
|
|
10269
|
+
function Badge({ tone = "neutral", children, className }) {
|
|
10270
|
+
return /* @__PURE__ */ jsx("span", {
|
|
10271
|
+
className: cn("inline-flex h-[22px] shrink-0 items-center gap-1 whitespace-nowrap rounded-[6px] px-2 text-[12px] font-medium", BADGE[tone], className),
|
|
10272
|
+
children
|
|
10273
|
+
});
|
|
10274
|
+
}
|
|
10275
|
+
function Count({ value, tone = "neutral" }) {
|
|
10276
|
+
return /* @__PURE__ */ jsx("span", {
|
|
10277
|
+
className: cn("inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-[4px] px-[5px] text-[11.5px] font-semibold tabular", tone === "action" ? "bg-action-soft text-action" : tone === "danger" ? "bg-danger-soft text-danger-text" : "bg-soft2 text-ink2"),
|
|
10278
|
+
children: value
|
|
10279
|
+
});
|
|
10280
|
+
}
|
|
10281
|
+
//#endregion
|
|
10282
|
+
//#region viewer/src/ui/panel.tsx
|
|
10283
|
+
function Panel({ children, className, style }) {
|
|
10284
|
+
return /* @__PURE__ */ jsx("section", {
|
|
10285
|
+
className: cn("min-w-0 shrink-0 overflow-hidden rounded-[10px] border border-rule bg-surface", className),
|
|
10286
|
+
style,
|
|
10287
|
+
children
|
|
10288
|
+
});
|
|
10289
|
+
}
|
|
10290
|
+
function PanelHead({ title, count, sub, right, as: Heading = "h2" }) {
|
|
10291
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
10292
|
+
className: "flex min-h-[46px] items-center justify-between gap-3 px-4",
|
|
10293
|
+
children: [/* @__PURE__ */ jsxs(Heading, {
|
|
10294
|
+
className: "m-0 flex min-w-0 items-center gap-2 whitespace-nowrap text-[14px] font-semibold",
|
|
10295
|
+
children: [
|
|
10296
|
+
title,
|
|
10297
|
+
count !== void 0 ? /* @__PURE__ */ jsx(Count, { value: count }) : null,
|
|
10298
|
+
sub ? /* @__PURE__ */ jsx("span", {
|
|
10299
|
+
className: "truncate text-[12.5px] font-normal text-muted",
|
|
10300
|
+
children: sub
|
|
10301
|
+
}) : null
|
|
10302
|
+
]
|
|
10303
|
+
}), right ? /* @__PURE__ */ jsx("div", {
|
|
10304
|
+
className: "flex items-center gap-2",
|
|
10305
|
+
children: right
|
|
10306
|
+
}) : null]
|
|
10307
|
+
});
|
|
10308
|
+
}
|
|
10309
|
+
function PanelTabs({ items, value, onChange, right }) {
|
|
10310
|
+
const indicator = useId();
|
|
10311
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
10312
|
+
className: "flex h-[46px] items-center justify-between gap-3 px-4",
|
|
10313
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
10314
|
+
role: "tablist",
|
|
10315
|
+
className: "flex gap-5",
|
|
10316
|
+
children: items.map((item) => {
|
|
10317
|
+
const on = item.value === value;
|
|
10318
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
10319
|
+
type: "button",
|
|
10320
|
+
role: "tab",
|
|
10321
|
+
"aria-selected": on,
|
|
10322
|
+
onClick: () => onChange(item.value),
|
|
10323
|
+
className: cn("relative flex h-[46px] cursor-pointer items-center gap-1.5 border-0 bg-transparent px-0.5 text-[14px] transition-colors", on ? "font-semibold text-ink" : "font-medium text-ink2 hover:text-ink"),
|
|
10324
|
+
children: [
|
|
10325
|
+
item.label,
|
|
10326
|
+
item.count !== void 0 ? /* @__PURE__ */ jsx(Count, { value: item.count }) : null,
|
|
10327
|
+
on ? /* @__PURE__ */ jsx(motion.span, {
|
|
10328
|
+
layoutId: indicator,
|
|
10329
|
+
className: "absolute inset-x-0 bottom-0 h-[2px] bg-ink",
|
|
10330
|
+
transition: {
|
|
10331
|
+
type: "spring",
|
|
10332
|
+
stiffness: 520,
|
|
10333
|
+
damping: 42
|
|
10334
|
+
}
|
|
10335
|
+
}) : null
|
|
10336
|
+
]
|
|
10337
|
+
}, item.value);
|
|
10338
|
+
})
|
|
10339
|
+
}), right ? /* @__PURE__ */ jsx("div", {
|
|
10340
|
+
className: "flex items-center gap-2",
|
|
10341
|
+
children: right
|
|
10342
|
+
}) : null]
|
|
10343
|
+
});
|
|
10344
|
+
}
|
|
10345
|
+
function Cell({ align, children }) {
|
|
10346
|
+
return /* @__PURE__ */ jsx("div", {
|
|
10347
|
+
className: cn("flex min-w-0 items-center gap-2", align === "r" && "justify-end text-right", align === "c" && "justify-center"),
|
|
10348
|
+
children
|
|
10349
|
+
});
|
|
10350
|
+
}
|
|
10351
|
+
function cells(children, align) {
|
|
10352
|
+
return Children.toArray(children).map((child, index) => /* @__PURE__ */ jsx(Cell, {
|
|
10353
|
+
align: align?.[index] ?? "l",
|
|
10354
|
+
children: child
|
|
10355
|
+
}, index));
|
|
10356
|
+
}
|
|
10357
|
+
function THead({ cols, align, labels }) {
|
|
10358
|
+
return /* @__PURE__ */ jsx("div", {
|
|
10359
|
+
className: "grid h-8 items-center gap-x-4 border-t border-rule bg-soft px-4 text-[12.5px] text-muted",
|
|
10360
|
+
style: { gridTemplateColumns: cols },
|
|
10361
|
+
children: labels.map((label, index) => /* @__PURE__ */ jsx(Cell, {
|
|
10362
|
+
align: align?.[index] ?? "l",
|
|
10363
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
10364
|
+
className: "whitespace-nowrap",
|
|
10365
|
+
children: label
|
|
10366
|
+
})
|
|
10367
|
+
}, index))
|
|
10368
|
+
});
|
|
10369
|
+
}
|
|
10370
|
+
var rowClass = (first, interactive, className) => cn("grid items-center gap-x-4 border-rule", !first && "border-t", interactive && "cursor-pointer transition-colors hover:bg-soft", className);
|
|
10371
|
+
function Row({ cols, align, minH = 44, pad = "10px 16px", children, first, className, onClick, title }) {
|
|
10372
|
+
return /* @__PURE__ */ jsx("div", {
|
|
10373
|
+
className: rowClass(first, !!onClick, className),
|
|
10374
|
+
style: {
|
|
10375
|
+
gridTemplateColumns: cols,
|
|
10376
|
+
minHeight: minH,
|
|
10377
|
+
padding: pad
|
|
10378
|
+
},
|
|
10379
|
+
onClick,
|
|
10380
|
+
title,
|
|
10381
|
+
children: cells(children, align)
|
|
10382
|
+
});
|
|
10383
|
+
}
|
|
10384
|
+
/** A table row that navigates somewhere in the app. */
|
|
10385
|
+
function RowLink({ to, cols, align, minH = 44, pad = "10px 16px", first, className, children }) {
|
|
10386
|
+
return /* @__PURE__ */ jsx(Link, {
|
|
10387
|
+
href: to,
|
|
10388
|
+
className: cn(rowClass(first, true, className), "text-inherit no-underline"),
|
|
10389
|
+
style: {
|
|
10390
|
+
gridTemplateColumns: cols,
|
|
10391
|
+
minHeight: minH,
|
|
10392
|
+
padding: pad
|
|
10393
|
+
},
|
|
10394
|
+
children: cells(children, align)
|
|
10395
|
+
});
|
|
10396
|
+
}
|
|
10397
|
+
function KV({ label, children, labelWidth = 180, minH = 40, end = false }) {
|
|
10398
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
10399
|
+
className: "grid content-center items-baseline gap-x-4 border-t border-rule px-4 py-2",
|
|
10400
|
+
style: {
|
|
10401
|
+
gridTemplateColumns: `min(${labelWidth}px, 42%) minmax(0, 1fr)`,
|
|
10402
|
+
minHeight: minH
|
|
10403
|
+
},
|
|
10404
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
10405
|
+
className: "text-[13px] text-muted",
|
|
10406
|
+
children: label
|
|
10407
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
10408
|
+
className: cn("flex min-w-0 flex-wrap items-center gap-2", end && "justify-end"),
|
|
10409
|
+
children
|
|
10410
|
+
})]
|
|
10411
|
+
});
|
|
10412
|
+
}
|
|
10413
|
+
function PanelBody({ children, pad = "16px", gap = 16, top = true, className }) {
|
|
10414
|
+
return /* @__PURE__ */ jsx("div", {
|
|
10415
|
+
className: cn("flex flex-col", top && "border-t border-rule", className),
|
|
10416
|
+
style: {
|
|
10417
|
+
padding: pad,
|
|
10418
|
+
gap
|
|
10419
|
+
},
|
|
10420
|
+
children
|
|
10421
|
+
});
|
|
10422
|
+
}
|
|
10423
|
+
function PanelFoot({ left, right }) {
|
|
10424
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
10425
|
+
className: "flex items-center justify-between gap-3 border-t border-rule bg-soft px-4 py-3",
|
|
10426
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
10427
|
+
className: "flex min-w-0 items-center gap-2 text-[12.5px] text-muted",
|
|
10428
|
+
children: left
|
|
10429
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
10430
|
+
className: "flex items-center gap-2",
|
|
10431
|
+
children: right
|
|
10432
|
+
})]
|
|
10433
|
+
});
|
|
10434
|
+
}
|
|
10435
|
+
/** Bottom summary strip. */
|
|
10436
|
+
function Strip({ children, className }) {
|
|
10437
|
+
return /* @__PURE__ */ jsx("div", {
|
|
10438
|
+
className: cn("flex min-h-[38px] shrink-0 items-center gap-2 border-t border-rule bg-soft px-4 py-2 text-[12.5px] text-muted", className),
|
|
10439
|
+
children
|
|
10440
|
+
});
|
|
10441
|
+
}
|
|
10442
|
+
/** Group header row inside tables. */
|
|
10443
|
+
function GroupRow({ children, sub }) {
|
|
10444
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
10445
|
+
className: "flex h-9 items-center gap-2.5 border-t border-rule bg-soft px-4 text-[13px] font-medium",
|
|
10446
|
+
children: [children, sub ? /* @__PURE__ */ jsx("span", {
|
|
10447
|
+
className: "text-[12.5px] font-normal text-muted",
|
|
10448
|
+
children: sub
|
|
10449
|
+
}) : null]
|
|
10450
|
+
});
|
|
10451
|
+
}
|
|
10452
|
+
//#endregion
|
|
10453
|
+
//#region viewer/src/components/issues.tsx
|
|
10454
|
+
function IssueList({ issues, className }) {
|
|
10455
|
+
return /* @__PURE__ */ jsx("div", {
|
|
10456
|
+
className: cn("flex flex-col", className),
|
|
10457
|
+
children: issues.map((issue, index) => /* @__PURE__ */ jsxs("div", {
|
|
10458
|
+
className: "flex items-start gap-2.5 border-t border-rule px-4 py-2.5 text-[13px] first:border-t-0",
|
|
10459
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
10460
|
+
name: issue.severity === "error" ? "alert" : "info",
|
|
10461
|
+
size: 15,
|
|
10462
|
+
className: cn("mt-0.5", issue.severity === "error" ? "text-danger-text" : "text-action")
|
|
10463
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
10464
|
+
className: "flex min-w-0 flex-col gap-0.5",
|
|
10465
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
10466
|
+
className: "text-ink",
|
|
10467
|
+
children: issue.message
|
|
10468
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
10469
|
+
className: "font-mono text-[12px] break-all text-muted",
|
|
10470
|
+
children: [issue.file, issue.at ? ` · ${issue.at}` : ""]
|
|
10471
|
+
})]
|
|
10472
|
+
})]
|
|
10473
|
+
}, index))
|
|
10474
|
+
});
|
|
10475
|
+
}
|
|
10476
|
+
/** Problems found in the project's files, as a panel. */
|
|
10477
|
+
function IssuesPanel({ issues, title = "Problems" }) {
|
|
10478
|
+
if (!issues.length) return null;
|
|
10479
|
+
const errors = issues.filter((issue) => issue.severity === "error").length;
|
|
10480
|
+
return /* @__PURE__ */ jsxs(Panel, {
|
|
10481
|
+
className: errors ? "border-danger/40" : void 0,
|
|
10482
|
+
children: [/* @__PURE__ */ jsx(PanelHead, {
|
|
10483
|
+
title,
|
|
10484
|
+
count: issues.length,
|
|
10485
|
+
sub: errors ? `${errors} blocking` : "warnings"
|
|
10486
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
10487
|
+
className: "border-t border-rule",
|
|
10488
|
+
children: /* @__PURE__ */ jsx(IssueList, { issues })
|
|
10489
|
+
})]
|
|
10490
|
+
});
|
|
10491
|
+
}
|
|
10492
|
+
//#endregion
|
|
10493
|
+
//#region viewer/src/components/slide-panel.tsx
|
|
10494
|
+
var EASE = [
|
|
10495
|
+
.2,
|
|
10496
|
+
.7,
|
|
10497
|
+
.2,
|
|
10498
|
+
1
|
|
10499
|
+
];
|
|
10500
|
+
/**
|
|
10501
|
+
* A side panel that slides open and shut. The wrapper animates its width while
|
|
10502
|
+
* the content keeps its own, so nothing inside reflows mid-animation.
|
|
10503
|
+
*/
|
|
10504
|
+
function SlidePanel({ open, side, width, children }) {
|
|
10505
|
+
return /* @__PURE__ */ jsx(AnimatePresence, {
|
|
10506
|
+
initial: false,
|
|
10507
|
+
children: open ? /* @__PURE__ */ jsx(motion.div, {
|
|
10508
|
+
initial: {
|
|
10509
|
+
width: 0,
|
|
10510
|
+
opacity: .6
|
|
10511
|
+
},
|
|
10512
|
+
animate: {
|
|
10513
|
+
width,
|
|
10514
|
+
opacity: 1
|
|
10515
|
+
},
|
|
10516
|
+
exit: {
|
|
10517
|
+
width: 0,
|
|
10518
|
+
opacity: .6
|
|
10519
|
+
},
|
|
10520
|
+
transition: {
|
|
10521
|
+
duration: .22,
|
|
10522
|
+
ease: EASE
|
|
10523
|
+
},
|
|
10524
|
+
className: cn("flex min-h-0 shrink-0 overflow-hidden", side === "right" ? "justify-start" : "justify-end"),
|
|
10525
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
10526
|
+
className: "flex min-h-0 shrink-0",
|
|
10527
|
+
style: { width },
|
|
10528
|
+
children
|
|
10529
|
+
})
|
|
10530
|
+
}, "panel") : null
|
|
10531
|
+
});
|
|
10532
|
+
}
|
|
10533
|
+
//#endregion
|
|
10534
|
+
//#region viewer/src/ui/page.tsx
|
|
10535
|
+
/** Page body under the top bar. */
|
|
10536
|
+
function Content({ children, className, gap = 20, wide = false }) {
|
|
10537
|
+
return /* @__PURE__ */ jsx("main", {
|
|
10538
|
+
className: cn("mx-auto flex w-full min-h-0 grow flex-col px-8 pt-6 pb-7 animate-[q-fade-in_160ms_ease-out] max-md:px-4 max-md:pt-4", wide ? "max-w-[1440px]" : "max-w-[1200px]", className),
|
|
10539
|
+
style: { gap },
|
|
10540
|
+
children
|
|
10541
|
+
});
|
|
10542
|
+
}
|
|
10543
|
+
function PageHead({ title, sub, right, badge, crumbs }) {
|
|
10544
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
10545
|
+
className: "flex shrink-0 items-end justify-between gap-6 max-md:flex-col max-md:items-stretch max-md:gap-3",
|
|
10546
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
10547
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
10548
|
+
children: [
|
|
10549
|
+
crumbs ? /* @__PURE__ */ jsx("div", {
|
|
10550
|
+
className: "flex items-center gap-1.5 text-[13px] text-muted",
|
|
10551
|
+
children: crumbs
|
|
10552
|
+
}) : null,
|
|
10553
|
+
/* @__PURE__ */ jsxs("div", {
|
|
10554
|
+
className: "flex items-center gap-3",
|
|
10555
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
10556
|
+
className: "m-0 text-[22px] font-semibold tracking-[-0.01em]",
|
|
10557
|
+
children: title
|
|
10558
|
+
}), badge]
|
|
10559
|
+
}),
|
|
10560
|
+
sub ? /* @__PURE__ */ jsx("div", {
|
|
10561
|
+
className: "text-[13px] [overflow-wrap:anywhere] text-muted",
|
|
10562
|
+
children: sub
|
|
10563
|
+
}) : null
|
|
10564
|
+
]
|
|
10565
|
+
}), right ? /* @__PURE__ */ jsx("div", {
|
|
10566
|
+
className: "flex flex-wrap items-center gap-2",
|
|
10567
|
+
children: right
|
|
10568
|
+
}) : null]
|
|
10569
|
+
});
|
|
10570
|
+
}
|
|
10571
|
+
function SubNav({ items, label }) {
|
|
10572
|
+
const [location] = useLocation();
|
|
10573
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
10574
|
+
"aria-label": label,
|
|
10575
|
+
className: "flex flex-col gap-0.5",
|
|
10576
|
+
children: items.map((item) => {
|
|
10577
|
+
const active = location === item.to;
|
|
10578
|
+
return /* @__PURE__ */ jsxs(Link, {
|
|
10579
|
+
href: item.to,
|
|
10580
|
+
"aria-current": active ? "page" : void 0,
|
|
10581
|
+
className: cn("relative flex h-8 items-center gap-2 whitespace-nowrap rounded-[6px] px-3 text-[13.5px] no-underline transition-colors focus-visible:outline-offset-[-2px]", active ? "font-medium text-ink" : "text-ink2 hover:bg-soft2"),
|
|
10582
|
+
children: [
|
|
10583
|
+
active ? /* @__PURE__ */ jsx(motion.span, {
|
|
10584
|
+
layoutId: "sub-nav-active",
|
|
10585
|
+
"aria-hidden": true,
|
|
10586
|
+
className: "absolute inset-0 z-0 rounded-[6px] bg-soft2",
|
|
10587
|
+
transition: {
|
|
10588
|
+
type: "spring",
|
|
10589
|
+
stiffness: 520,
|
|
10590
|
+
damping: 42
|
|
10591
|
+
}
|
|
10592
|
+
}) : null,
|
|
10593
|
+
/* @__PURE__ */ jsx("span", {
|
|
10594
|
+
className: "relative z-[1] min-w-0 truncate",
|
|
10595
|
+
children: item.label
|
|
10596
|
+
}),
|
|
10597
|
+
item.hint ? /* @__PURE__ */ jsx("span", {
|
|
10598
|
+
className: "relative z-[1] ml-auto text-[12px] font-normal text-muted",
|
|
10599
|
+
children: item.hint
|
|
10600
|
+
}) : null
|
|
10601
|
+
]
|
|
10602
|
+
}, item.to);
|
|
10603
|
+
})
|
|
10604
|
+
});
|
|
10605
|
+
}
|
|
10606
|
+
function NavLabel({ children }) {
|
|
10607
|
+
return /* @__PURE__ */ jsx("div", {
|
|
10608
|
+
className: "px-3 pt-4 pb-1 text-[12px] text-muted",
|
|
10609
|
+
children
|
|
10610
|
+
});
|
|
10611
|
+
}
|
|
10612
|
+
/** Standalone message card: not found, empty, failed to load. */
|
|
10613
|
+
function NoticeCard({ title, text, action }) {
|
|
10614
|
+
return /* @__PURE__ */ jsxs(Panel, {
|
|
10615
|
+
className: "flex w-[480px] max-w-full flex-col items-center gap-2 px-6 py-8 text-center",
|
|
10616
|
+
children: [
|
|
10617
|
+
/* @__PURE__ */ jsx("h1", {
|
|
10618
|
+
className: "m-0 text-[16px] font-semibold",
|
|
10619
|
+
children: title
|
|
10620
|
+
}),
|
|
10621
|
+
text ? /* @__PURE__ */ jsx("div", {
|
|
10622
|
+
className: "m-0 max-w-[400px] text-[14px] text-ink2",
|
|
10623
|
+
children: text
|
|
10624
|
+
}) : null,
|
|
10625
|
+
action ? /* @__PURE__ */ jsx("div", {
|
|
10626
|
+
className: "mt-3 flex flex-wrap justify-center gap-2",
|
|
10627
|
+
children: action
|
|
10628
|
+
}) : null
|
|
10629
|
+
]
|
|
10630
|
+
});
|
|
10631
|
+
}
|
|
10632
|
+
/** NoticeCard centred in the free space. */
|
|
10633
|
+
function Notice(props) {
|
|
10634
|
+
return /* @__PURE__ */ jsx("div", {
|
|
10635
|
+
className: "flex min-h-[50vh] grow items-center justify-center p-6 max-md:px-4",
|
|
10636
|
+
children: /* @__PURE__ */ jsx(NoticeCard, { ...props })
|
|
10637
|
+
});
|
|
10638
|
+
}
|
|
10639
|
+
//#endregion
|
|
10640
|
+
//#region viewer/src/pages/not-found.tsx
|
|
10641
|
+
function NotFound({ title = "Nothing here", text }) {
|
|
10642
|
+
const scoped = !!useViewer().scope;
|
|
10643
|
+
return /* @__PURE__ */ jsx(Notice, {
|
|
10644
|
+
title,
|
|
10645
|
+
text: text ?? "This address does not match a canvas or a page of the design system.",
|
|
10646
|
+
action: scoped ? void 0 : /* @__PURE__ */ jsx(ButtonLink, {
|
|
10647
|
+
to: "/",
|
|
10648
|
+
children: "All canvases"
|
|
10649
|
+
})
|
|
10650
|
+
});
|
|
10651
|
+
}
|
|
10652
|
+
//#endregion
|
|
10653
|
+
//#region viewer/src/pages/canvas/camera.ts
|
|
10654
|
+
/** The farthest out the camera goes; past it a page is specks, and every one of them still costs. */
|
|
10655
|
+
var MIN_ZOOM = .1;
|
|
10656
|
+
var clampZoom = (z, min = MIN_ZOOM) => Math.min(8, Math.max(min, z));
|
|
10657
|
+
/** The canvas camera. Lives outside React so panning never re-renders the tree. */
|
|
10658
|
+
var CameraStore = class {
|
|
10659
|
+
camera = {
|
|
10660
|
+
x: 0,
|
|
10661
|
+
y: 0,
|
|
10662
|
+
z: 1
|
|
10663
|
+
};
|
|
10664
|
+
/** How far out the camera may go; the viewport sets it from the size of the page. */
|
|
10665
|
+
minZoom = MIN_ZOOM;
|
|
10666
|
+
listeners = /* @__PURE__ */ new Set();
|
|
10667
|
+
animation = 0;
|
|
10668
|
+
subscribe(listener) {
|
|
10669
|
+
this.listeners.add(listener);
|
|
10670
|
+
return () => {
|
|
10671
|
+
this.listeners.delete(listener);
|
|
10672
|
+
};
|
|
11197
10673
|
}
|
|
11198
|
-
|
|
11199
|
-
|
|
11200
|
-
|
|
11201
|
-
|
|
10674
|
+
set(next) {
|
|
10675
|
+
this.camera = {
|
|
10676
|
+
x: next.x,
|
|
10677
|
+
y: next.y,
|
|
10678
|
+
z: clampZoom(next.z, this.minZoom)
|
|
11202
10679
|
};
|
|
11203
|
-
|
|
11204
|
-
let s = { ...n };
|
|
11205
|
-
if (s.async = this.defaults.async || s.async || !1, n.extensions && (n.extensions.forEach((r) => {
|
|
11206
|
-
if (!r.name) throw new Error("extension name required");
|
|
11207
|
-
if ("renderer" in r) {
|
|
11208
|
-
let o = t.renderers[r.name];
|
|
11209
|
-
o ? t.renderers[r.name] = function(...i) {
|
|
11210
|
-
let u = r.renderer.apply(this, i);
|
|
11211
|
-
return u === !1 && (u = o.apply(this, i)), u;
|
|
11212
|
-
} : t.renderers[r.name] = r.renderer;
|
|
11213
|
-
}
|
|
11214
|
-
if ("tokenizer" in r) {
|
|
11215
|
-
if (!r.level || r.level !== "block" && r.level !== "inline") throw new Error("extension level must be 'block' or 'inline'");
|
|
11216
|
-
let o = t[r.level];
|
|
11217
|
-
o ? o.unshift(r.tokenizer) : t[r.level] = [r.tokenizer], r.start && (r.level === "block" ? t.startBlock ? t.startBlock.push(r.start) : t.startBlock = [r.start] : r.level === "inline" && (t.startInline ? t.startInline.push(r.start) : t.startInline = [r.start]));
|
|
11218
|
-
}
|
|
11219
|
-
"childTokens" in r && r.childTokens && (t.childTokens[r.name] = r.childTokens);
|
|
11220
|
-
}), s.extensions = t), n.renderer) {
|
|
11221
|
-
let r = this.defaults.renderer || new S(this.defaults);
|
|
11222
|
-
for (let o in n.renderer) {
|
|
11223
|
-
if (!(o in r)) throw new Error(`renderer '${o}' does not exist`);
|
|
11224
|
-
if (["options", "parser"].includes(o)) continue;
|
|
11225
|
-
let i = o, u = n.renderer[i], a = r[i];
|
|
11226
|
-
r[i] = (...p) => {
|
|
11227
|
-
let c = u.apply(r, p);
|
|
11228
|
-
return c === !1 && (c = a.apply(r, p)), c || "";
|
|
11229
|
-
};
|
|
11230
|
-
}
|
|
11231
|
-
s.renderer = r;
|
|
11232
|
-
}
|
|
11233
|
-
if (n.tokenizer) {
|
|
11234
|
-
let r = this.defaults.tokenizer || new P(this.defaults);
|
|
11235
|
-
for (let o in n.tokenizer) {
|
|
11236
|
-
if (!(o in r)) throw new Error(`tokenizer '${o}' does not exist`);
|
|
11237
|
-
if ([
|
|
11238
|
-
"options",
|
|
11239
|
-
"rules",
|
|
11240
|
-
"lexer"
|
|
11241
|
-
].includes(o)) continue;
|
|
11242
|
-
let i = o, u = n.tokenizer[i], a = r[i];
|
|
11243
|
-
r[i] = (...p) => {
|
|
11244
|
-
let c = u.apply(r, p);
|
|
11245
|
-
return c === !1 && (c = a.apply(r, p)), c;
|
|
11246
|
-
};
|
|
11247
|
-
}
|
|
11248
|
-
s.tokenizer = r;
|
|
11249
|
-
}
|
|
11250
|
-
if (n.hooks) {
|
|
11251
|
-
let r = this.defaults.hooks || new _();
|
|
11252
|
-
for (let o in n.hooks) {
|
|
11253
|
-
if (!(o in r)) throw new Error(`hook '${o}' does not exist`);
|
|
11254
|
-
if (["options", "block"].includes(o)) continue;
|
|
11255
|
-
let i = o, u = n.hooks[i], a = r[i];
|
|
11256
|
-
_.passThroughHooks.has(o) ? r[i] = (p) => {
|
|
11257
|
-
if (this.defaults.async && _.passThroughHooksRespectAsync.has(o)) return (async () => {
|
|
11258
|
-
let d = await u.call(r, p);
|
|
11259
|
-
return a.call(r, d);
|
|
11260
|
-
})();
|
|
11261
|
-
let c = u.call(r, p);
|
|
11262
|
-
return a.call(r, c);
|
|
11263
|
-
} : r[i] = (...p) => {
|
|
11264
|
-
if (this.defaults.async) return (async () => {
|
|
11265
|
-
let d = await u.apply(r, p);
|
|
11266
|
-
return d === !1 && (d = await a.apply(r, p)), d;
|
|
11267
|
-
})();
|
|
11268
|
-
let c = u.apply(r, p);
|
|
11269
|
-
return c === !1 && (c = a.apply(r, p)), c;
|
|
11270
|
-
};
|
|
11271
|
-
}
|
|
11272
|
-
s.hooks = r;
|
|
11273
|
-
}
|
|
11274
|
-
if (n.walkTokens) {
|
|
11275
|
-
let r = this.defaults.walkTokens, o = n.walkTokens;
|
|
11276
|
-
s.walkTokens = function(i) {
|
|
11277
|
-
let u = [];
|
|
11278
|
-
return u.push(o.call(this, i)), r && (u = u.concat(r.call(this, i))), u;
|
|
11279
|
-
};
|
|
11280
|
-
}
|
|
11281
|
-
this.defaults = {
|
|
11282
|
-
...this.defaults,
|
|
11283
|
-
...s
|
|
11284
|
-
};
|
|
11285
|
-
}), this;
|
|
10680
|
+
for (const listener of this.listeners) listener(this.camera);
|
|
11286
10681
|
}
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11291
|
-
}, this;
|
|
10682
|
+
/** Stop any animation: the user took over. */
|
|
10683
|
+
interrupt() {
|
|
10684
|
+
cancelAnimationFrame(this.animation);
|
|
10685
|
+
this.animation = 0;
|
|
11292
10686
|
}
|
|
11293
|
-
|
|
11294
|
-
|
|
10687
|
+
panBy(dx, dy) {
|
|
10688
|
+
this.interrupt();
|
|
10689
|
+
const { x, y, z } = this.camera;
|
|
10690
|
+
this.set({
|
|
10691
|
+
x: x + dx,
|
|
10692
|
+
y: y + dy,
|
|
10693
|
+
z
|
|
10694
|
+
});
|
|
11295
10695
|
}
|
|
11296
|
-
|
|
11297
|
-
|
|
10696
|
+
/** Zoom by `factor` keeping the screen point (sx, sy) in place. */
|
|
10697
|
+
zoomAt(sx, sy, factor) {
|
|
10698
|
+
this.interrupt();
|
|
10699
|
+
const { x, y, z } = this.camera;
|
|
10700
|
+
const next = clampZoom(z * factor, this.minZoom);
|
|
10701
|
+
const wx = (sx - x) / z;
|
|
10702
|
+
const wy = (sy - y) / z;
|
|
10703
|
+
this.set({
|
|
10704
|
+
x: sx - wx * next,
|
|
10705
|
+
y: sy - wy * next,
|
|
10706
|
+
z: next
|
|
10707
|
+
});
|
|
11298
10708
|
}
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
|
|
11312
|
-
|
|
11313
|
-
|
|
11314
|
-
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
|
|
11320
|
-
}
|
|
11321
|
-
|
|
11322
|
-
|
|
10709
|
+
animateTo(target, duration = 320) {
|
|
10710
|
+
this.interrupt();
|
|
10711
|
+
const from = { ...this.camera };
|
|
10712
|
+
const to = {
|
|
10713
|
+
...target,
|
|
10714
|
+
z: clampZoom(target.z, this.minZoom)
|
|
10715
|
+
};
|
|
10716
|
+
if (duration <= 0 || matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
10717
|
+
this.set(to);
|
|
10718
|
+
return;
|
|
10719
|
+
}
|
|
10720
|
+
const start = performance.now();
|
|
10721
|
+
const step = (now) => {
|
|
10722
|
+
const t = Math.min(1, (now - start) / duration);
|
|
10723
|
+
const e = 1 - (1 - t) ** 3;
|
|
10724
|
+
const z = from.z * (to.z / from.z) ** e;
|
|
10725
|
+
const k = from.z === to.z ? e : (z - from.z) / (to.z - from.z);
|
|
10726
|
+
this.set({
|
|
10727
|
+
x: from.x + (to.x - from.x) * k,
|
|
10728
|
+
y: from.y + (to.y - from.y) * k,
|
|
10729
|
+
z
|
|
10730
|
+
});
|
|
10731
|
+
if (t < 1) this.animation = requestAnimationFrame(step);
|
|
10732
|
+
else this.animation = 0;
|
|
10733
|
+
};
|
|
10734
|
+
this.animation = requestAnimationFrame(step);
|
|
10735
|
+
}
|
|
10736
|
+
toWorld(sx, sy) {
|
|
10737
|
+
const { x, y, z } = this.camera;
|
|
10738
|
+
return {
|
|
10739
|
+
x: (sx - x) / z,
|
|
10740
|
+
y: (sy - y) / z
|
|
10741
|
+
};
|
|
10742
|
+
}
|
|
10743
|
+
};
|
|
10744
|
+
/**
|
|
10745
|
+
* The camera that shows `rect` inside a viewport of `w`×`h` with `pad` screen pixels around it.
|
|
10746
|
+
* When `rect` does not fit even at `minZoom`, it lines up with the top (and the left, if it is too wide).
|
|
10747
|
+
*/
|
|
10748
|
+
function fitRect(rect, w, h, pad = 72, maxZoom = 1, minZoom = MIN_ZOOM) {
|
|
10749
|
+
const z = clampZoom(Math.min((w - pad * 2) / Math.max(rect.w, 1), (h - pad * 2) / Math.max(rect.h, 1), maxZoom), minZoom);
|
|
10750
|
+
const place = (start, size, room) => size * z <= room - pad * 2 ? room / 2 - (start + size / 2) * z : pad - start * z;
|
|
10751
|
+
return {
|
|
10752
|
+
z,
|
|
10753
|
+
x: place(rect.x, rect.w, w),
|
|
10754
|
+
y: place(rect.y, rect.h, h)
|
|
10755
|
+
};
|
|
10756
|
+
}
|
|
10757
|
+
function intersects(a, b) {
|
|
10758
|
+
return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
|
|
10759
|
+
}
|
|
10760
|
+
//#endregion
|
|
10761
|
+
//#region viewer/src/ui/choice.tsx
|
|
10762
|
+
function Switch({ checked, onChange, label, disabled }) {
|
|
10763
|
+
return /* @__PURE__ */ jsx("button", {
|
|
10764
|
+
type: "button",
|
|
10765
|
+
role: "switch",
|
|
10766
|
+
"aria-checked": checked,
|
|
10767
|
+
"aria-label": label,
|
|
10768
|
+
disabled,
|
|
10769
|
+
onClick: () => onChange?.(!checked),
|
|
10770
|
+
className: cn("relative h-[18px] w-[32px] shrink-0 cursor-pointer rounded-[9px] border-0 p-0 transition-colors duration-200 disabled:cursor-not-allowed disabled:opacity-50", checked ? "bg-ink" : "bg-rule-strong"),
|
|
10771
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
10772
|
+
className: "absolute top-[2px] left-[2px] size-[14px] rounded-full bg-surface shadow-[0_1px_2px_rgba(0,0,0,0.25)] transition-transform duration-200 ease-[cubic-bezier(0.2,0.7,0.2,1)]",
|
|
10773
|
+
style: { transform: checked ? "translateX(14px)" : "none" }
|
|
10774
|
+
})
|
|
10775
|
+
});
|
|
10776
|
+
}
|
|
10777
|
+
function Segmented({ items, value, onChange, label, stretch, size = "ctrl" }) {
|
|
10778
|
+
const indicator = useId();
|
|
10779
|
+
return /* @__PURE__ */ jsx("div", {
|
|
10780
|
+
role: "group",
|
|
10781
|
+
"aria-label": label,
|
|
10782
|
+
className: cn("shrink-0 items-center gap-0.5 rounded-[6px] bg-track p-[3px]", size === "ctrl" ? "h-ctrl" : "h-7", stretch ? "flex" : "inline-flex"),
|
|
10783
|
+
children: items.map((item) => {
|
|
10784
|
+
const on = item.value === value;
|
|
10785
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
10786
|
+
type: "button",
|
|
10787
|
+
"aria-pressed": on,
|
|
10788
|
+
title: item.title,
|
|
10789
|
+
onClick: () => onChange(item.value),
|
|
10790
|
+
className: cn("relative flex h-full cursor-pointer items-center justify-center whitespace-nowrap rounded-[4px] border-0 bg-transparent font-medium transition-colors", size === "ctrl" ? "px-2.5 text-ctrl" : "px-2 text-[12.5px]", stretch && "flex-1", on ? "text-ink" : "text-muted hover:text-ink2"),
|
|
10791
|
+
children: [on ? /* @__PURE__ */ jsx(motion.span, {
|
|
10792
|
+
layoutId: indicator,
|
|
10793
|
+
className: "absolute inset-0 z-0 rounded-[4px] bg-surface shadow-[0_1px_2px_rgba(0,0,0,0.10)]",
|
|
10794
|
+
transition: {
|
|
10795
|
+
type: "spring",
|
|
10796
|
+
stiffness: 520,
|
|
10797
|
+
damping: 42
|
|
10798
|
+
}
|
|
10799
|
+
}) : null, /* @__PURE__ */ jsx("span", {
|
|
10800
|
+
className: "relative z-[1] flex items-center gap-1.5",
|
|
10801
|
+
children: item.label
|
|
10802
|
+
})]
|
|
10803
|
+
}, item.value);
|
|
10804
|
+
})
|
|
10805
|
+
});
|
|
10806
|
+
}
|
|
10807
|
+
//#endregion
|
|
10808
|
+
//#region viewer/src/pages/canvas/full-page.tsx
|
|
10809
|
+
/** The full-window page of one screen, relative to the viewer's base. */
|
|
10810
|
+
var fullHref = (canvas, item, theme) => `/c/${encodeURIComponent(canvas)}/full/${encodeURIComponent(item)}?theme=${theme}`;
|
|
10811
|
+
/**
|
|
10812
|
+
* "Open in a new tab" for a screen. A sandboxed viewer serves screens to its frames only, so the
|
|
10813
|
+
* tab gets the full-window page of the viewer; otherwise the screen's own URL.
|
|
10814
|
+
*/
|
|
10815
|
+
function useOpenUrl(canvasId, item, theme) {
|
|
10816
|
+
const { frameSandbox } = useViewer();
|
|
10817
|
+
const { base } = useRouter();
|
|
10818
|
+
if (!item) return null;
|
|
10819
|
+
if (item.kind === "url") return item.url;
|
|
10820
|
+
if (!frameSandbox) return absoluteUrl(frameSrc(item.url, item.theme ?? theme));
|
|
10821
|
+
return absoluteUrl(`${base}${fullHref(canvasId, item.id, item.theme ?? theme)}`);
|
|
10822
|
+
}
|
|
10823
|
+
/** `/c/:canvas/full/:item`: one screen filling the window, a way out on hover and Esc. */
|
|
10824
|
+
function FullPage({ canvasId, itemId }) {
|
|
10825
|
+
const { data: canvas, error, isLoading } = useCanvas(canvasId);
|
|
10826
|
+
const [, navigate] = useLocation();
|
|
10827
|
+
const { frameSandbox } = useViewer();
|
|
10828
|
+
const appTheme = useTheme();
|
|
10829
|
+
const frame = useRef(null);
|
|
10830
|
+
const param = new URLSearchParams(window.location.search).get("theme");
|
|
10831
|
+
const items = canvas ? canvas.pages.flatMap((page) => page.sections.flatMap((section) => section.items.filter((item) => item.kind === "screen" || item.kind === "url"))) : [];
|
|
10832
|
+
const item = items.find((entry) => entry.id === itemId);
|
|
10833
|
+
const theme = (item?.kind === "screen" ? item.theme : void 0) ?? (param === "light" || param === "dark" ? param : canvas?.theme ?? appTheme);
|
|
10834
|
+
const url = item ? item.kind === "screen" ? frameSrc(item.url, theme) : item.url : "";
|
|
10835
|
+
const back = `/c/${encodeURIComponent(canvasId)}`;
|
|
10836
|
+
useEffect(() => {
|
|
10837
|
+
if (item) document.title = `${item.title} · ${canvas?.title ?? ""}`;
|
|
10838
|
+
}, [item, canvas?.title]);
|
|
10839
|
+
useEffect(() => {
|
|
10840
|
+
const onKey = (event) => {
|
|
10841
|
+
if (event.key === "Escape") navigate(back);
|
|
10842
|
+
};
|
|
10843
|
+
window.addEventListener("keydown", onKey);
|
|
10844
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
10845
|
+
}, [navigate, back]);
|
|
10846
|
+
useEffect(() => {
|
|
10847
|
+
const el = frame.current;
|
|
10848
|
+
if (!el || item?.kind !== "screen") return;
|
|
10849
|
+
return listenToFrame(el, (message) => {
|
|
10850
|
+
if (message.type === "keydown" && message.code === "Escape") navigate(back);
|
|
10851
|
+
else if (message.type === "go") {
|
|
10852
|
+
const id = message.target.includes("/") ? message.target.split("/")[1] : message.target;
|
|
10853
|
+
if (items.some((entry) => entry.id === id)) navigate(fullHref(canvasId, id, theme), { replace: true });
|
|
10854
|
+
} else if (message.type === "link" && canvas) followLink(message, canvas, (id) => navigate(fullHref(canvasId, id, theme), { replace: true }), itemId);
|
|
10855
|
+
});
|
|
10856
|
+
}, [url, item?.kind]);
|
|
10857
|
+
if (error) return /* @__PURE__ */ jsx(NotFound, {
|
|
10858
|
+
title: "Canvas not found",
|
|
10859
|
+
text: error.message
|
|
10860
|
+
});
|
|
10861
|
+
if (isLoading || !canvas) return /* @__PURE__ */ jsx("div", { className: "h-dvh bg-surface" });
|
|
10862
|
+
if (!item) return /* @__PURE__ */ jsx(NotFound, {
|
|
10863
|
+
title: "Screen not found",
|
|
10864
|
+
text: `No screen "${itemId}" on this canvas.`
|
|
10865
|
+
});
|
|
10866
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
10867
|
+
className: "group fixed inset-0 bg-surface",
|
|
10868
|
+
children: [/* @__PURE__ */ jsx("iframe", {
|
|
10869
|
+
ref: frame,
|
|
10870
|
+
src: url,
|
|
10871
|
+
title: item.title,
|
|
10872
|
+
allow: "clipboard-read; clipboard-write; fullscreen",
|
|
10873
|
+
sandbox: frameSandbox,
|
|
10874
|
+
className: "block h-full w-full border-0",
|
|
10875
|
+
style: { colorScheme: theme },
|
|
10876
|
+
onLoad: () => item.kind === "screen" && sendTheme(frame.current, theme)
|
|
10877
|
+
}, `${item.id}:${item.kind === "screen" ? item.rev : item.url}`), /* @__PURE__ */ jsx("div", {
|
|
10878
|
+
className: "pointer-events-none absolute inset-x-0 top-0 flex justify-end p-3 opacity-0 transition-opacity duration-200 group-hover:opacity-100 focus-within:opacity-100",
|
|
10879
|
+
children: /* @__PURE__ */ jsxs(Link, {
|
|
10880
|
+
href: back,
|
|
10881
|
+
className: "pointer-events-auto flex h-8 items-center gap-2 rounded-[8px] border border-rule bg-surface px-3 text-[12.5px] text-ink2 no-underline shadow-pop hover:text-ink",
|
|
10882
|
+
children: [
|
|
10883
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
10884
|
+
name: "x",
|
|
10885
|
+
size: 14
|
|
10886
|
+
}),
|
|
10887
|
+
"Back to ",
|
|
10888
|
+
canvas.title,
|
|
10889
|
+
/* @__PURE__ */ jsx(Kbd, { children: "esc" })
|
|
10890
|
+
]
|
|
10891
|
+
})
|
|
10892
|
+
})]
|
|
10893
|
+
});
|
|
10894
|
+
}
|
|
10895
|
+
//#endregion
|
|
10896
|
+
//#region viewer/src/pages/canvas/sidebar.tsx
|
|
10897
|
+
function itemIcon(item) {
|
|
10898
|
+
if (item.kind === "note") return "note";
|
|
10899
|
+
if (item.kind === "image") return "image";
|
|
10900
|
+
if (item.kind === "url") return "globe";
|
|
10901
|
+
return item.frame.width < 600 ? "smartphone" : item.frame.width < 1100 ? "tablet" : "monitor";
|
|
10902
|
+
}
|
|
10903
|
+
function pageHref(canvas, page) {
|
|
10904
|
+
return `/c/${encodeURIComponent(canvas.id)}/p/${encodeURIComponent(page.id)}`;
|
|
10905
|
+
}
|
|
10906
|
+
/** Pages of the canvas and the items on the current one. */
|
|
10907
|
+
function Sidebar({ canvas, page, store, onPick }) {
|
|
10908
|
+
const selected = useStore(store, (state) => state.selected);
|
|
10909
|
+
const errors = useStore(store, (state) => state.frames);
|
|
10910
|
+
return /* @__PURE__ */ jsxs("aside", {
|
|
10911
|
+
"data-ui": true,
|
|
10912
|
+
className: "flex w-[264px] shrink-0 flex-col overflow-hidden border-r border-rule bg-surface",
|
|
10913
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
10914
|
+
className: "flex flex-col gap-1 border-b border-rule px-4 py-3.5",
|
|
10915
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
10916
|
+
className: "truncate text-[14px] font-semibold",
|
|
10917
|
+
children: canvas.title
|
|
10918
|
+
}), canvas.description ? /* @__PURE__ */ jsx("div", {
|
|
10919
|
+
className: "line-clamp-3 text-[12.5px] leading-[1.45] text-muted",
|
|
10920
|
+
children: canvas.description
|
|
10921
|
+
}) : null]
|
|
10922
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
10923
|
+
className: "min-h-0 grow overflow-y-auto pb-4",
|
|
10924
|
+
children: [
|
|
10925
|
+
canvas.pages.length > 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
10926
|
+
className: "px-4 pt-3 pb-1 text-[12px] text-muted",
|
|
10927
|
+
children: "Pages"
|
|
10928
|
+
}), /* @__PURE__ */ jsx("nav", {
|
|
10929
|
+
className: "flex flex-col gap-0.5 px-2",
|
|
10930
|
+
children: canvas.pages.map((p) => {
|
|
10931
|
+
const count = p.sections.reduce((sum, s) => sum + s.items.filter((i) => i.kind === "screen" || i.kind === "url").length, 0);
|
|
10932
|
+
return /* @__PURE__ */ jsxs(Link, {
|
|
10933
|
+
href: pageHref(canvas, p),
|
|
10934
|
+
className: cn("flex h-8 items-center gap-2 rounded-[6px] px-2.5 text-[13.5px] no-underline transition-colors focus-visible:outline-offset-[-2px]", p.id === page.id ? "bg-soft2 font-medium text-ink" : "text-ink2 hover:bg-soft2"),
|
|
10935
|
+
children: [
|
|
10936
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
10937
|
+
name: "file",
|
|
10938
|
+
size: 15,
|
|
10939
|
+
className: "text-muted"
|
|
10940
|
+
}),
|
|
10941
|
+
/* @__PURE__ */ jsx("span", {
|
|
10942
|
+
className: "min-w-0 grow truncate",
|
|
10943
|
+
children: p.title
|
|
10944
|
+
}),
|
|
10945
|
+
/* @__PURE__ */ jsx("span", {
|
|
10946
|
+
className: "text-[12px] text-muted tabular",
|
|
10947
|
+
children: count
|
|
10948
|
+
})
|
|
10949
|
+
]
|
|
10950
|
+
}, p.id);
|
|
10951
|
+
})
|
|
10952
|
+
})] }) : null,
|
|
10953
|
+
/* @__PURE__ */ jsx("div", {
|
|
10954
|
+
className: "px-4 pt-3 pb-1 text-[12px] text-muted",
|
|
10955
|
+
children: canvas.pages.length > 1 ? page.title : "Layers"
|
|
10956
|
+
}),
|
|
10957
|
+
/* @__PURE__ */ jsx("div", {
|
|
10958
|
+
className: "flex flex-col px-2",
|
|
10959
|
+
children: page.sections.map((section) => /* @__PURE__ */ jsxs("div", {
|
|
10960
|
+
className: "flex flex-col",
|
|
10961
|
+
children: [section.title ? /* @__PURE__ */ jsxs("div", {
|
|
10962
|
+
className: "flex h-7 items-center gap-2 px-2.5 pt-1 text-[12px] font-medium text-ink2",
|
|
10963
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
10964
|
+
className: "truncate",
|
|
10965
|
+
children: section.title
|
|
10966
|
+
}), /* @__PURE__ */ jsx(Count, { value: section.items.length })]
|
|
10967
|
+
}) : null, section.items.map((item) => {
|
|
10968
|
+
const hasErrors = (errors[item.id]?.errors.length ?? 0) > 0 || item.kind === "screen" && item.missing;
|
|
10969
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
10970
|
+
type: "button",
|
|
10971
|
+
onClick: () => onPick(item.id),
|
|
10972
|
+
className: cn("flex h-8 w-full cursor-pointer items-center gap-2 rounded-[6px] border-0 px-2.5 text-left text-[13px] transition-colors focus-visible:outline-offset-[-2px]", section.title && "pl-4", selected === item.id ? "bg-action-soft text-ink" : "bg-transparent text-ink2 hover:bg-soft2"),
|
|
10973
|
+
children: [
|
|
10974
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
10975
|
+
name: itemIcon(item),
|
|
10976
|
+
size: 15,
|
|
10977
|
+
className: "text-muted"
|
|
10978
|
+
}),
|
|
10979
|
+
/* @__PURE__ */ jsx("span", {
|
|
10980
|
+
className: "min-w-0 grow truncate",
|
|
10981
|
+
children: item.title || (item.kind === "note" ? "Note" : item.id)
|
|
10982
|
+
}),
|
|
10983
|
+
hasErrors ? /* @__PURE__ */ jsx("span", { className: "size-[7px] shrink-0 rounded-full bg-danger" }) : null
|
|
10984
|
+
]
|
|
10985
|
+
}, item.id);
|
|
10986
|
+
})]
|
|
10987
|
+
}, section.id))
|
|
10988
|
+
})
|
|
10989
|
+
]
|
|
10990
|
+
})]
|
|
10991
|
+
});
|
|
10992
|
+
}
|
|
10993
|
+
//#endregion
|
|
10994
|
+
//#region viewer/src/pages/canvas/canvas-bar.tsx
|
|
10995
|
+
/** The canvas page's top bar: the canvas and its pages, screen theme, inspect and play. */
|
|
10996
|
+
function CanvasBar({ canvas, page, theme, onTheme, sidebar, onSidebar, onPlay, canPlay, inspect, onInspect }) {
|
|
10997
|
+
const [, navigate] = useLocation();
|
|
10998
|
+
const { slots, scope } = useViewer();
|
|
10999
|
+
const titleRef = useRef(null);
|
|
11000
|
+
const endRef = useRef(null);
|
|
11001
|
+
const compact = useFitBar(titleRef, endRef, [
|
|
11002
|
+
canvas.title,
|
|
11003
|
+
page?.title,
|
|
11004
|
+
canvas.pages.length
|
|
11005
|
+
]);
|
|
11006
|
+
return /* @__PURE__ */ jsxs(BarCompactProvider, {
|
|
11007
|
+
value: compact,
|
|
11008
|
+
children: [/* @__PURE__ */ jsxs(Bar, { children: [
|
|
11009
|
+
slots.barStart,
|
|
11010
|
+
slots.brand ? slots.brand : scope ? /* @__PURE__ */ jsx(Mark, {}) : /* @__PURE__ */ jsx(Tooltip, {
|
|
11011
|
+
content: "All canvases",
|
|
11012
|
+
children: /* @__PURE__ */ jsx(Link, {
|
|
11013
|
+
href: "/",
|
|
11014
|
+
className: "flex items-center",
|
|
11015
|
+
"aria-label": "All canvases",
|
|
11016
|
+
children: /* @__PURE__ */ jsx(Mark, {})
|
|
11017
|
+
})
|
|
11018
|
+
}),
|
|
11019
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
11020
|
+
icon: "panel-left",
|
|
11021
|
+
label: sidebar ? "Hide sidebar (⌘\\)" : "Show sidebar (⌘\\)",
|
|
11022
|
+
onClick: () => onSidebar(!sidebar)
|
|
11023
|
+
}),
|
|
11024
|
+
/* @__PURE__ */ jsxs("div", {
|
|
11025
|
+
ref: titleRef,
|
|
11026
|
+
className: "flex min-w-0 items-center gap-1.5 text-[14px]",
|
|
11027
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
11028
|
+
"data-fit": true,
|
|
11029
|
+
className: "truncate font-semibold",
|
|
11030
|
+
children: canvas.title
|
|
11031
|
+
}), page && canvas.pages.length > 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Icon, {
|
|
11032
|
+
name: "chevron-right",
|
|
11033
|
+
size: 14,
|
|
11034
|
+
className: "shrink-0 text-muted"
|
|
11035
|
+
}), /* @__PURE__ */ jsxs(Menu, {
|
|
11036
|
+
width: 240,
|
|
11037
|
+
trigger: /* @__PURE__ */ jsxs("button", {
|
|
11038
|
+
type: "button",
|
|
11039
|
+
className: "flex h-ctrl cursor-pointer items-center gap-1 rounded-[6px] border-0 bg-transparent px-2 text-[14px] text-ink2 hover:bg-soft2",
|
|
11040
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
11041
|
+
"data-fit": true,
|
|
11042
|
+
className: "truncate",
|
|
11043
|
+
children: page.title
|
|
11044
|
+
}), /* @__PURE__ */ jsx(Icon, {
|
|
11045
|
+
name: "chevron-down",
|
|
11046
|
+
size: 14,
|
|
11047
|
+
className: "text-muted"
|
|
11048
|
+
})]
|
|
11049
|
+
}),
|
|
11050
|
+
children: [/* @__PURE__ */ jsx(MenuLabel, { children: "Pages" }), canvas.pages.map((p) => /* @__PURE__ */ jsxs(MenuItem, {
|
|
11051
|
+
active: p.id === page.id,
|
|
11052
|
+
onSelect: () => navigate(pageHref(canvas, p)),
|
|
11053
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
11054
|
+
name: "file",
|
|
11055
|
+
size: 15,
|
|
11056
|
+
className: "text-muted"
|
|
11057
|
+
}), p.title]
|
|
11058
|
+
}, p.id))]
|
|
11059
|
+
})] }) : null]
|
|
11060
|
+
}),
|
|
11061
|
+
/* @__PURE__ */ jsxs("div", {
|
|
11062
|
+
ref: endRef,
|
|
11063
|
+
className: "ml-auto flex shrink-0 items-center gap-2",
|
|
11064
|
+
children: [
|
|
11065
|
+
/* @__PURE__ */ jsx(Segmented, {
|
|
11066
|
+
label: "Screen theme",
|
|
11067
|
+
value: theme,
|
|
11068
|
+
onChange: onTheme,
|
|
11069
|
+
items: [{
|
|
11070
|
+
value: "light",
|
|
11071
|
+
label: /* @__PURE__ */ jsx(Icon, {
|
|
11072
|
+
name: "sun",
|
|
11073
|
+
size: 15
|
|
11074
|
+
}),
|
|
11075
|
+
title: "Light screens"
|
|
11076
|
+
}, {
|
|
11077
|
+
value: "dark",
|
|
11078
|
+
label: /* @__PURE__ */ jsx(Icon, {
|
|
11079
|
+
name: "moon",
|
|
11080
|
+
size: 15
|
|
11081
|
+
}),
|
|
11082
|
+
title: "Dark screens"
|
|
11083
|
+
}]
|
|
11084
|
+
}),
|
|
11085
|
+
/* @__PURE__ */ jsx(InspectToggle, {
|
|
11086
|
+
on: inspect,
|
|
11087
|
+
onChange: onInspect,
|
|
11088
|
+
compact
|
|
11089
|
+
}),
|
|
11090
|
+
/* @__PURE__ */ jsx(Tooltip, {
|
|
11091
|
+
content: /* @__PURE__ */ jsxs("span", {
|
|
11092
|
+
className: "flex items-center gap-1.5",
|
|
11093
|
+
children: ["Play ", /* @__PURE__ */ jsx(Kbd, { children: "P" })]
|
|
11094
|
+
}),
|
|
11095
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
11096
|
+
kind: "secondary",
|
|
11097
|
+
icon: "play",
|
|
11098
|
+
onClick: onPlay,
|
|
11099
|
+
disabled: !canPlay,
|
|
11100
|
+
"aria-label": "Play",
|
|
11101
|
+
className: compact ? "w-ctrl px-0" : void 0,
|
|
11102
|
+
children: compact ? null : "Play"
|
|
11103
|
+
})
|
|
11104
|
+
}),
|
|
11105
|
+
/* @__PURE__ */ jsx(SearchButton, {
|
|
11106
|
+
width: 180,
|
|
11107
|
+
label: "Search",
|
|
11108
|
+
compact
|
|
11109
|
+
}),
|
|
11110
|
+
/* @__PURE__ */ jsx(ThemeMenu, {}),
|
|
11111
|
+
slots.barEnd
|
|
11112
|
+
]
|
|
11113
|
+
})
|
|
11114
|
+
] }), slots.belowBar ? /* @__PURE__ */ jsx("div", {
|
|
11115
|
+
className: "shrink-0",
|
|
11116
|
+
children: slots.belowBar
|
|
11117
|
+
}) : null]
|
|
11118
|
+
});
|
|
11119
|
+
}
|
|
11120
|
+
/** The selected item's name and actions, floating over the bottom of the canvas. */
|
|
11121
|
+
function SelectionBar({ canvas, store, layoutItems, theme, onPlay, inspect }) {
|
|
11122
|
+
const selected = useStore(store, (state) => state.selected);
|
|
11123
|
+
const active = useStore(store, (state) => state.active);
|
|
11124
|
+
const status = useStore(store, (state) => selected ? state.frames[selected] : void 0);
|
|
11125
|
+
const item = layoutItems.find((i) => i.id === selected);
|
|
11126
|
+
const openUrl = useOpenUrl(canvas.id, item?.kind === "screen" || item?.kind === "url" ? item : void 0, theme);
|
|
11127
|
+
if (!item) return null;
|
|
11128
|
+
const frame = item.kind === "screen" || item.kind === "url";
|
|
11129
|
+
const reload = () => reloadFrame(document.querySelector(`[data-item="${CSS.escape(item.id)}"] iframe`));
|
|
11130
|
+
const errors = status?.errors ?? [];
|
|
11131
|
+
return /* @__PURE__ */ jsx("div", {
|
|
11132
|
+
"data-ui": true,
|
|
11133
|
+
className: "pointer-events-none absolute inset-x-0 bottom-5 z-20 flex justify-center px-4",
|
|
11134
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
11135
|
+
className: "pointer-events-auto flex max-w-full flex-col overflow-hidden rounded-[10px] border border-rule bg-surface shadow-pop animate-[q-pop_120ms_ease-out]",
|
|
11136
|
+
children: [errors.length ? /* @__PURE__ */ jsxs("div", {
|
|
11137
|
+
className: "flex max-h-40 items-start gap-2 overflow-y-auto border-b border-rule bg-danger-soft px-3.5 py-2 font-mono text-[12px] whitespace-pre-wrap text-danger-text",
|
|
11138
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
11139
|
+
name: "alert",
|
|
11140
|
+
size: 14,
|
|
11141
|
+
className: "mt-px"
|
|
11142
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
11143
|
+
className: "min-w-0",
|
|
11144
|
+
children: errors.join("\n")
|
|
11145
|
+
})]
|
|
11146
|
+
}) : null, /* @__PURE__ */ jsxs("div", {
|
|
11147
|
+
className: "flex h-[44px] items-center gap-1 pr-1.5 pl-3.5",
|
|
11148
|
+
children: [
|
|
11149
|
+
/* @__PURE__ */ jsxs("div", {
|
|
11150
|
+
className: "flex min-w-0 items-center gap-2 pr-2",
|
|
11151
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
11152
|
+
className: "truncate text-[13.5px] font-medium",
|
|
11153
|
+
children: item.title || item.id
|
|
11154
|
+
}), frame && active === item.id ? /* @__PURE__ */ jsxs("span", {
|
|
11155
|
+
className: "flex shrink-0 items-center gap-1.5 text-[12px] text-muted",
|
|
11156
|
+
children: [
|
|
11157
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
11158
|
+
name: "pointer",
|
|
11159
|
+
size: 13
|
|
11160
|
+
}),
|
|
11161
|
+
" ",
|
|
11162
|
+
inspect ? "Inspecting" : "Interacting",
|
|
11163
|
+
" ",
|
|
11164
|
+
/* @__PURE__ */ jsx(Kbd, { children: "esc" })
|
|
11165
|
+
]
|
|
11166
|
+
}) : frame ? /* @__PURE__ */ jsx("span", {
|
|
11167
|
+
className: "shrink-0 text-[12px] text-muted",
|
|
11168
|
+
children: inspect ? "Click an element to inspect it" : "Click the screen to interact"
|
|
11169
|
+
}) : null]
|
|
11170
|
+
}),
|
|
11171
|
+
frame ? /* @__PURE__ */ jsx(Tooltip, {
|
|
11172
|
+
content: /* @__PURE__ */ jsxs("span", {
|
|
11173
|
+
className: "flex items-center gap-1.5",
|
|
11174
|
+
children: ["Play ", /* @__PURE__ */ jsx(Kbd, { children: "↵" })]
|
|
11175
|
+
}),
|
|
11176
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
11177
|
+
icon: "play",
|
|
11178
|
+
label: "Play",
|
|
11179
|
+
onClick: () => onPlay(item.id)
|
|
11180
|
+
})
|
|
11181
|
+
}) : null,
|
|
11182
|
+
openUrl ? /* @__PURE__ */ jsx(Tooltip, {
|
|
11183
|
+
content: "Open in a new tab",
|
|
11184
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
11185
|
+
href: openUrl,
|
|
11186
|
+
target: "_blank",
|
|
11187
|
+
rel: "noreferrer",
|
|
11188
|
+
"aria-label": "Open in a new tab",
|
|
11189
|
+
className: "inline-flex h-ctrl w-ctrl items-center justify-center rounded-[6px] text-muted hover:bg-soft2 hover:text-ink2",
|
|
11190
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
11191
|
+
name: "external",
|
|
11192
|
+
size: 16
|
|
11193
|
+
})
|
|
11194
|
+
})
|
|
11195
|
+
}) : null,
|
|
11196
|
+
frame ? /* @__PURE__ */ jsx(Tooltip, {
|
|
11197
|
+
content: "Reload",
|
|
11198
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
11199
|
+
icon: "refresh",
|
|
11200
|
+
label: "Reload",
|
|
11201
|
+
onClick: reload
|
|
11202
|
+
})
|
|
11203
|
+
}) : null,
|
|
11204
|
+
item.kind === "screen" ? /* @__PURE__ */ jsx(Tooltip, {
|
|
11205
|
+
content: "Copy source path",
|
|
11206
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
11207
|
+
icon: "copy",
|
|
11208
|
+
label: "Copy source path",
|
|
11209
|
+
onClick: () => void copyText(item.file, "Path copied")
|
|
11210
|
+
})
|
|
11211
|
+
}) : null,
|
|
11212
|
+
/* @__PURE__ */ jsx(Tooltip, {
|
|
11213
|
+
content: /* @__PURE__ */ jsxs("span", {
|
|
11214
|
+
className: "flex items-center gap-1.5",
|
|
11215
|
+
children: ["Zoom to ", /* @__PURE__ */ jsx(Kbd, { children: "⇧2" })]
|
|
11216
|
+
}),
|
|
11217
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
11218
|
+
icon: "fit",
|
|
11219
|
+
label: "Zoom to",
|
|
11220
|
+
onClick: () => window.dispatchEvent(new CustomEvent("design:focus", { detail: {
|
|
11221
|
+
canvas: canvas.id,
|
|
11222
|
+
item: item.id
|
|
11223
|
+
} }))
|
|
11224
|
+
})
|
|
11225
|
+
})
|
|
11226
|
+
]
|
|
11227
|
+
})]
|
|
11228
|
+
})
|
|
11229
|
+
});
|
|
11230
|
+
}
|
|
11231
|
+
/** The inspect mode switch: a button that stays pressed while on. */
|
|
11232
|
+
function InspectToggle({ on, onChange, compact = false }) {
|
|
11233
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
11234
|
+
content: /* @__PURE__ */ jsxs("span", {
|
|
11235
|
+
className: "flex items-center gap-1.5",
|
|
11236
|
+
children: ["Inspect elements ", /* @__PURE__ */ jsx(Kbd, { children: "I" })]
|
|
11237
|
+
}),
|
|
11238
|
+
children: /* @__PURE__ */ jsxs("button", {
|
|
11239
|
+
type: "button",
|
|
11240
|
+
"aria-pressed": on,
|
|
11241
|
+
"aria-label": "Inspect elements",
|
|
11242
|
+
onClick: () => onChange(!on),
|
|
11243
|
+
className: cn("inline-flex h-ctrl shrink-0 cursor-pointer items-center justify-center gap-1.5 rounded-[6px] border text-ctrl font-medium", compact ? "w-ctrl px-0" : "px-ctrl", on ? "border-action-line bg-action-soft text-action" : "border-rule-strong bg-surface text-ink hover:bg-soft"),
|
|
11244
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
11245
|
+
name: "pointer",
|
|
11246
|
+
size: 15
|
|
11247
|
+
}), compact ? null : "Inspect"]
|
|
11248
|
+
})
|
|
11249
|
+
});
|
|
11250
|
+
}
|
|
11251
|
+
//#endregion
|
|
11252
|
+
//#region viewer/src/pages/canvas/layout.ts
|
|
11253
|
+
var SECTION_GAP = 280;
|
|
11254
|
+
var ITEM_GAP = 96;
|
|
11255
|
+
var ROW_GAP = 150;
|
|
11256
|
+
var HEADER_GAP = 110;
|
|
11257
|
+
var HEADER_MIN_WIDTH = 720;
|
|
11258
|
+
var isFrame = (item) => item.kind === "screen" || item.kind === "url";
|
|
11259
|
+
function itemSize(item, sizes) {
|
|
11260
|
+
const measured = sizes[item.id];
|
|
11261
|
+
if (item.kind === "screen" || item.kind === "url") {
|
|
11262
|
+
const w = item.frame.width;
|
|
11263
|
+
if (item.frame.height !== "auto") return {
|
|
11264
|
+
w,
|
|
11265
|
+
h: item.frame.height
|
|
11266
|
+
};
|
|
11267
|
+
const fallback = item.kind === "screen" ? item.measuredHeight : void 0;
|
|
11268
|
+
return {
|
|
11269
|
+
w,
|
|
11270
|
+
h: measured?.h ?? fallback ?? 900
|
|
11323
11271
|
};
|
|
11324
11272
|
}
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
|
|
11273
|
+
if (item.kind === "note") return {
|
|
11274
|
+
w: item.width,
|
|
11275
|
+
h: measured?.h ?? Math.max(80, Math.ceil(item.text.length / 38) * 26 + 40)
|
|
11276
|
+
};
|
|
11277
|
+
const w = item.width ?? measured?.w ?? 480;
|
|
11278
|
+
return {
|
|
11279
|
+
w,
|
|
11280
|
+
h: measured?.w ? measured.h / measured.w * w : measured?.h ?? w * .62
|
|
11281
|
+
};
|
|
11282
|
+
}
|
|
11283
|
+
function sectionHeaderHeight(section, sizes) {
|
|
11284
|
+
if (!section.title && !section.description) return 0;
|
|
11285
|
+
return sizes[`section:${section.id}`]?.h ?? (section.title ? 56 : 0) + (section.description ? 60 : 0);
|
|
11286
|
+
}
|
|
11287
|
+
/** Sections stack top to bottom; items run left to right, wrapping after `columns`. */
|
|
11288
|
+
function layoutPage(page, sizes) {
|
|
11289
|
+
const items = [];
|
|
11290
|
+
const sections = [];
|
|
11291
|
+
if (page.layout === "free") {
|
|
11292
|
+
let cursor = 0;
|
|
11293
|
+
for (const section of page.sections) for (const item of section.items) {
|
|
11294
|
+
const { w, h } = itemSize(item, sizes);
|
|
11295
|
+
const x = item.x ?? cursor;
|
|
11296
|
+
const y = item.y ?? 0;
|
|
11297
|
+
cursor = Math.max(cursor, x + w + ITEM_GAP);
|
|
11298
|
+
items.push({
|
|
11299
|
+
item,
|
|
11300
|
+
section,
|
|
11301
|
+
x,
|
|
11302
|
+
y,
|
|
11303
|
+
w,
|
|
11304
|
+
h
|
|
11305
|
+
});
|
|
11306
|
+
}
|
|
11307
|
+
} else {
|
|
11308
|
+
let y = 0;
|
|
11309
|
+
for (const section of page.sections) {
|
|
11310
|
+
const header = sectionHeaderHeight(section, sizes);
|
|
11311
|
+
const top = y;
|
|
11312
|
+
let rowY = y + (header ? header + HEADER_GAP : 0);
|
|
11313
|
+
let x = 0;
|
|
11314
|
+
let rowHeight = 0;
|
|
11315
|
+
let width = 0;
|
|
11316
|
+
let column = 0;
|
|
11317
|
+
for (const item of section.items) {
|
|
11318
|
+
if (section.columns && column === section.columns) {
|
|
11319
|
+
rowY += rowHeight + ROW_GAP;
|
|
11320
|
+
x = 0;
|
|
11321
|
+
rowHeight = 0;
|
|
11322
|
+
column = 0;
|
|
11323
|
+
}
|
|
11324
|
+
const { w, h } = itemSize(item, sizes);
|
|
11325
|
+
items.push({
|
|
11326
|
+
item,
|
|
11327
|
+
section,
|
|
11328
|
+
x,
|
|
11329
|
+
y: rowY,
|
|
11330
|
+
w,
|
|
11331
|
+
h
|
|
11332
|
+
});
|
|
11333
|
+
x += w + ITEM_GAP;
|
|
11334
|
+
width = Math.max(width, x - ITEM_GAP);
|
|
11335
|
+
rowHeight = Math.max(rowHeight, h);
|
|
11336
|
+
column++;
|
|
11331
11337
|
}
|
|
11332
|
-
|
|
11333
|
-
|
|
11334
|
-
|
|
11338
|
+
const bottom = rowY + rowHeight;
|
|
11339
|
+
sections.push({
|
|
11340
|
+
section,
|
|
11341
|
+
x: 0,
|
|
11342
|
+
y: top,
|
|
11343
|
+
w: Math.max(width, header ? HEADER_MIN_WIDTH : 0),
|
|
11344
|
+
h: bottom - top,
|
|
11345
|
+
header
|
|
11346
|
+
});
|
|
11347
|
+
y = bottom + SECTION_GAP;
|
|
11348
|
+
}
|
|
11335
11349
|
}
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
|
|
11339
|
-
|
|
11350
|
+
let minX = 0;
|
|
11351
|
+
let minY = 0;
|
|
11352
|
+
let maxX = 0;
|
|
11353
|
+
let maxY = 0;
|
|
11354
|
+
const all = [...items, ...sections];
|
|
11355
|
+
if (all.length) {
|
|
11356
|
+
minX = Math.min(...all.map((r) => r.x));
|
|
11357
|
+
minY = Math.min(...all.map((r) => r.y));
|
|
11358
|
+
maxX = Math.max(...all.map((r) => r.x + r.w));
|
|
11359
|
+
maxY = Math.max(...all.map((r) => r.y + r.h));
|
|
11360
|
+
}
|
|
11361
|
+
return {
|
|
11362
|
+
items,
|
|
11363
|
+
sections,
|
|
11364
|
+
bounds: {
|
|
11365
|
+
x: minX,
|
|
11366
|
+
y: minY,
|
|
11367
|
+
w: maxX - minX,
|
|
11368
|
+
h: maxY - minY
|
|
11369
|
+
}
|
|
11370
|
+
};
|
|
11340
11371
|
}
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
k.getDefaults = I;
|
|
11345
|
-
k.defaults = y;
|
|
11346
|
-
function Pt(...l) {
|
|
11347
|
-
return E.use(...l), k.defaults = E.defaults, W(k.defaults), k;
|
|
11372
|
+
/** Frames in reading order, for arrow-key stepping and play mode. */
|
|
11373
|
+
function frameOrder(layout) {
|
|
11374
|
+
return layout.items.filter((placed) => isFrame(placed.item));
|
|
11348
11375
|
}
|
|
11349
|
-
k.use = Pt;
|
|
11350
|
-
k.walkTokens = function(l, e) {
|
|
11351
|
-
return E.walkTokens(l, e);
|
|
11352
|
-
};
|
|
11353
|
-
k.parseInline = E.parseInline;
|
|
11354
|
-
k.Parser = T;
|
|
11355
|
-
k.parser = T.parse;
|
|
11356
|
-
k.Renderer = S;
|
|
11357
|
-
k.TextRenderer = z;
|
|
11358
|
-
k.Lexer = R;
|
|
11359
|
-
k.lexer = R.lex;
|
|
11360
|
-
k.Tokenizer = P;
|
|
11361
|
-
k.Hooks = _;
|
|
11362
|
-
k.parse = k;
|
|
11363
|
-
k.options;
|
|
11364
|
-
k.setOptions;
|
|
11365
|
-
k.walkTokens;
|
|
11366
|
-
k.parseInline;
|
|
11367
|
-
T.parse;
|
|
11368
|
-
R.lex;
|
|
11369
11376
|
//#endregion
|
|
11370
|
-
//#region viewer/src/
|
|
11371
|
-
|
|
11372
|
-
|
|
11373
|
-
|
|
11374
|
-
|
|
11375
|
-
|
|
11376
|
-
|
|
11377
|
-
|
|
11378
|
-
|
|
11379
|
-
|
|
11380
|
-
|
|
11377
|
+
//#region viewer/src/pages/canvas/view-state.ts
|
|
11378
|
+
function createViewState() {
|
|
11379
|
+
return new Store({
|
|
11380
|
+
selected: null,
|
|
11381
|
+
active: null,
|
|
11382
|
+
hovered: null,
|
|
11383
|
+
sizes: {},
|
|
11384
|
+
frames: {},
|
|
11385
|
+
live: [],
|
|
11386
|
+
panning: false,
|
|
11387
|
+
zoomTiny: false,
|
|
11388
|
+
frameEls: {},
|
|
11389
|
+
inspecting: false
|
|
11390
|
+
});
|
|
11381
11391
|
}
|
|
11382
|
-
function
|
|
11383
|
-
|
|
11392
|
+
function setSize(store, key, size) {
|
|
11393
|
+
store.set((state) => {
|
|
11394
|
+
const prev = state.sizes[key];
|
|
11395
|
+
if (prev && Math.abs(prev.h - size.h) < 1 && prev.w === size.w) return state;
|
|
11396
|
+
return {
|
|
11397
|
+
...state,
|
|
11398
|
+
sizes: {
|
|
11399
|
+
...state.sizes,
|
|
11400
|
+
[key]: size
|
|
11401
|
+
}
|
|
11402
|
+
};
|
|
11403
|
+
});
|
|
11384
11404
|
}
|
|
11385
|
-
|
|
11386
|
-
|
|
11387
|
-
|
|
11388
|
-
|
|
11389
|
-
|
|
11390
|
-
|
|
11391
|
-
return
|
|
11392
|
-
|
|
11393
|
-
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
|
|
11397
|
-
|
|
11398
|
-
|
|
11405
|
+
function setFrame(store, id, patch) {
|
|
11406
|
+
store.set((state) => {
|
|
11407
|
+
const prev = state.frames[id] ?? {
|
|
11408
|
+
ready: false,
|
|
11409
|
+
errors: []
|
|
11410
|
+
};
|
|
11411
|
+
return {
|
|
11412
|
+
...state,
|
|
11413
|
+
frames: {
|
|
11414
|
+
...state.frames,
|
|
11415
|
+
[id]: {
|
|
11416
|
+
...prev,
|
|
11417
|
+
...patch
|
|
11418
|
+
}
|
|
11419
|
+
}
|
|
11420
|
+
};
|
|
11421
|
+
});
|
|
11399
11422
|
}
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11423
|
+
function setFrameEl(store, id, el) {
|
|
11424
|
+
store.set((state) => state.frameEls[id] === el ? state : {
|
|
11425
|
+
...state,
|
|
11426
|
+
frameEls: {
|
|
11427
|
+
...state.frameEls,
|
|
11428
|
+
[id]: el
|
|
11429
|
+
}
|
|
11406
11430
|
});
|
|
11407
11431
|
}
|
|
11408
11432
|
//#endregion
|
|
@@ -11444,7 +11468,7 @@ var FrameItem = memo(function FrameItem({ placed, mode, thumb, theme, store, eve
|
|
|
11444
11468
|
const src = item.kind === "screen" ? frameSrc(item.url, openTheme, capture ? { capture: "1" } : void 0) : item.url;
|
|
11445
11469
|
const ready = !!status?.ready;
|
|
11446
11470
|
const off = mode === "off";
|
|
11447
|
-
const mounted = !off && !(item.kind === "screen" && item.missing);
|
|
11471
|
+
const mounted = !off && !(item.kind === "screen" && item.missing) && !!item.url;
|
|
11448
11472
|
useEffect(() => {
|
|
11449
11473
|
if (off) setFrame(store, item.id, { ready: false });
|
|
11450
11474
|
}, [
|