@lumencast/runtime 0.4.0 → 0.5.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.
Files changed (130) hide show
  1. package/README.md +57 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/animate/frame-coalescer.d.ts +13 -0
  4. package/dist/animate/frame-coalescer.d.ts.map +1 -0
  5. package/dist/animate/frame-coalescer.js +46 -0
  6. package/dist/animate/frame-coalescer.js.map +1 -0
  7. package/dist/animate/keyframes.d.ts +1 -1
  8. package/dist/animate/keyframes.d.ts.map +1 -1
  9. package/dist/animate/keyframes.js +20 -6
  10. package/dist/animate/keyframes.js.map +1 -1
  11. package/dist/animate/transitions.d.ts +4 -1
  12. package/dist/animate/transitions.d.ts.map +1 -1
  13. package/dist/animate/transitions.js +30 -3
  14. package/dist/animate/transitions.js.map +1 -1
  15. package/dist/{broadcast-DzZ8TVGZ.js → broadcast-3vYij4k-.js} +3 -3
  16. package/dist/{broadcast-DzZ8TVGZ.js.map → broadcast-3vYij4k-.js.map} +1 -1
  17. package/dist/{control-gbDGvdR0.js → control-BFNkY7-6.js} +4 -4
  18. package/dist/{control-gbDGvdR0.js.map → control-BFNkY7-6.js.map} +1 -1
  19. package/dist/{index-oteiocFe.js → index-CyOlpZAL.js} +305 -150
  20. package/dist/index-CyOlpZAL.js.map +1 -0
  21. package/dist/index.d.ts +5 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.html +1 -1
  24. package/dist/index.js +10 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/lumencast.js +9 -2
  27. package/dist/mount.d.ts.map +1 -1
  28. package/dist/mount.js +11 -1
  29. package/dist/mount.js.map +1 -1
  30. package/dist/render/bind-animate.d.ts +40 -0
  31. package/dist/render/bind-animate.d.ts.map +1 -0
  32. package/dist/render/bind-animate.js +329 -0
  33. package/dist/render/bind-animate.js.map +1 -0
  34. package/dist/render/bundle.d.ts +48 -6
  35. package/dist/render/bundle.d.ts.map +1 -1
  36. package/dist/render/bundle.js +71 -4
  37. package/dist/render/bundle.js.map +1 -1
  38. package/dist/render/color-interp.d.ts +18 -0
  39. package/dist/render/color-interp.d.ts.map +1 -0
  40. package/dist/render/color-interp.js +303 -0
  41. package/dist/render/color-interp.js.map +1 -0
  42. package/dist/render/css-color.d.ts +16 -0
  43. package/dist/render/css-color.d.ts.map +1 -0
  44. package/dist/render/css-color.js +130 -0
  45. package/dist/render/css-color.js.map +1 -0
  46. package/dist/render/diagnostics.d.ts +26 -0
  47. package/dist/render/diagnostics.d.ts.map +1 -0
  48. package/dist/render/diagnostics.js +58 -0
  49. package/dist/render/diagnostics.js.map +1 -0
  50. package/dist/render/fill.d.ts +15 -3
  51. package/dist/render/fill.d.ts.map +1 -1
  52. package/dist/render/fill.js +81 -14
  53. package/dist/render/fill.js.map +1 -1
  54. package/dist/render/filter-clamp.d.ts +35 -0
  55. package/dist/render/filter-clamp.d.ts.map +1 -0
  56. package/dist/render/filter-clamp.js +90 -0
  57. package/dist/render/filter-clamp.js.map +1 -0
  58. package/dist/render/keyframe-player.d.ts +4 -1
  59. package/dist/render/keyframe-player.d.ts.map +1 -1
  60. package/dist/render/keyframe-player.js +2 -2
  61. package/dist/render/keyframe-player.js.map +1 -1
  62. package/dist/render/primitives/frame.d.ts +16 -1
  63. package/dist/render/primitives/frame.d.ts.map +1 -1
  64. package/dist/render/primitives/frame.js +42 -7
  65. package/dist/render/primitives/frame.js.map +1 -1
  66. package/dist/render/primitives/image.d.ts +1 -1
  67. package/dist/render/primitives/image.d.ts.map +1 -1
  68. package/dist/render/primitives/image.js +6 -3
  69. package/dist/render/primitives/image.js.map +1 -1
  70. package/dist/render/primitives/index.d.ts +3 -0
  71. package/dist/render/primitives/index.d.ts.map +1 -1
  72. package/dist/render/primitives/index.js.map +1 -1
  73. package/dist/render/primitives/instance.d.ts +1 -1
  74. package/dist/render/primitives/instance.d.ts.map +1 -1
  75. package/dist/render/primitives/instance.js +10 -13
  76. package/dist/render/primitives/instance.js.map +1 -1
  77. package/dist/render/primitives/shape.d.ts +9 -3
  78. package/dist/render/primitives/shape.d.ts.map +1 -1
  79. package/dist/render/primitives/shape.js +56 -12
  80. package/dist/render/primitives/shape.js.map +1 -1
  81. package/dist/render/primitives/text.d.ts +35 -4
  82. package/dist/render/primitives/text.d.ts.map +1 -1
  83. package/dist/render/primitives/text.js +179 -7
  84. package/dist/render/primitives/text.js.map +1 -1
  85. package/dist/render/prop-allowlist.d.ts +10 -0
  86. package/dist/render/prop-allowlist.d.ts.map +1 -0
  87. package/dist/render/prop-allowlist.js +112 -0
  88. package/dist/render/prop-allowlist.js.map +1 -0
  89. package/dist/render/svg-path.d.ts +35 -0
  90. package/dist/render/svg-path.d.ts.map +1 -0
  91. package/dist/render/svg-path.js +211 -0
  92. package/dist/render/svg-path.js.map +1 -0
  93. package/dist/render/tree.d.ts.map +1 -1
  94. package/dist/render/tree.js +30 -5
  95. package/dist/render/tree.js.map +1 -1
  96. package/dist/{status-pill-Cgdl9FtP.js → status-pill-DIpXc5du.js} +2 -2
  97. package/dist/{status-pill-Cgdl9FtP.js.map → status-pill-DIpXc5du.js.map} +1 -1
  98. package/dist/{test-CAnkHA0n.js → test-ByRec1kd.js} +4 -4
  99. package/dist/{test-CAnkHA0n.js.map → test-ByRec1kd.js.map} +1 -1
  100. package/dist/tree-D5wYHpPu.js +1230 -0
  101. package/dist/tree-D5wYHpPu.js.map +1 -0
  102. package/dist/types.d.ts +26 -0
  103. package/dist/types.d.ts.map +1 -1
  104. package/package.json +5 -4
  105. package/src/animate/frame-coalescer.ts +63 -0
  106. package/src/animate/keyframes.ts +24 -5
  107. package/src/animate/transitions.ts +33 -3
  108. package/src/index.ts +24 -0
  109. package/src/mount.ts +12 -1
  110. package/src/render/bind-animate.tsx +370 -0
  111. package/src/render/bundle.ts +102 -10
  112. package/src/render/color-interp.ts +303 -0
  113. package/src/render/css-color.ts +145 -0
  114. package/src/render/diagnostics.ts +75 -0
  115. package/src/render/fill.tsx +85 -14
  116. package/src/render/filter-clamp.ts +99 -0
  117. package/src/render/keyframe-player.tsx +10 -2
  118. package/src/render/primitives/frame.tsx +47 -7
  119. package/src/render/primitives/image.tsx +6 -2
  120. package/src/render/primitives/index.ts +3 -0
  121. package/src/render/primitives/instance.tsx +14 -15
  122. package/src/render/primitives/shape.tsx +76 -12
  123. package/src/render/primitives/text.tsx +224 -7
  124. package/src/render/prop-allowlist.ts +119 -0
  125. package/src/render/svg-path.ts +215 -0
  126. package/src/render/tree.tsx +41 -6
  127. package/src/types.ts +27 -0
  128. package/dist/index-oteiocFe.js.map +0 -1
  129. package/dist/tree-DVYXwItH.js +0 -512
  130. 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 behave
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;AAS9C;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,EAAE,cAAc,2CAyGhF"}
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
- /** Rectangle / circle / line. Renders as SVG so stroke + fill behave
6
- * predictably across hosts. Opacity animatable.
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
- const kind = resolved.kind ?? "rect";
16
- const legacyFill = resolved.fill ?? "transparent";
17
- const legacyStroke = resolved.stroke ?? "transparent";
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
- const fills = parseFills(resolved.fills);
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) => ({ color: s.color ?? "transparent", width: s.width ?? 0 }))
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}`)), stackedStrokes.map((s, i) => renderShape("none", s, `stroke-${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":"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"}
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 / alignment. Opacity is animated when
4
- * a transition is declared on `opacity` or `value`. An `animate.from`
5
- * makes it mount-play (initial target) on mount. */
6
- export declare function Text({ resolved, transitionFor, animateInitial }: PrimitiveProps): import("react/jsx-runtime").JSX.Element;
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;AAG9C;;;uDAGuD;AACvD,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,EAAE,cAAc,2CAgC/E"}
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 / alignment. Opacity is animated when
6
- * a transition is declared on `opacity` or `value`. An `animate.from`
7
- * makes it mount-play (initial target) on mount. */
8
- export function Text({ resolved, transitionFor, animateInitial }) {
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
- const colour = resolved.colour ?? "currentColor";
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;AAEnF;;;uDAGuD;AACvD,MAAM,UAAU,IAAI,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAkB;IAC9E,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzE,MAAM,IAAI,GAAI,QAAQ,CAAC,IAAoC,IAAI,MAAM,CAAC;IACtE,MAAM,IAAI,GAAG,QAAQ,CAAC,IAA0B,CAAC;IACjD,MAAM,MAAM,GAAI,QAAQ,CAAC,MAA6B,IAAI,GAAG,CAAC;IAC9D,MAAM,MAAM,GAAI,QAAQ,CAAC,MAA6B,IAAI,cAAc,CAAC;IACzE,MAAM,KAAK,GAAI,QAAQ,CAAC,KAA4B,IAAI,OAAO,CAAC;IAChE,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAE9C,MAAM,EAAE,GAAG,iBAAiB,CAAC,aAAa,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,cAAc,CAAC,CAAC;IAClF,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC;IAEpD,OAAO,CACL,KAAC,MAAM,CAAC,IAAI,IACV,KAAK,EAAE;YACL,OAAO,EAAE,cAAc;YACvB,QAAQ,EAAE,IAAI;YACd,qEAAqE;YACrE,8CAA8C;YAC9C,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnD,UAAU,EAAE,MAAM;YAClB,KAAK,EAAE,MAAM;YACb,SAAS,EAAE,KAAyC;YACpD,UAAU,EAAE,oBAAoB;SACjC,EACD,OAAO,EAAE,IAAI,CAAC,OAAO,EACrB,OAAO,EAAE,IAAI,CAAC,OAAO,EACrB,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC,YAEvB,KAAK,GACM,CACf,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,CAAU,EAAE,QAAgB;IAC5C,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AACpE,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":"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"}
@@ -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"}