( parsed: ParsedDocument, id: string, value: string | number | boolean | FontValue | ImageValue, )
| 815 | } |
| 816 | |
| 817 | function handleSetVariableValue( |
| 818 | parsed: ParsedDocument, |
| 819 | id: string, |
| 820 | value: string | number | boolean | FontValue | ImageValue, |
| 821 | ): MutationResult { |
| 822 | const root = findRoot(parsed.document); |
| 823 | if (!root) return EMPTY; |
| 824 | |
| 825 | const modelPath = variablePath(id); |
| 826 | const oldVarDefault = readVariableDefault(parsed.document, id); |
| 827 | |
| 828 | if (isObjectVariableValue(value)) { |
| 829 | // Object values (font / image): write to JSON model only — objects are not |
| 830 | // valid CSS custom property values (LOCKED §7). |
| 831 | writeVariableDefault(parsed.document, id, value); |
| 832 | const p = valueChange(modelPath, oldVarDefault ?? null, value); |
| 833 | return { forward: [p.forward], inverse: [p.inverse] }; |
| 834 | } |
| 835 | |
| 836 | // Scalar values: update the JSON model (B1 — drives the runtime) and also |
| 837 | // keep the CSS custom prop as secondary / compat for compositions that |
| 838 | // CSS-bind directly to --{id}. |
| 839 | const cssVar = `--${id}`; |
| 840 | const rootId = root.getAttribute("data-hf-id"); |
| 841 | const oldStyles = getElementStyles(root); |
| 842 | const oldCssValue = oldStyles[cssVar] ?? null; |
| 843 | const newVal = String(value); |
| 844 | setElementStyles(root, { [cssVar]: newVal }); |
| 845 | writeVariableDefault(parsed.document, id, value); |
| 846 | |
| 847 | // Emit explicit patches for both the JSON model (canonical) and the CSS compat |
| 848 | // prop. Keeping them separate means apply-patches.ts can handle each path type |
| 849 | // purely (variable path → model only; style path → CSS only), so inverse patches |
| 850 | // correctly restore the exact pre-call state without CSS-side-effect ambiguity. |
| 851 | const modelP = valueChange(modelPath, oldVarDefault ?? null, value); |
| 852 | const forward: JsonPatchOp[] = [modelP.forward]; |
| 853 | const inverse: JsonPatchOp[] = [modelP.inverse]; |
| 854 | |
| 855 | if (rootId) { |
| 856 | const cssPatch = scalarChange(stylePath(rootId, cssVar), oldCssValue, newVal); |
| 857 | forward.push(cssPatch.forward); |
| 858 | inverse.push(cssPatch.inverse); |
| 859 | } |
| 860 | |
| 861 | return { forward, inverse }; |
| 862 | } |
| 863 | |
| 864 | // ─── GSAP selector helpers ─────────────────────────────────────────────────── |
| 865 |
no test coverage detected