@compact-design/core 0.1.0 → 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.
@@ -462,7 +462,7 @@ declare const _default: {
462
462
  readonly required: readonly ["type"];
463
463
  readonly properties: {
464
464
  readonly type: {
465
- readonly enum: readonly ["DROP_SHADOW", "INNER_SHADOW", "LAYER_BLUR", "BACKGROUND_BLUR"];
465
+ readonly enum: readonly ["DROP_SHADOW", "INNER_SHADOW", "LAYER_BLUR", "BACKGROUND_BLUR", "NOISE", "TEXTURE", "GLASS", "SHADER"];
466
466
  };
467
467
  readonly color: {
468
468
  readonly oneOf: readonly [{
@@ -489,7 +489,94 @@ declare const _default: {
489
489
  readonly blendMode: {
490
490
  readonly type: "string";
491
491
  };
492
+ readonly showShadowBehindNode: {
493
+ readonly type: "boolean";
494
+ };
495
+ readonly blurType: {
496
+ readonly enum: readonly ["NORMAL", "PROGRESSIVE"];
497
+ };
498
+ readonly startRadius: {
499
+ readonly $ref: "#/$defs/nonNegativeNumber";
500
+ };
501
+ readonly startOffset: {
502
+ readonly $ref: "#/$defs/vector";
503
+ };
504
+ readonly endOffset: {
505
+ readonly $ref: "#/$defs/vector";
506
+ };
507
+ readonly noiseType: {
508
+ readonly enum: readonly ["MONOTONE", "DUOTONE", "MULTITONE"];
509
+ };
510
+ readonly noiseSize: {
511
+ readonly $ref: "#/$defs/nonNegativeNumber";
512
+ };
513
+ readonly density: {
514
+ readonly type: "number";
515
+ readonly minimum: 0;
516
+ readonly maximum: 1;
517
+ };
518
+ readonly secondaryColor: {
519
+ readonly oneOf: readonly [{
520
+ readonly $ref: "#/$defs/hexColor";
521
+ }, {
522
+ readonly $ref: "#/$defs/color";
523
+ }];
524
+ };
525
+ readonly opacity: {
526
+ readonly type: "number";
527
+ readonly minimum: 0;
528
+ readonly maximum: 1;
529
+ };
530
+ readonly clipToShape: {
531
+ readonly type: "boolean";
532
+ };
533
+ readonly lightIntensity: {
534
+ readonly type: "number";
535
+ readonly minimum: 0;
536
+ readonly maximum: 1;
537
+ };
538
+ readonly lightAngle: {
539
+ readonly type: "number";
540
+ };
541
+ readonly refraction: {
542
+ readonly type: "number";
543
+ readonly minimum: 0;
544
+ readonly maximum: 1;
545
+ };
546
+ readonly depth: {
547
+ readonly type: "number";
548
+ readonly minimum: 1;
549
+ };
550
+ readonly dispersion: {
551
+ readonly type: "number";
552
+ readonly minimum: 0;
553
+ readonly maximum: 1;
554
+ };
555
+ readonly id: {
556
+ readonly type: "string";
557
+ readonly minLength: 1;
558
+ };
559
+ readonly properties: {
560
+ readonly type: "object";
561
+ readonly description: "SHADER property assignments keyed by Figma property-definition id. Values pass through verbatim (numbers, booleans, strings, colors, points, gradients, or variable aliases).";
562
+ readonly additionalProperties: {
563
+ readonly type: readonly ["boolean", "string", "number", "object"];
564
+ };
565
+ };
492
566
  };
567
+ readonly allOf: readonly [{
568
+ readonly if: {
569
+ readonly properties: {
570
+ readonly type: {
571
+ readonly const: "SHADER";
572
+ };
573
+ };
574
+ readonly required: readonly ["type"];
575
+ };
576
+ readonly then: {
577
+ readonly required: readonly ["id"];
578
+ };
579
+ }];
493
580
  };
494
581
  readonly shadow: {
495
582
  readonly type: "object";
@@ -873,7 +960,7 @@ declare const _default: {
873
960
  readonly required: readonly ["data"];
874
961
  readonly properties: {
875
962
  readonly windingRule: {
876
- readonly enum: readonly ["NONZERO", "EVENODD"];
963
+ readonly enum: readonly ["NONZERO", "EVENODD", "NONE"];
877
964
  };
878
965
  readonly data: {
879
966
  readonly type: "string";
@@ -1187,7 +1274,7 @@ declare const _default: {
1187
1274
  };
1188
1275
  };
1189
1276
  readonly overflowDirection: {
1190
- readonly enum: readonly ["NONE", "HORIZONTAL_SCROLLING", "VERTICAL_SCROLLING", "HORIZONTAL_AND_VERTICAL_SCROLLING"];
1277
+ readonly enum: readonly ["NONE", "HORIZONTAL", "VERTICAL", "BOTH", "HORIZONTAL_SCROLLING", "VERTICAL_SCROLLING", "HORIZONTAL_AND_VERTICAL_SCROLLING"];
1191
1278
  };
1192
1279
  readonly numberOfFixedChildren: {
1193
1280
  readonly type: "integer";
package/dist/index.cjs CHANGED
@@ -7314,15 +7314,85 @@ function effects(raw) {
7314
7314
  values.push(...raw.effects || []);
7315
7315
  return values.map(effect);
7316
7316
  }
7317
+ function resolveColor(value) {
7318
+ return hexColor(value) || (typeof value === "object" && value ? value : void 0);
7319
+ }
7317
7320
  function effect(value) {
7318
7321
  if (!value || typeof value !== "object") return value;
7322
+ const type = String(value.type || "DROP_SHADOW").toUpperCase();
7323
+ const visible = value.visible !== false;
7324
+ if (type === "DROP_SHADOW" || type === "INNER_SHADOW") {
7325
+ return {
7326
+ type,
7327
+ color: resolveColor(value.color),
7328
+ offset: value.offset || { x: 0, y: 4 },
7329
+ radius: value.blur ?? value.radius ?? 8,
7330
+ spread: value.spread || 0,
7331
+ visible,
7332
+ ...value.blendMode ? { blendMode: value.blendMode } : {},
7333
+ ...type === "DROP_SHADOW" && typeof value.showShadowBehindNode === "boolean" ? { showShadowBehindNode: value.showShadowBehindNode } : {}
7334
+ };
7335
+ }
7336
+ if (type === "LAYER_BLUR" || type === "BACKGROUND_BLUR") {
7337
+ return {
7338
+ type,
7339
+ radius: value.blur ?? value.radius ?? 8,
7340
+ visible,
7341
+ ...value.blurType ? { blurType: value.blurType } : {},
7342
+ ...value.startRadius !== void 0 ? { startRadius: value.startRadius } : {},
7343
+ ...value.startOffset ? { startOffset: value.startOffset } : {},
7344
+ ...value.endOffset ? { endOffset: value.endOffset } : {}
7345
+ };
7346
+ }
7347
+ if (type === "NOISE") {
7348
+ return {
7349
+ type,
7350
+ color: resolveColor(value.color),
7351
+ visible,
7352
+ ...value.blendMode ? { blendMode: value.blendMode } : {},
7353
+ noiseType: value.noiseType || "MONOTONE",
7354
+ noiseSize: value.noiseSize ?? 1,
7355
+ density: value.density ?? 1,
7356
+ ...value.secondaryColor !== void 0 ? { secondaryColor: resolveColor(value.secondaryColor) } : {},
7357
+ ...value.opacity !== void 0 ? { opacity: value.opacity } : {}
7358
+ };
7359
+ }
7360
+ if (type === "TEXTURE") {
7361
+ return {
7362
+ type,
7363
+ visible,
7364
+ noiseSize: value.noiseSize ?? 1,
7365
+ radius: value.blur ?? value.radius ?? 0,
7366
+ clipToShape: value.clipToShape !== false
7367
+ };
7368
+ }
7369
+ if (type === "GLASS") {
7370
+ return {
7371
+ type,
7372
+ visible,
7373
+ lightIntensity: value.lightIntensity ?? 0.5,
7374
+ lightAngle: value.lightAngle ?? -45,
7375
+ refraction: value.refraction ?? 0.5,
7376
+ depth: value.depth ?? 1,
7377
+ dispersion: value.dispersion ?? 0.1,
7378
+ radius: value.blur ?? value.radius ?? 0
7379
+ };
7380
+ }
7381
+ if (type === "SHADER") {
7382
+ return {
7383
+ type,
7384
+ id: typeof value.id === "string" ? value.id : "",
7385
+ visible,
7386
+ ...value.properties && typeof value.properties === "object" && !Array.isArray(value.properties) ? { properties: value.properties } : {}
7387
+ };
7388
+ }
7319
7389
  return {
7320
- type: String(value.type || "DROP_SHADOW").toUpperCase(),
7321
- color: hexColor(value.color) || (typeof value.color === "object" ? value.color : void 0),
7390
+ type,
7391
+ color: resolveColor(value.color),
7322
7392
  offset: value.offset || { x: 0, y: 4 },
7323
7393
  radius: value.blur ?? value.radius ?? 8,
7324
7394
  spread: value.spread || 0,
7325
- visible: value.visible !== false
7395
+ visible
7326
7396
  };
7327
7397
  }
7328
7398
  var passthrough = [
@@ -7399,6 +7469,9 @@ function normalizeNode(node, parent, path) {
7399
7469
  if (typeof raw.overflowDirection === "string") {
7400
7470
  const overflowDirections = {
7401
7471
  NONE: "NONE",
7472
+ HORIZONTAL: "HORIZONTAL",
7473
+ VERTICAL: "VERTICAL",
7474
+ BOTH: "BOTH",
7402
7475
  HORIZONTAL_SCROLLING: "HORIZONTAL",
7403
7476
  VERTICAL_SCROLLING: "VERTICAL",
7404
7477
  HORIZONTAL_AND_VERTICAL_SCROLLING: "BOTH"
@@ -7528,6 +7601,8 @@ function normalizeDocument(value) {
7528
7601
  children: canvas.nodes || []
7529
7602
  }, { x: 0, y: 0 }, `canvas-${index}`);
7530
7603
  root.properties.breakpoint = canvas.breakpoint;
7604
+ if (canvas.variableModes) root.properties.variableModes = canvas.variableModes;
7605
+ if (canvas.bindings) root.properties.bindings = canvas.bindings;
7531
7606
  return root;
7532
7607
  }),
7533
7608
  styles: normalizeStyles(source.styles),
@@ -7866,15 +7941,43 @@ var generated_schema_default = {
7866
7941
  "additionalProperties": false,
7867
7942
  "required": ["type"],
7868
7943
  "properties": {
7869
- "type": { "enum": ["DROP_SHADOW", "INNER_SHADOW", "LAYER_BLUR", "BACKGROUND_BLUR"] },
7944
+ "type": { "enum": ["DROP_SHADOW", "INNER_SHADOW", "LAYER_BLUR", "BACKGROUND_BLUR", "NOISE", "TEXTURE", "GLASS", "SHADER"] },
7870
7945
  "color": { "oneOf": [{ "$ref": "#/$defs/hexColor" }, { "$ref": "#/$defs/color" }] },
7871
7946
  "offset": { "$ref": "#/$defs/vector" },
7872
7947
  "blur": { "$ref": "#/$defs/nonNegativeNumber" },
7873
7948
  "radius": { "$ref": "#/$defs/nonNegativeNumber" },
7874
7949
  "spread": { "type": "number" },
7875
7950
  "visible": { "type": "boolean" },
7876
- "blendMode": { "type": "string" }
7877
- }
7951
+ "blendMode": { "type": "string" },
7952
+ "showShadowBehindNode": { "type": "boolean" },
7953
+ "blurType": { "enum": ["NORMAL", "PROGRESSIVE"] },
7954
+ "startRadius": { "$ref": "#/$defs/nonNegativeNumber" },
7955
+ "startOffset": { "$ref": "#/$defs/vector" },
7956
+ "endOffset": { "$ref": "#/$defs/vector" },
7957
+ "noiseType": { "enum": ["MONOTONE", "DUOTONE", "MULTITONE"] },
7958
+ "noiseSize": { "$ref": "#/$defs/nonNegativeNumber" },
7959
+ "density": { "type": "number", "minimum": 0, "maximum": 1 },
7960
+ "secondaryColor": { "oneOf": [{ "$ref": "#/$defs/hexColor" }, { "$ref": "#/$defs/color" }] },
7961
+ "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
7962
+ "clipToShape": { "type": "boolean" },
7963
+ "lightIntensity": { "type": "number", "minimum": 0, "maximum": 1 },
7964
+ "lightAngle": { "type": "number" },
7965
+ "refraction": { "type": "number", "minimum": 0, "maximum": 1 },
7966
+ "depth": { "type": "number", "minimum": 1 },
7967
+ "dispersion": { "type": "number", "minimum": 0, "maximum": 1 },
7968
+ "id": { "type": "string", "minLength": 1 },
7969
+ "properties": {
7970
+ "type": "object",
7971
+ "description": "SHADER property assignments keyed by Figma property-definition id. Values pass through verbatim (numbers, booleans, strings, colors, points, gradients, or variable aliases).",
7972
+ "additionalProperties": { "type": ["boolean", "string", "number", "object"] }
7973
+ }
7974
+ },
7975
+ "allOf": [
7976
+ {
7977
+ "if": { "properties": { "type": { "const": "SHADER" } }, "required": ["type"] },
7978
+ "then": { "required": ["id"] }
7979
+ }
7980
+ ]
7878
7981
  },
7879
7982
  "shadow": {
7880
7983
  "type": "object",
@@ -8180,7 +8283,7 @@ var generated_schema_default = {
8180
8283
  "additionalProperties": false,
8181
8284
  "required": ["data"],
8182
8285
  "properties": {
8183
- "windingRule": { "enum": ["NONZERO", "EVENODD"] },
8286
+ "windingRule": { "enum": ["NONZERO", "EVENODD", "NONE"] },
8184
8287
  "data": { "type": "string", "minLength": 1 }
8185
8288
  }
8186
8289
  },
@@ -8338,6 +8441,9 @@ var generated_schema_default = {
8338
8441
  "overflowDirection": {
8339
8442
  "enum": [
8340
8443
  "NONE",
8444
+ "HORIZONTAL",
8445
+ "VERTICAL",
8446
+ "BOTH",
8341
8447
  "HORIZONTAL_SCROLLING",
8342
8448
  "VERTICAL_SCROLLING",
8343
8449
  "HORIZONTAL_AND_VERTICAL_SCROLLING"
@@ -8702,6 +8808,7 @@ function validateDocument(document) {
8702
8808
  if (node.type === "SVG" && typeof props.svg !== "string") add(`${path}.svg`, "SVG requires markup");
8703
8809
  if (node.type === "VECTOR" && !Array.isArray(props.vectorPaths)) add(`${path}.vectorPaths`, "VECTOR requires vectorPaths");
8704
8810
  if (node.type === "INSTANCE" && typeof props.componentId !== "string") add(`${path}.componentId`, "INSTANCE requires componentId");
8811
+ if (node.type === "BOOLEAN_OPERATION" && (typeof props.operation !== "string" || !["UNION", "SUBTRACT", "INTERSECT", "EXCLUDE"].includes(props.operation))) add(`${path}.operation`, "BOOLEAN_OPERATION requires operation UNION, SUBTRACT, INTERSECT, or EXCLUDE");
8705
8812
  if (node.type === "COMPONENT_SET" && (!node.children.length || node.children.some((child) => child.type !== "COMPONENT"))) add(`${path}.children`, "COMPONENT_SET requires COMPONENT children");
8706
8813
  if (node.type === "COMPONENT_SET" && props.variantAxes) {
8707
8814
  for (const [axis, options] of Object.entries(props.variantAxes)) if (!axis || !Array.isArray(options) || !options.length || options.some((option) => typeof option !== "string")) add(`${path}.variantAxes.${axis}`, "must be a non-empty string array");
@@ -8720,13 +8827,29 @@ function validateDocument(document) {
8720
8827
  node.children.forEach((child, index) => references(child, `${path}.children[${index}]`));
8721
8828
  }
8722
8829
  document.nodes.forEach((node, index) => references(node, `nodes[${index}]`));
8830
+ const styleKeys = /* @__PURE__ */ new Map();
8831
+ for (const style of document.styles || []) {
8832
+ if (typeof style.id === "string" && style.id) styleKeys.set(style.id, style.type);
8833
+ if (typeof style.name === "string" && style.name) styleKeys.set(style.name, style.type);
8834
+ }
8835
+ const variableKeys = /* @__PURE__ */ new Set();
8836
+ const collectionNames = /* @__PURE__ */ new Set();
8837
+ const modesByCollection = /* @__PURE__ */ new Map();
8723
8838
  for (const [collectionIndex, collection] of (document.variables || []).entries()) {
8724
8839
  const modes = Array.isArray(collection.modes) && collection.modes.length ? collection.modes : [];
8725
8840
  if (collection.modes !== void 0 && (!modes.length || modes.some((mode) => typeof mode !== "string"))) add(`variables[${collectionIndex}].modes`, "must be a non-empty array of mode names");
8841
+ if (typeof collection.name === "string" && collection.name) {
8842
+ collectionNames.add(collection.name);
8843
+ if (collection.modes !== void 0 && modes.length && modes.every((mode) => typeof mode === "string")) {
8844
+ modesByCollection.set(collection.name, modes);
8845
+ }
8846
+ }
8726
8847
  for (const [variableIndex, variable] of (collection.items || []).entries()) {
8727
8848
  const path = `variables[${collectionIndex}].items[${variableIndex}]`;
8728
8849
  if (!variable.name || typeof variable.name !== "string") add(`${path}.name`, "must be a non-empty string");
8729
8850
  if (!["COLOR", "FLOAT", "STRING", "BOOLEAN"].includes(variable.type)) add(`${path}.type`, "must be COLOR, FLOAT, STRING, or BOOLEAN");
8851
+ if (typeof variable.id === "string" && variable.id) variableKeys.add(variable.id);
8852
+ if (typeof variable.name === "string" && variable.name) variableKeys.add(variable.name);
8730
8853
  const values = variable.values || (variable.value !== void 0 ? { default: variable.value } : {});
8731
8854
  if (!Object.keys(values).length) add(`${path}`, "requires value or values");
8732
8855
  if (variable.values && modes.length) {
@@ -8741,6 +8864,54 @@ function validateDocument(document) {
8741
8864
  }
8742
8865
  }
8743
8866
  }
8867
+ function tokenReferences(node, path) {
8868
+ const bindings = node.properties.bindings;
8869
+ if (bindings) {
8870
+ for (const [field, key] of Object.entries(bindings)) {
8871
+ if (typeof key !== "string" || !key) {
8872
+ add(`${path}.bindings.${field}`, "must be a non-empty variable id or name");
8873
+ continue;
8874
+ }
8875
+ if (!variableKeys.has(key)) add(`${path}.bindings.${field}`, `variable '${key}' is not defined in this document`);
8876
+ }
8877
+ }
8878
+ const variableModes = node.properties.variableModes;
8879
+ if (variableModes) {
8880
+ for (const [collectionName, modeName] of Object.entries(variableModes)) {
8881
+ if (!collectionNames.has(collectionName)) {
8882
+ add(`${path}.variableModes.${collectionName}`, `variable collection '${collectionName}' is not defined in this document`);
8883
+ continue;
8884
+ }
8885
+ if (typeof modeName !== "string" || !modeName) {
8886
+ add(`${path}.variableModes.${collectionName}`, "must be a non-empty mode name");
8887
+ continue;
8888
+ }
8889
+ const declared = modesByCollection.get(collectionName);
8890
+ if (declared && !declared.includes(modeName)) add(`${path}.variableModes.${collectionName}`, `mode '${modeName}' is not declared on collection '${collectionName}'`);
8891
+ }
8892
+ }
8893
+ const styleRefs = node.properties.styleRefs;
8894
+ if (styleRefs) {
8895
+ for (const [field, key] of Object.entries(styleRefs)) {
8896
+ if (typeof key !== "string" || !key) {
8897
+ add(`${path}.styleRefs.${field}`, "must be a non-empty style id or name");
8898
+ continue;
8899
+ }
8900
+ const styleType = styleKeys.get(key);
8901
+ if (!styleType) {
8902
+ add(`${path}.styleRefs.${field}`, `style '${key}' is not defined in this document`);
8903
+ continue;
8904
+ }
8905
+ if ((field === "fill" || field === "stroke") && styleType !== "PAINT") {
8906
+ add(`${path}.styleRefs.${field}`, `style '${key}' has type ${styleType} but ${field} requires PAINT`);
8907
+ } else if (field === "text" && styleType !== "TEXT") {
8908
+ add(`${path}.styleRefs.${field}`, `style '${key}' has type ${styleType} but text requires TEXT`);
8909
+ }
8910
+ }
8911
+ }
8912
+ node.children.forEach((child, index) => tokenReferences(child, `${path}.children[${index}]`));
8913
+ }
8914
+ document.nodes.forEach((node, index) => tokenReferences(node, `nodes[${index}]`));
8744
8915
  return errors;
8745
8916
  }
8746
8917
 
package/dist/index.js CHANGED
@@ -7293,15 +7293,85 @@ function effects(raw) {
7293
7293
  values.push(...raw.effects || []);
7294
7294
  return values.map(effect);
7295
7295
  }
7296
+ function resolveColor(value) {
7297
+ return hexColor(value) || (typeof value === "object" && value ? value : void 0);
7298
+ }
7296
7299
  function effect(value) {
7297
7300
  if (!value || typeof value !== "object") return value;
7301
+ const type = String(value.type || "DROP_SHADOW").toUpperCase();
7302
+ const visible = value.visible !== false;
7303
+ if (type === "DROP_SHADOW" || type === "INNER_SHADOW") {
7304
+ return {
7305
+ type,
7306
+ color: resolveColor(value.color),
7307
+ offset: value.offset || { x: 0, y: 4 },
7308
+ radius: value.blur ?? value.radius ?? 8,
7309
+ spread: value.spread || 0,
7310
+ visible,
7311
+ ...value.blendMode ? { blendMode: value.blendMode } : {},
7312
+ ...type === "DROP_SHADOW" && typeof value.showShadowBehindNode === "boolean" ? { showShadowBehindNode: value.showShadowBehindNode } : {}
7313
+ };
7314
+ }
7315
+ if (type === "LAYER_BLUR" || type === "BACKGROUND_BLUR") {
7316
+ return {
7317
+ type,
7318
+ radius: value.blur ?? value.radius ?? 8,
7319
+ visible,
7320
+ ...value.blurType ? { blurType: value.blurType } : {},
7321
+ ...value.startRadius !== void 0 ? { startRadius: value.startRadius } : {},
7322
+ ...value.startOffset ? { startOffset: value.startOffset } : {},
7323
+ ...value.endOffset ? { endOffset: value.endOffset } : {}
7324
+ };
7325
+ }
7326
+ if (type === "NOISE") {
7327
+ return {
7328
+ type,
7329
+ color: resolveColor(value.color),
7330
+ visible,
7331
+ ...value.blendMode ? { blendMode: value.blendMode } : {},
7332
+ noiseType: value.noiseType || "MONOTONE",
7333
+ noiseSize: value.noiseSize ?? 1,
7334
+ density: value.density ?? 1,
7335
+ ...value.secondaryColor !== void 0 ? { secondaryColor: resolveColor(value.secondaryColor) } : {},
7336
+ ...value.opacity !== void 0 ? { opacity: value.opacity } : {}
7337
+ };
7338
+ }
7339
+ if (type === "TEXTURE") {
7340
+ return {
7341
+ type,
7342
+ visible,
7343
+ noiseSize: value.noiseSize ?? 1,
7344
+ radius: value.blur ?? value.radius ?? 0,
7345
+ clipToShape: value.clipToShape !== false
7346
+ };
7347
+ }
7348
+ if (type === "GLASS") {
7349
+ return {
7350
+ type,
7351
+ visible,
7352
+ lightIntensity: value.lightIntensity ?? 0.5,
7353
+ lightAngle: value.lightAngle ?? -45,
7354
+ refraction: value.refraction ?? 0.5,
7355
+ depth: value.depth ?? 1,
7356
+ dispersion: value.dispersion ?? 0.1,
7357
+ radius: value.blur ?? value.radius ?? 0
7358
+ };
7359
+ }
7360
+ if (type === "SHADER") {
7361
+ return {
7362
+ type,
7363
+ id: typeof value.id === "string" ? value.id : "",
7364
+ visible,
7365
+ ...value.properties && typeof value.properties === "object" && !Array.isArray(value.properties) ? { properties: value.properties } : {}
7366
+ };
7367
+ }
7298
7368
  return {
7299
- type: String(value.type || "DROP_SHADOW").toUpperCase(),
7300
- color: hexColor(value.color) || (typeof value.color === "object" ? value.color : void 0),
7369
+ type,
7370
+ color: resolveColor(value.color),
7301
7371
  offset: value.offset || { x: 0, y: 4 },
7302
7372
  radius: value.blur ?? value.radius ?? 8,
7303
7373
  spread: value.spread || 0,
7304
- visible: value.visible !== false
7374
+ visible
7305
7375
  };
7306
7376
  }
7307
7377
  var passthrough = [
@@ -7378,6 +7448,9 @@ function normalizeNode(node, parent, path) {
7378
7448
  if (typeof raw.overflowDirection === "string") {
7379
7449
  const overflowDirections = {
7380
7450
  NONE: "NONE",
7451
+ HORIZONTAL: "HORIZONTAL",
7452
+ VERTICAL: "VERTICAL",
7453
+ BOTH: "BOTH",
7381
7454
  HORIZONTAL_SCROLLING: "HORIZONTAL",
7382
7455
  VERTICAL_SCROLLING: "VERTICAL",
7383
7456
  HORIZONTAL_AND_VERTICAL_SCROLLING: "BOTH"
@@ -7507,6 +7580,8 @@ function normalizeDocument(value) {
7507
7580
  children: canvas.nodes || []
7508
7581
  }, { x: 0, y: 0 }, `canvas-${index}`);
7509
7582
  root.properties.breakpoint = canvas.breakpoint;
7583
+ if (canvas.variableModes) root.properties.variableModes = canvas.variableModes;
7584
+ if (canvas.bindings) root.properties.bindings = canvas.bindings;
7510
7585
  return root;
7511
7586
  }),
7512
7587
  styles: normalizeStyles(source.styles),
@@ -7845,15 +7920,43 @@ var generated_schema_default = {
7845
7920
  "additionalProperties": false,
7846
7921
  "required": ["type"],
7847
7922
  "properties": {
7848
- "type": { "enum": ["DROP_SHADOW", "INNER_SHADOW", "LAYER_BLUR", "BACKGROUND_BLUR"] },
7923
+ "type": { "enum": ["DROP_SHADOW", "INNER_SHADOW", "LAYER_BLUR", "BACKGROUND_BLUR", "NOISE", "TEXTURE", "GLASS", "SHADER"] },
7849
7924
  "color": { "oneOf": [{ "$ref": "#/$defs/hexColor" }, { "$ref": "#/$defs/color" }] },
7850
7925
  "offset": { "$ref": "#/$defs/vector" },
7851
7926
  "blur": { "$ref": "#/$defs/nonNegativeNumber" },
7852
7927
  "radius": { "$ref": "#/$defs/nonNegativeNumber" },
7853
7928
  "spread": { "type": "number" },
7854
7929
  "visible": { "type": "boolean" },
7855
- "blendMode": { "type": "string" }
7856
- }
7930
+ "blendMode": { "type": "string" },
7931
+ "showShadowBehindNode": { "type": "boolean" },
7932
+ "blurType": { "enum": ["NORMAL", "PROGRESSIVE"] },
7933
+ "startRadius": { "$ref": "#/$defs/nonNegativeNumber" },
7934
+ "startOffset": { "$ref": "#/$defs/vector" },
7935
+ "endOffset": { "$ref": "#/$defs/vector" },
7936
+ "noiseType": { "enum": ["MONOTONE", "DUOTONE", "MULTITONE"] },
7937
+ "noiseSize": { "$ref": "#/$defs/nonNegativeNumber" },
7938
+ "density": { "type": "number", "minimum": 0, "maximum": 1 },
7939
+ "secondaryColor": { "oneOf": [{ "$ref": "#/$defs/hexColor" }, { "$ref": "#/$defs/color" }] },
7940
+ "opacity": { "type": "number", "minimum": 0, "maximum": 1 },
7941
+ "clipToShape": { "type": "boolean" },
7942
+ "lightIntensity": { "type": "number", "minimum": 0, "maximum": 1 },
7943
+ "lightAngle": { "type": "number" },
7944
+ "refraction": { "type": "number", "minimum": 0, "maximum": 1 },
7945
+ "depth": { "type": "number", "minimum": 1 },
7946
+ "dispersion": { "type": "number", "minimum": 0, "maximum": 1 },
7947
+ "id": { "type": "string", "minLength": 1 },
7948
+ "properties": {
7949
+ "type": "object",
7950
+ "description": "SHADER property assignments keyed by Figma property-definition id. Values pass through verbatim (numbers, booleans, strings, colors, points, gradients, or variable aliases).",
7951
+ "additionalProperties": { "type": ["boolean", "string", "number", "object"] }
7952
+ }
7953
+ },
7954
+ "allOf": [
7955
+ {
7956
+ "if": { "properties": { "type": { "const": "SHADER" } }, "required": ["type"] },
7957
+ "then": { "required": ["id"] }
7958
+ }
7959
+ ]
7857
7960
  },
7858
7961
  "shadow": {
7859
7962
  "type": "object",
@@ -8159,7 +8262,7 @@ var generated_schema_default = {
8159
8262
  "additionalProperties": false,
8160
8263
  "required": ["data"],
8161
8264
  "properties": {
8162
- "windingRule": { "enum": ["NONZERO", "EVENODD"] },
8265
+ "windingRule": { "enum": ["NONZERO", "EVENODD", "NONE"] },
8163
8266
  "data": { "type": "string", "minLength": 1 }
8164
8267
  }
8165
8268
  },
@@ -8317,6 +8420,9 @@ var generated_schema_default = {
8317
8420
  "overflowDirection": {
8318
8421
  "enum": [
8319
8422
  "NONE",
8423
+ "HORIZONTAL",
8424
+ "VERTICAL",
8425
+ "BOTH",
8320
8426
  "HORIZONTAL_SCROLLING",
8321
8427
  "VERTICAL_SCROLLING",
8322
8428
  "HORIZONTAL_AND_VERTICAL_SCROLLING"
@@ -8681,6 +8787,7 @@ function validateDocument(document) {
8681
8787
  if (node.type === "SVG" && typeof props.svg !== "string") add(`${path}.svg`, "SVG requires markup");
8682
8788
  if (node.type === "VECTOR" && !Array.isArray(props.vectorPaths)) add(`${path}.vectorPaths`, "VECTOR requires vectorPaths");
8683
8789
  if (node.type === "INSTANCE" && typeof props.componentId !== "string") add(`${path}.componentId`, "INSTANCE requires componentId");
8790
+ if (node.type === "BOOLEAN_OPERATION" && (typeof props.operation !== "string" || !["UNION", "SUBTRACT", "INTERSECT", "EXCLUDE"].includes(props.operation))) add(`${path}.operation`, "BOOLEAN_OPERATION requires operation UNION, SUBTRACT, INTERSECT, or EXCLUDE");
8684
8791
  if (node.type === "COMPONENT_SET" && (!node.children.length || node.children.some((child) => child.type !== "COMPONENT"))) add(`${path}.children`, "COMPONENT_SET requires COMPONENT children");
8685
8792
  if (node.type === "COMPONENT_SET" && props.variantAxes) {
8686
8793
  for (const [axis, options] of Object.entries(props.variantAxes)) if (!axis || !Array.isArray(options) || !options.length || options.some((option) => typeof option !== "string")) add(`${path}.variantAxes.${axis}`, "must be a non-empty string array");
@@ -8699,13 +8806,29 @@ function validateDocument(document) {
8699
8806
  node.children.forEach((child, index) => references(child, `${path}.children[${index}]`));
8700
8807
  }
8701
8808
  document.nodes.forEach((node, index) => references(node, `nodes[${index}]`));
8809
+ const styleKeys = /* @__PURE__ */ new Map();
8810
+ for (const style of document.styles || []) {
8811
+ if (typeof style.id === "string" && style.id) styleKeys.set(style.id, style.type);
8812
+ if (typeof style.name === "string" && style.name) styleKeys.set(style.name, style.type);
8813
+ }
8814
+ const variableKeys = /* @__PURE__ */ new Set();
8815
+ const collectionNames = /* @__PURE__ */ new Set();
8816
+ const modesByCollection = /* @__PURE__ */ new Map();
8702
8817
  for (const [collectionIndex, collection] of (document.variables || []).entries()) {
8703
8818
  const modes = Array.isArray(collection.modes) && collection.modes.length ? collection.modes : [];
8704
8819
  if (collection.modes !== void 0 && (!modes.length || modes.some((mode) => typeof mode !== "string"))) add(`variables[${collectionIndex}].modes`, "must be a non-empty array of mode names");
8820
+ if (typeof collection.name === "string" && collection.name) {
8821
+ collectionNames.add(collection.name);
8822
+ if (collection.modes !== void 0 && modes.length && modes.every((mode) => typeof mode === "string")) {
8823
+ modesByCollection.set(collection.name, modes);
8824
+ }
8825
+ }
8705
8826
  for (const [variableIndex, variable] of (collection.items || []).entries()) {
8706
8827
  const path = `variables[${collectionIndex}].items[${variableIndex}]`;
8707
8828
  if (!variable.name || typeof variable.name !== "string") add(`${path}.name`, "must be a non-empty string");
8708
8829
  if (!["COLOR", "FLOAT", "STRING", "BOOLEAN"].includes(variable.type)) add(`${path}.type`, "must be COLOR, FLOAT, STRING, or BOOLEAN");
8830
+ if (typeof variable.id === "string" && variable.id) variableKeys.add(variable.id);
8831
+ if (typeof variable.name === "string" && variable.name) variableKeys.add(variable.name);
8709
8832
  const values = variable.values || (variable.value !== void 0 ? { default: variable.value } : {});
8710
8833
  if (!Object.keys(values).length) add(`${path}`, "requires value or values");
8711
8834
  if (variable.values && modes.length) {
@@ -8720,6 +8843,54 @@ function validateDocument(document) {
8720
8843
  }
8721
8844
  }
8722
8845
  }
8846
+ function tokenReferences(node, path) {
8847
+ const bindings = node.properties.bindings;
8848
+ if (bindings) {
8849
+ for (const [field, key] of Object.entries(bindings)) {
8850
+ if (typeof key !== "string" || !key) {
8851
+ add(`${path}.bindings.${field}`, "must be a non-empty variable id or name");
8852
+ continue;
8853
+ }
8854
+ if (!variableKeys.has(key)) add(`${path}.bindings.${field}`, `variable '${key}' is not defined in this document`);
8855
+ }
8856
+ }
8857
+ const variableModes = node.properties.variableModes;
8858
+ if (variableModes) {
8859
+ for (const [collectionName, modeName] of Object.entries(variableModes)) {
8860
+ if (!collectionNames.has(collectionName)) {
8861
+ add(`${path}.variableModes.${collectionName}`, `variable collection '${collectionName}' is not defined in this document`);
8862
+ continue;
8863
+ }
8864
+ if (typeof modeName !== "string" || !modeName) {
8865
+ add(`${path}.variableModes.${collectionName}`, "must be a non-empty mode name");
8866
+ continue;
8867
+ }
8868
+ const declared = modesByCollection.get(collectionName);
8869
+ if (declared && !declared.includes(modeName)) add(`${path}.variableModes.${collectionName}`, `mode '${modeName}' is not declared on collection '${collectionName}'`);
8870
+ }
8871
+ }
8872
+ const styleRefs = node.properties.styleRefs;
8873
+ if (styleRefs) {
8874
+ for (const [field, key] of Object.entries(styleRefs)) {
8875
+ if (typeof key !== "string" || !key) {
8876
+ add(`${path}.styleRefs.${field}`, "must be a non-empty style id or name");
8877
+ continue;
8878
+ }
8879
+ const styleType = styleKeys.get(key);
8880
+ if (!styleType) {
8881
+ add(`${path}.styleRefs.${field}`, `style '${key}' is not defined in this document`);
8882
+ continue;
8883
+ }
8884
+ if ((field === "fill" || field === "stroke") && styleType !== "PAINT") {
8885
+ add(`${path}.styleRefs.${field}`, `style '${key}' has type ${styleType} but ${field} requires PAINT`);
8886
+ } else if (field === "text" && styleType !== "TEXT") {
8887
+ add(`${path}.styleRefs.${field}`, `style '${key}' has type ${styleType} but text requires TEXT`);
8888
+ }
8889
+ }
8890
+ }
8891
+ node.children.forEach((child, index) => tokenReferences(child, `${path}.children[${index}]`));
8892
+ }
8893
+ document.nodes.forEach((node, index) => tokenReferences(node, `nodes[${index}]`));
8723
8894
  return errors;
8724
8895
  }
8725
8896
 
package/dist/types.d.ts CHANGED
@@ -11,7 +11,7 @@ export interface LetterSpacing {
11
11
  value: number;
12
12
  }
13
13
  export interface VectorPath {
14
- windingRule: "NONZERO" | "EVENODD";
14
+ windingRule: "NONZERO" | "EVENODD" | "NONE";
15
15
  data: string;
16
16
  }
17
17
  export interface VariableAlias {
@@ -80,6 +80,26 @@ export interface DesignEffect {
80
80
  spread?: number;
81
81
  visible?: boolean;
82
82
  blendMode?: string;
83
+ showShadowBehindNode?: boolean;
84
+ blurType?: string;
85
+ startRadius?: number;
86
+ startOffset?: DesignPoint;
87
+ endOffset?: DesignPoint;
88
+ noiseType?: string;
89
+ noiseSize?: number;
90
+ density?: number;
91
+ secondaryColor?: DesignColor;
92
+ opacity?: number;
93
+ clipToShape?: boolean;
94
+ lightIntensity?: number;
95
+ lightAngle?: number;
96
+ refraction?: number;
97
+ depth?: number;
98
+ dispersion?: number;
99
+ /** SHADER only: Figma shader id (from listAvailableShaders / importShaderById). */
100
+ id?: string;
101
+ /** SHADER only: property assignments keyed by property-definition id, passed through verbatim. */
102
+ properties?: Record<string, JsonValue>;
83
103
  }
84
104
  export interface DesignStyles {
85
105
  fills: DesignPaint[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compact-design/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Reusable parser, normalizer, validator, patch engine, and linter for Compact Design JSON.",
5
5
  "license": "MPL-2.0",
6
6
  "type": "module",