* feat(scene-graph): model variables as CSS tokens A variable now has a CSS custom property name, a unit, raw CSS expressions per mode, and each mode a CSS condition (a selector or @media prelude), so code export can treat variables as design tokens rather than resolved literals. Names are derived when not set: Tailwind v4 theme namespaces from the type, the scopes or the leading name segment, so Gray/50 is --color-gray-50. The first token to claim an explicit name keeps it; Figma files contain duplicates, and later claimants fall back to a derived name. FLOAT tokens infer px except for opacity and font weights. Lengths stay in canvas pixels and only convert when written, so rem does not change what the canvas or Figma sees. In .fig, the name goes to codeSyntax.WEB in the form the snippet already uses, or to plugin data when WEB holds something else such as a Tailwind class. Unit, expressions and conditions are OpenPencil plugin data, validated with Valibot. Conditions and expressions reject braces and semicolons because they are written into stylesheets, and an expression whose mode value was edited elsewhere is dropped so the number stays authoritative. * refactor: move token naming to dom-css and keep fig to persistence CSS naming, namespaces and units are CSS projection, which dom-css owns; scene-graph keeps only the token data and the px/rem storage conversion, and fig only persists plugin data, validated for shape. Drop Variable.cssName: codeSyntax.WEB is the single place a token's name lives, read with postcss-value-parser when it is --x or var(--x), so no second copy has to stay in sync with Figma's field. Derived names use es-toolkit kebabCase and twirlwind's Tailwind namespace table, which excludes opacity since Tailwind v4 has no such namespace. Whether a condition or expression is valid CSS is no longer guessed with a regex in fig; the stylesheet generator will check it with cssom where the string enters a stylesheet. * refactor(fig): parse token plugin data with Valibot's parseJson Invalid JSON becomes a validation issue like any wrong shape instead of a caught exception, and the plugin data lookup reuses getOpenPencilPluginValue rather than repeating it. * refactor: use es-toolkit for token expression keys and name segments mapKeys re-keys expressions by file mode id instead of a manual loop, and compact drops empty name segments. Reading expressions keeps the plain filter: pickBy returns Partial<T>, which would need a cast. * fix: keep token expressions on float32 values and rem precision .fig stores numbers as float32 while plugin data keeps the resolved value as a double, so a value such as 1234.567 differed by more than the 1e-6 tolerance and its expression was dropped as stale on reopen. Compare both at float32 precision. Token numbers were written with four decimals, which turned 0.5px into 0.0313rem; six keep every pixel step down to 1/1024px exact. Also note that derived names can collide, so stylesheets take them from variableCSSNames.
16 lines
500 B
TypeScript
16 lines
500 B
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
|
|
import { tokenNumberFromUnit, tokenNumberInUnit } from '@open-pencil/scene-graph'
|
|
|
|
describe('token units', () => {
|
|
test('store rem as canvas pixels and read it back', () => {
|
|
expect(tokenNumberFromUnit(1.5, 'rem')).toBe(24)
|
|
expect(tokenNumberInUnit(24, 'rem')).toBe(1.5)
|
|
})
|
|
|
|
test('store every other unit as written', () => {
|
|
expect(tokenNumberFromUnit(150, 'ms')).toBe(150)
|
|
expect(tokenNumberInUnit(12, 'px')).toBe(12)
|
|
})
|
|
})
|