@rojaostudio/ds-core 1.1.0-next.7 → 1.1.0-next.8

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.
@@ -706,18 +706,19 @@ function ramp(c, custom) {
706
706
  }
707
707
  var on = (bg) => onColor(bg);
708
708
  var AA = 4.5;
709
- var readsOn = (fg, bgs) => bgs.every((bg) => contrastRatio(fg, bg) >= AA);
710
- function readableFrom(base, scale, bgs, toward, fallback) {
709
+ var NON_TEXT = 3;
710
+ var readsOn = (fg, bgs, min = AA) => bgs.every((bg) => contrastRatio(fg, bg) >= min);
711
+ function readableFrom(base, scale, bgs, toward, fallback, min = AA) {
711
712
  var _a;
712
- if (readsOn(base, bgs)) return base;
713
+ if (readsOn(base, bgs, min)) return base;
713
714
  const lb = relativeLuminance(base);
714
715
  const steps = SCALE_STEPS.map((s2) => scale[s2]).filter((c) => !!c && isHex(c)).filter((c) => toward === "darker" ? relativeLuminance(c) < lb : relativeLuminance(c) > lb).sort((a, b) => Math.abs(relativeLuminance(a) - lb) - Math.abs(relativeLuminance(b) - lb));
715
- const step2 = steps.find((c) => readsOn(c, bgs));
716
+ const step2 = steps.find((c) => readsOn(c, bgs, min));
716
717
  if (step2) return step2;
717
718
  const [h, s, l0] = hexToHsl((_a = steps[steps.length - 1]) != null ? _a : base);
718
719
  for (let l = l0; toward === "darker" ? l >= 0 : l <= 100; l += toward === "darker" ? -2 : 2) {
719
720
  const c = hslToHex(h, s, l);
720
- if (readsOn(c, bgs)) return c;
721
+ if (readsOn(c, bgs, min)) return c;
721
722
  }
722
723
  return fallback;
723
724
  }
@@ -828,8 +829,10 @@ var ROLES = [
828
829
  ["colors/state/info-strong", "d", "d"],
829
830
  ["colors/state/neutral-strong", "d", "d"],
830
831
  ["text/on/primary-subtle", "d", "b"],
831
- ["surface/tint/subtle", "d", "b"]
832
+ ["surface/tint/subtle", "d", "b"],
833
+ ["border/error", "d", "d"]
832
834
  ];
835
+ var awayFrom = (bg) => contrastRatio(bg, BLACK) >= contrastRatio(bg, WHITE) ? "darker" : "lighter";
833
836
  var TINT_DEFAULT = 200;
834
837
  var lighterStep = (s) => s - 100 ;
835
838
  var roleVar = (role) => `--${role.replaceAll("/", "-")}`;
@@ -930,8 +933,7 @@ function generateRdsTheme(def, opts = {}) {
930
933
  "text/warning": orange[700],
931
934
  "surface/neutral": N[100],
932
935
  "text/neutral": N[800],
933
- "chart/series/1": P.scale[600],
934
- "chart/series/2": orange[500],
936
+ "chart/series/2": orange[600],
935
937
  "chart/series/3": teal[500],
936
938
  "chart/series/4": purple[500],
937
939
  "chart/series/5": green[500],
@@ -959,6 +961,8 @@ function generateRdsTheme(def, opts = {}) {
959
961
  ["colors/primary/dark", "text/on/primary-strong"],
960
962
  ["surface/tint/default", "text/on/tint"]
961
963
  ]) l[text] = on(l[fill]);
964
+ l["border/error"] = readableFrom(l["colors/state/error"], red, [l["surface/card"]], awayFrom(l["surface/card"]), BLACK, NON_TEXT);
965
+ l["chart/series/1"] = readableFrom(P.scale[600], P.scale, [l["surface/card"]], awayFrom(l["surface/card"]), BLACK, NON_TEXT);
962
966
  l["logo/primary"] = l["colors/primary/default"];
963
967
  l["logo/signature"] = l["colors/primary/default"];
964
968
  l["logo/accent"] = l["colors/accent/logo"];
@@ -1023,7 +1027,6 @@ function generateRdsTheme(def, opts = {}) {
1023
1027
  "text/warning": orange[300],
1024
1028
  "surface/neutral": N[800],
1025
1029
  "text/neutral": N[200],
1026
- "chart/series/1": P.scale[400],
1027
1030
  "chart/series/2": orange[400],
1028
1031
  "chart/series/3": teal[400],
1029
1032
  "chart/series/4": purple[400],
@@ -1046,12 +1049,15 @@ function generateRdsTheme(def, opts = {}) {
1046
1049
  ["surface/tint/default", "text/on/tint"],
1047
1050
  ["surface/lift/action", "text/on/lift-action"]
1048
1051
  ]) d[text] = on(d[fill]);
1052
+ d["border/error"] = readableFrom(d["colors/state/error-strong"], red, [d["surface/card"]], awayFrom(d["surface/card"]), WHITE, NON_TEXT);
1053
+ d["chart/series/1"] = readableFrom(P.scale[400], P.scale, [d["surface/card"]], awayFrom(d["surface/card"]), WHITE, NON_TEXT);
1049
1054
  const plate = l["colors/primary/default"];
1050
1055
  const ink = on(plate);
1051
1056
  const inkA = ink === WHITE ? white : black;
1052
1057
  const antiInk = ink === WHITE ? black : white;
1053
1058
  const lp = relativeLuminance(plate);
1054
- const plateCard = (_b = SCALE_STEPS.map((st) => P.scale[st]).filter((c) => !!c && isHex(c) && c.toLowerCase() !== plate.toLowerCase()).sort((a, b2) => Math.abs(relativeLuminance(a) - lp) - Math.abs(relativeLuminance(b2) - lp)).find((c) => contrastRatio(ink, c) >= AA)) != null ? _b : plate;
1059
+ const isPlate = (c) => c.toLowerCase() === plate.toLowerCase();
1060
+ const plateCard = P.scale[800] && !isPlate(P.scale[800]) && contrastRatio(ink, P.scale[800]) >= AA ? P.scale[800] : (_b = SCALE_STEPS.map((st) => P.scale[st]).filter((c) => !!c && isHex(c) && !isPlate(c)).sort((a, b2) => Math.abs(relativeLuminance(a) - lp) - Math.abs(relativeLuminance(b2) - lp)).find((c) => contrastRatio(ink, c) >= AA)) != null ? _b : plate;
1055
1061
  const onPrimaryStrong = contrastRatio(plate, P.scale[100]) >= AA ? plate : on(P.scale[100]);
1056
1062
  const br = {
1057
1063
  "colors/primary/light": P.scale[300],
@@ -1096,6 +1102,7 @@ function generateRdsTheme(def, opts = {}) {
1096
1102
  "border/strong-hover": inkA(80),
1097
1103
  "text/on/primary-subtle": inkA(30)
1098
1104
  };
1105
+ const brandError = readableFrom(red[300], red, [plateCard], awayFrom(plateCard), ink, NON_TEXT);
1099
1106
  const out = { light: {}, dark: {}, brand: {} };
1100
1107
  const pick = (src, role) => {
1101
1108
  var _a2;
@@ -1109,6 +1116,7 @@ function generateRdsTheme(def, opts = {}) {
1109
1116
  out.dark[roleVar(role)] = pick(darkSrc, role);
1110
1117
  out.brand[roleVar(role)] = pick(brandSrc, role);
1111
1118
  }
1119
+ out.brand[roleVar("border/error")] = brandError;
1112
1120
  return out;
1113
1121
  }
1114
1122
  var RDS_SCOPE_SELECTORS = [
@@ -1200,10 +1208,15 @@ function rdsContrastReport(theme) {
1200
1208
  return out;
1201
1209
  }
1202
1210
  var isColourRef = (v) => /^[a-z][a-z0-9-]*\/[a-z]?\d+$/i.test(v);
1211
+ var ADDED_ROLES = {
1212
+ // Figma: light → base colors/state/error, dark → base dark/colors/state/error-strong, brand → base dark/text/error.
1213
+ "border/error": { light: "colors/state/error", dark: "colors/state/error-strong", brand: "text/error" }
1214
+ };
1203
1215
  function rdsThemeFromTable(table, opts = {}) {
1204
- var _a, _b;
1216
+ var _a, _b, _c;
1205
1217
  const out = { light: {}, dark: {}, brand: {} };
1206
1218
  const problems = [];
1219
+ const derived = /* @__PURE__ */ new Set();
1207
1220
  for (const mode of ["light", "dark", "brand"]) {
1208
1221
  const roles = (_a = table.modes) == null ? void 0 : _a[mode];
1209
1222
  if (!roles) {
@@ -1211,7 +1224,12 @@ function rdsThemeFromTable(table, opts = {}) {
1211
1224
  continue;
1212
1225
  }
1213
1226
  for (const [role] of ROLES) {
1214
- const ref2 = roles[role];
1227
+ let ref2 = roles[role];
1228
+ const added = (_b = ADDED_ROLES[role]) == null ? void 0 : _b[mode];
1229
+ if (ref2 === void 0 && added && roles[added] !== void 0) {
1230
+ ref2 = roles[added];
1231
+ derived.add(role);
1232
+ }
1215
1233
  if (ref2 === void 0) problems.push(`${mode}: role "${role}" is missing`);
1216
1234
  else if (isColourRef(ref2)) {
1217
1235
  const value = table.primitives[ref2];
@@ -1235,9 +1253,14 @@ function rdsThemeFromTable(table, opts = {}) {
1235
1253
  }
1236
1254
  if (problems.length) throw new Error(`rdsThemeFromTable(${table.name}):
1237
1255
  ${problems.join("\n ")}`);
1256
+ const warn = (_c = opts.warn) != null ? _c : defaultWarn;
1257
+ if (derived.size)
1258
+ warn(
1259
+ `rdsThemeFromTable(${table.name}): the table was exported before ${[...derived].map((r) => `"${r}"`).join(", ")} existed in the [RDS] theme. Taken from the tokens Figma aliases it to (` + [...derived].map((r) => Object.entries(ADDED_ROLES[r]).map(([m, src]) => `${m}: ${src}`).join(", ")).join("; ") + `). Export the table again with figma/export-brand.js.`
1260
+ );
1238
1261
  const fails = rdsContrastReport(out);
1239
1262
  if (fails.length)
1240
- ((_b = opts.warn) != null ? _b : defaultWarn)(
1263
+ warn(
1241
1264
  `rdsThemeFromTable(${table.name}): ${fails.length} text pair(s) below 4.5:1
1242
1265
  ` + fails.map((f) => `${f.mode}: ${f.fg} on ${f.bg} ${f.ratio}:1`).join("\n ")
1243
1266
  );
@@ -84,9 +84,10 @@ type RdsBrandTable = {
84
84
  vars?: Record<string, string>;
85
85
  };
86
86
  /**
87
- * The theme of a brand table. Fails on a missing role or an unknown primitive, listing them all. The contrast of
88
- * the main text pairs (rdsContrastReport) is only reported, through `opts.warn`: the table is the brand as drawn
89
- * in Figma, kept one to one even where a pair fails.
87
+ * The theme of a brand table. Fails on a missing role or an unknown primitive, listing them all. A table exported
88
+ * before a role of ADDED_ROLES existed is the exception: the role is taken from the token Figma aliases it to, and
89
+ * `opts.warn` asks to export the table again. The contrast of the main text pairs (rdsContrastReport) is only
90
+ * reported, through `opts.warn`: the table is the brand as drawn in Figma, kept one to one even where a pair fails.
90
91
  */
91
92
  declare function rdsThemeFromTable(table: RdsBrandTable, opts?: RdsThemeOptions): RdsTheme;
92
93
 
package/dist/generate.js CHANGED
@@ -1,3 +1,3 @@
1
- export { RDS_CONTRAST_PAIRS, ROLES as RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-Y25PXY26.js';
1
+ export { RDS_CONTRAST_PAIRS, ROLES as RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-6XIFYDSU.js';
2
2
  import './chunk-FRWRLCYM.js';
3
3
  import './chunk-ORMEWXMH.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { recipes } from './chunk-PV6GFEAM.js';
2
- export { RDS_CONTRAST_PAIRS, ROLES as RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-Y25PXY26.js';
2
+ export { RDS_CONTRAST_PAIRS, ROLES as RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-6XIFYDSU.js';
3
3
  export { primitives, tokens } from './chunk-FRWRLCYM.js';
4
4
  import './chunk-ORMEWXMH.js';
package/dist/themes.js CHANGED
@@ -1,4 +1,4 @@
1
- import { generateTheme } from './chunk-Y25PXY26.js';
1
+ import { generateTheme } from './chunk-6XIFYDSU.js';
2
2
  import './chunk-FRWRLCYM.js';
3
3
  import { __spreadValues, __objRest } from './chunk-ORMEWXMH.js';
4
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rojaostudio/ds-core",
3
- "version": "1.1.0-next.7",
3
+ "version": "1.1.0-next.8",
4
4
  "description": "O motor do Rojão DS — tokens, derivação de tema e emissores. Sem React, sem Tailwind, sem CSS.",
5
5
  "license": "MIT",
6
6
  "author": "Rojão Studio (https://rojao.studio)",