terrazzo-plugin-figma-json 0.1.1 → 0.2.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 CHANGED
@@ -81,6 +81,9 @@ Without a resolver, all tokens are output to a single file (`tokens.figma.json`
81
81
  | `fontWeight` | Number or String | Values pass through with validation |
82
82
  | `number` | Number or Boolean | Numbers pass through; use `com.figma.type: "boolean"` for booleans |
83
83
  | `typography` | Split tokens | Split into fontFamily, fontSize, fontWeight, lineHeight, letterSpacing |
84
+ | `shadow` | Split tokens | Split into color, offsetX, offsetY, blur, spread; indexed for multiple layers (`inset` not applicable in Figma) |
85
+ | `border` | Split tokens (partial) | Split into color, width (`style` not applicable in Figma) |
86
+ | `gradient` | Split tokens (partial) | Stop colors extracted (`position` not applicable in Figma) |
84
87
 
85
88
  ### Typography Token Splitting
86
89
 
@@ -121,11 +124,113 @@ Figma doesn't support composite typography tokens, so they're automatically spli
121
124
 
122
125
  Note: `lineHeight` is converted from a unitless multiplier to absolute px (see [Figma Limitations](#figma-limitations)).
123
126
 
127
+ ### Shadow Token Splitting
128
+
129
+ Shadow tokens are split into individual sub-tokens. Array shadows (multiple layers) use indexed prefixes.
130
+
131
+ **Input:**
132
+ ```json
133
+ {
134
+ "shadow": {
135
+ "$type": "shadow",
136
+ "medium": {
137
+ "$value": {
138
+ "color": { "colorSpace": "srgb", "components": [0, 0, 0], "alpha": 0.2 },
139
+ "offsetX": { "value": 0, "unit": "px" },
140
+ "offsetY": { "value": 4, "unit": "px" },
141
+ "blur": { "value": 8, "unit": "px" },
142
+ "spread": { "value": 0, "unit": "px" }
143
+ }
144
+ }
145
+ }
146
+ }
147
+ ```
148
+
149
+ **Output:**
150
+ ```json
151
+ {
152
+ "shadow": {
153
+ "medium": {
154
+ "color": { "$type": "color", "$value": { "colorSpace": "srgb", "components": [0, 0, 0], "alpha": 0.2 } },
155
+ "offsetX": { "$type": "dimension", "$value": { "value": 0, "unit": "px" } },
156
+ "offsetY": { "$type": "dimension", "$value": { "value": 4, "unit": "px" } },
157
+ "blur": { "$type": "dimension", "$value": { "value": 8, "unit": "px" } },
158
+ "spread": { "$type": "dimension", "$value": { "value": 0, "unit": "px" } }
159
+ }
160
+ }
161
+ }
162
+ ```
163
+
164
+ Note: Single shadows produce flat sub-tokens. Multiple shadow layers (arrays with 2+ items) use indexed prefixes (`0.color`, `0.offsetX`, ..., `1.color`, etc.). The `inset` property is dropped since variables cannot be applied to inset shadows in Figma.
165
+
166
+ ### Border Token Splitting (Partial)
167
+
168
+ Border tokens are partially split — only `color` and `width` are extracted. The `style` property is dropped since variables cannot be applied to border style in Figma.
169
+
170
+ **Input:**
171
+ ```json
172
+ {
173
+ "border": {
174
+ "$type": "border",
175
+ "default": {
176
+ "$value": {
177
+ "color": { "colorSpace": "srgb", "components": [0.8, 0.8, 0.8] },
178
+ "width": { "value": 1, "unit": "px" },
179
+ "style": "solid"
180
+ }
181
+ }
182
+ }
183
+ }
184
+ ```
185
+
186
+ **Output:**
187
+ ```json
188
+ {
189
+ "border": {
190
+ "default": {
191
+ "color": { "$type": "color", "$value": { "colorSpace": "srgb", "components": [0.8, 0.8, 0.8], "alpha": 1 } },
192
+ "width": { "$type": "dimension", "$value": { "value": 1, "unit": "px" } }
193
+ }
194
+ }
195
+ }
196
+ ```
197
+
198
+ ### Gradient Token Splitting (Partial)
199
+
200
+ Gradient tokens are partially split — only stop colors are extracted. Stop `position` values are dropped since variables cannot be applied to gradient stop positions in Figma.
201
+
202
+ **Input:**
203
+ ```json
204
+ {
205
+ "gradient": {
206
+ "$type": "gradient",
207
+ "primary": {
208
+ "$value": [
209
+ { "color": { "colorSpace": "srgb", "components": [1, 0, 0] }, "position": 0 },
210
+ { "color": { "colorSpace": "srgb", "components": [0, 0, 1] }, "position": 1 }
211
+ ]
212
+ }
213
+ }
214
+ }
215
+ ```
216
+
217
+ **Output:**
218
+ ```json
219
+ {
220
+ "gradient": {
221
+ "primary": {
222
+ "0": { "color": { "$type": "color", "$value": { "colorSpace": "srgb", "components": [1, 0, 0], "alpha": 1 } } },
223
+ "1": { "color": { "$type": "color", "$value": { "colorSpace": "srgb", "components": [0, 0, 1], "alpha": 1 } } }
224
+ }
225
+ }
226
+ }
227
+ ```
228
+
124
229
  ## Unsupported Token Types
125
230
 
126
231
  The following DTCG token types are **not supported** by Figma and will be skipped:
127
232
 
128
- - `shadow`, `border`, `gradient`, `transition`, `strokeStyle`, `cubicBezier`
233
+ - `transition`, `strokeStyle`, `cubicBezier`
129
234
 
130
235
  ## Alias Handling
131
236
 
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEnE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AASzD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,sBAAsB,CAAC,SAAS,CAAC,CAAC;IAC3C,SAAS,CAAC,EAAE,sBAAsB,CAAC,WAAW,CAAC,CAAC;IAChD,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAC;IACjD,kBAAkB,CAAC,EAAE,sBAAsB,CAAC,oBAAoB,CAAC,CAAC;IAClE,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAsSD;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EACrC,aAAa,EACb,OAAO,EACP,SAAS,EACT,kBAAyB,EACzB,QAAQ,GACT,EAAE,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAqOpC"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEnE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AASzD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,sBAAsB,CAAC,SAAS,CAAC,CAAC;IAC3C,SAAS,CAAC,EAAE,sBAAsB,CAAC,WAAW,CAAC,CAAC;IAChD,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAC;IACjD,kBAAkB,CAAC,EAAE,sBAAsB,CAAC,oBAAoB,CAAC,CAAC;IAClE,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAsSD;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EACrC,aAAa,EACb,OAAO,EACP,SAAS,EACT,kBAAyB,EACzB,QAAQ,GACT,EAAE,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CA6OpC"}
@@ -15,12 +15,12 @@ export declare const INTERNAL_KEYS: {
15
15
  /**
16
16
  * Token types supported by Figma.
17
17
  */
18
- export declare const SUPPORTED_TYPES: readonly ["color", "dimension", "duration", "fontFamily", "fontWeight", "number", "typography"];
18
+ export declare const SUPPORTED_TYPES: readonly ["color", "dimension", "duration", "fontFamily", "fontWeight", "number", "typography", "shadow", "border", "gradient"];
19
19
  export type SupportedType = (typeof SUPPORTED_TYPES)[number];
20
20
  /**
21
21
  * Token types that are not supported by Figma and will be dropped with a warning.
22
22
  */
23
- export declare const UNSUPPORTED_TYPES: readonly ["shadow", "border", "gradient", "transition", "strokeStyle", "cubicBezier"];
23
+ export declare const UNSUPPORTED_TYPES: readonly ["transition", "strokeStyle", "cubicBezier"];
24
24
  export type UnsupportedType = (typeof UNSUPPORTED_TYPES)[number];
25
25
  /**
26
26
  * Color spaces that Figma natively supports.
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,+BAA+B,CAAC;AAExD,eAAO,MAAM,SAAS,eAAe,CAAC;AAEtC;;;GAGG;AACH,eAAO,MAAM,aAAa;IACxB,2CAA2C;;IAE3C,8DAA8D;;IAE9D,oCAAoC;;CAE5B,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,eAAe,iGAQlB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D;;GAEG;AACH,eAAO,MAAM,iBAAiB,uFAAwF,CAAC;AAEvH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjE;;GAEG;AACH,eAAO,MAAM,kBAAkB,0BAA2B,CAAC;AAE3D,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,+BAA+B,CAAC;AAExD,eAAO,MAAM,SAAS,eAAe,CAAC;AAEtC;;;GAGG;AACH,eAAO,MAAM,aAAa;IACxB,2CAA2C;;IAE3C,8DAA8D;;IAE9D,oCAAoC;;CAE5B,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,eAAe,iIAWlB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D;;GAEG;AACH,eAAO,MAAM,iBAAiB,uDAAwD,CAAC;AAEvF,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjE;;GAEG;AACH,eAAO,MAAM,kBAAkB,0BAA2B,CAAC;AAE3D,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { ConverterContext, ConverterResult } from '../types.js';
2
+ /**
3
+ * Convert a DTCG border value to Figma-compatible format.
4
+ * Border tokens are partially split into individual sub-tokens.
5
+ * Only color and width are supported; style is dropped.
6
+ */
7
+ export declare function convertBorder(value: unknown, context: ConverterContext): ConverterResult;
8
+ //# sourceMappingURL=border.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"border.d.ts","sourceRoot":"","sources":["../../src/converters/border.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAY,MAAM,aAAa,CAAC;AAK/E;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAqExF"}
@@ -0,0 +1,8 @@
1
+ import type { ConverterContext, ConverterResult } from '../types.js';
2
+ /**
3
+ * Convert a DTCG gradient value to Figma-compatible format.
4
+ * Gradient tokens are partially split: only stop colors are extracted.
5
+ * Stop positions are dropped since they can't be represented as Figma variables.
6
+ */
7
+ export declare function convertGradient(value: unknown, context: ConverterContext): ConverterResult;
8
+ //# sourceMappingURL=gradient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gradient.d.ts","sourceRoot":"","sources":["../../src/converters/gradient.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAY,MAAM,aAAa,CAAC;AAI/E;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,GAAG,eAAe,CA2D1F"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/converters/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAgC,KAAK,aAAa,EAAqB,MAAM,iBAAiB,CAAC;AACtG,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AASrE;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,KAAK,eAAe,CAAC;AAevF;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,aAAa,CAEnE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEvD;AA6CD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAgE/G"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/converters/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAgC,KAAK,aAAa,EAAqB,MAAM,iBAAiB,CAAC;AACtG,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAYrE;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,KAAK,eAAe,CAAC;AAkBvF;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,aAAa,CAEnE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEvD;AA6CD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAgE/G"}
@@ -0,0 +1,11 @@
1
+ import type { ConverterContext, ConverterResult } from '../types.js';
2
+ /**
3
+ * Convert a DTCG shadow value to Figma-compatible format.
4
+ * Shadow tokens are split into individual sub-tokens since Figma
5
+ * doesn't support the composite shadow type.
6
+ *
7
+ * Single shadows produce: color, offsetX, offsetY, blur, spread
8
+ * Multiple shadow layers produce indexed sub-tokens: 0.color, 0.offsetX, ..., 1.color, etc.
9
+ */
10
+ export declare function convertShadow(value: unknown, context: ConverterContext): ConverterResult;
11
+ //# sourceMappingURL=shadow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow.d.ts","sourceRoot":"","sources":["../../src/converters/shadow.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAA6B,MAAM,aAAa,CAAC;AAiIhG;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,GAAG,eAAe,CA0CxF"}
@@ -1 +1 @@
1
- {"version":3,"file":"typography.d.ts","sourceRoot":"","sources":["../../src/converters/typography.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAY,MAAM,aAAa,CAAC;AA+C/E;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAiI5F"}
1
+ {"version":3,"file":"typography.d.ts","sourceRoot":"","sources":["../../src/converters/typography.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAY,MAAM,aAAa,CAAC;AAO/E;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAiI5F"}
package/dist/index.js CHANGED
@@ -23,15 +23,15 @@ const SUPPORTED_TYPES = [
23
23
  "fontFamily",
24
24
  "fontWeight",
25
25
  "number",
26
- "typography"
26
+ "typography",
27
+ "shadow",
28
+ "border",
29
+ "gradient"
27
30
  ];
28
31
  /**
29
32
  * Token types that are not supported by Figma and will be dropped with a warning.
30
33
  */
31
34
  const UNSUPPORTED_TYPES = [
32
- "shadow",
33
- "border",
34
- "gradient",
35
35
  "transition",
36
36
  "strokeStyle",
37
37
  "cubicBezier"
@@ -154,6 +154,44 @@ function isDTCGDurationValue(value) {
154
154
  function isDTCGTypographyValue(value) {
155
155
  return value !== null && typeof value === "object" && !Array.isArray(value);
156
156
  }
157
+ /**
158
+ * Type guard to validate DTCGShadowValue structure.
159
+ * Accepts a single shadow object or an array of shadow objects.
160
+ */
161
+ function isDTCGShadowValue(value) {
162
+ if (Array.isArray(value)) return value.length > 0 && value.every((item) => item !== null && typeof item === "object");
163
+ return value !== null && typeof value === "object";
164
+ }
165
+ /**
166
+ * Type guard to validate DTCGBorderValue structure.
167
+ * Only checks that it's an object - individual properties are validated during conversion.
168
+ */
169
+ function isDTCGBorderValue(value) {
170
+ return value !== null && typeof value === "object" && !Array.isArray(value);
171
+ }
172
+ /**
173
+ * Type guard to validate DTCGGradientValue structure.
174
+ * Checks that it's an array of gradient stops.
175
+ */
176
+ function isDTCGGradientValue(value) {
177
+ return Array.isArray(value) && value.length > 0 && value.every((item) => item !== null && typeof item === "object");
178
+ }
179
+ /**
180
+ * Get the correct alias reference for a composite sub-property.
181
+ * When a composite property references another composite token of the same type,
182
+ * the alias needs to point to the corresponding sub-token.
183
+ *
184
+ * @param aliasOf - The referenced token ID, or undefined if not an alias
185
+ * @param propertyName - The sub-property name (e.g., fontFamily, color, offsetX)
186
+ * @param allTokens - Map of all tokens for type lookup
187
+ * @param parentType - The composite token type (e.g., 'typography', 'shadow', 'border', 'gradient')
188
+ * @returns Adjusted alias target, or undefined if not an alias
189
+ */
190
+ function getSubTokenAlias(aliasOf, propertyName, allTokens, parentType) {
191
+ if (!aliasOf) return;
192
+ if ((allTokens?.[aliasOf])?.$type === parentType) return `${aliasOf}.${propertyName}`;
193
+ return aliasOf;
194
+ }
157
195
 
158
196
  //#endregion
159
197
  //#region src/build.ts
@@ -349,12 +387,18 @@ function buildFigmaJson({ getTransforms, exclude, tokenName, preserveReferences
349
387
  if (transforms.length === 0) return /* @__PURE__ */ new Map();
350
388
  const output = {};
351
389
  for (const transform of transforms) {
352
- if (!transform.token) continue;
353
- const tokenId = transform.token.id;
354
- if (shouldExclude(tokenId)) continue;
355
- const outputName = tokenName?.(transform.token) ?? tokenId;
356
390
  const parsedValue = parseTransformValue(transform.value);
357
391
  if (!parsedValue) continue;
392
+ let tokenId;
393
+ let outputName;
394
+ if (transform.token) {
395
+ tokenId = transform.token.id;
396
+ outputName = tokenName?.(transform.token) ?? tokenId;
397
+ } else if (parsedValue[INTERNAL_KEYS.SPLIT_FROM] && parsedValue[INTERNAL_KEYS.TOKEN_ID]) {
398
+ tokenId = parsedValue[INTERNAL_KEYS.TOKEN_ID];
399
+ outputName = tokenId;
400
+ } else continue;
401
+ if (shouldExclude(tokenId)) continue;
358
402
  removeInternalMetadata(parsedValue);
359
403
  setNestedProperty(output, outputName, parsedValue);
360
404
  }
@@ -666,6 +710,77 @@ function convertDimension(value, context) {
666
710
  };
667
711
  }
668
712
 
713
+ //#endregion
714
+ //#region src/converters/border.ts
715
+ /**
716
+ * Convert a DTCG border value to Figma-compatible format.
717
+ * Border tokens are partially split into individual sub-tokens.
718
+ * Only color and width are supported; style is dropped.
719
+ */
720
+ function convertBorder(value, context) {
721
+ if (!isDTCGBorderValue(value)) {
722
+ context.logger.warn({
723
+ group: "plugin",
724
+ label: PLUGIN_NAME,
725
+ message: `Token "${context.tokenId}" has invalid border value: expected object, got ${typeof value}`
726
+ });
727
+ return {
728
+ value: void 0,
729
+ skip: true
730
+ };
731
+ }
732
+ const border = value;
733
+ const partialAliasOf = context.partialAliasOf;
734
+ const subTokens = [];
735
+ if (border.color !== void 0) {
736
+ const aliasOf = getSubTokenAlias(partialAliasOf?.color, "color", context.allTokens, "border");
737
+ const result = convertColor(border.color, {
738
+ ...context,
739
+ tokenId: `${context.tokenId}.color`
740
+ });
741
+ if (!result.skip) subTokens.push({
742
+ idSuffix: "color",
743
+ $type: "color",
744
+ value: result.value,
745
+ aliasOf
746
+ });
747
+ }
748
+ if (border.width !== void 0) {
749
+ const aliasOf = getSubTokenAlias(partialAliasOf?.width, "width", context.allTokens, "border");
750
+ const result = convertDimension(border.width, {
751
+ ...context,
752
+ tokenId: `${context.tokenId}.width`
753
+ });
754
+ if (!result.skip) subTokens.push({
755
+ idSuffix: "width",
756
+ $type: "dimension",
757
+ value: result.value,
758
+ aliasOf
759
+ });
760
+ }
761
+ if (border.style !== void 0) context.logger.info({
762
+ group: "plugin",
763
+ label: PLUGIN_NAME,
764
+ message: `Token "${context.tokenId}" border "style" property dropped (variables cannot be applied to border style in Figma)`
765
+ });
766
+ if (subTokens.length === 0) {
767
+ context.logger.warn({
768
+ group: "plugin",
769
+ label: PLUGIN_NAME,
770
+ message: `Token "${context.tokenId}" border value has no valid sub-properties`
771
+ });
772
+ return {
773
+ value: void 0,
774
+ skip: true
775
+ };
776
+ }
777
+ return {
778
+ value: void 0,
779
+ split: true,
780
+ subTokens
781
+ };
782
+ }
783
+
669
784
  //#endregion
670
785
  //#region src/converters/duration.ts
671
786
  /**
@@ -865,6 +980,69 @@ function convertFontWeight(value, context) {
865
980
  };
866
981
  }
867
982
 
983
+ //#endregion
984
+ //#region src/converters/gradient.ts
985
+ /**
986
+ * Convert a DTCG gradient value to Figma-compatible format.
987
+ * Gradient tokens are partially split: only stop colors are extracted.
988
+ * Stop positions are dropped since they can't be represented as Figma variables.
989
+ */
990
+ function convertGradient(value, context) {
991
+ if (!isDTCGGradientValue(value)) {
992
+ context.logger.warn({
993
+ group: "plugin",
994
+ label: PLUGIN_NAME,
995
+ message: `Token "${context.tokenId}" has invalid gradient value: expected array of gradient stops, got ${typeof value}`
996
+ });
997
+ return {
998
+ value: void 0,
999
+ skip: true
1000
+ };
1001
+ }
1002
+ const partialAliasOf = context.partialAliasOf;
1003
+ const subTokens = [];
1004
+ let hasPosition = false;
1005
+ for (let i = 0; i < value.length; i++) {
1006
+ const stop = value[i];
1007
+ if (stop.color !== void 0) {
1008
+ const aliasKey = `${i}.color`;
1009
+ const aliasOf = getSubTokenAlias(partialAliasOf?.[aliasKey], aliasKey, context.allTokens, "gradient");
1010
+ const result = convertColor(stop.color, {
1011
+ ...context,
1012
+ tokenId: `${context.tokenId}.${aliasKey}`
1013
+ });
1014
+ if (!result.skip) subTokens.push({
1015
+ idSuffix: aliasKey,
1016
+ $type: "color",
1017
+ value: result.value,
1018
+ aliasOf
1019
+ });
1020
+ }
1021
+ if (stop.position !== void 0) hasPosition = true;
1022
+ }
1023
+ if (hasPosition) context.logger.info({
1024
+ group: "plugin",
1025
+ label: PLUGIN_NAME,
1026
+ message: `Token "${context.tokenId}" gradient "position" values dropped (variables cannot be applied to gradient stop positions in Figma)`
1027
+ });
1028
+ if (subTokens.length === 0) {
1029
+ context.logger.warn({
1030
+ group: "plugin",
1031
+ label: PLUGIN_NAME,
1032
+ message: `Token "${context.tokenId}" gradient value has no valid color stops`
1033
+ });
1034
+ return {
1035
+ value: void 0,
1036
+ skip: true
1037
+ };
1038
+ }
1039
+ return {
1040
+ value: void 0,
1041
+ split: true,
1042
+ subTokens
1043
+ };
1044
+ }
1045
+
868
1046
  //#endregion
869
1047
  //#region src/converters/number.ts
870
1048
  /**
@@ -908,6 +1086,148 @@ function convertNumber(value, context) {
908
1086
  return { value };
909
1087
  }
910
1088
 
1089
+ //#endregion
1090
+ //#region src/converters/shadow.ts
1091
+ /**
1092
+ * Convert a single shadow object's properties into sub-tokens.
1093
+ *
1094
+ * @param shadow - The shadow value object
1095
+ * @param prefix - Prefix for sub-token IDs (empty for single, "0." for arrays)
1096
+ * @param context - Converter context
1097
+ * @param partialAliasOf - Alias information for sub-properties
1098
+ * @returns Array of sub-tokens
1099
+ */
1100
+ function convertShadowLayer(shadow, prefix, context, partialAliasOf) {
1101
+ const subTokens = [];
1102
+ if (shadow.color !== void 0) {
1103
+ const aliasKey = `${prefix}color`;
1104
+ const aliasOf = getSubTokenAlias(partialAliasOf?.[aliasKey], aliasKey, context.allTokens, "shadow");
1105
+ const result = convertColor(shadow.color, {
1106
+ ...context,
1107
+ tokenId: `${context.tokenId}.${aliasKey}`
1108
+ });
1109
+ if (!result.skip) subTokens.push({
1110
+ idSuffix: aliasKey,
1111
+ $type: "color",
1112
+ value: result.value,
1113
+ aliasOf
1114
+ });
1115
+ }
1116
+ if (shadow.offsetX !== void 0) {
1117
+ const aliasKey = `${prefix}offsetX`;
1118
+ const aliasOf = getSubTokenAlias(partialAliasOf?.[aliasKey], aliasKey, context.allTokens, "shadow");
1119
+ const result = convertDimension(shadow.offsetX, {
1120
+ ...context,
1121
+ tokenId: `${context.tokenId}.${aliasKey}`
1122
+ });
1123
+ if (!result.skip) subTokens.push({
1124
+ idSuffix: aliasKey,
1125
+ $type: "dimension",
1126
+ value: result.value,
1127
+ aliasOf
1128
+ });
1129
+ }
1130
+ if (shadow.offsetY !== void 0) {
1131
+ const aliasKey = `${prefix}offsetY`;
1132
+ const aliasOf = getSubTokenAlias(partialAliasOf?.[aliasKey], aliasKey, context.allTokens, "shadow");
1133
+ const result = convertDimension(shadow.offsetY, {
1134
+ ...context,
1135
+ tokenId: `${context.tokenId}.${aliasKey}`
1136
+ });
1137
+ if (!result.skip) subTokens.push({
1138
+ idSuffix: aliasKey,
1139
+ $type: "dimension",
1140
+ value: result.value,
1141
+ aliasOf
1142
+ });
1143
+ }
1144
+ if (shadow.blur !== void 0) {
1145
+ const aliasKey = `${prefix}blur`;
1146
+ const aliasOf = getSubTokenAlias(partialAliasOf?.[aliasKey], aliasKey, context.allTokens, "shadow");
1147
+ const result = convertDimension(shadow.blur, {
1148
+ ...context,
1149
+ tokenId: `${context.tokenId}.${aliasKey}`
1150
+ });
1151
+ if (!result.skip) subTokens.push({
1152
+ idSuffix: aliasKey,
1153
+ $type: "dimension",
1154
+ value: result.value,
1155
+ aliasOf
1156
+ });
1157
+ }
1158
+ if (shadow.spread !== void 0) {
1159
+ const aliasKey = `${prefix}spread`;
1160
+ const aliasOf = getSubTokenAlias(partialAliasOf?.[aliasKey], aliasKey, context.allTokens, "shadow");
1161
+ const result = convertDimension(shadow.spread, {
1162
+ ...context,
1163
+ tokenId: `${context.tokenId}.${aliasKey}`
1164
+ });
1165
+ if (!result.skip) subTokens.push({
1166
+ idSuffix: aliasKey,
1167
+ $type: "dimension",
1168
+ value: result.value,
1169
+ aliasOf
1170
+ });
1171
+ }
1172
+ if (shadow.inset !== void 0) context.logger.info({
1173
+ group: "plugin",
1174
+ label: PLUGIN_NAME,
1175
+ message: `Token "${context.tokenId}" shadow "inset" property dropped (variables cannot be applied to inset shadows in Figma)`
1176
+ });
1177
+ return subTokens;
1178
+ }
1179
+ /**
1180
+ * Convert a DTCG shadow value to Figma-compatible format.
1181
+ * Shadow tokens are split into individual sub-tokens since Figma
1182
+ * doesn't support the composite shadow type.
1183
+ *
1184
+ * Single shadows produce: color, offsetX, offsetY, blur, spread
1185
+ * Multiple shadow layers produce indexed sub-tokens: 0.color, 0.offsetX, ..., 1.color, etc.
1186
+ */
1187
+ function convertShadow(value, context) {
1188
+ if (!isDTCGShadowValue(value)) {
1189
+ context.logger.warn({
1190
+ group: "plugin",
1191
+ label: PLUGIN_NAME,
1192
+ message: `Token "${context.tokenId}" has invalid shadow value: expected object or array, got ${typeof value}`
1193
+ });
1194
+ return {
1195
+ value: void 0,
1196
+ skip: true
1197
+ };
1198
+ }
1199
+ const partialAliasOf = context.partialAliasOf;
1200
+ const subTokens = [];
1201
+ if (Array.isArray(value)) if (value.length === 1) {
1202
+ const layerTokens = convertShadowLayer(value[0], "", context, partialAliasOf);
1203
+ subTokens.push(...layerTokens);
1204
+ } else for (let i = 0; i < value.length; i++) {
1205
+ const layer = value[i];
1206
+ const layerTokens = convertShadowLayer(layer, `${i}.`, context, partialAliasOf);
1207
+ subTokens.push(...layerTokens);
1208
+ }
1209
+ else {
1210
+ const layerTokens = convertShadowLayer(value, "", context, partialAliasOf);
1211
+ subTokens.push(...layerTokens);
1212
+ }
1213
+ if (subTokens.length === 0) {
1214
+ context.logger.warn({
1215
+ group: "plugin",
1216
+ label: PLUGIN_NAME,
1217
+ message: `Token "${context.tokenId}" shadow value has no valid sub-properties`
1218
+ });
1219
+ return {
1220
+ value: void 0,
1221
+ skip: true
1222
+ };
1223
+ }
1224
+ return {
1225
+ value: void 0,
1226
+ split: true,
1227
+ subTokens
1228
+ };
1229
+ }
1230
+
911
1231
  //#endregion
912
1232
  //#region src/converters/line-height.ts
913
1233
  /**
@@ -1002,30 +1322,6 @@ function convertLineHeight(value, context) {
1002
1322
  //#endregion
1003
1323
  //#region src/converters/typography.ts
1004
1324
  /**
1005
- * Get the correct alias reference for a typography sub-property.
1006
- * When a typography property references another typography token,
1007
- * the alias needs to point to the corresponding sub-token.
1008
- *
1009
- * @param aliasOf - The referenced token ID, or undefined if not an alias
1010
- * @param propertyName - The sub-property name (fontFamily, fontSize, etc.)
1011
- * @param allTokens - Map of all tokens for type lookup
1012
- * @returns Adjusted alias target, or undefined if not an alias
1013
- *
1014
- * @example
1015
- * // If typography.base is a typography token:
1016
- * getSubTokenAlias("typography.base", "fontFamily", tokens)
1017
- * // "typography.base.fontFamily"
1018
- *
1019
- * // If dimension.100 is a primitive:
1020
- * getSubTokenAlias("dimension.100", "fontSize", tokens)
1021
- * // "dimension.100" (unchanged)
1022
- */
1023
- function getSubTokenAlias(aliasOf, propertyName, allTokens) {
1024
- if (!aliasOf) return;
1025
- if ((allTokens?.[aliasOf])?.$type === "typography") return `${aliasOf}.${propertyName}`;
1026
- return aliasOf;
1027
- }
1028
- /**
1029
1325
  * Convert a DTCG typography value to Figma-compatible format.
1030
1326
  * Typography tokens are split into individual sub-tokens since Figma
1031
1327
  * doesn't support the composite typography type.
@@ -1057,7 +1353,7 @@ function convertTypography(value, context) {
1057
1353
  const partialAliasOf = context.partialAliasOf;
1058
1354
  const subTokens = [];
1059
1355
  if (typography.fontFamily !== void 0) {
1060
- const aliasOf = getSubTokenAlias(partialAliasOf?.fontFamily, "fontFamily", context.allTokens);
1356
+ const aliasOf = getSubTokenAlias(partialAliasOf?.fontFamily, "fontFamily", context.allTokens, "typography");
1061
1357
  const result = convertFontFamily(typography.fontFamily, {
1062
1358
  ...context,
1063
1359
  tokenId: `${context.tokenId}.fontFamily`
@@ -1071,7 +1367,7 @@ function convertTypography(value, context) {
1071
1367
  }
1072
1368
  let resolvedFontSize;
1073
1369
  if (typography.fontSize !== void 0) {
1074
- const aliasOf = getSubTokenAlias(partialAliasOf?.fontSize, "fontSize", context.allTokens);
1370
+ const aliasOf = getSubTokenAlias(partialAliasOf?.fontSize, "fontSize", context.allTokens, "typography");
1075
1371
  const result = convertDimension(typography.fontSize, {
1076
1372
  ...context,
1077
1373
  tokenId: `${context.tokenId}.fontSize`
@@ -1087,7 +1383,7 @@ function convertTypography(value, context) {
1087
1383
  }
1088
1384
  }
1089
1385
  if (typography.fontWeight !== void 0) {
1090
- const aliasOf = getSubTokenAlias(partialAliasOf?.fontWeight, "fontWeight", context.allTokens);
1386
+ const aliasOf = getSubTokenAlias(partialAliasOf?.fontWeight, "fontWeight", context.allTokens, "typography");
1091
1387
  const result = convertFontWeight(typography.fontWeight, {
1092
1388
  ...context,
1093
1389
  tokenId: `${context.tokenId}.fontWeight`
@@ -1112,7 +1408,7 @@ function convertTypography(value, context) {
1112
1408
  });
1113
1409
  }
1114
1410
  if (typography.letterSpacing !== void 0) {
1115
- const aliasOf = getSubTokenAlias(partialAliasOf?.letterSpacing, "letterSpacing", context.allTokens);
1411
+ const aliasOf = getSubTokenAlias(partialAliasOf?.letterSpacing, "letterSpacing", context.allTokens, "typography");
1116
1412
  const result = convertDimension(typography.letterSpacing, {
1117
1413
  ...context,
1118
1414
  tokenId: `${context.tokenId}.letterSpacing`
@@ -1154,7 +1450,10 @@ const converters = {
1154
1450
  fontFamily: convertFontFamily,
1155
1451
  fontWeight: convertFontWeight,
1156
1452
  number: convertNumber,
1157
- typography: convertTypography
1453
+ typography: convertTypography,
1454
+ shadow: convertShadow,
1455
+ border: convertBorder,
1456
+ gradient: convertGradient
1158
1457
  };
1159
1458
  /**
1160
1459
  * Check if a token type is supported by Figma.
@@ -1450,5 +1749,5 @@ function figmaJsonPlugin(options) {
1450
1749
  }
1451
1750
 
1452
1751
  //#endregion
1453
- export { FIGMA_COLOR_SPACES, FORMAT_ID, INTERNAL_KEYS, PLUGIN_NAME, SUPPORTED_TYPES, UNSUPPORTED_TYPES, buildDefaultInput, createExcludeMatcher, figmaJsonPlugin as default, getPartialAliasOf, hasValidResolverConfig, isDTCGColorValue, isDTCGDimensionValue, isDTCGDurationValue, isDTCGTypographyValue, parseTransformValue, removeInternalMetadata };
1752
+ export { FIGMA_COLOR_SPACES, FORMAT_ID, INTERNAL_KEYS, PLUGIN_NAME, SUPPORTED_TYPES, UNSUPPORTED_TYPES, buildDefaultInput, createExcludeMatcher, figmaJsonPlugin as default, getPartialAliasOf, getSubTokenAlias, hasValidResolverConfig, isDTCGBorderValue, isDTCGColorValue, isDTCGDimensionValue, isDTCGDurationValue, isDTCGGradientValue, isDTCGShadowValue, isDTCGTypographyValue, parseTransformValue, removeInternalMetadata };
1454
1753
  //# sourceMappingURL=index.js.map