@squinch/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +41 -0
- package/dist/api.d.ts +91 -0
- package/dist/api.js +232 -0
- package/dist/browser.d.ts +3 -0
- package/dist/browser.js +9 -0
- package/dist/diff/diff.d.ts +30 -0
- package/dist/diff/diff.js +365 -0
- package/dist/fonts.generated.d.ts +1 -0
- package/dist/fonts.generated.js +6 -0
- package/dist/grammar/parser.js +22 -0
- package/dist/grammar/parser.terms.js +115 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/layout/layout.d.ts +197 -0
- package/dist/layout/layout.js +1721 -0
- package/dist/metrics.d.ts +20 -0
- package/dist/metrics.generated.d.ts +4 -0
- package/dist/metrics.generated.js +4 -0
- package/dist/metrics.js +57 -0
- package/dist/model/build.d.ts +8 -0
- package/dist/model/build.js +1343 -0
- package/dist/model/packs.d.ts +13 -0
- package/dist/model/packs.js +29 -0
- package/dist/model/source.d.ts +10 -0
- package/dist/model/source.js +28 -0
- package/dist/model/suggest.d.ts +2 -0
- package/dist/model/suggest.js +24 -0
- package/dist/model/types.d.ts +226 -0
- package/dist/model/types.js +24 -0
- package/dist/packs/node-fs.d.ts +1 -0
- package/dist/packs/node-fs.js +37 -0
- package/dist/packs/registry.d.ts +61 -0
- package/dist/packs/registry.js +122 -0
- package/dist/packs/sanitize.d.ts +12 -0
- package/dist/packs/sanitize.js +127 -0
- package/dist/packs/sysGlyphs.d.ts +2 -0
- package/dist/packs/sysGlyphs.js +21 -0
- package/dist/render/adaptive.d.ts +13 -0
- package/dist/render/adaptive.js +112 -0
- package/dist/render/html/runtime.d.ts +1 -0
- package/dist/render/html/runtime.generated.d.ts +1 -0
- package/dist/render/html/runtime.generated.js +6 -0
- package/dist/render/html/runtime.js +362 -0
- package/dist/render/html.d.ts +39 -0
- package/dist/render/html.js +235 -0
- package/dist/render/svg.d.ts +75 -0
- package/dist/render/svg.js +1403 -0
- package/dist/render/validate.d.ts +4 -0
- package/dist/render/validate.js +9 -0
- package/dist/themes/index.d.ts +84 -0
- package/dist/themes/index.js +90 -0
- package/dist/view/dive.d.ts +55 -0
- package/dist/view/dive.js +57 -0
- package/dist/view/navigate.d.ts +38 -0
- package/dist/view/navigate.js +81 -0
- package/dist/view/resolve.d.ts +92 -0
- package/dist/view/resolve.js +591 -0
- package/fonts/inter-400.ttf +0 -0
- package/fonts/inter-500.ttf +0 -0
- package/fonts/inter-600.ttf +0 -0
- package/fonts/mono-400.ttf +0 -0
- package/metrics.json +510 -0
- package/package.json +89 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Pack SVGs are third-party content that ends up inside the SPA, VSCode webviews
|
|
2
|
+
// and committed files — so every asset is sanitized at load with an allowlist.
|
|
3
|
+
// Anything not explicitly permitted is dropped.
|
|
4
|
+
import { XMLParser, XMLBuilder } from "fast-xml-parser";
|
|
5
|
+
const ELEMENTS = new Set([
|
|
6
|
+
"svg", "g", "defs", "symbol", "use", "title", "desc",
|
|
7
|
+
"path", "rect", "circle", "ellipse", "line", "polyline", "polygon",
|
|
8
|
+
"linearGradient", "radialGradient", "stop", "clipPath", "mask", "pattern",
|
|
9
|
+
"text", "tspan",
|
|
10
|
+
]);
|
|
11
|
+
const ATTRS = new Set([
|
|
12
|
+
"d", "fill", "stroke", "opacity", "transform", "viewBox",
|
|
13
|
+
"x", "y", "width", "height", "cx", "cy", "r", "rx", "ry",
|
|
14
|
+
"x1", "y1", "x2", "y2", "points", "offset",
|
|
15
|
+
"fill-rule", "clip-rule", "fill-opacity", "stroke-opacity",
|
|
16
|
+
"stroke-width", "stroke-linecap", "stroke-linejoin", "stroke-dasharray",
|
|
17
|
+
"stop-color", "stop-opacity", "gradientUnits", "gradientTransform",
|
|
18
|
+
"spreadMethod", "clipPathUnits", "maskUnits", "patternUnits",
|
|
19
|
+
"id", "clip-path", "mask", "filter", "font-size", "font-family",
|
|
20
|
+
"text-anchor", "dominant-baseline",
|
|
21
|
+
]);
|
|
22
|
+
/** Attribute values that reference document ids as `url(#foo)`. */
|
|
23
|
+
const REF_ATTRS = new Set(["clip-path", "mask", "filter", "fill", "stroke"]);
|
|
24
|
+
/** Attributes whose value is a bare `#id` fragment rather than a colour.
|
|
25
|
+
*
|
|
26
|
+
* `href`/`xlink:href` are the only SVG attributes shaped that way, and ATTRS
|
|
27
|
+
* drops both today — so in practice nothing here fires. It stays an explicit
|
|
28
|
+
* allowlist because the alternative (rewrite any value starting with `#`,
|
|
29
|
+
* minus a denylist of known colour attributes) is what shipped, and it was
|
|
30
|
+
* wrong: `stop-color="#5ea0ef"` is a hex colour, not a reference to an element
|
|
31
|
+
* named `5ea0ef`. Namespacing it produced `#pack-5ea0ef`, an invalid colour
|
|
32
|
+
* that paints black — which is every gradient in a pack of 597 gradient
|
|
33
|
+
* icons, rendered as black blobs. A denylist can only ever be as complete as
|
|
34
|
+
* the last pack to expose it. */
|
|
35
|
+
const BARE_REF_ATTRS = new Set(["href", "xlink:href"]);
|
|
36
|
+
const parser = new XMLParser({
|
|
37
|
+
ignoreAttributes: false,
|
|
38
|
+
attributeNamePrefix: "@",
|
|
39
|
+
preserveOrder: true,
|
|
40
|
+
allowBooleanAttributes: true,
|
|
41
|
+
trimValues: false,
|
|
42
|
+
});
|
|
43
|
+
const builder = new XMLBuilder({
|
|
44
|
+
ignoreAttributes: false,
|
|
45
|
+
attributeNamePrefix: "@",
|
|
46
|
+
preserveOrder: true,
|
|
47
|
+
suppressEmptyNode: true,
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Strip everything not on the allowlist and namespace internal ids so several
|
|
51
|
+
* icons can coexist in one document. Does not alter geometry or colour — the
|
|
52
|
+
* asset itself must stay byte-faithful (CC-BY-ND).
|
|
53
|
+
*/
|
|
54
|
+
export function sanitizeIcon(svg, idPrefix) {
|
|
55
|
+
const tree = parser.parse(svg);
|
|
56
|
+
const root = findElement(tree, "svg");
|
|
57
|
+
if (!root)
|
|
58
|
+
throw new Error("pack asset has no <svg> root");
|
|
59
|
+
const viewBox = String(attrOf(root, "viewBox") ?? "0 0 80 80");
|
|
60
|
+
const clean = (nodes) => {
|
|
61
|
+
const out = [];
|
|
62
|
+
for (const node of nodes) {
|
|
63
|
+
const tag = Object.keys(node).find((k) => k !== ":@" && k !== "#text");
|
|
64
|
+
if (tag === undefined) {
|
|
65
|
+
if (typeof node["#text"] === "string" && node["#text"].trim())
|
|
66
|
+
out.push(node);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (!ELEMENTS.has(tag))
|
|
70
|
+
continue; // drops script, foreignObject, image, …
|
|
71
|
+
const attrs = node[":@"] ?? {};
|
|
72
|
+
const kept = {};
|
|
73
|
+
for (const [rawName, value] of Object.entries(attrs)) {
|
|
74
|
+
const name = rawName.replace(/^@/, "");
|
|
75
|
+
if (!ATTRS.has(name))
|
|
76
|
+
continue; // drops on*, xlink:href, style, class, …
|
|
77
|
+
let v = String(value);
|
|
78
|
+
if (name === "id")
|
|
79
|
+
v = `${idPrefix}-${v}`;
|
|
80
|
+
else if (REF_ATTRS.has(name) && v.includes("url(#"))
|
|
81
|
+
v = v.replace(/url\(#([^)]+)\)/g, (_m, id) => `url(#${idPrefix}-${id})`);
|
|
82
|
+
else if (BARE_REF_ATTRS.has(name) && v.startsWith("#"))
|
|
83
|
+
v = `#${idPrefix}-${v.slice(1)}`;
|
|
84
|
+
kept[`@${name}`] = v;
|
|
85
|
+
}
|
|
86
|
+
const children = node[tag];
|
|
87
|
+
out.push({ [tag]: Array.isArray(children) ? clean(children) : children, ":@": kept });
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
};
|
|
91
|
+
// The root <svg>'s presentation attributes are *inherited* by everything
|
|
92
|
+
// inside it, and the body gets lifted out of that root into a <symbol> — so
|
|
93
|
+
// dropping them silently restyles the artwork. Stroke-only sets are where it
|
|
94
|
+
// shows: Lucide puts `fill="none" stroke="currentColor" stroke-width="2"` on
|
|
95
|
+
// the root and nothing on the paths, so without this the shapes fall back to
|
|
96
|
+
// the SVG defaults — filled black, unstroked — and every icon renders as a
|
|
97
|
+
// solid blob. Hoist them onto a wrapping <g> so inheritance survives the move.
|
|
98
|
+
//
|
|
99
|
+
// Only inheritable paint/stroke/text properties: geometry (`width`, `x`,
|
|
100
|
+
// `viewBox`) describes the root viewport, not its children, and carrying it
|
|
101
|
+
// down would move the artwork.
|
|
102
|
+
const INHERITED = [
|
|
103
|
+
"fill", "stroke", "stroke-width", "stroke-linecap", "stroke-linejoin",
|
|
104
|
+
"stroke-dasharray", "stroke-opacity", "fill-opacity", "fill-rule",
|
|
105
|
+
"clip-rule", "opacity", "font-size", "font-family", "text-anchor",
|
|
106
|
+
"dominant-baseline",
|
|
107
|
+
];
|
|
108
|
+
const inherited = {};
|
|
109
|
+
for (const name of INHERITED) {
|
|
110
|
+
const v = attrOf(root, name);
|
|
111
|
+
if (v !== undefined && v !== null)
|
|
112
|
+
inherited[`@${name}`] = String(v);
|
|
113
|
+
}
|
|
114
|
+
const children = clean(root.svg);
|
|
115
|
+
const body = builder.build(Object.keys(inherited).length ? [{ g: children, ":@": inherited }] : children);
|
|
116
|
+
return { body, viewBox };
|
|
117
|
+
}
|
|
118
|
+
function findElement(nodes, tag) {
|
|
119
|
+
for (const node of nodes) {
|
|
120
|
+
if (node[tag])
|
|
121
|
+
return node;
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
function attrOf(node, name) {
|
|
126
|
+
return node[":@"]?.[`@${name}`];
|
|
127
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// First-party glyph artwork for the `builtin` pseudo-pack: `person` and `box`,
|
|
2
|
+
// drawn as 24×24 monoline strokes with round caps/joins. Both paint with
|
|
3
|
+
// currentColor so themes tint them natively — muted on card badges, plate-text
|
|
4
|
+
// white on icon plates, ink in sketch.
|
|
5
|
+
//
|
|
6
|
+
// These two stay compiled into core because they are language sugar, not an icon
|
|
7
|
+
// choice: `person x "…"` and `= box` desugar to them, so they must resolve with
|
|
8
|
+
// nothing installed. The far larger `sys/*` set moved out to @squinch/pack-sys
|
|
9
|
+
// (Lucide, ISC) — 142 icons is more than belongs in an isomorphic bundle, and
|
|
10
|
+
// vendoring gives one coherent hand instead of one person's.
|
|
11
|
+
const S = `fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"`;
|
|
12
|
+
const F = `fill="currentColor" stroke="none"`;
|
|
13
|
+
const g = (inner) => `<g ${S}>${inner}</g>`;
|
|
14
|
+
export const SYS_GLYPH_ART = {
|
|
15
|
+
builtin: {
|
|
16
|
+
person: g(`<circle cx="12" cy="8" r="3.75"/><path d="M5 20c0-4.1 3.1-6.5 7-6.5s7 2.4 7 6.5"/>`),
|
|
17
|
+
box: g(`<path d="M12 3.5 20 7.75v8.5L12 20.5 4 16.25v-8.5Z"/>` +
|
|
18
|
+
`<path d="M4 7.75 12 12l8-4.25"/><path d="M12 12v8.5"/>`),
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
export const SYS_GLYPH_VIEWBOX = "0 0 24 24";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class AdaptivePairError extends Error {
|
|
2
|
+
}
|
|
3
|
+
/**
|
|
4
|
+
* Fold a light and a dark render of the same diagram into one file.
|
|
5
|
+
*
|
|
6
|
+
* Both must be the same drawing — same geometry, same text, same icons —
|
|
7
|
+
* differing only in colour. That holds for themes sharing a font (light/dark,
|
|
8
|
+
* sketch/sketch-dark) because nothing in the render path branches on the theme
|
|
9
|
+
* beyond its palette and its sketch settings. It does *not* hold across fonts,
|
|
10
|
+
* since type metrics drive layout, so a mismatched pair is rejected rather
|
|
11
|
+
* than quietly emitting a file whose dark half is misaligned.
|
|
12
|
+
*/
|
|
13
|
+
export declare function mergeAdaptive(light: string, dark: string): string;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// One SVG that carries both palettes and picks one with `prefers-color-scheme`.
|
|
2
|
+
//
|
|
3
|
+
// Why merge two renders instead of emitting CSS variables from the emitters:
|
|
4
|
+
// the artwork in an icon pack is verbatim third-party SVG (AWS ships CC-BY-ND),
|
|
5
|
+
// and it shares hex values with the theme — `#FFFFFF` appears in both a light
|
|
6
|
+
// card surface and inside the icons, unchanged between themes. A search and
|
|
7
|
+
// replace over the colour literals would silently recolour someone else's
|
|
8
|
+
// trademark. Rendering the pair and walking their attributes in lockstep tells
|
|
9
|
+
// us *positionally* which values are theme-driven: the ones that moved.
|
|
10
|
+
//
|
|
11
|
+
// The dark palette rides in a `<style>` media query rather than replacing the
|
|
12
|
+
// attributes, so the file degrades to exactly today's output. A renderer with
|
|
13
|
+
// no CSS support at all — resvg, which is what PNG export uses — still reads
|
|
14
|
+
// the presentation attributes and draws the light theme correctly. Nothing
|
|
15
|
+
// executes: this is a stylesheet, not script, so the no-JS contract holds.
|
|
16
|
+
const ATTR = /([a-zA-Z_][\w:.-]*)="([^"]*)"/g;
|
|
17
|
+
const TAG = /<[a-zA-Z]/g;
|
|
18
|
+
function attrs(svg) {
|
|
19
|
+
// Tag starts, so each attribute can be attributed to its element. Both
|
|
20
|
+
// strings come straight from our own emitter, which never puts a `<` inside
|
|
21
|
+
// an attribute value, so a scan is enough — no parser needed.
|
|
22
|
+
const tags = [];
|
|
23
|
+
for (const m of svg.matchAll(TAG))
|
|
24
|
+
tags.push(m.index);
|
|
25
|
+
const out = [];
|
|
26
|
+
let ti = 0;
|
|
27
|
+
for (const m of svg.matchAll(ATTR)) {
|
|
28
|
+
const at = m.index + m[1].length + 2;
|
|
29
|
+
while (ti + 1 < tags.length && tags[ti + 1] < m.index)
|
|
30
|
+
ti++;
|
|
31
|
+
out.push({ name: m[1], value: m[2], at, tag: tags[ti] });
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
/** Colour-bearing properties. A differing `d` or `x` would mean the geometry
|
|
36
|
+
* moved, which is a bug in the pairing rather than something to style. */
|
|
37
|
+
const COLOUR = new Set(["fill", "stroke", "stop-color", "color", "flood-color"]);
|
|
38
|
+
export class AdaptivePairError extends Error {
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Fold a light and a dark render of the same diagram into one file.
|
|
42
|
+
*
|
|
43
|
+
* Both must be the same drawing — same geometry, same text, same icons —
|
|
44
|
+
* differing only in colour. That holds for themes sharing a font (light/dark,
|
|
45
|
+
* sketch/sketch-dark) because nothing in the render path branches on the theme
|
|
46
|
+
* beyond its palette and its sketch settings. It does *not* hold across fonts,
|
|
47
|
+
* since type metrics drive layout, so a mismatched pair is rejected rather
|
|
48
|
+
* than quietly emitting a file whose dark half is misaligned.
|
|
49
|
+
*/
|
|
50
|
+
export function mergeAdaptive(light, dark) {
|
|
51
|
+
const la = attrs(light);
|
|
52
|
+
const da = attrs(dark);
|
|
53
|
+
const skeleton = (s) => s.replace(/="[^"]*"/g, '=""');
|
|
54
|
+
if (la.length !== da.length || skeleton(light) !== skeleton(dark))
|
|
55
|
+
throw new AdaptivePairError("the two themes did not draw the same diagram, so they cannot share one file — " +
|
|
56
|
+
"an adaptive pair must differ only in colour (check they use the same font)");
|
|
57
|
+
// Distinct (property, light, dark) triples become one CSS rule each; the
|
|
58
|
+
// class name is the triple's index in sorted order, so the output is a pure
|
|
59
|
+
// function of the input and stays byte-identical across runs.
|
|
60
|
+
const triples = new Map();
|
|
61
|
+
const hits = [];
|
|
62
|
+
for (let i = 0; i < la.length; i++) {
|
|
63
|
+
if (la[i].value === da[i].value)
|
|
64
|
+
continue;
|
|
65
|
+
const { name } = la[i];
|
|
66
|
+
if (!COLOUR.has(name))
|
|
67
|
+
throw new AdaptivePairError(`the two themes disagree on \`${name}\`, which is geometry rather than colour — ` +
|
|
68
|
+
"an adaptive pair must differ only in colour");
|
|
69
|
+
const key = `${name}|${la[i].value}|${da[i].value}`;
|
|
70
|
+
triples.set(key, { prop: name, dark: da[i].value });
|
|
71
|
+
hits.push({ tag: la[i].tag, key });
|
|
72
|
+
}
|
|
73
|
+
if (!triples.size)
|
|
74
|
+
return light; // identical palettes: nothing to adapt
|
|
75
|
+
const order = [...triples.keys()].sort();
|
|
76
|
+
const cls = new Map(order.map((k, i) => [k, `sq-t${i}`]));
|
|
77
|
+
// Group by element: one element can have both a themed fill and a themed
|
|
78
|
+
// stroke, and it needs both classes.
|
|
79
|
+
const byTag = new Map();
|
|
80
|
+
for (const h of hits) {
|
|
81
|
+
const name = cls.get(h.key);
|
|
82
|
+
const list = byTag.get(h.tag) ?? [];
|
|
83
|
+
if (!list.includes(name))
|
|
84
|
+
list.push(name);
|
|
85
|
+
byTag.set(h.tag, list);
|
|
86
|
+
}
|
|
87
|
+
// Rewrite back-to-front so earlier offsets stay valid.
|
|
88
|
+
let out = light;
|
|
89
|
+
for (const [tag, names] of [...byTag].sort((a, b) => b[0] - a[0])) {
|
|
90
|
+
const close = out.indexOf(">", tag);
|
|
91
|
+
const head = out.slice(tag, close);
|
|
92
|
+
// `class="sq-flow"` already exists on animated edges — extend it, never
|
|
93
|
+
// replace it, or the flow animation stops.
|
|
94
|
+
const existing = /\sclass="([^"]*)"/.exec(head);
|
|
95
|
+
if (existing)
|
|
96
|
+
out =
|
|
97
|
+
out.slice(0, tag + existing.index) +
|
|
98
|
+
` class="${existing[1]} ${names.join(" ")}"` +
|
|
99
|
+
out.slice(tag + existing.index + existing[0].length);
|
|
100
|
+
else {
|
|
101
|
+
const nameEnd = tag + 1 + /^[a-zA-Z][\w:.-]*/.exec(out.slice(tag + 1))[0].length;
|
|
102
|
+
out = out.slice(0, nameEnd) + ` class="${names.join(" ")}"` + out.slice(nameEnd);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const rules = order
|
|
106
|
+
.map((k) => `.${cls.get(k)}{${triples.get(k).prop}:${triples.get(k).dark}}`)
|
|
107
|
+
.join("");
|
|
108
|
+
const style = `<style>@media (prefers-color-scheme: dark){${rules}}</style>`;
|
|
109
|
+
// After the opening tag, alongside the font faces.
|
|
110
|
+
const rootEnd = out.indexOf(">") + 1;
|
|
111
|
+
return out.slice(0, rootEnd) + "\n" + style + out.slice(rootEnd);
|
|
112
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const RUNTIME_JS = "\"use strict\";(()=>{var Q={ms:460,ease:\"cubic-bezier(.32,.72,0,1)\"},X={ms:240,ease:\"cubic-bezier(.4,0,.2,1)\"},K=e=>({x:e.x+e.w/2,y:e.y+e.h/2}),Y=(e,n,i)=>Math.min(i,Math.max(n,e)),Z=(e,n)=>Y(Math.min(e.w/n.w,e.h/n.h),1.15,3.2);function U(e){let{view:n,ghostBox:i,liveBox:a,anchor:p,dir:o}=e,{ms:l,ease:d}=p?Q:X;if(!p)return{ms:l,ease:d,gOrigin:\"50% 50%\",lOrigin:\"50% 50%\",gEnd:\"scale(.97)\",lStart:\"scale(1.03)\"};let f=Z(n,p),T=1+(f-1)*.62,E=K(p),h=K(n),y=h.x-E.x,w=h.y-E.y,x=o===\"in\",g=x?E:h,k=x?h:E;return{ms:l,ease:d,gOrigin:`${g.x-i.x}px ${g.y-i.y}px`,lOrigin:`${k.x-a.x}px ${k.y-a.y}px`,gEnd:x?`translate(${y}px, ${w}px) scale(${f})`:`translate(${-y}px, ${-w}px) scale(${1/f})`,lStart:x?`translate(${-y}px, ${-w}px) scale(${1/T})`:`translate(${y}px, ${w}px) scale(${T})`}}function W(e,n){if(n){if(!e)return n.split(\".\")[0];if(!(n===e||!n.startsWith(`${e}.`)))return`${e}.${n.slice(e.length+1).split(\".\")[0]}`}}function ee(e){if(!e)return[];let n=e.split(\".\");return n.map((i,a)=>n.slice(0,a+1).join(\".\"))}function O(e,n,i){return e.find(a=>a.scope===i&&a.name!==n)}function _(e,n,i){let a=e.find(d=>d.name===n)?.scope,p=e.find(d=>d.name===i)?.scope,o=W(a,p);if(o)return{dir:\"in\",anchor:o};let l=W(p,a);return l?{dir:\"out\",anchor:l}:{dir:\"in\"}}function te(e,n){let i=[],a=e.find(o=>!o.scope);if(a&&i.push({label:\"landscape\",view:a.name}),!n)return i;let p=n.split(\".\");for(let[o,l]of ee(n).entries())i.push({label:p[o],view:e.find(d=>d.scope===l)?.name});return i}function S(e,n,i){return[...te(e,i)].reverse().find(a=>a.view&&a.view!==n)?.view}var L=e=>document.querySelector(e);function j(){let e=JSON.parse(L(\"#sq-data\").textContent||\"{}\"),n=L(\"#sq-live\"),i=L(\"#sq-ghost\"),a=L(\"#sq-stage\"),p=()=>matchMedia(\"(prefers-reduced-motion: reduce)\").matches,o=e.entry,l=e.themes[0],d=0,f=!1;if(e.themes.length>1&&matchMedia(\"(prefers-color-scheme: dark)\").matches){let t=e.themes.find(s=>s.includes(\"dark\"));t&&(l=t)}let T=`${e.entry}|${e.themes[0]}`,E=n.firstElementChild?.cloneNode(!0)??null,h=(t,s,c=0)=>{let r=c?`${t}|${s}|${c}`:`${t}|${s}`;if(r===T)return E?.cloneNode(!0)??null;let u=document.querySelector(`template[data-key=\"${CSS.escape(r)}\"]`);return u?u.content.cloneNode(!0):null},y=t=>e.views.find(s=>s.name===t)?.scope,w=t=>{let s=t.getBoundingClientRect(),c=a.getBoundingClientRect();return{x:s.left-c.left,y:s.top-c.top,w:s.width,h:s.height}};function x(){document.title=e.views.find(r=>r.name===o)?.title??document.title;let t=document.querySelector(\"#sq-tabs\");if(t){t.replaceChildren();for(let r of e.views){let u=document.createElement(\"button\");u.type=\"button\",u.className=r.name===o?\"on\":\"\",u.textContent=r.name,u.title=r.title??r.name,u.onclick=()=>g(r.name),t.append(u)}t.querySelector(\".on\")?.scrollIntoView?.({block:\"nearest\",inline:\"nearest\"})}for(let r of n.querySelectorAll(\"[data-path]\")){let u=r.getAttribute(\"data-path\");r.classList.toggle(\"sq-zoom\",!!(u&&O(e.views,o,u)))}let s=document.querySelector(\"#sq-step\"),c=e.flows[o]??0;s&&(s.textContent=f&&c?`${d||1} / ${c}`:\"\")}function g(t,s=!1){if(!t||t===o)return;let c=f&&e.flows[t]?s?e.flows[t]:1:0,r=h(t,l,c);if(!r)return;d=c;let{dir:u,anchor:v}=_(e.views,o,t),C=w(n),H=n.firstElementChild;if(p()||!H){n.replaceChildren(r),o=t,x();return}i.replaceChildren(H.cloneNode(!0)),i.style.cssText=`position:absolute;left:${C.x}px;top:${C.y}px;width:${C.w}px;height:${C.h}px;z-index:1;pointer-events:none`,n.replaceChildren(r),o=t,x();let G=w(n),P=v?(u===\"in\"?i:n).querySelector(`[data-path=\"${CSS.escape(v)}\"]`):null,m=U({view:{x:0,y:0,w:a.clientWidth,h:a.clientHeight},ghostBox:C,liveBox:G,anchor:P?w(P):void 0,dir:u}),b=i.style,$=n.style;b.transition=\"none\",b.transformOrigin=m.gOrigin,b.transform=\"none\",b.opacity=\"1\",$.transition=\"none\",$.transformOrigin=m.lOrigin,$.transform=m.lStart,$.opacity=\"0\",n.offsetHeight,b.transition=`transform ${m.ms}ms ${m.ease}, opacity ${Math.round(m.ms*.55)}ms ${m.ease}`,$.transition=`transform ${m.ms}ms ${m.ease}, opacity ${Math.round(m.ms*.6)}ms ${m.ease} ${Math.round(m.ms*.25)}ms`,b.transform=m.gEnd,b.opacity=\"0\",$.transform=\"none\",$.opacity=\"1\";let I=!1,q=0,R=()=>{I||(I=!0,cancelAnimationFrame(q),i.replaceChildren(),i.removeAttribute(\"style\"),n.removeAttribute(\"style\"))},z=()=>{getComputedStyle(n).transform===\"none\"?R():q=requestAnimationFrame(z)};q=requestAnimationFrame(z),setTimeout(R,m.ms+1e3)}function k(t){let s=t===l?null:h(o,t,d);s&&(l=t,document.documentElement.dataset.theme=t,n.replaceChildren(s))}function M(t){let s=e.flows[o]??0,c=d+t;if(s&&c>=1&&c<=s){let v=h(o,l,c);v&&(d=c,n.replaceChildren(v),x());return}let r=e.views.findIndex(v=>v.name===o),u=e.views[r+t];u&&g(u.name,t<0)}n.addEventListener(\"click\",t=>{let c=t.target.closest?.(\"[data-path]\")?.getAttribute(\"data-path\");if(c){let r=O(e.views,o,c);if(r)return g(r.name)}if(!c){let r=S(e.views,o,y(o));r&&g(r)}});let A=document.querySelector(\"#sq-theme\");A&&(A.onclick=()=>k(e.themes[(e.themes.indexOf(l)+1)%e.themes.length]));function B(t){if(t!==f){if(f=t,document.body.classList.toggle(\"presenting\",t),t){if(document.documentElement.requestFullscreen?.().catch(()=>{}),(e.flows[o]??0)&&!d){let c=h(o,l,1);c&&(d=1,n.replaceChildren(c))}}else if(document.fullscreenElement&&document.exitFullscreen?.().catch(()=>{}),d){let s=h(o,l,0);s&&(d=0,n.replaceChildren(s))}x()}}let N,V=()=>{document.body.classList.remove(\"idle\"),clearTimeout(N),f&&(N=setTimeout(()=>document.body.classList.add(\"idle\"),3500))};addEventListener(\"mousemove\",V),addEventListener(\"keydown\",V);let J=()=>k(e.themes[(e.themes.indexOf(l)+1)%e.themes.length]);addEventListener(\"keydown\",t=>{if(!(t.metaKey||t.ctrlKey||t.altKey))switch(t.key){case\"ArrowRight\":case\"PageDown\":case\" \":case\"Enter\":t.preventDefault(),M(1);break;case\"ArrowLeft\":case\"PageUp\":t.preventDefault(),M(-1);break;case\"ArrowUp\":case\"Backspace\":{let s=S(e.views,o,y(o));s&&(t.preventDefault(),g(s));break}case\"Home\":t.preventDefault(),g(e.views[0].name);break;case\"End\":t.preventDefault(),g(e.views[e.views.length-1].name,!0);break;case\"Escape\":f&&(t.preventDefault(),B(!1));break;case\"p\":case\"P\":B(!f);break;case\"f\":case\"F\":document.fullscreenElement?document.exitFullscreen?.().catch(()=>{}):document.documentElement.requestFullscreen?.().catch(()=>{});break;case\"t\":case\"T\":e.themes.length>1&&J();break}}),addEventListener(\"fullscreenchange\",()=>{!document.fullscreenElement&&f&&B(!1)});let D=document.querySelector(\"#sq-present\");D&&(D.onclick=()=>B(!f));let F=()=>{let t=decodeURIComponent(location.hash.slice(1));t&&t!==o&&e.views.some(s=>s.name===t)&&g(t)};if(addEventListener(\"hashchange\",F),document.documentElement.dataset.theme=l,l!==e.themes[0]){let t=h(o,l);t&&n.replaceChildren(t)}x(),F()}document.readyState===\"loading\"?addEventListener(\"DOMContentLoaded\",j):j();})();";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// GENERATED by scripts/gen-html-runtime.ts from src/render/html/runtime.ts —
|
|
2
|
+
// do not edit. Committed so that rendering never runs a bundler and the
|
|
3
|
+
// output cannot drift with an esbuild upgrade; CI re-runs the generator and
|
|
4
|
+
// diffs. It bundles view/dive.ts and view/navigate.ts, which is what makes
|
|
5
|
+
// "the export moves exactly like the playground" true by construction.
|
|
6
|
+
export const RUNTIME_JS = "\"use strict\";(()=>{var Q={ms:460,ease:\"cubic-bezier(.32,.72,0,1)\"},X={ms:240,ease:\"cubic-bezier(.4,0,.2,1)\"},K=e=>({x:e.x+e.w/2,y:e.y+e.h/2}),Y=(e,n,i)=>Math.min(i,Math.max(n,e)),Z=(e,n)=>Y(Math.min(e.w/n.w,e.h/n.h),1.15,3.2);function U(e){let{view:n,ghostBox:i,liveBox:a,anchor:p,dir:o}=e,{ms:l,ease:d}=p?Q:X;if(!p)return{ms:l,ease:d,gOrigin:\"50% 50%\",lOrigin:\"50% 50%\",gEnd:\"scale(.97)\",lStart:\"scale(1.03)\"};let f=Z(n,p),T=1+(f-1)*.62,E=K(p),h=K(n),y=h.x-E.x,w=h.y-E.y,x=o===\"in\",g=x?E:h,k=x?h:E;return{ms:l,ease:d,gOrigin:`${g.x-i.x}px ${g.y-i.y}px`,lOrigin:`${k.x-a.x}px ${k.y-a.y}px`,gEnd:x?`translate(${y}px, ${w}px) scale(${f})`:`translate(${-y}px, ${-w}px) scale(${1/f})`,lStart:x?`translate(${-y}px, ${-w}px) scale(${1/T})`:`translate(${y}px, ${w}px) scale(${T})`}}function W(e,n){if(n){if(!e)return n.split(\".\")[0];if(!(n===e||!n.startsWith(`${e}.`)))return`${e}.${n.slice(e.length+1).split(\".\")[0]}`}}function ee(e){if(!e)return[];let n=e.split(\".\");return n.map((i,a)=>n.slice(0,a+1).join(\".\"))}function O(e,n,i){return e.find(a=>a.scope===i&&a.name!==n)}function _(e,n,i){let a=e.find(d=>d.name===n)?.scope,p=e.find(d=>d.name===i)?.scope,o=W(a,p);if(o)return{dir:\"in\",anchor:o};let l=W(p,a);return l?{dir:\"out\",anchor:l}:{dir:\"in\"}}function te(e,n){let i=[],a=e.find(o=>!o.scope);if(a&&i.push({label:\"landscape\",view:a.name}),!n)return i;let p=n.split(\".\");for(let[o,l]of ee(n).entries())i.push({label:p[o],view:e.find(d=>d.scope===l)?.name});return i}function S(e,n,i){return[...te(e,i)].reverse().find(a=>a.view&&a.view!==n)?.view}var L=e=>document.querySelector(e);function j(){let e=JSON.parse(L(\"#sq-data\").textContent||\"{}\"),n=L(\"#sq-live\"),i=L(\"#sq-ghost\"),a=L(\"#sq-stage\"),p=()=>matchMedia(\"(prefers-reduced-motion: reduce)\").matches,o=e.entry,l=e.themes[0],d=0,f=!1;if(e.themes.length>1&&matchMedia(\"(prefers-color-scheme: dark)\").matches){let t=e.themes.find(s=>s.includes(\"dark\"));t&&(l=t)}let T=`${e.entry}|${e.themes[0]}`,E=n.firstElementChild?.cloneNode(!0)??null,h=(t,s,c=0)=>{let r=c?`${t}|${s}|${c}`:`${t}|${s}`;if(r===T)return E?.cloneNode(!0)??null;let u=document.querySelector(`template[data-key=\"${CSS.escape(r)}\"]`);return u?u.content.cloneNode(!0):null},y=t=>e.views.find(s=>s.name===t)?.scope,w=t=>{let s=t.getBoundingClientRect(),c=a.getBoundingClientRect();return{x:s.left-c.left,y:s.top-c.top,w:s.width,h:s.height}};function x(){document.title=e.views.find(r=>r.name===o)?.title??document.title;let t=document.querySelector(\"#sq-tabs\");if(t){t.replaceChildren();for(let r of e.views){let u=document.createElement(\"button\");u.type=\"button\",u.className=r.name===o?\"on\":\"\",u.textContent=r.name,u.title=r.title??r.name,u.onclick=()=>g(r.name),t.append(u)}t.querySelector(\".on\")?.scrollIntoView?.({block:\"nearest\",inline:\"nearest\"})}for(let r of n.querySelectorAll(\"[data-path]\")){let u=r.getAttribute(\"data-path\");r.classList.toggle(\"sq-zoom\",!!(u&&O(e.views,o,u)))}let s=document.querySelector(\"#sq-step\"),c=e.flows[o]??0;s&&(s.textContent=f&&c?`${d||1} / ${c}`:\"\")}function g(t,s=!1){if(!t||t===o)return;let c=f&&e.flows[t]?s?e.flows[t]:1:0,r=h(t,l,c);if(!r)return;d=c;let{dir:u,anchor:v}=_(e.views,o,t),C=w(n),H=n.firstElementChild;if(p()||!H){n.replaceChildren(r),o=t,x();return}i.replaceChildren(H.cloneNode(!0)),i.style.cssText=`position:absolute;left:${C.x}px;top:${C.y}px;width:${C.w}px;height:${C.h}px;z-index:1;pointer-events:none`,n.replaceChildren(r),o=t,x();let G=w(n),P=v?(u===\"in\"?i:n).querySelector(`[data-path=\"${CSS.escape(v)}\"]`):null,m=U({view:{x:0,y:0,w:a.clientWidth,h:a.clientHeight},ghostBox:C,liveBox:G,anchor:P?w(P):void 0,dir:u}),b=i.style,$=n.style;b.transition=\"none\",b.transformOrigin=m.gOrigin,b.transform=\"none\",b.opacity=\"1\",$.transition=\"none\",$.transformOrigin=m.lOrigin,$.transform=m.lStart,$.opacity=\"0\",n.offsetHeight,b.transition=`transform ${m.ms}ms ${m.ease}, opacity ${Math.round(m.ms*.55)}ms ${m.ease}`,$.transition=`transform ${m.ms}ms ${m.ease}, opacity ${Math.round(m.ms*.6)}ms ${m.ease} ${Math.round(m.ms*.25)}ms`,b.transform=m.gEnd,b.opacity=\"0\",$.transform=\"none\",$.opacity=\"1\";let I=!1,q=0,R=()=>{I||(I=!0,cancelAnimationFrame(q),i.replaceChildren(),i.removeAttribute(\"style\"),n.removeAttribute(\"style\"))},z=()=>{getComputedStyle(n).transform===\"none\"?R():q=requestAnimationFrame(z)};q=requestAnimationFrame(z),setTimeout(R,m.ms+1e3)}function k(t){let s=t===l?null:h(o,t,d);s&&(l=t,document.documentElement.dataset.theme=t,n.replaceChildren(s))}function M(t){let s=e.flows[o]??0,c=d+t;if(s&&c>=1&&c<=s){let v=h(o,l,c);v&&(d=c,n.replaceChildren(v),x());return}let r=e.views.findIndex(v=>v.name===o),u=e.views[r+t];u&&g(u.name,t<0)}n.addEventListener(\"click\",t=>{let c=t.target.closest?.(\"[data-path]\")?.getAttribute(\"data-path\");if(c){let r=O(e.views,o,c);if(r)return g(r.name)}if(!c){let r=S(e.views,o,y(o));r&&g(r)}});let A=document.querySelector(\"#sq-theme\");A&&(A.onclick=()=>k(e.themes[(e.themes.indexOf(l)+1)%e.themes.length]));function B(t){if(t!==f){if(f=t,document.body.classList.toggle(\"presenting\",t),t){if(document.documentElement.requestFullscreen?.().catch(()=>{}),(e.flows[o]??0)&&!d){let c=h(o,l,1);c&&(d=1,n.replaceChildren(c))}}else if(document.fullscreenElement&&document.exitFullscreen?.().catch(()=>{}),d){let s=h(o,l,0);s&&(d=0,n.replaceChildren(s))}x()}}let N,V=()=>{document.body.classList.remove(\"idle\"),clearTimeout(N),f&&(N=setTimeout(()=>document.body.classList.add(\"idle\"),3500))};addEventListener(\"mousemove\",V),addEventListener(\"keydown\",V);let J=()=>k(e.themes[(e.themes.indexOf(l)+1)%e.themes.length]);addEventListener(\"keydown\",t=>{if(!(t.metaKey||t.ctrlKey||t.altKey))switch(t.key){case\"ArrowRight\":case\"PageDown\":case\" \":case\"Enter\":t.preventDefault(),M(1);break;case\"ArrowLeft\":case\"PageUp\":t.preventDefault(),M(-1);break;case\"ArrowUp\":case\"Backspace\":{let s=S(e.views,o,y(o));s&&(t.preventDefault(),g(s));break}case\"Home\":t.preventDefault(),g(e.views[0].name);break;case\"End\":t.preventDefault(),g(e.views[e.views.length-1].name,!0);break;case\"Escape\":f&&(t.preventDefault(),B(!1));break;case\"p\":case\"P\":B(!f);break;case\"f\":case\"F\":document.fullscreenElement?document.exitFullscreen?.().catch(()=>{}):document.documentElement.requestFullscreen?.().catch(()=>{});break;case\"t\":case\"T\":e.themes.length>1&&J();break}}),addEventListener(\"fullscreenchange\",()=>{!document.fullscreenElement&&f&&B(!1)});let D=document.querySelector(\"#sq-present\");D&&(D.onclick=()=>B(!f));let F=()=>{let t=decodeURIComponent(location.hash.slice(1));t&&t!==o&&e.views.some(s=>s.name===t)&&g(t)};if(addEventListener(\"hashchange\",F),document.documentElement.dataset.theme=l,l!==e.themes[0]){let t=h(o,l);t&&n.replaceChildren(t)}x(),F()}document.readyState===\"loading\"?addEventListener(\"DOMContentLoaded\",j):j();})();";
|