@phcdevworks/spectre-tokens 3.3.0 → 3.4.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
@@ -1,5 +1,15 @@
1
1
  # @phcdevworks/spectre-tokens
2
2
 
3
+ `@phcdevworks/spectre-tokens` is the design-token package of the Spectre
4
+ system. It provides a complete, UI-ready token surface for downstream Spectre
5
+ packages and compatible applications.
6
+
7
+ Maintained by [PHCDevworks](https://go.phcdev.co). It defines the visual
8
+ language, semantic roles, and token contracts that downstream consumers can
9
+ rely on without filling gaps with raw palette values or local token
10
+ inventions. Downstream UI packages define structure; adapter packages
11
+ translate Spectre contracts for specific frameworks and runtimes.
12
+
3
13
  ## Repository Snapshot
4
14
 
5
15
  | Field | Value |
@@ -7,7 +17,7 @@
7
17
  | Project team | `project-design` |
8
18
  | Repository role | Spectre L1 design-token contract |
9
19
  | Package/artifact | `@phcdevworks/spectre-tokens` |
10
- | Current version/status | 3.3.0 |
20
+ | Current version/status | 3.4.0 |
11
21
 
12
22
  ## Standard Workflow
13
23
 
@@ -500,7 +510,7 @@ This project expects Node.js `^22.12.0 || >=24.0.0` and npm `11.17.0`.
500
510
  | Command | What it does |
501
511
  | ------------------------ | ------------------------------------------------------------ |
502
512
  | `npm run build` | Regenerate all outputs — run after any token source change |
503
- | `npm run check` | Full validation gate — all 15 steps must pass before commit |
513
+ | `npm run check` | Full validation gate — all 16 steps must pass before commit |
504
514
  | `npm run lint` | Run ESLint against all source files |
505
515
  | `npm run format` | Apply Prettier formatting to all files |
506
516
  | `npm run generate` | Regenerate `src/generated/tokens.ts` from token sources only |
package/dist/index.cjs CHANGED
@@ -1215,98 +1215,13 @@ var coreTokens = {
1215
1215
  // src/css.ts
1216
1216
  var DEFAULT_PREFIX = "sp";
1217
1217
  var DEFAULT_SELECTOR = ":root";
1218
+ var LEGACY_COMPONENT_PREFIX_GROUPS = /* @__PURE__ */ new Set(["card", "input"]);
1218
1219
  var formatKey = (segment) => segment.replace(/[^a-z0-9]+/gi, "-").replace(/^-+|-+$/g, "").toLowerCase();
1219
1220
  var toVariableName = (prefix, ...parts) => {
1220
1221
  const filtered = parts.filter(Boolean).map(formatKey);
1221
1222
  return `--${prefix}-${filtered.join("-")}`;
1222
1223
  };
1223
- var BADGE_VARIANTS = [
1224
- { variant: "neutral", bgKey: "neutralBg", textKey: "neutralText" },
1225
- { variant: "info", bgKey: "infoBg", textKey: "infoText" },
1226
- { variant: "success", bgKey: "successBg", textKey: "successText" },
1227
- { variant: "warning", bgKey: "warningBg", textKey: "warningText" },
1228
- { variant: "danger", bgKey: "dangerBg", textKey: "dangerText" }
1229
- ];
1230
- var ICON_BOX_FIELDS = [
1231
- { name: "bg", tokenKey: "bg" },
1232
- { name: "border", tokenKey: "border" },
1233
- { name: "icon-default", tokenKey: "iconDefault" },
1234
- { name: "icon-success", tokenKey: "iconSuccess" },
1235
- { name: "icon-warning", tokenKey: "iconWarning" },
1236
- { name: "icon-danger", tokenKey: "iconDanger" }
1237
- ];
1238
- var NAV_FIELDS = [
1239
- { name: "bg", tokenKey: "bg" },
1240
- { name: "text", tokenKey: "text" },
1241
- { name: "link", tokenKey: "link" },
1242
- { name: "link-hover", tokenKey: "linkHover" },
1243
- { name: "link-active", tokenKey: "linkActive" },
1244
- { name: "border", tokenKey: "border" }
1245
- ];
1246
- var MODAL_FIELDS = [
1247
- { name: "bg", tokenKey: "bg" },
1248
- { name: "shadow", tokenKey: "shadow" },
1249
- { name: "border", tokenKey: "border" },
1250
- { name: "overlay", tokenKey: "overlay" }
1251
- ];
1252
- var TOAST_VARIANTS = ["success", "warning", "danger", "info"].map((variant) => ({
1253
- variant,
1254
- fields: [
1255
- { name: "bg", tokenKey: "bg" },
1256
- { name: "text", tokenKey: "text" },
1257
- { name: "border", tokenKey: "border" },
1258
- { name: "icon", tokenKey: "icon" }
1259
- ]
1260
- }));
1261
- var TOOLTIP_FIELDS = [
1262
- { name: "bg", tokenKey: "bg" },
1263
- { name: "text", tokenKey: "text" },
1264
- { name: "border", tokenKey: "border" }
1265
- ];
1266
- var DROPDOWN_FIELDS = [
1267
- { name: "bg", modePath: ["bg"], aliasPath: ["bg"] },
1268
- { name: "border", modePath: ["border"], aliasPath: ["border"] },
1269
- { name: "item-default", modePath: ["item", "default"], aliasPath: ["item", "default"] },
1270
- { name: "item-hover", modePath: ["item", "hover"], aliasPath: ["item", "hover"] },
1271
- { name: "item-active", modePath: ["item", "active"], aliasPath: ["item", "active"] },
1272
- { name: "item-text", modePath: ["item", "text"], aliasPath: ["item", "text"] }
1273
- ];
1274
- var SELECTION_CONTROL_FIELDS = [
1275
- { name: "bg", tokenKey: "bg" },
1276
- { name: "border", tokenKey: "border" },
1277
- { name: "checked-bg", tokenKey: "checkedBg" },
1278
- { name: "checked-border", tokenKey: "checkedBorder" },
1279
- { name: "text", tokenKey: "text" },
1280
- { name: "disabled-bg", tokenKey: "disabledBg" },
1281
- { name: "disabled-border", tokenKey: "disabledBorder" }
1282
- ];
1283
- var SELECT_FIELDS = [
1284
- { name: "bg", tokenKey: "bg" },
1285
- { name: "border", tokenKey: "border" },
1286
- { name: "text", tokenKey: "text" },
1287
- { name: "placeholder-text", tokenKey: "placeholderText" },
1288
- { name: "disabled-bg", tokenKey: "disabledBg" },
1289
- { name: "disabled-border", tokenKey: "disabledBorder" },
1290
- { name: "focus-border", tokenKey: "focusBorder" }
1291
- ];
1292
- var TEXTAREA_FIELDS = [
1293
- { name: "bg", tokenKey: "bg" },
1294
- { name: "border", tokenKey: "border" },
1295
- { name: "text", tokenKey: "text" },
1296
- { name: "placeholder", tokenKey: "placeholder" },
1297
- { name: "disabled-bg", tokenKey: "disabledBg" },
1298
- { name: "disabled-border", tokenKey: "disabledBorder" },
1299
- { name: "focus-border", tokenKey: "focusBorder" }
1300
- ];
1301
- var FIELDSET_FIELDS = [
1302
- { name: "border", tokenKey: "border" },
1303
- { name: "legend-text", tokenKey: "legendText" }
1304
- ];
1305
- var LABEL_FIELDS = [
1306
- { name: "text", tokenKey: "text" },
1307
- { name: "disabled-text", tokenKey: "disabledText" },
1308
- { name: "required-indicator-text", tokenKey: "requiredIndicatorText" }
1309
- ];
1224
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1310
1225
  var resolveTokenReference = (tokens2, reference) => {
1311
1226
  const path = reference.slice(1, -1).split(".");
1312
1227
  let current = tokens2;
@@ -1344,14 +1259,21 @@ var resolveValue = (tokens2, value) => {
1344
1259
  str = str.replace(opacityRegex, (match, hex, opacity) => hexToRgba(hex, opacity));
1345
1260
  return str;
1346
1261
  };
1347
- var resolveSemanticValue = (value, tokens2) => {
1262
+ var resolveSemanticValue = (value, tokens2, path) => {
1263
+ if (value === void 0) return void 0;
1348
1264
  if (typeof value === "string" || typeof value === "number") {
1349
1265
  return resolveValue(tokens2, value);
1350
1266
  }
1351
- if (value && typeof value === "object" && "value" in value) {
1352
- return resolveValue(tokens2, value.value);
1267
+ if (isPlainObject(value)) {
1268
+ if ("value" in value) {
1269
+ return resolveValue(tokens2, value.value);
1270
+ }
1271
+ if ("metadata" in value || "description" in value) {
1272
+ throw new Error(`Unsupported token value shape at "${path ?? "(unknown path)"}": expected a string, number, or { value } wrapper.`);
1273
+ }
1274
+ return void 0;
1353
1275
  }
1354
- return void 0;
1276
+ throw new Error(`Unsupported token value shape at "${path ?? "(unknown path)"}": ${JSON.stringify(value)}`);
1355
1277
  };
1356
1278
  var getPath = (source, path) => path.reduce((acc, key) => acc && typeof acc === "object" ? acc[key] : void 0, source);
1357
1279
  var pickSemantic = (tokens2, ...candidates) => {
@@ -1436,6 +1358,18 @@ var createCssVariableMap = (tokens2, options = {}) => {
1436
1358
  Object.entries(baseTokens.radii).forEach(([key, value]) => {
1437
1359
  assign(toVariableName(prefix, "radius", key), value);
1438
1360
  });
1361
+ const icons = baseTokens.icons;
1362
+ if (icons) {
1363
+ Object.entries(icons).forEach(([key, value]) => {
1364
+ assign(toVariableName(prefix, "icon", key), value);
1365
+ });
1366
+ }
1367
+ const aspectRatios = baseTokens.aspectRatios;
1368
+ if (aspectRatios) {
1369
+ Object.entries(aspectRatios).forEach(([key, value]) => {
1370
+ assign(toVariableName(prefix, "aspect-ratio", key), value);
1371
+ });
1372
+ }
1439
1373
  Object.entries(baseTokens.typography.families).forEach(([key, value]) => {
1440
1374
  assign(toVariableName(prefix, "font-family", key), value);
1441
1375
  });
@@ -1482,6 +1416,7 @@ var createCssVariableMap = (tokens2, options = {}) => {
1482
1416
  assign(toVariableName(prefix, "min-touch-target"), baseTokens.accessibility.minTouchTarget);
1483
1417
  assign(toVariableName(prefix, "min-text-size"), baseTokens.accessibility.minTextSize);
1484
1418
  assign(toVariableName(prefix, "reduced-motion"), baseTokens.accessibility.reducedMotion);
1419
+ assign(toVariableName(prefix, "forced-colors"), baseTokens.accessibility.forcedColors);
1485
1420
  Object.entries(baseTokens.buttons).forEach(([variant, states]) => {
1486
1421
  Object.entries(states).forEach(([state, value]) => {
1487
1422
  assign(toVariableName(prefix, "button", variant, state), value);
@@ -1523,110 +1458,7 @@ var generateCssVariables = (tokens2, options = {}) => {
1523
1458
  const textAliases = tokens2.text ?? {};
1524
1459
  const componentAliases = tokens2.component ?? {};
1525
1460
  const linkTokens = tokens2.link ?? {};
1526
- const semanticEntries = [
1527
- { varParts: ["surface", "page"], modePath: ["surface", "page"], aliasSrc: surfaceAliases, aliasPath: ["page"] },
1528
- { varParts: ["surface", "card"], modePath: ["surface", "card"], aliasSrc: surfaceAliases, aliasPath: ["card"] },
1529
- { varParts: ["surface", "input"], modePath: ["surface", "input"], aliasSrc: surfaceAliases, aliasPath: ["input"] },
1530
- { varParts: ["surface", "overlay"], modePath: ["surface", "overlay"], aliasSrc: surfaceAliases, aliasPath: ["overlay"] },
1531
- { varParts: ["surface", "subtle"], modePath: ["surface", "subtle"] },
1532
- { varParts: ["surface", "hero"], modePath: ["surface", "hero"], aliasSrc: surfaceAliases, aliasPath: ["hero"] },
1533
- { varParts: ["surface", "hover"], modePath: ["surface", "hover"], aliasSrc: surfaceAliases, aliasPath: ["hover"] },
1534
- { varParts: ["surface", "selected"], modePath: ["surface", "selected"], aliasSrc: surfaceAliases, aliasPath: ["selected"] },
1535
- { varParts: ["surface", "active"], modePath: ["surface", "active"], aliasSrc: surfaceAliases, aliasPath: ["active"] },
1536
- { varParts: ["surface", "divider"], modePath: ["surface", "divider"], aliasSrc: surfaceAliases, aliasPath: ["divider"] },
1537
- { varParts: ["text", "on", "page", "default"], modePath: ["text", "onPage", "default"], aliasSrc: textAliases, aliasPath: ["onPage", "default"] },
1538
- { varParts: ["text", "on", "page", "muted"], modePath: ["text", "onPage", "muted"], aliasSrc: textAliases, aliasPath: ["onPage", "muted"] },
1539
- { varParts: ["text", "on", "page", "subtle"], modePath: ["text", "onPage", "subtle"], aliasSrc: textAliases, aliasPath: ["onPage", "subtle"] },
1540
- { varParts: ["text", "on", "page", "meta"], modePath: ["text", "onPage", "meta"], aliasSrc: textAliases, aliasPath: ["onPage", "meta"] },
1541
- { varParts: ["text", "on", "page", "brand"], modePath: ["text", "onPage", "brand"], aliasSrc: textAliases, aliasPath: ["onPage", "brand"] },
1542
- { varParts: ["text", "on", "surface", "default"], modePath: ["text", "onSurface", "default"], aliasSrc: textAliases, aliasPath: ["onSurface", "default"] },
1543
- { varParts: ["text", "on", "surface", "muted"], modePath: ["text", "onSurface", "muted"], aliasSrc: textAliases, aliasPath: ["onSurface", "muted"] },
1544
- { varParts: ["text", "on", "surface", "subtle"], modePath: ["text", "onSurface", "subtle"], aliasSrc: textAliases, aliasPath: ["onSurface", "subtle"] },
1545
- { varParts: ["text", "on", "surface", "meta"], modePath: ["text", "onSurface", "meta"], aliasSrc: textAliases, aliasPath: ["onSurface", "meta"] },
1546
- { varParts: ["text", "on", "surface", "brand"], modePath: ["text", "onSurface", "brand"], aliasSrc: textAliases, aliasPath: ["onSurface", "brand"] },
1547
- { varParts: ["component", "card", "text"], modePath: ["component", "card", "text"], aliasSrc: componentAliases, aliasPath: ["card", "text"] },
1548
- { varParts: ["component", "card", "text-muted"], modePath: ["component", "card", "textMuted"], aliasSrc: componentAliases, aliasPath: ["card", "textMuted"] },
1549
- { varParts: ["component", "input", "text"], modePath: ["component", "input", "text"], aliasSrc: componentAliases, aliasPath: ["input", "text"] },
1550
- { varParts: ["component", "input", "placeholder"], modePath: ["component", "input", "placeholder"], aliasSrc: componentAliases, aliasPath: ["input", "placeholder"] },
1551
- { varParts: ["button", "text", "default"], modePath: ["component", "button", "textDefault"], aliasSrc: componentAliases, aliasPath: ["button", "textDefault"] },
1552
- { varParts: ["button", "text", "on", "primary"], modePath: ["component", "button", "textOnPrimary"], aliasSrc: componentAliases, aliasPath: ["button", "textOnPrimary"] },
1553
- ...BADGE_VARIANTS.flatMap(({ variant, bgKey, textKey }) => [
1554
- { varParts: ["badge", variant, "bg"], modePath: ["component", "badge", bgKey], aliasSrc: componentAliases, aliasPath: ["badge", bgKey] },
1555
- { varParts: ["badge", variant, "text"], modePath: ["component", "badge", textKey], aliasSrc: componentAliases, aliasPath: ["badge", textKey] }
1556
- ]),
1557
- ...ICON_BOX_FIELDS.map(({ name, tokenKey }) => ({
1558
- varParts: ["icon-box", name],
1559
- modePath: ["component", "iconBox", tokenKey],
1560
- aliasSrc: componentAliases,
1561
- aliasPath: ["iconBox", tokenKey]
1562
- })),
1563
- ...NAV_FIELDS.map(({ name, tokenKey }) => ({
1564
- varParts: ["nav", name],
1565
- modePath: ["component", "nav", tokenKey],
1566
- aliasSrc: componentAliases,
1567
- aliasPath: ["nav", tokenKey]
1568
- })),
1569
- ...MODAL_FIELDS.map(({ name, tokenKey }) => ({
1570
- varParts: ["modal", name],
1571
- modePath: ["component", "modal", tokenKey],
1572
- aliasSrc: componentAliases,
1573
- aliasPath: ["modal", tokenKey]
1574
- })),
1575
- ...TOAST_VARIANTS.flatMap(({ variant, fields }) => fields.map(({ name, tokenKey }) => ({
1576
- varParts: ["toast", variant, name],
1577
- modePath: ["component", "toast", variant, tokenKey],
1578
- aliasSrc: componentAliases,
1579
- aliasPath: ["toast", variant, tokenKey]
1580
- }))),
1581
- ...TOOLTIP_FIELDS.map(({ name, tokenKey }) => ({
1582
- varParts: ["tooltip", name],
1583
- modePath: ["component", "tooltip", tokenKey],
1584
- aliasSrc: componentAliases,
1585
- aliasPath: ["tooltip", tokenKey]
1586
- })),
1587
- ...DROPDOWN_FIELDS.map(({ name, modePath, aliasPath }) => ({
1588
- varParts: ["dropdown", name],
1589
- modePath: ["component", "dropdown", ...modePath],
1590
- aliasSrc: componentAliases,
1591
- aliasPath: ["dropdown", ...aliasPath]
1592
- })),
1593
- ...SELECTION_CONTROL_FIELDS.map(({ name, tokenKey }) => ({
1594
- varParts: ["checkbox", name],
1595
- modePath: ["component", "checkbox", tokenKey],
1596
- aliasSrc: componentAliases,
1597
- aliasPath: ["checkbox", tokenKey]
1598
- })),
1599
- ...SELECTION_CONTROL_FIELDS.map(({ name, tokenKey }) => ({
1600
- varParts: ["radio", name],
1601
- modePath: ["component", "radio", tokenKey],
1602
- aliasSrc: componentAliases,
1603
- aliasPath: ["radio", tokenKey]
1604
- })),
1605
- ...SELECT_FIELDS.map(({ name, tokenKey }) => ({
1606
- varParts: ["select", name],
1607
- modePath: ["component", "select", tokenKey],
1608
- aliasSrc: componentAliases,
1609
- aliasPath: ["select", tokenKey]
1610
- })),
1611
- ...TEXTAREA_FIELDS.map(({ name, tokenKey }) => ({
1612
- varParts: ["textarea", name],
1613
- modePath: ["component", "textarea", tokenKey],
1614
- aliasSrc: componentAliases,
1615
- aliasPath: ["textarea", tokenKey]
1616
- })),
1617
- ...FIELDSET_FIELDS.map(({ name, tokenKey }) => ({
1618
- varParts: ["fieldset", name],
1619
- modePath: ["component", "fieldset", tokenKey],
1620
- aliasSrc: componentAliases,
1621
- aliasPath: ["fieldset", tokenKey]
1622
- })),
1623
- ...LABEL_FIELDS.map(({ name, tokenKey }) => ({
1624
- varParts: ["label", name],
1625
- modePath: ["component", "label", tokenKey],
1626
- aliasSrc: componentAliases,
1627
- aliasPath: ["label", tokenKey]
1628
- }))
1629
- ];
1461
+ const componentVarParts = (group, kebabGroup, path) => LEGACY_COMPONENT_PREFIX_GROUPS.has(group) ? ["component", kebabGroup, ...path] : [kebabGroup, ...path];
1630
1462
  const baseLines = [];
1631
1463
  const darkLines = [];
1632
1464
  const addBase = (name, value) => {
@@ -1635,12 +1467,56 @@ var generateCssVariables = (tokens2, options = {}) => {
1635
1467
  const addDark = (name, value) => {
1636
1468
  if (value !== void 0) darkLines.push(` ${name}: ${value};`);
1637
1469
  };
1638
- semanticEntries.forEach(({ varParts, modePath, aliasSrc, aliasPath }) => {
1639
- const varName = toVariableName(prefix, ...varParts);
1640
- const aliasCandidate = aliasSrc && aliasPath ? [getPath(aliasSrc, aliasPath)] : [];
1641
- addBase(varName, pickSemantic(tokens2, getPath(defaultMode, modePath), ...aliasCandidate));
1642
- addDark(varName, pickSemantic(tokens2, getPath(darkMode, modePath), getPath(defaultMode, modePath), ...aliasCandidate));
1643
- });
1470
+ const walkSemanticGroup = (namespace, varPartsFor, aliasSrc) => {
1471
+ const defaultNode = defaultMode[namespace];
1472
+ const darkNode = darkMode[namespace];
1473
+ const paths = /* @__PURE__ */ new Set();
1474
+ const collectPaths = (node, path) => {
1475
+ if (node === void 0) return;
1476
+ const leaf = resolveSemanticValue(node, tokens2, [namespace, ...path].join("."));
1477
+ if (leaf !== void 0) {
1478
+ paths.add(path.join("."));
1479
+ return;
1480
+ }
1481
+ if (isPlainObject(node)) {
1482
+ Object.keys(node).forEach((key) => collectPaths(node[key], [...path, key]));
1483
+ }
1484
+ };
1485
+ collectPaths(defaultNode, []);
1486
+ collectPaths(darkNode, []);
1487
+ collectPaths(aliasSrc, []);
1488
+ paths.forEach((joinedPath) => {
1489
+ const path = joinedPath.split(".");
1490
+ const varName = toVariableName(prefix, ...varPartsFor(path));
1491
+ const aliasCandidate = getPath(aliasSrc, path);
1492
+ const baseValue = pickSemantic(tokens2, getPath(defaultNode, path), aliasCandidate);
1493
+ const darkValue = pickSemantic(tokens2, getPath(darkNode, path), getPath(defaultNode, path), aliasCandidate);
1494
+ addBase(varName, baseValue);
1495
+ addDark(varName, darkValue);
1496
+ });
1497
+ };
1498
+ const kebabPathSegment = (segment) => formatKey(segment.replace(/([a-z0-9])([A-Z])/g, "$1-$2"));
1499
+ walkSemanticGroup(
1500
+ "surface",
1501
+ (path) => ["surface", ...path.map(kebabPathSegment)],
1502
+ surfaceAliases
1503
+ );
1504
+ walkSemanticGroup(
1505
+ "text",
1506
+ (path) => {
1507
+ const [scope, ...rest] = path;
1508
+ return ["text", ...kebabPathSegment(scope).split("-"), ...rest.map(kebabPathSegment)];
1509
+ },
1510
+ textAliases
1511
+ );
1512
+ walkSemanticGroup(
1513
+ "component",
1514
+ (path) => {
1515
+ const [group, ...rest] = path;
1516
+ return componentVarParts(group, kebabPathSegment(group), rest.map(kebabPathSegment));
1517
+ },
1518
+ componentAliases
1519
+ );
1644
1520
  Object.entries(linkTokens).forEach(([key, value]) => {
1645
1521
  const varName = toVariableName(prefix, "link", key);
1646
1522
  const resolved = pickSemantic(tokens2, value);