@hyperframes/core 0.7.27 → 0.7.29

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 (82) hide show
  1. package/dist/compiler/compositionScoping.d.ts.map +1 -1
  2. package/dist/compiler/compositionScoping.js +13 -0
  3. package/dist/compiler/compositionScoping.js.map +1 -1
  4. package/dist/compiler/inlineSubCompositions.d.ts.map +1 -1
  5. package/dist/compiler/inlineSubCompositions.js +10 -0
  6. package/dist/compiler/inlineSubCompositions.js.map +1 -1
  7. package/dist/figma/assetSnippet.d.ts +3 -0
  8. package/dist/figma/assetSnippet.d.ts.map +1 -0
  9. package/dist/figma/assetSnippet.js +18 -0
  10. package/dist/figma/assetSnippet.js.map +1 -0
  11. package/dist/figma/bindings.d.ts +38 -0
  12. package/dist/figma/bindings.d.ts.map +1 -0
  13. package/dist/figma/bindings.js +84 -0
  14. package/dist/figma/bindings.js.map +1 -0
  15. package/dist/figma/client.d.ts +67 -0
  16. package/dist/figma/client.d.ts.map +1 -0
  17. package/dist/figma/client.js +134 -0
  18. package/dist/figma/client.js.map +1 -0
  19. package/dist/figma/color.d.ts +3 -0
  20. package/dist/figma/color.d.ts.map +1 -0
  21. package/dist/figma/color.js +23 -0
  22. package/dist/figma/color.js.map +1 -0
  23. package/dist/figma/emitTimelineScript.d.ts +3 -0
  24. package/dist/figma/emitTimelineScript.d.ts.map +1 -0
  25. package/dist/figma/emitTimelineScript.js +42 -0
  26. package/dist/figma/emitTimelineScript.js.map +1 -0
  27. package/dist/figma/freeze.d.ts +16 -0
  28. package/dist/figma/freeze.d.ts.map +1 -0
  29. package/dist/figma/freeze.js +67 -0
  30. package/dist/figma/freeze.js.map +1 -0
  31. package/dist/figma/index.d.ts +21 -0
  32. package/dist/figma/index.d.ts.map +1 -0
  33. package/dist/figma/index.js +15 -0
  34. package/dist/figma/index.js.map +1 -0
  35. package/dist/figma/jsonl.d.ts +3 -0
  36. package/dist/figma/jsonl.d.ts.map +1 -0
  37. package/dist/figma/jsonl.js +22 -0
  38. package/dist/figma/jsonl.js.map +1 -0
  39. package/dist/figma/manifest.d.ts +17 -0
  40. package/dist/figma/manifest.d.ts.map +1 -0
  41. package/dist/figma/manifest.js +96 -0
  42. package/dist/figma/manifest.js.map +1 -0
  43. package/dist/figma/motionEase.d.ts +3 -0
  44. package/dist/figma/motionEase.d.ts.map +1 -0
  45. package/dist/figma/motionEase.js +45 -0
  46. package/dist/figma/motionEase.js.map +1 -0
  47. package/dist/figma/motionToGsap.d.ts +3 -0
  48. package/dist/figma/motionToGsap.d.ts.map +1 -0
  49. package/dist/figma/motionToGsap.js +61 -0
  50. package/dist/figma/motionToGsap.js.map +1 -0
  51. package/dist/figma/nodeDocument.d.ts +4 -0
  52. package/dist/figma/nodeDocument.d.ts.map +1 -0
  53. package/dist/figma/nodeDocument.js +26 -0
  54. package/dist/figma/nodeDocument.js.map +1 -0
  55. package/dist/figma/nodeToHtml.d.ts +31 -0
  56. package/dist/figma/nodeToHtml.d.ts.map +1 -0
  57. package/dist/figma/nodeToHtml.js +244 -0
  58. package/dist/figma/nodeToHtml.js.map +1 -0
  59. package/dist/figma/parseFigmaRef.d.ts +3 -0
  60. package/dist/figma/parseFigmaRef.d.ts.map +1 -0
  61. package/dist/figma/parseFigmaRef.js +31 -0
  62. package/dist/figma/parseFigmaRef.js.map +1 -0
  63. package/dist/figma/resolveBindings.d.ts +31 -0
  64. package/dist/figma/resolveBindings.d.ts.map +1 -0
  65. package/dist/figma/resolveBindings.js +77 -0
  66. package/dist/figma/resolveBindings.js.map +1 -0
  67. package/dist/figma/sanitizeSvg.d.ts +18 -0
  68. package/dist/figma/sanitizeSvg.d.ts.map +1 -0
  69. package/dist/figma/sanitizeSvg.js +52 -0
  70. package/dist/figma/sanitizeSvg.js.map +1 -0
  71. package/dist/figma/tokensToVariables.d.ts +40 -0
  72. package/dist/figma/tokensToVariables.d.ts.map +1 -0
  73. package/dist/figma/tokensToVariables.js +118 -0
  74. package/dist/figma/tokensToVariables.js.map +1 -0
  75. package/dist/figma/types.d.ts +81 -0
  76. package/dist/figma/types.d.ts.map +1 -0
  77. package/dist/figma/types.js +2 -0
  78. package/dist/figma/types.js.map +1 -0
  79. package/dist/generated/runtime-inline.js +1 -1
  80. package/dist/generated/runtime-inline.js.map +1 -1
  81. package/dist/hyperframe.runtime.iife.js +19 -19
  82. package/package.json +8 -4
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Phase 3 mapper: figma node tree → editable HTML with inline styles
3
+ * (design spec §7, hybrid fidelity routing).
4
+ *
5
+ * - Geometry is exact for free: absolute positioning at figma bounds inside
6
+ * a fixed-size root — no responsive reflow, no drift.
7
+ * - CSS where CSS is faithful: solid/linear-gradient fills, corner radius,
8
+ * opacity, drop shadow, blur, text styles.
9
+ * - Everything CSS can't match faithfully (vectors, boolean ops, exotic
10
+ * paint) routes to the rasterize list — the caller exports those nodes as
11
+ * images (Phase 1) and fills in the placeholder src.
12
+ * - Bindings (§7.1): resolved sites emit var(--slug, literal) so a brand
13
+ * refresh propagates; unresolved sites bake the literal and carry a
14
+ * data-figma-unresolved flag. Never a dangling var().
15
+ * - visible:false nodes and fills are skipped — figma's own renderer
16
+ * semantics, not ours.
17
+ */
18
+ import { figmaColorToCss } from "./color.js";
19
+ import { childDocuments } from "./nodeDocument.js";
20
+ const RASTERIZE_TYPES = new Set([
21
+ "VECTOR",
22
+ "BOOLEAN_OPERATION",
23
+ "STAR",
24
+ "LINE",
25
+ "POLYGON",
26
+ "REGULAR_POLYGON",
27
+ ]);
28
+ function isRecord(value) {
29
+ return typeof value === "object" && value !== null;
30
+ }
31
+ function boxOf(node) {
32
+ const b = node.absoluteBoundingBox;
33
+ if (isRecord(b) &&
34
+ typeof b.x === "number" &&
35
+ typeof b.y === "number" &&
36
+ typeof b.width === "number" &&
37
+ typeof b.height === "number")
38
+ return { x: b.x, y: b.y, width: b.width, height: b.height };
39
+ return null;
40
+ }
41
+ export function slugify(name) {
42
+ const slug = name
43
+ .toLowerCase()
44
+ .replace(/[^a-z0-9]+/g, "-")
45
+ .replace(/^-+|-+$/g, "");
46
+ return slug.length > 0 ? slug : "node";
47
+ }
48
+ function cssVarName(compositionVariableId) {
49
+ return `--${slugify(compositionVariableId)}`;
50
+ }
51
+ function escapeHtml(text) {
52
+ return text
53
+ .replace(/&/g, "&")
54
+ .replace(/</g, "&lt;")
55
+ .replace(/>/g, "&gt;")
56
+ .replace(/"/g, "&quot;");
57
+ }
58
+ function round(n) {
59
+ return Math.round(n * 100) / 100;
60
+ }
61
+ function firstVisibleFill(node) {
62
+ const fills = node.fills;
63
+ if (!Array.isArray(fills))
64
+ return null;
65
+ for (const fill of fills) {
66
+ if (isRecord(fill) && fill.visible !== false)
67
+ return fill;
68
+ }
69
+ return null;
70
+ }
71
+ function gradientCss(fill) {
72
+ const stops = fill.gradientStops;
73
+ if (!Array.isArray(stops))
74
+ return null;
75
+ const parts = [];
76
+ for (const stop of stops) {
77
+ if (!isRecord(stop) || typeof stop.position !== "number")
78
+ return null;
79
+ const color = figmaColorToCss(stop.color);
80
+ if (color === null)
81
+ return null;
82
+ parts.push(`${color} ${round(stop.position * 100)}%`);
83
+ }
84
+ if (parts.length === 0)
85
+ return null;
86
+ // ponytail: fixed 180deg; exact angle from gradientHandlePositions when the
87
+ // probe confirms the handle-space math (spec §7.1 probe list).
88
+ return `linear-gradient(180deg, ${parts.join(", ")})`;
89
+ }
90
+ function fillCss(node) {
91
+ const fill = firstVisibleFill(node);
92
+ if (fill === null)
93
+ return null;
94
+ if (fill.type === "SOLID")
95
+ return figmaColorToCss(fill.color);
96
+ if (fill.type === "GRADIENT_LINEAR")
97
+ return gradientCss(fill);
98
+ return null;
99
+ }
100
+ function dropShadowCss(effect) {
101
+ if (!isRecord(effect.offset))
102
+ return null;
103
+ const color = figmaColorToCss(effect.color);
104
+ const { x, y } = effect.offset;
105
+ if (color === null || typeof x !== "number" || typeof y !== "number")
106
+ return null;
107
+ const radius = typeof effect.radius === "number" ? effect.radius : 0;
108
+ return `box-shadow: ${round(x)}px ${round(y)}px ${round(radius)}px ${color}`;
109
+ }
110
+ function effectCss(effect) {
111
+ if (!isRecord(effect) || effect.visible === false)
112
+ return null;
113
+ if (effect.type === "DROP_SHADOW")
114
+ return dropShadowCss(effect);
115
+ if (effect.type === "LAYER_BLUR" && typeof effect.radius === "number")
116
+ return `filter: blur(${round(effect.radius)}px)`;
117
+ return null;
118
+ }
119
+ function effectsCss(node, styles) {
120
+ const effects = node.effects;
121
+ if (!Array.isArray(effects))
122
+ return;
123
+ for (const effect of effects) {
124
+ const css = effectCss(effect);
125
+ if (css !== null)
126
+ styles.push(css);
127
+ }
128
+ }
129
+ function textCss(node, styles) {
130
+ const s = node.style;
131
+ if (!isRecord(s))
132
+ return;
133
+ // fontFamily is the one content-controlled string that reaches CSS — strip
134
+ // quote/escape chars so it can't break out of the font-family value (the
135
+ // whole style attribute is additionally HTML-escaped at emission).
136
+ if (typeof s.fontFamily === "string")
137
+ styles.push(`font-family: '${s.fontFamily.replace(/['"\\;]/g, "")}'`);
138
+ if (typeof s.fontWeight === "number")
139
+ styles.push(`font-weight: ${s.fontWeight}`);
140
+ if (typeof s.fontSize === "number")
141
+ styles.push(`font-size: ${round(s.fontSize)}px`);
142
+ if (typeof s.lineHeightPx === "number")
143
+ styles.push(`line-height: ${round(s.lineHeightPx)}px`);
144
+ if (typeof s.letterSpacing === "number" && s.letterSpacing !== 0)
145
+ styles.push(`letter-spacing: ${round(s.letterSpacing)}px`);
146
+ }
147
+ function uniqueSlug(ctx, name) {
148
+ const base = slugify(name);
149
+ let slug = base;
150
+ let n = 2;
151
+ while (ctx.usedSlugs.has(slug))
152
+ slug = `${base}-${n++}`;
153
+ ctx.usedSlugs.add(slug);
154
+ return slug;
155
+ }
156
+ function backgroundValue(node, ctx) {
157
+ const literal = fillCss(node);
158
+ if (literal === null)
159
+ return null;
160
+ const resolved = ctx.bindings.resolved.find((r) => r.nodeId === node.id && r.property === "fills");
161
+ if (resolved)
162
+ return `var(${cssVarName(resolved.compositionVariableId)}, ${literal})`;
163
+ return literal;
164
+ }
165
+ function unresolvedAttr(node, ctx) {
166
+ const props = ctx.bindings.unresolved.filter((u) => u.nodeId === node.id).map((u) => u.property);
167
+ if (props.length === 0)
168
+ return "";
169
+ return ` data-figma-unresolved="${escapeHtml(props.join(" "))}"`;
170
+ }
171
+ function geometryCss(node, ctx, isRoot) {
172
+ const box = boxOf(node);
173
+ const styles = [];
174
+ if (!box)
175
+ return styles;
176
+ if (isRoot) {
177
+ styles.push("position: relative", `width: ${round(box.width)}px`, `height: ${round(box.height)}px`);
178
+ }
179
+ else {
180
+ styles.push("position: absolute", `left: ${round(box.x - ctx.origin.x)}px`, `top: ${round(box.y - ctx.origin.y)}px`, `width: ${round(box.width)}px`, `height: ${round(box.height)}px`);
181
+ }
182
+ return styles;
183
+ }
184
+ function decorationCss(node, ctx) {
185
+ const styles = [];
186
+ // backgroundValue is the binding-aware path (var(--slug, literal)) — TEXT
187
+ // color goes through it too, so a token-bound text fill keeps its link.
188
+ const bg = backgroundValue(node, ctx);
189
+ if (node.type === "TEXT") {
190
+ if (bg !== null)
191
+ styles.push(`color: ${bg}`);
192
+ textCss(node, styles);
193
+ }
194
+ else if (bg !== null) {
195
+ styles.push(`background: ${bg}`);
196
+ }
197
+ if (node.type === "ELLIPSE") {
198
+ styles.push("border-radius: 50%");
199
+ }
200
+ else if (typeof node.cornerRadius === "number" && node.cornerRadius > 0) {
201
+ styles.push(`border-radius: ${round(node.cornerRadius)}px`);
202
+ }
203
+ if (node.clipsContent === true)
204
+ styles.push("overflow: hidden");
205
+ if (typeof node.opacity === "number" && node.opacity < 1)
206
+ styles.push(`opacity: ${round(node.opacity)}`);
207
+ effectsCss(node, styles);
208
+ return styles;
209
+ }
210
+ // ponytail: depth cap so a runaway auto-generated tree degrades to a skip
211
+ // instead of a RangeError; real figma frames are nowhere near this deep.
212
+ const MAX_DEPTH = 500;
213
+ function renderChildren(node, ctx, depth) {
214
+ const childHtml = [];
215
+ for (const child of childDocuments(node)) {
216
+ const rendered = renderNodeHtml(child, ctx, false, depth + 1);
217
+ if (rendered.length > 0)
218
+ childHtml.push(rendered);
219
+ }
220
+ return childHtml.length > 0 ? `\n${childHtml.join("\n")}\n` : "";
221
+ }
222
+ function renderNodeHtml(node, ctx, isRoot, depth = 0) {
223
+ if (node.visible === false || depth > MAX_DEPTH)
224
+ return "";
225
+ const slug = uniqueSlug(ctx, node.name);
226
+ const style = escapeHtml([...geometryCss(node, ctx, isRoot), ...decorationCss(node, ctx)].join("; "));
227
+ const idAttrs = `id="${slug}" data-figma-id="${escapeHtml(node.id)}"${unresolvedAttr(node, ctx)}`;
228
+ if (RASTERIZE_TYPES.has(node.type)) {
229
+ ctx.rasterize.push({ nodeId: node.id, name: node.name, slug });
230
+ return `<img ${idAttrs} data-figma-rasterize="${escapeHtml(node.id)}" alt="${escapeHtml(node.name)}" style="${style}" />`;
231
+ }
232
+ if (node.type === "TEXT") {
233
+ const text = typeof node.characters === "string" ? escapeHtml(node.characters) : "";
234
+ return `<div ${idAttrs} style="${style}">${text}</div>`;
235
+ }
236
+ return `<div ${idAttrs} style="${style}">${renderChildren(node, ctx, depth)}</div>`;
237
+ }
238
+ export function nodeToHtml(root, bindings) {
239
+ const origin = boxOf(root) ?? { x: 0, y: 0, width: 0, height: 0 };
240
+ const ctx = { origin, bindings, rasterize: [], usedSlugs: new Set() };
241
+ const html = renderNodeHtml(root, ctx, true);
242
+ return { html, rasterize: ctx.rasterize };
243
+ }
244
+ //# sourceMappingURL=nodeToHtml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodeToHtml.js","sourceRoot":"","sources":["../../src/figma/nodeToHtml.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAchD,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,QAAQ;IACR,mBAAmB;IACnB,MAAM;IACN,MAAM;IACN,SAAS;IACT,iBAAiB;CAClB,CAAC,CAAC;AASH,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACrD,CAAC;AAED,SAAS,KAAK,CAAC,IAAuB;IACpC,MAAM,CAAC,GAAG,IAAI,CAAC,mBAAmB,CAAC;IACnC,IACE,QAAQ,CAAC,CAAC,CAAC;QACX,OAAO,CAAC,CAAC,CAAC,KAAK,QAAQ;QACvB,OAAO,CAAC,CAAC,CAAC,KAAK,QAAQ;QACvB,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAC3B,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ;QAE5B,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;IAC9D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,MAAM,IAAI,GAAG,IAAI;SACd,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;AACzC,CAAC;AAED,SAAS,UAAU,CAAC,qBAA6B;IAC/C,OAAO,KAAK,OAAO,CAAC,qBAAqB,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI;SACR,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,KAAK,CAAC,CAAS;IACtB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AACnC,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAuB;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACzB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC;IAC5D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,WAAW,CAAC,IAA6B;IAChD,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC;IACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACtE,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,4EAA4E;IAC5E,+DAA+D;IAC/D,OAAO,2BAA2B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AACxD,CAAC;AAED,SAAS,OAAO,CAAC,IAAuB;IACtC,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9D,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB;QAAE,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;IAC9D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,aAAa,CAAC,MAA+B;IACpD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5C,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC;IAC/B,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAClF,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,OAAO,eAAe,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,EAAE,CAAC;AAC/E,CAAC;AAED,SAAS,SAAS,CAAC,MAAe;IAChC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAC/D,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa;QAAE,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;IAChE,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ;QACnE,OAAO,gBAAgB,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;IACnD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,UAAU,CAAC,IAAuB,EAAE,MAAgB;IAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC7B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO;IACpC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,GAAG,KAAK,IAAI;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrC,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,IAAuB,EAAE,MAAgB;IACxD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;IACrB,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO;IACzB,2EAA2E;IAC3E,yEAAyE;IACzE,mEAAmE;IACnE,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ;QAClC,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;IACxE,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ;QAAE,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;IAClF,IAAI,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ;QAAE,MAAM,CAAC,IAAI,CAAC,cAAc,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACrF,IAAI,OAAO,CAAC,CAAC,YAAY,KAAK,QAAQ;QAAE,MAAM,CAAC,IAAI,CAAC,gBAAgB,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAC/F,IAAI,OAAO,CAAC,CAAC,aAAa,KAAK,QAAQ,IAAI,CAAC,CAAC,aAAa,KAAK,CAAC;QAC9D,MAAM,CAAC,IAAI,CAAC,mBAAmB,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC;AASD,SAAS,UAAU,CAAC,GAAkB,EAAE,IAAY;IAClD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,IAAI,GAAG,IAAI,CAAC;IAChB,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;IACxD,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACxB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,eAAe,CAAC,IAAuB,EAAE,GAAkB;IAClE,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAClC,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CACzC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO,CACtD,CAAC;IACF,IAAI,QAAQ;QAAE,OAAO,OAAO,UAAU,CAAC,QAAQ,CAAC,qBAAqB,CAAC,KAAK,OAAO,GAAG,CAAC;IACtF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,cAAc,CAAC,IAAuB,EAAE,GAAkB;IACjE,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IACjG,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,OAAO,2BAA2B,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;AACnE,CAAC;AAED,SAAS,WAAW,CAAC,IAAuB,EAAE,GAAkB,EAAE,MAAe;IAC/E,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IACxB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,CAAC,GAAG;QAAE,OAAO,MAAM,CAAC;IACxB,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,CAAC,IAAI,CACT,oBAAoB,EACpB,UAAU,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAC9B,WAAW,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CACjC,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CACT,oBAAoB,EACpB,SAAS,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EACxC,QAAQ,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EACvC,UAAU,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAC9B,WAAW,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CACjC,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,aAAa,CAAC,IAAuB,EAAE,GAAkB;IAChE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,0EAA0E;IAC1E,wEAAwE;IACxE,MAAM,EAAE,GAAG,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACtC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACzB,IAAI,EAAE,KAAK,IAAI;YAAE,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAC7C,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACxB,CAAC;SAAM,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;QACvB,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;IACnC,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACpC,CAAC;SAAM,IAAI,OAAO,IAAI,CAAC,YAAY,KAAK,QAAQ,IAAI,IAAI,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;QAC1E,MAAM,CAAC,IAAI,CAAC,kBAAkB,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,IAAI,CAAC,YAAY,KAAK,IAAI;QAAE,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAChE,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,YAAY,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACjD,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACzB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,0EAA0E;AAC1E,yEAAyE;AACzE,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB,SAAS,cAAc,CAAC,IAAuB,EAAE,GAAkB,EAAE,KAAa;IAChF,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QAC9D,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;AACnE,CAAC;AAED,SAAS,cAAc,CACrB,IAAuB,EACvB,GAAkB,EAClB,MAAe,EACf,KAAK,GAAG,CAAC;IAET,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,IAAI,KAAK,GAAG,SAAS;QAAE,OAAO,EAAE,CAAC;IAC3D,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,UAAU,CACtB,CAAC,GAAG,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,GAAG,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAC5E,CAAC;IACF,MAAM,OAAO,GAAG,OAAO,IAAI,oBAAoB,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;IAElG,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,OAAO,QAAQ,OAAO,0BAA0B,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,KAAK,MAAM,CAAC;IAC5H,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpF,OAAO,QAAQ,OAAO,WAAW,KAAK,KAAK,IAAI,QAAQ,CAAC;IAC1D,CAAC;IAED,OAAO,QAAQ,OAAO,WAAW,KAAK,KAAK,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC;AACtF,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,IAAuB,EACvB,QAA+B;IAE/B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IAClE,MAAM,GAAG,GAAkB,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;IACrF,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC7C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { FigmaRef } from "./types";
2
+ export declare function parseFigmaRef(input: string): FigmaRef;
3
+ //# sourceMappingURL=parseFigmaRef.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseFigmaRef.d.ts","sourceRoot":"","sources":["../../src/figma/parseFigmaRef.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAQxC,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAuBrD"}
@@ -0,0 +1,31 @@
1
+ const FILE_KEY_RE = /\/(?:design|file|proto)\/([A-Za-z0-9]+)/;
2
+ function normalizeNodeId(raw) {
3
+ return raw.replaceAll("-", ":");
4
+ }
5
+ export function parseFigmaRef(input) {
6
+ const trimmed = input.trim();
7
+ if (trimmed.length === 0)
8
+ throw new Error("parseFigmaRef: empty input");
9
+ if (!trimmed.includes("/")) {
10
+ const colon = trimmed.indexOf(":");
11
+ if (colon === -1)
12
+ return { fileKey: trimmed };
13
+ const fileKey = trimmed.slice(0, colon);
14
+ const node = trimmed.slice(colon + 1);
15
+ if (fileKey.length === 0)
16
+ throw new Error(`parseFigmaRef: invalid ref "${input}"`);
17
+ return node.length > 0 ? { fileKey, nodeId: normalizeNodeId(node) } : { fileKey };
18
+ }
19
+ const keyMatch = trimmed.match(FILE_KEY_RE);
20
+ const fileKey = keyMatch?.[1];
21
+ if (fileKey === undefined)
22
+ throw new Error(`parseFigmaRef: no fileKey in "${input}"`);
23
+ const q = trimmed.indexOf("?");
24
+ if (q !== -1) {
25
+ const raw = new URLSearchParams(trimmed.slice(q + 1)).get("node-id");
26
+ if (raw !== null && raw.length > 0)
27
+ return { fileKey, nodeId: normalizeNodeId(raw) };
28
+ }
29
+ return { fileKey };
30
+ }
31
+ //# sourceMappingURL=parseFigmaRef.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseFigmaRef.js","sourceRoot":"","sources":["../../src/figma/parseFigmaRef.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAAG,yCAAyC,CAAC;AAE9D,SAAS,eAAe,CAAC,GAAW;IAClC,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAExE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,KAAK,KAAK,CAAC,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;QAC9C,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QACtC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,KAAK,GAAG,CAAC,CAAC;QACnF,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;IACpF,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,OAAO,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,KAAK,GAAG,CAAC,CAAC;IAEtF,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACrE,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;IACvF,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,CAAC;AACrB,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Binding resolution pass (design spec §7.1): scan a node tree's complete
3
+ * binding set — boundVariables and style ids, alias chains honored — and
4
+ * partition against the binding index BEFORE any CSS is emitted.
5
+ *
6
+ * Exact-ID matching only. A missed link bakes a visually-correct literal;
7
+ * a wrong link silently changes color at the next brand refresh. Never
8
+ * match by value or name.
9
+ *
10
+ * NOTE: the consumer-side boundVariables shape is probe-flagged in the spec
11
+ * (§7.1 pre-build probes). Extraction here is tolerant: single alias objects
12
+ * and arrays of alias objects both count; unknown shapes are ignored rather
13
+ * than guessed at.
14
+ */
15
+ import type { FigmaBindingRecord } from "./bindings";
16
+ import type { FigmaNodeDocument } from "./client";
17
+ export interface BindingSite {
18
+ nodeId: string;
19
+ /** boundVariables property ("fills") or style slot prefixed "style:" ("style:text") */
20
+ property: string;
21
+ figmaId: string;
22
+ }
23
+ export interface ResolvedBindingSite extends BindingSite {
24
+ compositionVariableId: string;
25
+ }
26
+ export interface ResolveBindingsResult {
27
+ resolved: ResolvedBindingSite[];
28
+ unresolved: BindingSite[];
29
+ }
30
+ export declare function resolveBindings(node: FigmaNodeDocument, index: FigmaBindingRecord[]): ResolveBindingsResult;
31
+ //# sourceMappingURL=resolveBindings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveBindings.d.ts","sourceRoot":"","sources":["../../src/figma/resolveBindings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAGlD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACtD,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,UAAU,EAAE,WAAW,EAAE,CAAC;CAC3B;AAgDD,wBAAgB,eAAe,CAC7B,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,kBAAkB,EAAE,GAC1B,qBAAqB,CAYvB"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Binding resolution pass (design spec §7.1): scan a node tree's complete
3
+ * binding set — boundVariables and style ids, alias chains honored — and
4
+ * partition against the binding index BEFORE any CSS is emitted.
5
+ *
6
+ * Exact-ID matching only. A missed link bakes a visually-correct literal;
7
+ * a wrong link silently changes color at the next brand refresh. Never
8
+ * match by value or name.
9
+ *
10
+ * NOTE: the consumer-side boundVariables shape is probe-flagged in the spec
11
+ * (§7.1 pre-build probes). Extraction here is tolerant: single alias objects
12
+ * and arrays of alias objects both count; unknown shapes are ignored rather
13
+ * than guessed at.
14
+ */
15
+ import { childDocuments } from "./nodeDocument.js";
16
+ function isRecord(value) {
17
+ return typeof value === "object" && value !== null;
18
+ }
19
+ function aliasId(value) {
20
+ if (isRecord(value) && value.type === "VARIABLE_ALIAS" && typeof value.id === "string")
21
+ return value.id;
22
+ return null;
23
+ }
24
+ function boundVariableSites(node, out) {
25
+ const bound = node.boundVariables;
26
+ if (!isRecord(bound))
27
+ return;
28
+ for (const [property, value] of Object.entries(bound)) {
29
+ const candidates = Array.isArray(value) ? value : [value];
30
+ for (const item of candidates) {
31
+ const id = aliasId(item);
32
+ if (id)
33
+ out.push({ nodeId: node.id, property, figmaId: id });
34
+ }
35
+ }
36
+ }
37
+ function styleSites(node, out) {
38
+ const styles = node.styles;
39
+ if (!isRecord(styles))
40
+ return;
41
+ for (const [slot, styleId] of Object.entries(styles)) {
42
+ if (typeof styleId === "string" && styleId.length > 0)
43
+ out.push({ nodeId: node.id, property: `style:${slot}`, figmaId: styleId });
44
+ }
45
+ }
46
+ function collectSites(node, out) {
47
+ boundVariableSites(node, out);
48
+ styleSites(node, out);
49
+ for (const child of childDocuments(node))
50
+ collectSites(child, out);
51
+ }
52
+ function findInIndex(index, figmaId) {
53
+ for (const b of index) {
54
+ if (b.figmaId === figmaId)
55
+ return b;
56
+ if (b.aliasChain?.includes(figmaId))
57
+ return b;
58
+ if (b.key !== undefined && b.key === figmaId)
59
+ return b;
60
+ }
61
+ return null;
62
+ }
63
+ export function resolveBindings(node, index) {
64
+ const sites = [];
65
+ collectSites(node, sites);
66
+ const resolved = [];
67
+ const unresolved = [];
68
+ for (const site of sites) {
69
+ const match = findInIndex(index, site.figmaId);
70
+ if (match)
71
+ resolved.push({ ...site, compositionVariableId: match.compositionVariableId });
72
+ else
73
+ unresolved.push(site);
74
+ }
75
+ return { resolved, unresolved };
76
+ }
77
+ //# sourceMappingURL=resolveBindings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveBindings.js","sourceRoot":"","sources":["../../src/figma/resolveBindings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAkBhD,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACrD,CAAC;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ;QACpF,OAAO,KAAK,CAAC,EAAE,CAAC;IAClB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAuB,EAAE,GAAkB;IACrE,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC;IAClC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO;IAC7B,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC1D,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC9B,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;YACzB,IAAI,EAAE;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,IAAuB,EAAE,GAAkB;IAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO;IAC9B,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACrD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YACnD,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,IAAuB,EAAE,GAAkB;IAC/D,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC9B,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACtB,KAAK,MAAM,KAAK,IAAI,cAAc,CAAC,IAAI,CAAC;QAAE,YAAY,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACrE,CAAC;AAED,SAAS,WAAW,CAAC,KAA2B,EAAE,OAAe;IAC/D,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO;YAAE,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO,CAAC,CAAC;QAC9C,IAAI,CAAC,CAAC,GAAG,KAAK,SAAS,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO;YAAE,OAAO,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,IAAuB,EACvB,KAA2B;IAE3B,MAAM,KAAK,GAAkB,EAAE,CAAC;IAChC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAE1B,MAAM,QAAQ,GAA0B,EAAE,CAAC;IAC3C,MAAM,UAAU,GAAkB,EAAE,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,KAAK;YAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,qBAAqB,EAAE,KAAK,CAAC,qBAAqB,EAAE,CAAC,CAAC;;YACrF,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;AAClC,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Sanitize a figma-exported SVG before it touches disk (design spec §5).
3
+ *
4
+ * Threat model: figma-SHAPED exports, hardened against the cheap adversarial
5
+ * variants (nesting, unquoted attrs, scheme smuggling). This is a lexical
6
+ * pass, not a general-purpose HTML sanitizer — content from arbitrary
7
+ * untrusted sources should go through a real sanitizer (DOMPurify) instead.
8
+ * Strips:
9
+ * - <script> elements (with content) and <style> blocks (@import exfil)
10
+ * - <foreignObject> subtrees (arbitrary embedded HTML)
11
+ * - on* event-handler attributes (quoted and unquoted)
12
+ * - href/xlink:href values unless local fragment (#id) or data:image/
13
+ *
14
+ * Keeps local fragment refs (#id) and data:image embeds, which figma uses
15
+ * for clip paths and embedded rasters.
16
+ */
17
+ export declare function sanitizeSvg(svg: string): string;
18
+ //# sourceMappingURL=sanitizeSvg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizeSvg.d.ts","sourceRoot":"","sources":["../../src/figma/sanitizeSvg.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAaH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAwB/C"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Sanitize a figma-exported SVG before it touches disk (design spec §5).
3
+ *
4
+ * Threat model: figma-SHAPED exports, hardened against the cheap adversarial
5
+ * variants (nesting, unquoted attrs, scheme smuggling). This is a lexical
6
+ * pass, not a general-purpose HTML sanitizer — content from arbitrary
7
+ * untrusted sources should go through a real sanitizer (DOMPurify) instead.
8
+ * Strips:
9
+ * - <script> elements (with content) and <style> blocks (@import exfil)
10
+ * - <foreignObject> subtrees (arbitrary embedded HTML)
11
+ * - on* event-handler attributes (quoted and unquoted)
12
+ * - href/xlink:href values unless local fragment (#id) or data:image/
13
+ *
14
+ * Keeps local fragment refs (#id) and data:image embeds, which figma uses
15
+ * for clip paths and embedded rasters.
16
+ */
17
+ /** Apply a replacement until the output stops changing (defeats nesting). */
18
+ function replaceStable(input, pattern, replacement) {
19
+ let out = input;
20
+ let prev;
21
+ do {
22
+ prev = out;
23
+ out = out.replace(pattern, replacement);
24
+ } while (out !== prev);
25
+ return out;
26
+ }
27
+ export function sanitizeSvg(svg) {
28
+ let out = svg;
29
+ out = replaceStable(out, /<script\b[\s\S]*?<\/script\b[^>]*>/gi, "");
30
+ out = replaceStable(out, /<script\b[^>]*\/>/gi, "");
31
+ out = replaceStable(out, /<style\b[\s\S]*?<\/style\b[^>]*>/gi, "");
32
+ out = replaceStable(out, /<foreignObject\b[\s\S]*?<\/foreignObject\b[^>]*>/gi, "");
33
+ out = replaceStable(out, /<foreignObject\b[^>]*\/>/gi, "");
34
+ // Nesting leaves inert orphan close tags after the stable pass — drop them.
35
+ out = out.replace(/<\/(?:script|style|foreignObject)\b[^>]*>/gi, "");
36
+ // on* handler attributes: quoted and unquoted forms.
37
+ out = replaceStable(out, /\son[a-z]+\s*=\s*"[^"]*"/gi, "");
38
+ out = replaceStable(out, /\son[a-z]+\s*=\s*'[^']*'/gi, "");
39
+ out = replaceStable(out, /\son[a-z]+\s*=\s*[^\s>'"]+/gi, "");
40
+ // href-like attributes: POSITIVE allowlist — keep only local fragments and
41
+ // data:image embeds; drop everything else (javascript:, https?:, blob:,
42
+ // vbscript:, data:text/html, protocol-relative, …).
43
+ // xmlns declarations are attribute *names*, not href values — untouched.
44
+ out = out.replace(/\s(href|xlink:href)\s*=\s*"([^"]*)"/gi, (m, attr, value) => isAllowedHref(value) ? m : "");
45
+ out = out.replace(/\s(href|xlink:href)\s*=\s*'([^']*)'/gi, (m, attr, value) => isAllowedHref(value) ? m : "");
46
+ return out;
47
+ }
48
+ function isAllowedHref(value) {
49
+ const v = value.trim().toLowerCase();
50
+ return v.startsWith("#") || v.startsWith("data:image/");
51
+ }
52
+ //# sourceMappingURL=sanitizeSvg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizeSvg.js","sourceRoot":"","sources":["../../src/figma/sanitizeSvg.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,6EAA6E;AAC7E,SAAS,aAAa,CAAC,KAAa,EAAE,OAAe,EAAE,WAAmB;IACxE,IAAI,GAAG,GAAG,KAAK,CAAC;IAChB,IAAI,IAAI,CAAC;IACT,GAAG,CAAC;QACF,IAAI,GAAG,GAAG,CAAC;QACX,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC1C,CAAC,QAAQ,GAAG,KAAK,IAAI,EAAE;IACvB,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,GAAG,GAAG,GAAG,CAAC;IACd,GAAG,GAAG,aAAa,CAAC,GAAG,EAAE,sCAAsC,EAAE,EAAE,CAAC,CAAC;IACrE,GAAG,GAAG,aAAa,CAAC,GAAG,EAAE,qBAAqB,EAAE,EAAE,CAAC,CAAC;IACpD,GAAG,GAAG,aAAa,CAAC,GAAG,EAAE,oCAAoC,EAAE,EAAE,CAAC,CAAC;IACnE,GAAG,GAAG,aAAa,CAAC,GAAG,EAAE,oDAAoD,EAAE,EAAE,CAAC,CAAC;IACnF,GAAG,GAAG,aAAa,CAAC,GAAG,EAAE,4BAA4B,EAAE,EAAE,CAAC,CAAC;IAC3D,4EAA4E;IAC5E,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,6CAA6C,EAAE,EAAE,CAAC,CAAC;IACrE,qDAAqD;IACrD,GAAG,GAAG,aAAa,CAAC,GAAG,EAAE,4BAA4B,EAAE,EAAE,CAAC,CAAC;IAC3D,GAAG,GAAG,aAAa,CAAC,GAAG,EAAE,4BAA4B,EAAE,EAAE,CAAC,CAAC;IAC3D,GAAG,GAAG,aAAa,CAAC,GAAG,EAAE,8BAA8B,EAAE,EAAE,CAAC,CAAC;IAC7D,2EAA2E;IAC3E,wEAAwE;IACxE,oDAAoD;IACpD,yEAAyE;IACzE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,uCAAuC,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,KAAa,EAAE,EAAE,CAC5F,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAC9B,CAAC;IACF,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,uCAAuC,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,KAAa,EAAE,EAAE,CAC5F,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAC9B,CAAC;IACF,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,aAAa,CAAC,KAAa;IAClC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,OAAO,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Phase 2 translator: figma variables → composition brand-variable entries,
3
+ * a human-readable sidecar, and binding-index records (design spec §6, §7.1).
4
+ *
5
+ * Pure — REST payload in, artifacts out. Alias chains are walked to the leaf
6
+ * value (cycle-safe) but the binding records the semantic id the designer
7
+ * bound, so a swapped primitive doesn't orphan the link.
8
+ */
9
+ import type { FigmaVariablesResult } from "./client";
10
+ import type { FigmaBindingRecord } from "./bindings";
11
+ /** data-composition-variables entry (runtime getVariables contract). */
12
+ export interface CompositionVariableEntry {
13
+ id: string;
14
+ type: "string" | "number" | "color" | "boolean";
15
+ label: string;
16
+ default: string | number | boolean;
17
+ brandRole?: string;
18
+ }
19
+ export interface FigmaTokenSidecarEntry {
20
+ name: string;
21
+ type: string;
22
+ figmaId: string;
23
+ key?: string;
24
+ value: string | number | boolean | null;
25
+ }
26
+ export interface FigmaTokensSidecar {
27
+ source: TokenSource;
28
+ tokens: FigmaTokenSidecarEntry[];
29
+ }
30
+ export interface TokenSource {
31
+ fileKey: string;
32
+ version: string;
33
+ }
34
+ export interface TokensToVariablesResult {
35
+ entries: CompositionVariableEntry[];
36
+ bindings: FigmaBindingRecord[];
37
+ sidecar: FigmaTokensSidecar;
38
+ }
39
+ export declare function tokensToVariables(vars: FigmaVariablesResult, source: TokenSource): TokensToVariablesResult;
40
+ //# sourceMappingURL=tokensToVariables.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokensToVariables.d.ts","sourceRoot":"","sources":["../../src/figma/tokensToVariables.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAwB,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrD,wEAAwE;AACxE,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,sBAAsB,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,wBAAwB,EAAE,CAAC;IACpC,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,OAAO,EAAE,kBAAkB,CAAC;CAC7B;AA4ED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,oBAAoB,EAC1B,MAAM,EAAE,WAAW,GAClB,uBAAuB,CAgDzB"}