@lumencast/runtime 0.4.0 → 0.6.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/README.md +57 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/animate/frame-coalescer.d.ts +13 -0
- package/dist/animate/frame-coalescer.d.ts.map +1 -0
- package/dist/animate/frame-coalescer.js +46 -0
- package/dist/animate/frame-coalescer.js.map +1 -0
- package/dist/animate/keyframes.d.ts +1 -1
- package/dist/animate/keyframes.d.ts.map +1 -1
- package/dist/animate/keyframes.js +20 -6
- package/dist/animate/keyframes.js.map +1 -1
- package/dist/animate/transitions.d.ts +4 -1
- package/dist/animate/transitions.d.ts.map +1 -1
- package/dist/animate/transitions.js +30 -3
- package/dist/animate/transitions.js.map +1 -1
- package/dist/{broadcast-DzZ8TVGZ.js → broadcast-DO7jEkix.js} +3 -3
- package/dist/{broadcast-DzZ8TVGZ.js.map → broadcast-DO7jEkix.js.map} +1 -1
- package/dist/{control-gbDGvdR0.js → control-BSfl4_cO.js} +4 -4
- package/dist/{control-gbDGvdR0.js.map → control-BSfl4_cO.js.map} +1 -1
- package/dist/{index-oteiocFe.js → index-Crkij3C4.js} +352 -179
- package/dist/index-Crkij3C4.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.html +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/lumencast.js +9 -2
- package/dist/mount.d.ts.map +1 -1
- package/dist/mount.js +16 -1
- package/dist/mount.js.map +1 -1
- package/dist/render/bind-animate.d.ts +40 -0
- package/dist/render/bind-animate.d.ts.map +1 -0
- package/dist/render/bind-animate.js +329 -0
- package/dist/render/bind-animate.js.map +1 -0
- package/dist/render/bundle.d.ts +56 -6
- package/dist/render/bundle.d.ts.map +1 -1
- package/dist/render/bundle.js +86 -5
- package/dist/render/bundle.js.map +1 -1
- package/dist/render/color-interp.d.ts +18 -0
- package/dist/render/color-interp.d.ts.map +1 -0
- package/dist/render/color-interp.js +303 -0
- package/dist/render/color-interp.js.map +1 -0
- package/dist/render/css-color.d.ts +16 -0
- package/dist/render/css-color.d.ts.map +1 -0
- package/dist/render/css-color.js +130 -0
- package/dist/render/css-color.js.map +1 -0
- package/dist/render/diagnostics.d.ts +26 -0
- package/dist/render/diagnostics.d.ts.map +1 -0
- package/dist/render/diagnostics.js +58 -0
- package/dist/render/diagnostics.js.map +1 -0
- package/dist/render/fill.d.ts +15 -3
- package/dist/render/fill.d.ts.map +1 -1
- package/dist/render/fill.js +81 -14
- package/dist/render/fill.js.map +1 -1
- package/dist/render/filter-clamp.d.ts +35 -0
- package/dist/render/filter-clamp.d.ts.map +1 -0
- package/dist/render/filter-clamp.js +90 -0
- package/dist/render/filter-clamp.js.map +1 -0
- package/dist/render/keyframe-player.d.ts +4 -1
- package/dist/render/keyframe-player.d.ts.map +1 -1
- package/dist/render/keyframe-player.js +2 -2
- package/dist/render/keyframe-player.js.map +1 -1
- package/dist/render/primitives/frame.d.ts +16 -1
- package/dist/render/primitives/frame.d.ts.map +1 -1
- package/dist/render/primitives/frame.js +42 -7
- package/dist/render/primitives/frame.js.map +1 -1
- package/dist/render/primitives/image.d.ts +1 -1
- package/dist/render/primitives/image.d.ts.map +1 -1
- package/dist/render/primitives/image.js +6 -3
- package/dist/render/primitives/image.js.map +1 -1
- package/dist/render/primitives/index.d.ts +3 -0
- package/dist/render/primitives/index.d.ts.map +1 -1
- package/dist/render/primitives/index.js.map +1 -1
- package/dist/render/primitives/instance.d.ts +1 -1
- package/dist/render/primitives/instance.d.ts.map +1 -1
- package/dist/render/primitives/instance.js +10 -13
- package/dist/render/primitives/instance.js.map +1 -1
- package/dist/render/primitives/shape.d.ts +9 -3
- package/dist/render/primitives/shape.d.ts.map +1 -1
- package/dist/render/primitives/shape.js +56 -12
- package/dist/render/primitives/shape.js.map +1 -1
- package/dist/render/primitives/text.d.ts +35 -4
- package/dist/render/primitives/text.d.ts.map +1 -1
- package/dist/render/primitives/text.js +179 -7
- package/dist/render/primitives/text.js.map +1 -1
- package/dist/render/prop-allowlist.d.ts +10 -0
- package/dist/render/prop-allowlist.d.ts.map +1 -0
- package/dist/render/prop-allowlist.js +112 -0
- package/dist/render/prop-allowlist.js.map +1 -0
- package/dist/render/svg-path.d.ts +35 -0
- package/dist/render/svg-path.d.ts.map +1 -0
- package/dist/render/svg-path.js +211 -0
- package/dist/render/svg-path.js.map +1 -0
- package/dist/render/tree.d.ts.map +1 -1
- package/dist/render/tree.js +30 -5
- package/dist/render/tree.js.map +1 -1
- package/dist/{status-pill-Cgdl9FtP.js → status-pill-BT5b-yET.js} +2 -2
- package/dist/{status-pill-Cgdl9FtP.js.map → status-pill-BT5b-yET.js.map} +1 -1
- package/dist/{test-CAnkHA0n.js → test-_hh1JvAd.js} +4 -4
- package/dist/{test-CAnkHA0n.js.map → test-_hh1JvAd.js.map} +1 -1
- package/dist/transport/ws.d.ts +5 -0
- package/dist/transport/ws.d.ts.map +1 -1
- package/dist/transport/ws.js +7 -0
- package/dist/transport/ws.js.map +1 -1
- package/dist/tree-DBj9SJgs.js +1230 -0
- package/dist/tree-DBj9SJgs.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/animate/frame-coalescer.ts +63 -0
- package/src/animate/keyframes.ts +24 -5
- package/src/animate/transitions.ts +33 -3
- package/src/index.ts +24 -0
- package/src/mount.ts +17 -1
- package/src/render/bind-animate.tsx +370 -0
- package/src/render/bundle.ts +124 -11
- package/src/render/color-interp.ts +303 -0
- package/src/render/css-color.ts +145 -0
- package/src/render/diagnostics.ts +75 -0
- package/src/render/fill.tsx +85 -14
- package/src/render/filter-clamp.ts +99 -0
- package/src/render/keyframe-player.tsx +10 -2
- package/src/render/primitives/frame.tsx +47 -7
- package/src/render/primitives/image.tsx +6 -2
- package/src/render/primitives/index.ts +3 -0
- package/src/render/primitives/instance.tsx +14 -15
- package/src/render/primitives/shape.tsx +76 -12
- package/src/render/primitives/text.tsx +224 -7
- package/src/render/prop-allowlist.ts +119 -0
- package/src/render/svg-path.ts +215 -0
- package/src/render/tree.tsx +41 -6
- package/src/transport/ws.ts +8 -0
- package/src/types.ts +27 -0
- package/dist/index-oteiocFe.js.map +0 -1
- package/dist/tree-DVYXwItH.js +0 -512
- package/dist/tree-DVYXwItH.js.map +0 -1
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import type { PrimitiveProps } from "./index";
|
|
2
|
-
/** Rectangle / circle / line. Renders as SVG so stroke + fill
|
|
3
|
-
* predictably across hosts. Opacity animatable.
|
|
2
|
+
/** Rectangle / circle / line / path. Renders as SVG so stroke + fill
|
|
3
|
+
* behave predictably across hosts. Opacity animatable.
|
|
4
4
|
*
|
|
5
5
|
* LSML 1.1 §4.6 + §4.12 add `fills[]` / `strokes[]` arrays as the
|
|
6
6
|
* preferred way to declare multi-layer fills with linear/radial
|
|
7
7
|
* gradients. The legacy single `fill` / `stroke` props remain
|
|
8
8
|
* accepted for 1.0 bundles ; when both are present the array form
|
|
9
9
|
* wins (the spec forbids mixing, but we tolerate to ease migration).
|
|
10
|
+
*
|
|
11
|
+
* Security (ADR 001 §6 RC#10 + RC#11, issue #30) : every colour that
|
|
12
|
+
* reaches an SVG `fill`/`stroke`/`stop-color` attribute goes through
|
|
13
|
+
* the strict `parseCssColor` gate, and every path `d` goes through
|
|
14
|
+
* `validatePathData` — at EVERY render, because props are wire-
|
|
15
|
+
* drivable live via LSDP deltas (`resolveProps`, tree.tsx).
|
|
10
16
|
*/
|
|
11
|
-
export declare function Shape({ resolved, transitionFor, animateInitial }: PrimitiveProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export declare function Shape({ resolved, nodeId, transitionFor, animateInitial }: PrimitiveProps): import("react/jsx-runtime").JSX.Element;
|
|
12
18
|
//# sourceMappingURL=shape.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shape.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/shape.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"shape.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/shape.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAW9C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,EAAE,cAAc,2CAsJxF"}
|
|
@@ -1,32 +1,51 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { motion } from "framer-motion";
|
|
3
3
|
import { toFramer, mountPlay, resolveTransition } from "../../animate/transitions";
|
|
4
|
-
import { parseFills, renderFill } from "../fill";
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { parseFills, renderFill, sanitizeFills } from "../fill";
|
|
5
|
+
import { parseCssColor, warnRejectedColor } from "../css-color";
|
|
6
|
+
import { parseShapePaths } from "../svg-path";
|
|
7
|
+
/** Rectangle / circle / line / path. Renders as SVG so stroke + fill
|
|
8
|
+
* behave predictably across hosts. Opacity animatable.
|
|
7
9
|
*
|
|
8
10
|
* LSML 1.1 §4.6 + §4.12 add `fills[]` / `strokes[]` arrays as the
|
|
9
11
|
* preferred way to declare multi-layer fills with linear/radial
|
|
10
12
|
* gradients. The legacy single `fill` / `stroke` props remain
|
|
11
13
|
* accepted for 1.0 bundles ; when both are present the array form
|
|
12
14
|
* wins (the spec forbids mixing, but we tolerate to ease migration).
|
|
15
|
+
*
|
|
16
|
+
* Security (ADR 001 §6 RC#10 + RC#11, issue #30) : every colour that
|
|
17
|
+
* reaches an SVG `fill`/`stroke`/`stop-color` attribute goes through
|
|
18
|
+
* the strict `parseCssColor` gate, and every path `d` goes through
|
|
19
|
+
* `validatePathData` — at EVERY render, because props are wire-
|
|
20
|
+
* drivable live via LSDP deltas (`resolveProps`, tree.tsx).
|
|
13
21
|
*/
|
|
14
|
-
export function Shape({ resolved, transitionFor, animateInitial }) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
export function Shape({ resolved, nodeId, transitionFor, animateInitial }) {
|
|
23
|
+
// Canonical prop name is `geometry` (LSML §4.6 — what the compiler
|
|
24
|
+
// emits) ; `kind` is kept as a fallback for hand-rolled Solar-lineage
|
|
25
|
+
// RenderNodes that predate the compiler.
|
|
26
|
+
const kind = resolved.geometry ?? resolved.kind ?? "rect";
|
|
27
|
+
const legacyFill = safeColor(resolved.fill, "shape.fill", nodeId) ?? "transparent";
|
|
28
|
+
const legacyStroke = safeColor(resolved.stroke, "shape.stroke", nodeId) ?? "transparent";
|
|
18
29
|
const legacyStrokeWidth = numberOr(resolved.stroke_width, 0);
|
|
19
30
|
const width = numberOr(resolved.width, 100);
|
|
20
31
|
const height = numberOr(resolved.height, 100);
|
|
21
32
|
const radius = numberOr(resolved.radius, 0);
|
|
22
33
|
const opacity = numberOr(resolved.opacity, 1);
|
|
34
|
+
// LSML §4.6 `ariaLabel` was silently unrendered until issue #34's
|
|
35
|
+
// allowlist audit surfaced it — now forwarded as the SVG label.
|
|
36
|
+
const ariaLabel = typeof resolved.ariaLabel === "string" ? resolved.ariaLabel : undefined;
|
|
23
37
|
const tx = resolveTransition(transitionFor, ["opacity"], animateInitial);
|
|
24
38
|
const transition = toFramer(tx);
|
|
25
|
-
const play = mountPlay({ opacity }, animateInitial);
|
|
39
|
+
const play = mountPlay({ opacity }, animateInitial, nodeId);
|
|
26
40
|
// LSML 1.1 §4.6 — `fills[]` is the preferred multi-fill form. Fall
|
|
27
|
-
// back to the singular `fill` for 1.0 bundles.
|
|
28
|
-
|
|
41
|
+
// back to the singular `fill` for 1.0 bundles. Colours are strict-
|
|
42
|
+
// validated (a rejected colour drops its layer, with diagnostic).
|
|
43
|
+
const fills = sanitizeFills(parseFills(resolved.fills, "shape.fills", nodeId), "shape.fills", nodeId);
|
|
29
44
|
const strokes = parseStrokes(resolved.strokes);
|
|
45
|
+
// LSML 1.1 §4.6 — `geometry:"path"` : validated subpaths, one
|
|
46
|
+
// `<path>` element per entry (ADR 001 §3.2.3). Re-validated at every
|
|
47
|
+
// render — see module header of svg-path.ts (RC#10).
|
|
48
|
+
const subpaths = kind === "path" ? parseShapePaths(resolved, nodeId) : [];
|
|
30
49
|
// Each fill compiles to a (defs, ref) pair. We render the shape
|
|
31
50
|
// outline once per fill, layered top-to-bottom (first entry → on
|
|
32
51
|
// top, per §4.12). The defs are aggregated for a single <defs>.
|
|
@@ -37,14 +56,27 @@ export function Shape({ resolved, transitionFor, animateInitial }) {
|
|
|
37
56
|
// strokes are out of scope for §4.6 1.1). Each stroke is rendered
|
|
38
57
|
// as an additional pass over the same shape outline.
|
|
39
58
|
const strokeLayers = strokes.length > 0
|
|
40
|
-
? strokes.map((s) => ({
|
|
59
|
+
? strokes.map((s) => ({
|
|
60
|
+
color: safeColor(s.color, "shape.strokes.color", nodeId) ?? "transparent",
|
|
61
|
+
width: s.width ?? 0,
|
|
62
|
+
}))
|
|
41
63
|
: [{ color: legacyStroke, width: legacyStrokeWidth }];
|
|
42
64
|
// Stack order : fillRefs are emitted top-to-bottom per §4.12. SVG
|
|
43
65
|
// paints later siblings on top, so we reverse here so the first
|
|
44
66
|
// entry in fills[] ends up rendered last (visually on top).
|
|
45
67
|
const stackedFills = [...fillRefs].reverse();
|
|
46
68
|
const stackedStrokes = [...strokeLayers].reverse();
|
|
69
|
+
// For paths, a zero-width / transparent stroke pass would only emit
|
|
70
|
+
// invisible duplicate <path> elements — skip it.
|
|
71
|
+
const effectiveStrokes = kind === "path"
|
|
72
|
+
? stackedStrokes.filter((s) => s.width > 0 && s.color !== "transparent")
|
|
73
|
+
: stackedStrokes;
|
|
47
74
|
const renderShape = (fill, stroke, keyPrefix) => {
|
|
75
|
+
if (kind === "path") {
|
|
76
|
+
// §4.6 — fills and strokes apply to the union of all subpaths ;
|
|
77
|
+
// each subpath keeps its own winding rule (fill-rule).
|
|
78
|
+
return (_jsx("g", { children: subpaths.map((p, i) => (_jsx("path", { d: p.d, fillRule: p.fillRule, fill: fill, stroke: stroke.color, strokeWidth: stroke.width }, i))) }, keyPrefix));
|
|
79
|
+
}
|
|
48
80
|
if (kind === "circle") {
|
|
49
81
|
return (_jsx("circle", { cx: width / 2, cy: height / 2, r: Math.min(width, height) / 2 - stroke.width / 2, fill: fill, stroke: stroke.color, strokeWidth: stroke.width }, keyPrefix));
|
|
50
82
|
}
|
|
@@ -54,7 +86,19 @@ export function Shape({ resolved, transitionFor, animateInitial }) {
|
|
|
54
86
|
// rect default
|
|
55
87
|
return (_jsx("rect", { x: stroke.width / 2, y: stroke.width / 2, width: Math.max(0, width - stroke.width), height: Math.max(0, height - stroke.width), rx: radius, ry: radius, fill: fill, stroke: stroke.color, strokeWidth: stroke.width }, keyPrefix));
|
|
56
88
|
};
|
|
57
|
-
return (_jsxs(motion.svg, { width: width, height: height, viewBox: `0 0 ${width} ${height}`, initial: play.initial, animate: play.animate, transition: transition, style: { willChange: "opacity, transform" }, children: [allDefs.length > 0 && _jsx("defs", { children: allDefs }), stackedFills.map((ref, i) => renderShape(ref, { color: "transparent", width: 0 }, `fill-${i}`)),
|
|
89
|
+
return (_jsxs(motion.svg, { width: width, height: height, viewBox: `0 0 ${width} ${height}`, ...(ariaLabel !== undefined ? { "aria-label": ariaLabel, role: "img" } : {}), initial: play.initial, animate: play.animate, transition: transition, style: { willChange: "opacity, transform" }, children: [allDefs.length > 0 && _jsx("defs", { children: allDefs }), stackedFills.map((ref, i) => renderShape(ref, { color: "transparent", width: 0 }, `fill-${i}`)), effectiveStrokes.map((s, i) => renderShape("none", s, `stroke-${i}`))] }));
|
|
90
|
+
}
|
|
91
|
+
/** Strict-validate a colour prop (RC#11 — SVG attributes are injection
|
|
92
|
+
* sites too once values are wire-drivable). Non-strings resolve to
|
|
93
|
+
* null silently (absent prop) ; a string that fails the strict grammar
|
|
94
|
+
* is rejected with a diagnostic (value withheld per R9). */
|
|
95
|
+
function safeColor(value, field, nodeId) {
|
|
96
|
+
if (typeof value !== "string")
|
|
97
|
+
return null;
|
|
98
|
+
const color = parseCssColor(value);
|
|
99
|
+
if (color === null)
|
|
100
|
+
warnRejectedColor(field, nodeId);
|
|
101
|
+
return color;
|
|
58
102
|
}
|
|
59
103
|
function parseStrokes(value) {
|
|
60
104
|
if (!Array.isArray(value))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shape.js","sourceRoot":"","sources":["../../../src/render/primitives/shape.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAGvC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"shape.js","sourceRoot":"","sources":["../../../src/render/primitives/shape.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,38 @@
|
|
|
1
1
|
import type { PrimitiveProps } from "./index";
|
|
2
|
+
/** Max `maxLines` accepted (Bastion suggested ≤ 1000 on PR #38). */
|
|
3
|
+
export declare const MAX_MAX_LINES = 1000;
|
|
4
|
+
/** Max unitless `lineHeight` multiplier (100× the font size is already
|
|
5
|
+
* far beyond any broadcast layout). */
|
|
6
|
+
export declare const MAX_LINE_HEIGHT = 100;
|
|
7
|
+
/** Max |letterSpacing| in px, both directions (±1000 px covers any
|
|
8
|
+
* legitimate broadcast typography). */
|
|
9
|
+
export declare const MAX_LETTER_SPACING_PX = 1000;
|
|
10
|
+
/** Validate an untrusted `fontFamily` value. Returns the string or
|
|
11
|
+
* `null` on rejection (handled as "omit → inherit", with diagnostic). */
|
|
12
|
+
export declare function parseFontFamily(value: unknown): string | null;
|
|
2
13
|
/** Text leaf. Value renders as the displayed string ; style props
|
|
3
|
-
* cover size / weight / colour /
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
14
|
+
* cover the full LSML TextStyle (size / font / weight / colour /
|
|
15
|
+
* alignment / lineHeight / letterSpacing / textTransform /
|
|
16
|
+
* textDecoration / fontStyle) plus `maxLines` (§4.4 ellipsis
|
|
17
|
+
* truncation). Opacity is animated when a transition is declared on
|
|
18
|
+
* `opacity` or `value`. An `animate.from` makes it mount-play
|
|
19
|
+
* (initial → target) on mount. */
|
|
20
|
+
export declare function Text({ resolved, nodeId, transitionFor, animateInitial }: PrimitiveProps): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the LSML 1.1 TextStyle typography props (`lineHeight`,
|
|
23
|
+
* `letterSpacing`, `textTransform`, `textDecoration`, `fontStyle`) and
|
|
24
|
+
* `maxLines` (§4.4) into a validated React style fragment.
|
|
25
|
+
*
|
|
26
|
+
* Exported for boundary testing : happy-dom drops `-webkit-*`
|
|
27
|
+
* declarations from `CSSStyleDeclaration`, so the line-clamp pattern is
|
|
28
|
+
* asserted on the exact object handed to React's inline style (same
|
|
29
|
+
* approach as `backgroundsToCss` for `color-mix`).
|
|
30
|
+
*
|
|
31
|
+
* Defaults = omit the declaration (inherit / CSS initial). A
|
|
32
|
+
* non-conforming value → R9 diagnostic + omit ; the returned object
|
|
33
|
+
* only ever contains allowlisted constants or validated finite
|
|
34
|
+
* numbers — never the raw input. Numeric fields additionally enforce
|
|
35
|
+
* the defence-in-depth caps above (issue #34).
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveTypography(resolved: Record<string, unknown>, nodeId?: string): React.CSSProperties;
|
|
7
38
|
//# sourceMappingURL=text.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/text.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/text.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AA6B9C,oEAAoE;AACpE,eAAO,MAAM,aAAa,OAAO,CAAC;AAClC;wCACwC;AACxC,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC;wCACwC;AACxC,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAiB1C;0EAC0E;AAC1E,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAK7D;AAED;;;;;;mCAMmC;AACnC,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,EAAE,cAAc,2CAsDvF;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,MAAM,CAAC,EAAE,MAAM,GACd,KAAK,CAAC,aAAa,CAsDrB"}
|
|
@@ -1,20 +1,103 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { motion } from "framer-motion";
|
|
3
3
|
import { toFramer, mountPlay, resolveTransition } from "../../animate/transitions";
|
|
4
|
+
import { parseCssColor, warnRejectedColor } from "../css-color";
|
|
5
|
+
import { emitDiagnostic } from "../diagnostics";
|
|
6
|
+
// ── Typography grammars (LSML 1.1 TextStyle, schema.json) ───────────
|
|
7
|
+
// Every typo prop is wire-drivable (static bundle prop OR live LSDP
|
|
8
|
+
// delta via `resolveProps`, tree.tsx) and lands in inline CSS, so each
|
|
9
|
+
// value is validated against the field's spec'd grammar before it may
|
|
10
|
+
// reach the style object. Enum fields go through a closed allowlist
|
|
11
|
+
// (the emitted string is always one of these constants — never the
|
|
12
|
+
// input), numeric fields through finite-number checks. There is NO
|
|
13
|
+
// string passthrough on any of these sites (ADR 001 RC#11 by
|
|
14
|
+
// construction : no untrusted string ever reaches the style object).
|
|
15
|
+
const TEXT_TRANSFORMS = new Set(["none", "uppercase", "lowercase", "capitalize"]);
|
|
16
|
+
const TEXT_DECORATIONS = new Set(["none", "underline", "line-through"]);
|
|
17
|
+
const FONT_STYLES = new Set(["normal", "italic", "oblique"]);
|
|
18
|
+
// ── Defence-in-depth upper bounds (issue #34, Bastion follow-up on
|
|
19
|
+
// PR #38) ─────────────────────────────────────────────────────────────
|
|
20
|
+
// The numeric typo fields were type-validated but unbounded ; an absurd
|
|
21
|
+
// value pushed by a hostile bundle or live delta could degrade layout /
|
|
22
|
+
// rendering (e.g. a 10⁹-line clamp or a kilometric letter spacing).
|
|
23
|
+
// Policy : a value beyond its cap is REJECTED (diagnostic + omit → CSS
|
|
24
|
+
// initial), consistent with the existing typo grammar gates — NOT
|
|
25
|
+
// clamped, unlike the R8 filter caps where the spec explicitly blesses
|
|
26
|
+
// clamping. Rationale : there is no "nearest sensible rendering" for an
|
|
27
|
+
// absurd typographic value, the author's intent is unknowable ; safe
|
|
28
|
+
// default beats silently-altered output.
|
|
29
|
+
/** Max `maxLines` accepted (Bastion suggested ≤ 1000 on PR #38). */
|
|
30
|
+
export const MAX_MAX_LINES = 1000;
|
|
31
|
+
/** Max unitless `lineHeight` multiplier (100× the font size is already
|
|
32
|
+
* far beyond any broadcast layout). */
|
|
33
|
+
export const MAX_LINE_HEIGHT = 100;
|
|
34
|
+
/** Max |letterSpacing| in px, both directions (±1000 px covers any
|
|
35
|
+
* legitimate broadcast typography). */
|
|
36
|
+
export const MAX_LETTER_SPACING_PX = 1000;
|
|
37
|
+
// ── fontFamily policy (issue #34, Bastion follow-up on PR #38) ───────
|
|
38
|
+
// Decision : SHAPE validation, not a font allowlist. `fontFamily` is
|
|
39
|
+
// assigned through the React style object (per-property assignment via
|
|
40
|
+
// CSSStyleDeclaration), which cannot break out of the declaration — so
|
|
41
|
+
// the residual risk is malformed CSS, not injection. A font allowlist
|
|
42
|
+
// would couple the runtime to a host-specific font inventory (a spec /
|
|
43
|
+
// RFC matter — flagged for Atlas in the PR), whereas shape validation
|
|
44
|
+
// keeps any legitimate family name working. The grammar accepts
|
|
45
|
+
// comma-separated family lists with optional quotes ; the injection
|
|
46
|
+
// metacharacters (`;` `}` `{` `:` `\` `<` `>` `(` `)`) and `url(` are
|
|
47
|
+
// rejected by construction (none of their characters are allowed).
|
|
48
|
+
// Anchored, single character class with a bounded quantifier — linear
|
|
49
|
+
// time (RC#12).
|
|
50
|
+
const FONT_FAMILY_RE = /^[a-zA-Z0-9 ,.'"_-]{1,256}$/;
|
|
51
|
+
/** Validate an untrusted `fontFamily` value. Returns the string or
|
|
52
|
+
* `null` on rejection (handled as "omit → inherit", with diagnostic). */
|
|
53
|
+
export function parseFontFamily(value) {
|
|
54
|
+
if (typeof value !== "string")
|
|
55
|
+
return null;
|
|
56
|
+
const v = value.trim();
|
|
57
|
+
if (v.length === 0)
|
|
58
|
+
return null;
|
|
59
|
+
return FONT_FAMILY_RE.test(v) ? v : null;
|
|
60
|
+
}
|
|
4
61
|
/** Text leaf. Value renders as the displayed string ; style props
|
|
5
|
-
* cover size / weight / colour /
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
62
|
+
* cover the full LSML TextStyle (size / font / weight / colour /
|
|
63
|
+
* alignment / lineHeight / letterSpacing / textTransform /
|
|
64
|
+
* textDecoration / fontStyle) plus `maxLines` (§4.4 ellipsis
|
|
65
|
+
* truncation). Opacity is animated when a transition is declared on
|
|
66
|
+
* `opacity` or `value`. An `animate.from` makes it mount-play
|
|
67
|
+
* (initial → target) on mount. */
|
|
68
|
+
export function Text({ resolved, nodeId, transitionFor, animateInitial }) {
|
|
9
69
|
const value = resolved.value === undefined ? "" : String(resolved.value);
|
|
10
70
|
const size = resolved.size ?? "1rem";
|
|
11
|
-
const font = resolved.font;
|
|
12
71
|
const weight = resolved.weight ?? 400;
|
|
13
|
-
|
|
72
|
+
// Issue #34 — `font` is untrusted and lands in inline CSS : shape-
|
|
73
|
+
// validate (see fontFamily policy above) ; rejected → inherit.
|
|
74
|
+
let font;
|
|
75
|
+
if (resolved.font !== undefined) {
|
|
76
|
+
const parsed = parseFontFamily(resolved.font);
|
|
77
|
+
if (parsed === null) {
|
|
78
|
+
emitDiagnostic(nodeId, "text.font", "rejected fontFamily : outside the family-list grammar");
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
font = parsed;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// RC#11 : `colour` is untrusted (static prop OR live LSDP delta) and
|
|
85
|
+
// lands in inline CSS — strict-parse ; rejected → safe default.
|
|
86
|
+
let colour = "currentColor";
|
|
87
|
+
if (resolved.colour !== undefined) {
|
|
88
|
+
const parsed = parseCssColor(resolved.colour);
|
|
89
|
+
if (parsed === null) {
|
|
90
|
+
warnRejectedColor("text.colour", nodeId);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
colour = parsed;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
14
96
|
const align = resolved.align ?? "start";
|
|
15
97
|
const opacity = numberOr(resolved.opacity, 1);
|
|
98
|
+
const typography = resolveTypography(resolved, nodeId);
|
|
16
99
|
const tx = resolveTransition(transitionFor, ["opacity", "value"], animateInitial);
|
|
17
|
-
const play = mountPlay({ opacity }, animateInitial);
|
|
100
|
+
const play = mountPlay({ opacity }, animateInitial, nodeId);
|
|
18
101
|
return (_jsx(motion.span, { style: {
|
|
19
102
|
display: "inline-block",
|
|
20
103
|
fontSize: size,
|
|
@@ -24,10 +107,99 @@ export function Text({ resolved, transitionFor, animateInitial }) {
|
|
|
24
107
|
fontWeight: weight,
|
|
25
108
|
color: colour,
|
|
26
109
|
textAlign: align,
|
|
110
|
+
...typography,
|
|
27
111
|
willChange: "opacity, transform",
|
|
28
112
|
}, initial: play.initial, animate: play.animate, transition: toFramer(tx), children: value }));
|
|
29
113
|
}
|
|
30
114
|
function numberOr(v, fallback) {
|
|
31
115
|
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
32
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Resolve the LSML 1.1 TextStyle typography props (`lineHeight`,
|
|
119
|
+
* `letterSpacing`, `textTransform`, `textDecoration`, `fontStyle`) and
|
|
120
|
+
* `maxLines` (§4.4) into a validated React style fragment.
|
|
121
|
+
*
|
|
122
|
+
* Exported for boundary testing : happy-dom drops `-webkit-*`
|
|
123
|
+
* declarations from `CSSStyleDeclaration`, so the line-clamp pattern is
|
|
124
|
+
* asserted on the exact object handed to React's inline style (same
|
|
125
|
+
* approach as `backgroundsToCss` for `color-mix`).
|
|
126
|
+
*
|
|
127
|
+
* Defaults = omit the declaration (inherit / CSS initial). A
|
|
128
|
+
* non-conforming value → R9 diagnostic + omit ; the returned object
|
|
129
|
+
* only ever contains allowlisted constants or validated finite
|
|
130
|
+
* numbers — never the raw input. Numeric fields additionally enforce
|
|
131
|
+
* the defence-in-depth caps above (issue #34).
|
|
132
|
+
*/
|
|
133
|
+
export function resolveTypography(resolved, nodeId) {
|
|
134
|
+
// schema.json : lineHeight is a unitless multiplier ≥ 0 ;
|
|
135
|
+
// letterSpacing is a number (px) ; the three enums are closed sets.
|
|
136
|
+
const lineHeight = boundedNumber(resolved.lineHeight, 0, MAX_LINE_HEIGHT, "text.lineHeight", nodeId);
|
|
137
|
+
const letterSpacing = boundedNumber(resolved.letterSpacing, -MAX_LETTER_SPACING_PX, MAX_LETTER_SPACING_PX, "text.letterSpacing", nodeId);
|
|
138
|
+
const textTransform = enumValue(resolved.textTransform, TEXT_TRANSFORMS, "text.textTransform", nodeId);
|
|
139
|
+
const textDecoration = enumValue(resolved.textDecoration, TEXT_DECORATIONS, "text.textDecoration", nodeId);
|
|
140
|
+
const fontStyle = enumValue(resolved.fontStyle, FONT_STYLES, "text.fontStyle", nodeId);
|
|
141
|
+
// §4.4 maxLines — truncation with ellipsis after N lines, via the
|
|
142
|
+
// standard line-clamp pattern (display:-webkit-box overrides the
|
|
143
|
+
// base inline-block ; this fragment is spread after it so it wins).
|
|
144
|
+
const maxLines = positiveInteger(resolved.maxLines, MAX_MAX_LINES, "text.maxLines", nodeId);
|
|
145
|
+
return {
|
|
146
|
+
...(lineHeight !== undefined ? { lineHeight } : {}),
|
|
147
|
+
// Built from a validated finite number — no string passthrough.
|
|
148
|
+
...(letterSpacing !== undefined ? { letterSpacing: `${letterSpacing}px` } : {}),
|
|
149
|
+
...(textTransform !== undefined
|
|
150
|
+
? { textTransform: textTransform }
|
|
151
|
+
: {}),
|
|
152
|
+
...(textDecoration !== undefined ? { textDecoration } : {}),
|
|
153
|
+
...(fontStyle !== undefined ? { fontStyle } : {}),
|
|
154
|
+
...(maxLines !== undefined
|
|
155
|
+
? {
|
|
156
|
+
display: "-webkit-box",
|
|
157
|
+
WebkitBoxOrient: "vertical",
|
|
158
|
+
WebkitLineClamp: maxLines,
|
|
159
|
+
overflow: "hidden",
|
|
160
|
+
textOverflow: "ellipsis",
|
|
161
|
+
}
|
|
162
|
+
: {}),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** Closed-allowlist enum gate. Returns the canonical constant from the
|
|
166
|
+
* allowlist (NEVER the raw input) or `undefined` (field omitted →
|
|
167
|
+
* CSS initial). Non-conforming value → R9 diagnostic, no passthrough. */
|
|
168
|
+
function enumValue(v, allow, field, nodeId) {
|
|
169
|
+
if (v === undefined)
|
|
170
|
+
return undefined;
|
|
171
|
+
if (typeof v === "string" && allow.has(v))
|
|
172
|
+
return v;
|
|
173
|
+
warnRejectedTypo(field, nodeId);
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
/** Finite number within [min, max] or omit (R9 diagnostic on a
|
|
177
|
+
* non-conforming or out-of-cap input — rejected, never clamped). */
|
|
178
|
+
function boundedNumber(v, min, max, field, nodeId) {
|
|
179
|
+
if (v === undefined)
|
|
180
|
+
return undefined;
|
|
181
|
+
if (typeof v === "number" && Number.isFinite(v) && v >= min && v <= max)
|
|
182
|
+
return v;
|
|
183
|
+
warnRejectedTypo(field, nodeId);
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
/** Integer in [1, max] or omit (schema : maxLines is a line count ;
|
|
187
|
+
* capped per the issue #34 defence-in-depth bounds). */
|
|
188
|
+
function positiveInteger(v, max, field, nodeId) {
|
|
189
|
+
if (v === undefined)
|
|
190
|
+
return undefined;
|
|
191
|
+
if (typeof v === "number" && Number.isInteger(v) && v >= 1 && v <= max)
|
|
192
|
+
return v;
|
|
193
|
+
warnRejectedTypo(field, nodeId);
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Diagnostic for a typo value outside its spec'd grammar or caps.
|
|
198
|
+
* Bastion R9 (ADR 001 §5.1) : the rejected VALUE is never logged nor
|
|
199
|
+
* forwarded — only `node.id` (RC#7), the field name and a static
|
|
200
|
+
* reason. Routed through the structured diagnostics channel.
|
|
201
|
+
*/
|
|
202
|
+
function warnRejectedTypo(field, nodeId) {
|
|
203
|
+
emitDiagnostic(nodeId, field, "rejected typography value : outside the field's spec'd grammar or caps");
|
|
204
|
+
}
|
|
33
205
|
//# sourceMappingURL=text.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../../src/render/primitives/text.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../../src/render/primitives/text.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RenderKind, RenderNode } from "./bundle";
|
|
2
|
+
/** Resolved-prop keys consumed per primitive (component + wrapper). */
|
|
3
|
+
export declare const PRIMITIVE_PROP_ALLOWLIST: Readonly<Record<RenderKind, ReadonlySet<string>>>;
|
|
4
|
+
/**
|
|
5
|
+
* Audit a node's static props + binding keys against its primitive's
|
|
6
|
+
* allowlist. Every unknown key emits ONE structured diagnostic naming
|
|
7
|
+
* `node.id` + the prop (never the value, R9). Idempotent per node.
|
|
8
|
+
*/
|
|
9
|
+
export declare function checkNodeProps(node: RenderNode): void;
|
|
10
|
+
//# sourceMappingURL=prop-allowlist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prop-allowlist.d.ts","sourceRoot":"","sources":["../../src/render/prop-allowlist.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAWvD,uEAAuE;AACvE,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAiDtF,CAAC;AAgBF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAgBrD"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Per-primitive prop allowlists (ADR 001 §3.4 D4, issue #34).
|
|
2
|
+
//
|
|
3
|
+
// Each primitive declares the exact set of resolved-prop keys it
|
|
4
|
+
// consumes at render time. Any prop reaching the renderer outside the
|
|
5
|
+
// allowlist — whether from a compiled bundle, a hand-rolled RenderNode
|
|
6
|
+
// or a binding key — produces a structured diagnostic (never a silent
|
|
7
|
+
// drop). Values are NEVER inspected nor reported (R9) : the check is
|
|
8
|
+
// purely key-based.
|
|
9
|
+
//
|
|
10
|
+
// Key-based is sufficient for live deltas too : an LSDP delta can only
|
|
11
|
+
// change the VALUE behind an already-declared binding key
|
|
12
|
+
// (`resolveProps`, tree.tsx) — it can never introduce a new prop key.
|
|
13
|
+
// The per-node key set is therefore static, and the check runs once per
|
|
14
|
+
// RenderNode object (WeakSet dedup) instead of once per render.
|
|
15
|
+
//
|
|
16
|
+
// These sets mirror what each primitive's component ACTUALLY reads
|
|
17
|
+
// today. Spec'd fields the renderer does not consume yet (e.g. `text`
|
|
18
|
+
// `format`, `stack` `padding`) are deliberately NOT listed : per the
|
|
19
|
+
// anti-silent-drop policy they must warn until they are implemented.
|
|
20
|
+
import { emitDiagnostic } from "./diagnostics";
|
|
21
|
+
/** Universal props consumed by the Tree renderer itself
|
|
22
|
+
* (`UniversalWrapper`, LSML 1.1 §5.4) on every primitive. */
|
|
23
|
+
const UNIVERSAL_PROPS = ["visible", "opacity", "universal_opacity", "rotation", "sizing"];
|
|
24
|
+
function allow(keys) {
|
|
25
|
+
return new Set([...UNIVERSAL_PROPS, ...keys]);
|
|
26
|
+
}
|
|
27
|
+
/** Resolved-prop keys consumed per primitive (component + wrapper). */
|
|
28
|
+
export const PRIMITIVE_PROP_ALLOWLIST = {
|
|
29
|
+
stack: allow(["direction", "gap", "wrap", "crossGap", "align", "justify"]),
|
|
30
|
+
grid: allow(["cols", "rows", "gap"]),
|
|
31
|
+
frame: allow([
|
|
32
|
+
"x",
|
|
33
|
+
"y",
|
|
34
|
+
"width",
|
|
35
|
+
"height",
|
|
36
|
+
"scale",
|
|
37
|
+
"rotate",
|
|
38
|
+
"background",
|
|
39
|
+
"backgrounds",
|
|
40
|
+
"clipsContent",
|
|
41
|
+
]),
|
|
42
|
+
text: allow([
|
|
43
|
+
"value",
|
|
44
|
+
"size",
|
|
45
|
+
"font",
|
|
46
|
+
"weight",
|
|
47
|
+
"colour",
|
|
48
|
+
"align",
|
|
49
|
+
"lineHeight",
|
|
50
|
+
"letterSpacing",
|
|
51
|
+
"textTransform",
|
|
52
|
+
"textDecoration",
|
|
53
|
+
"fontStyle",
|
|
54
|
+
"maxLines",
|
|
55
|
+
]),
|
|
56
|
+
image: allow(["src", "alt", "fit", "position", "width", "height"]),
|
|
57
|
+
shape: allow([
|
|
58
|
+
"geometry",
|
|
59
|
+
"kind",
|
|
60
|
+
"width",
|
|
61
|
+
"height",
|
|
62
|
+
"radius",
|
|
63
|
+
"fill",
|
|
64
|
+
"fills",
|
|
65
|
+
"stroke",
|
|
66
|
+
"stroke_width",
|
|
67
|
+
"strokes",
|
|
68
|
+
"pathData",
|
|
69
|
+
"paths",
|
|
70
|
+
"ariaLabel",
|
|
71
|
+
]),
|
|
72
|
+
media: allow(["src", "loop", "mute", "autoplay", "fit"]),
|
|
73
|
+
instance: allow(["scene_id", "scene_version", "size", "position"]),
|
|
74
|
+
// `repeat` is dispatched specially by the tree ; its only consumed
|
|
75
|
+
// binding is `items`.
|
|
76
|
+
repeat: new Set(["items"]),
|
|
77
|
+
};
|
|
78
|
+
function isAllowed(kind, key) {
|
|
79
|
+
const allowed = PRIMITIVE_PROP_ALLOWLIST[kind];
|
|
80
|
+
if (allowed === undefined)
|
|
81
|
+
return true; // unknown kind warns separately (tree.tsx)
|
|
82
|
+
if (allowed.has(key))
|
|
83
|
+
return true;
|
|
84
|
+
// `instance` exposes bound sub-scene parameters under `params.*`
|
|
85
|
+
// (LSML §4.9) — the whole namespace is part of its contract.
|
|
86
|
+
if (kind === "instance" && (key === "params" || key.startsWith("params.")))
|
|
87
|
+
return true;
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
// One check per RenderNode object — bundles are immutable once fetched,
|
|
91
|
+
// and a node's key set cannot change live (see module header).
|
|
92
|
+
const checkedNodes = new WeakSet();
|
|
93
|
+
/**
|
|
94
|
+
* Audit a node's static props + binding keys against its primitive's
|
|
95
|
+
* allowlist. Every unknown key emits ONE structured diagnostic naming
|
|
96
|
+
* `node.id` + the prop (never the value, R9). Idempotent per node.
|
|
97
|
+
*/
|
|
98
|
+
export function checkNodeProps(node) {
|
|
99
|
+
if (checkedNodes.has(node))
|
|
100
|
+
return;
|
|
101
|
+
checkedNodes.add(node);
|
|
102
|
+
const keys = new Set([
|
|
103
|
+
...Object.keys(node.props ?? {}),
|
|
104
|
+
...Object.keys(node.bindings ?? {}),
|
|
105
|
+
]);
|
|
106
|
+
for (const key of keys) {
|
|
107
|
+
if (!isAllowed(node.kind, key)) {
|
|
108
|
+
emitDiagnostic(node.id, `${node.kind}.${key}`, "is not consumed by this primitive's renderer ; the prop is ignored (anti-silent-drop, ADR 001 §3.4)");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=prop-allowlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prop-allowlist.js","sourceRoot":"","sources":["../../src/render/prop-allowlist.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,EAAE;AACF,iEAAiE;AACjE,sEAAsE;AACtE,uEAAuE;AACvE,sEAAsE;AACtE,qEAAqE;AACrE,oBAAoB;AACpB,EAAE;AACF,uEAAuE;AACvE,0DAA0D;AAC1D,sEAAsE;AACtE,wEAAwE;AACxE,gEAAgE;AAChE,EAAE;AACF,mEAAmE;AACnE,sEAAsE;AACtE,qEAAqE;AACrE,qEAAqE;AAGrE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C;8DAC8D;AAC9D,MAAM,eAAe,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,mBAAmB,EAAE,UAAU,EAAE,QAAQ,CAAU,CAAC;AAEnG,SAAS,KAAK,CAAC,IAAuB;IACpC,OAAO,IAAI,GAAG,CAAC,CAAC,GAAG,eAAe,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,MAAM,wBAAwB,GAAsD;IACzF,KAAK,EAAE,KAAK,CAAC,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;IAC1E,IAAI,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACpC,KAAK,EAAE,KAAK,CAAC;QACX,GAAG;QACH,GAAG;QACH,OAAO;QACP,QAAQ;QACR,OAAO;QACP,QAAQ;QACR,YAAY;QACZ,aAAa;QACb,cAAc;KACf,CAAC;IACF,IAAI,EAAE,KAAK,CAAC;QACV,OAAO;QACP,MAAM;QACN,MAAM;QACN,QAAQ;QACR,QAAQ;QACR,OAAO;QACP,YAAY;QACZ,eAAe;QACf,eAAe;QACf,gBAAgB;QAChB,WAAW;QACX,UAAU;KACX,CAAC;IACF,KAAK,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAClE,KAAK,EAAE,KAAK,CAAC;QACX,UAAU;QACV,MAAM;QACN,OAAO;QACP,QAAQ;QACR,QAAQ;QACR,MAAM;QACN,OAAO;QACP,QAAQ;QACR,cAAc;QACd,SAAS;QACT,UAAU;QACV,OAAO;QACP,WAAW;KACZ,CAAC;IACF,KAAK,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;IACxD,QAAQ,EAAE,KAAK,CAAC,CAAC,UAAU,EAAE,eAAe,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAClE,mEAAmE;IACnE,sBAAsB;IACtB,MAAM,EAAE,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;CAC3B,CAAC;AAEF,SAAS,SAAS,CAAC,IAAgB,EAAE,GAAW;IAC9C,MAAM,OAAO,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,CAAC,2CAA2C;IACnF,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,iEAAiE;IACjE,6DAA6D;IAC7D,IAAI,IAAI,KAAK,UAAU,IAAI,CAAC,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACxF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wEAAwE;AACxE,+DAA+D;AAC/D,MAAM,YAAY,GAAG,IAAI,OAAO,EAAc,CAAC;AAE/C;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAgB;IAC7C,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO;IACnC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAS;QAC3B,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAChC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;KACpC,CAAC,CAAC;IACH,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;YAC/B,cAAc,CACZ,IAAI,CAAC,EAAE,EACP,GAAG,IAAI,CAAC,IAAI,IAAI,GAAG,EAAE,EACrB,qGAAqG,CACtG,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** RC#10 — hard cap : 8 KiB per subpath `d` string. */
|
|
2
|
+
export declare const MAX_SUBPATH_LEN = 8192;
|
|
3
|
+
/**
|
|
4
|
+
* RC#10 — hard cap on commands per subpath. Aligned on the compiler's
|
|
5
|
+
* MAX_PATH_COMMANDS = 4000 (PR #39, deliberate authoring cap) so a live
|
|
6
|
+
* LSDP delta can never carry more commands than the authoring pipeline
|
|
7
|
+
* accepts. A shared constant module is tracked in issue #41 — do not
|
|
8
|
+
* diverge these two values in the meantime.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MAX_SUBPATH_COMMANDS = 4000;
|
|
11
|
+
/** RC#10 — hard cap on subpaths per shape. */
|
|
12
|
+
export declare const MAX_SUBPATHS = 64;
|
|
13
|
+
/**
|
|
14
|
+
* Validate an untrusted SVG path `d` string against the strict grammar
|
|
15
|
+
* above. Returns the validated (trimmed) string or `null` on rejection.
|
|
16
|
+
* A `null` MUST be handled as "omit the subpath + emit a diagnostic" —
|
|
17
|
+
* never interpolate the raw input into the DOM.
|
|
18
|
+
*/
|
|
19
|
+
export declare function validatePathData(value: unknown): string | null;
|
|
20
|
+
export interface SubPath {
|
|
21
|
+
d: string;
|
|
22
|
+
fillRule: "nonzero" | "evenodd";
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve a shape's `pathData` / `paths[]` props (LSML 1.1 §4.6) into
|
|
26
|
+
* validated subpaths ready for rendering — one `<path>` element per
|
|
27
|
+
* entry (ADR 001 §3.2.3). `pathData` is equivalent to
|
|
28
|
+
* `paths: [{ data: pathData, windingRule: "NONZERO" }]`.
|
|
29
|
+
*
|
|
30
|
+
* Re-runs at every render (RC#10 — props are live via LSDP deltas).
|
|
31
|
+
* Every rejected or unrendered field emits a diagnostic (ADR 001 §3.4,
|
|
32
|
+
* anti-silent-drop) that NEVER contains the value (R9).
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseShapePaths(resolved: Record<string, unknown>, nodeId?: string): SubPath[];
|
|
35
|
+
//# sourceMappingURL=svg-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svg-path.d.ts","sourceRoot":"","sources":["../../src/render/svg-path.ts"],"names":[],"mappings":"AAgCA,uDAAuD;AACvD,eAAO,MAAM,eAAe,OAAO,CAAC;AACpC;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AACzC,8CAA8C;AAC9C,eAAO,MAAM,YAAY,KAAK,CAAC;AAa/B;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAyE9D;AAED,MAAM,WAAW,OAAO;IACtB,CAAC,EAAE,MAAM,CAAC;IACV,QAAQ,EAAE,SAAS,GAAG,SAAS,CAAC;CACjC;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,CA6C7F"}
|