@weasel-js/theme 1.9.3 → 1.10.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/dist/engine.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { overrideKey, AXES_EXT, ALPHA_EXT } from './chunk-Z675UQ26.js';
2
- import { mergeAxes, toLch, toHex, generate, DEFAULT_CONSTRAINTS, fullSelection, pick, enumerateSelections, resolveTokens, pickAll, themeAxes, selectionKey, isByAxis, contrast } from './chunk-NUILKH2Z.js';
3
- export { CHROMA_WEIGHT, DEFAULT_CONSTRAINTS, anchorFromHex, chromaCap, contrast, deltaE, floorDegrees, generate, hexToRgb, hueGap, hueName, luminance, toHex, toHexPreview, toLab, toLch, vividAt } from './chunk-NUILKH2Z.js';
2
+ import { toLch, toHex, generate, DEFAULT_CONSTRAINTS, resolveTokens, mixAlpha, themeAxes, tokensAt, contrast, tokenAt } from './chunk-2PY2YTDY.js';
3
+ export { CHROMA_WEIGHT, DEFAULT_CONSTRAINTS, anchorFromHex, chromaCap, contrast, deltaE, floorDegrees, generate, hexToRgb, hueGap, hueName, luminance, toHex, toHexPreview, toLab, toLch, vividAt } from './chunk-2PY2YTDY.js';
4
+ import { mergeAxes, fullSelection, pick, enumerateSelections, pickAll, selectionKey, isByAxis } from './chunk-P5F3GZV6.js';
5
+ import { isPlainObject } from '@weasel-js/registry';
4
6
 
5
7
  // src/engine/steps.ts
6
8
  var stepList = (x) => Array.isArray(x) ? [...new Set(x.filter((s) => typeof s === "string"))] : [];
@@ -217,6 +219,20 @@ function axisDependencies(definition, lookup) {
217
219
  return result;
218
220
  }
219
221
 
222
+ // src/engine/axisIssues.ts
223
+ function axisIssues(axes) {
224
+ const issues = [];
225
+ for (const [name, def] of Object.entries(axes)) {
226
+ const values = Object.keys(def.values ?? {});
227
+ if (values.length === 0) {
228
+ issues.push({ kind: "invalid", path: `axes.${name}`, message: `axis "${name}" has no values` });
229
+ } else if (!values.includes(def.default)) {
230
+ issues.push({ kind: "invalid", path: `axes.${name}`, message: `axis "${name}" defaults to "${def.default}", which is not one of its values (${values.join(", ")})` });
231
+ }
232
+ }
233
+ return issues;
234
+ }
235
+
220
236
  // src/engine/ramps.ts
221
237
  var MAX_CHROMA = 0.4;
222
238
  var smoothstep = (t) => t * t * (3 - 2 * t);
@@ -290,12 +306,10 @@ function scale(steps, p) {
290
306
  });
291
307
  return out;
292
308
  }
293
-
294
- // src/engine/semantics.ts
295
309
  var TOKEN_REF2 = /^\{([^}.]+)\}$/;
296
310
  var HEX = /^#[0-9a-f]{6}$/i;
297
311
  var MAX_DEPTH = 32;
298
- var isRecord = (x) => typeof x === "object" && x !== null && !Array.isArray(x);
312
+ var isRecord = isPlainObject;
299
313
  var refTarget = (value) => typeof value === "string" ? TOKEN_REF2.exec(value.trim())?.[1] : void 0;
300
314
  function deriveSemantics(rules, ctx) {
301
315
  const done = /* @__PURE__ */ new Map();
@@ -577,12 +591,12 @@ var Reader = class {
577
591
  }
578
592
  record(x, path) {
579
593
  if (x === void 0) return {};
580
- if (typeof x === "object" && x !== null && !Array.isArray(x)) return x;
594
+ if (isPlainObject(x)) return x;
581
595
  this.fail(path, "expected an object");
582
596
  return {};
583
597
  }
584
598
  };
585
- var isRecord2 = (x) => typeof x === "object" && x !== null && !Array.isArray(x);
599
+ var isRecord2 = isPlainObject;
586
600
  var stepNames = (x) => Array.isArray(x) && x.every((s) => typeof s === "string") ? x : void 0;
587
601
  function rampColors(name, r, steps, ctx) {
588
602
  const path = `ramps.${name}`;
@@ -726,7 +740,7 @@ function inferTypes(tokens, provenance, failed, issues) {
726
740
  issues.push({ kind: "untyped-pin", token: name });
727
741
  }
728
742
  }
729
- var isPinObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && "value" in v;
743
+ var isPinObject = (v) => isPlainObject(v) && "value" in v;
730
744
  var NAMED_LAYERS = ["ramps", "scales", "semantics", "components", "pins"];
731
745
  var SAFE_NAME = /^[A-Za-z0-9_-]+$/;
732
746
  var isSafeName = (name) => SAFE_NAME.test(name);
@@ -746,6 +760,7 @@ function withoutUnsafeNames(def, issues) {
746
760
  function derive(definition, selection = {}, lookup) {
747
761
  const issues = [];
748
762
  const def = withoutUnsafeNames(mergeChain(definition, lookup), issues);
763
+ issues.push(...axisIssues(def.axes ?? {}));
749
764
  const sel = fullSelection(def.axes ?? {}, selection);
750
765
  const seeds = {};
751
766
  for (const [k, v] of Object.entries(def.seeds ?? {})) {
@@ -937,23 +952,14 @@ function bakeOver(definition, merged, above, lookup) {
937
952
  const axes = merged.axes ?? {};
938
953
  const own = table(definition, axes, lookup);
939
954
  const at = (sel, name) => own.get(selectionKey(axes, sel))?.[name];
940
- const inherited = (sel, name) => {
941
- const full = fullSelection(axes, sel);
942
- for (let i = above.length - 1; i >= 0; i--) {
943
- const tokens2 = above[i].tokens;
944
- if (!Object.hasOwn(tokens2, name)) continue;
945
- const picked = pick(tokens2[name], full);
946
- if (picked.ok) return picked.value;
947
- }
948
- return void 0;
949
- };
955
+ const inherited = (sel, name) => tokenAt(above, name, fullSelection(axes, sel));
950
956
  const produced = new Set([...own.values()].flatMap((t) => Object.keys(t)));
951
957
  const ordered = definitionOrder(merged).filter((n) => produced.has(n));
952
958
  const names = [...ordered, ...[...produced].filter((n) => !ordered.includes(n))];
953
959
  const selections = enumerateSelections(axes);
954
960
  const tokens = {};
955
961
  for (const name of names) {
956
- if (selections.every((sel) => same(at(sel, name), inherited(sel, name)))) continue;
962
+ if (selections.every((sel) => at(sel, name) === void 0 || same(at(sel, name), inherited(sel, name)))) continue;
957
963
  const varying = Object.keys(axes).filter(
958
964
  (axis) => selections.some((sel) => Object.keys(axes[axis].values).some((v) => !same(at(sel, name), at({ ...sel, [axis]: v }, name))))
959
965
  );
@@ -998,7 +1004,7 @@ function cssValue(name, token) {
998
1004
  const target = refTarget2(token);
999
1005
  if (target === null) return resolveTokens({ [name]: token })[name];
1000
1006
  if (token.alpha === void 0) return `var(--wzl-${target})`;
1001
- return `color-mix(in srgb, var(--wzl-${target}) ${Math.round(token.alpha * 100)}%, transparent)`;
1007
+ return mixAlpha(`var(--wzl-${target})`, token.alpha);
1002
1008
  }
1003
1009
  function axisSets(axes) {
1004
1010
  const names = Object.keys(axes);
@@ -1030,21 +1036,16 @@ function combineAxes(own, other) {
1030
1036
  var Resolved = class {
1031
1037
  axes;
1032
1038
  names;
1039
+ /** Root first. */
1033
1040
  chain;
1034
1041
  memo = /* @__PURE__ */ new Map();
1035
1042
  constructor(axes, chain) {
1036
1043
  this.axes = axes;
1037
1044
  this.chain = chain;
1038
- this.names = [...new Set([...chain].reverse().flatMap((t) => Object.keys(t.tokens)))];
1045
+ this.names = [...new Set(chain.flatMap((t) => Object.keys(t.tokens)))];
1039
1046
  }
1040
1047
  token(name, sel) {
1041
- const full = fullSelection(this.axes, sel);
1042
- for (const t of this.chain) {
1043
- if (!Object.hasOwn(t.tokens, name)) continue;
1044
- const picked = pick(t.tokens[name], full);
1045
- if (picked.ok) return picked.value;
1046
- }
1047
- return void 0;
1048
+ return tokenAt(this.chain, name, fullSelection(this.axes, sel));
1048
1049
  }
1049
1050
  /** What a browser computes: every `var()` substituted, down to literals. */
1050
1051
  expanded(name, sel, stack = []) {
@@ -1064,7 +1065,7 @@ function chainOf(baked, byName) {
1064
1065
  if (!baked.extends) return [baked];
1065
1066
  const parent = byName.get(baked.extends);
1066
1067
  if (!parent) throw new Error(`Theme "${baked.name}" extends "${baked.extends}", which is not being emitted`);
1067
- return [baked, ...chainOf(parent, byName)];
1068
+ return [...chainOf(parent, byName), baked];
1068
1069
  }
1069
1070
  var union = (axes, ...sets) => {
1070
1071
  const all = new Set(sets.flatMap((s) => s ?? []));
@@ -1249,6 +1250,138 @@ function toDTCG(theme) {
1249
1250
 
1250
1251
  // src/hooks.ts
1251
1252
  var TOKEN_HOOKS = [
1253
+ {
1254
+ name: "titlebar-surface",
1255
+ type: "color",
1256
+ value: "var(--_edge)",
1257
+ description: "Background of the strip a `titlebar` frame carries its title in: a color, a gradient, or stripes. Unset, the frame\u2019s edge color. A frame that names a tone paints the tone instead."
1258
+ },
1259
+ {
1260
+ name: "titlebar-fg",
1261
+ type: "color",
1262
+ value: "var(--_edge-ink)",
1263
+ description: "Text color on a `titlebar` frame\u2019s strip. Unset, black or white, whichever reads on the edge color."
1264
+ },
1265
+ {
1266
+ name: "titlebar-align",
1267
+ type: "string",
1268
+ value: "start",
1269
+ description: "Text alignment of a `titlebar` frame\u2019s title along its strip."
1270
+ },
1271
+ {
1272
+ name: "titlebar-weight",
1273
+ type: "fontWeight",
1274
+ value: "var(--wzl-font-weight-bold)",
1275
+ description: "Font weight of a `titlebar` frame\u2019s title."
1276
+ },
1277
+ {
1278
+ name: "titlebar-label-surface",
1279
+ type: "color",
1280
+ value: "transparent",
1281
+ description: "Background behind a `titlebar` frame\u2019s title alone, for a strip drawn in stripes that must not run through the letters."
1282
+ },
1283
+ {
1284
+ name: "titlebar-label-pad",
1285
+ type: "dimension",
1286
+ value: "0",
1287
+ description: "Inline padding of the patch --wzl-titlebar-label-surface paints behind a `titlebar` frame\u2019s title."
1288
+ },
1289
+ {
1290
+ name: "titlebar-border",
1291
+ type: "border",
1292
+ value: "1px solid var(--_edge)",
1293
+ description: "Border of a `titlebar` frame. A theme whose --wzl-bevel-raised draws the edge sets it to a border of no width."
1294
+ },
1295
+ {
1296
+ name: "titlebar-inset",
1297
+ type: "dimension",
1298
+ value: "0",
1299
+ description: "Padding between a `titlebar` frame\u2019s edge and its strip and body: the width of the bevel a theme draws there."
1300
+ },
1301
+ {
1302
+ name: "bevel-raised",
1303
+ type: "shadow",
1304
+ value: "none",
1305
+ description: "Box shadow of a raised face: a Button, a PropertyPanel, a menu, popover, tooltip, or Dialog, a thumb, the chosen segment of a segmented control, a toolbar\u2019s strip, and every edge of a tab but the one its panel is on. A theme that draws its edges in relief declares it; unset, each keeps the shadow it draws for itself, which for most is none."
1306
+ },
1307
+ {
1308
+ name: "bevel-pressed",
1309
+ type: "shadow",
1310
+ value: "none",
1311
+ description: "Box shadow of a Button held down, or of a Button or a toolbar\u2019s button that stays down. Unset, a Button draws its own inset (--btn-relief-pressed) and a toolbar\u2019s button none."
1312
+ },
1313
+ {
1314
+ name: "bevel-sunken",
1315
+ type: "shadow",
1316
+ value: "none",
1317
+ description: "Box shadow of a well: the frame a field draws its value in, a checkbox, a radio, the track of a switch, a slider, or a segmented control, and a cell of a flat one."
1318
+ },
1319
+ {
1320
+ name: "field-border",
1321
+ type: "border",
1322
+ value: "1px solid var(--wzl-border)",
1323
+ description: "Border of a field\u2019s frame. A theme whose --wzl-bevel-sunken draws the edge sets it to a border of no width."
1324
+ },
1325
+ {
1326
+ name: "field-focus-shadow",
1327
+ type: "shadow",
1328
+ value: "0 0 0 1px var(--wzl-focus-ring)",
1329
+ description: "Box shadow of a field\u2019s frame while it holds focus. It replaces --wzl-bevel-sunken there, so a beveled theme restates the bevel in it."
1330
+ },
1331
+ {
1332
+ name: "button-radius",
1333
+ type: "dimension",
1334
+ value: "calc(var(--btn-height) * 0.1)",
1335
+ description: "Corner radius of a Button. Unset, a tenth of its height."
1336
+ },
1337
+ {
1338
+ name: "button-surface",
1339
+ type: "color",
1340
+ value: "var(--btn-glass)",
1341
+ description: "Background of a secondary Button. Unset, neutral glass mixed from the surfaces."
1342
+ },
1343
+ {
1344
+ name: "button-pressed-surface",
1345
+ type: "color",
1346
+ value: "var(--btn-glass-pressed)",
1347
+ description: "Background of a secondary Button while it is held down."
1348
+ },
1349
+ {
1350
+ name: "button-primary-surface",
1351
+ type: "color",
1352
+ value: "var(--btn-glass)",
1353
+ description: "Background of a primary Button. Unset, glass tinted with --wzl-accent."
1354
+ },
1355
+ {
1356
+ name: "button-primary-pressed-surface",
1357
+ type: "color",
1358
+ value: "var(--btn-glass-pressed)",
1359
+ description: "Background of a primary Button while it is held down."
1360
+ },
1361
+ {
1362
+ name: "button-primary-fg",
1363
+ type: "color",
1364
+ value: "var(--wzl-fg-on-accent)",
1365
+ description: "Text color of a primary Button, for a theme whose --wzl-button-primary-surface is not an accent fill."
1366
+ },
1367
+ {
1368
+ name: "button-primary-bevel",
1369
+ type: "shadow",
1370
+ value: "var(--wzl-bevel-raised)",
1371
+ description: "Box shadow of a primary Button, where it differs from --wzl-bevel-raised: the heavier frame of a default button."
1372
+ },
1373
+ {
1374
+ name: "button-gloss",
1375
+ type: "number",
1376
+ value: "1",
1377
+ description: "Opacity of the specular sheen over a Button\u2019s upper half. 0 for a flat face."
1378
+ },
1379
+ {
1380
+ name: "button-text-shadow",
1381
+ type: "shadow",
1382
+ value: "var(--btn-engraving)",
1383
+ description: "Text shadow of a primary or secondary Button\u2019s label. Unset, an engraving stamp under the glyphs in dark mode."
1384
+ },
1252
1385
  {
1253
1386
  name: "detail-figure-min-width",
1254
1387
  type: "dimension",
@@ -1321,6 +1454,12 @@ var TOKEN_HOOKS = [
1321
1454
  value: "300px",
1322
1455
  description: "Width of the Prefs category column."
1323
1456
  },
1457
+ {
1458
+ name: "prop-columns",
1459
+ type: "string",
1460
+ value: "1fr 1fr",
1461
+ description: "Column tracks of a Properties list, group body, and subpanel. A container too narrow for two columns sets `minmax(0, 1fr)` to stack every row."
1462
+ },
1324
1463
  {
1325
1464
  name: "prop-number-width",
1326
1465
  type: "dimension",
@@ -1380,6 +1519,60 @@ var TOKEN_HOOKS = [
1380
1519
  type: "dimension",
1381
1520
  value: "calc(var(--wzl-control-h-xs) + var(--wzl-space-2))",
1382
1521
  description: "Indent of each Tree level. Unset, it is one twisty and one gap, so a child\u2019s twisty sits under its parent\u2019s label."
1522
+ },
1523
+ {
1524
+ name: "tree-font-size",
1525
+ type: "dimension",
1526
+ value: "var(--wzl-font-size-sm)",
1527
+ description: "Size of a Tree row\u2019s text."
1528
+ },
1529
+ {
1530
+ name: "tree-fold-size",
1531
+ type: "dimension",
1532
+ value: "16px",
1533
+ description: "Size of the leading glyph a Tree folds a branch by, where its branches fold by their glyphs."
1534
+ },
1535
+ {
1536
+ name: "tree-guide",
1537
+ type: "color",
1538
+ value: "var(--wzl-fg-subtle)",
1539
+ description: "Color of the bracket a Tree draws down a branch."
1540
+ },
1541
+ {
1542
+ name: "tree-leaf-indent",
1543
+ type: "dimension",
1544
+ value: "0",
1545
+ description: "How far in from its branches' glyphs a Tree with guides sets a row that folds nothing."
1546
+ },
1547
+ {
1548
+ name: "dialog-max-width",
1549
+ type: "dimension",
1550
+ value: "900px",
1551
+ description: "Widest a Dialog grows, modal or inline, before its contents wrap or scroll."
1552
+ },
1553
+ {
1554
+ name: "prefs-across-gap",
1555
+ type: "dimension",
1556
+ value: "28px",
1557
+ description: "Gap between the two columns of rows a PrefsForm pane sets side by side."
1558
+ },
1559
+ {
1560
+ name: "prefs-control-min",
1561
+ type: "dimension",
1562
+ value: "80px",
1563
+ description: "Narrowest a PrefsForm row\u2019s control gets in a two-across pane before the row wraps it under its label."
1564
+ },
1565
+ {
1566
+ name: "prop-panel-actions-basis",
1567
+ type: "dimension",
1568
+ value: "auto",
1569
+ description: "Width a PropertyPanel\u2019s title row gives its actions. Set, it holds that width whatever the actions are."
1570
+ },
1571
+ {
1572
+ name: "prop-panel-actions-align",
1573
+ type: "string",
1574
+ value: "center",
1575
+ description: "How a PropertyPanel\u2019s title row lines its actions up with the title: center, or baseline for actions that are text."
1383
1576
  }
1384
1577
  ];
1385
1578
 
@@ -1434,9 +1627,6 @@ function chainOf2(baked, byName) {
1434
1627
  if (baked.extends && !parent) throw new Error(`Theme "${baked.name}" extends "${baked.extends}", which is not in themes/`);
1435
1628
  return [...parent ? chainOf2(parent, byName) : [], baked];
1436
1629
  }
1437
- function tokensAt(chain, sel) {
1438
- return Object.assign({}, ...chain.map((t) => pickAll(t.tokens, sel)));
1439
- }
1440
1630
  function emitThemes(themes) {
1441
1631
  const byName = new Map(themes.map((t) => [t.baked.name, t.baked]));
1442
1632
  const names = /* @__PURE__ */ new Set();
@@ -1506,11 +1696,12 @@ function generateTokens(definitions) {
1506
1696
  const roots = definitions.filter((d) => !d.extends);
1507
1697
  if (roots.length !== 1) throw new Error(`themes/ needs exactly one theme that extends nothing; found ${roots.length}`);
1508
1698
  const ordered = [roots[0], ...definitions.filter((d) => d.extends)];
1509
- const problems = ordered.flatMap(
1510
- (def) => enumerateSelections(mergeChain(def, lookup).axes ?? {}).flatMap(
1699
+ const problems = ordered.flatMap((def) => {
1700
+ const selections = enumerateSelections(mergeChain(def, lookup).axes ?? {});
1701
+ return (selections.length > 0 ? selections : [{}]).flatMap(
1511
1702
  (sel) => derive(def, sel, lookup).issues.map((issue) => `${def.name} ${JSON.stringify(sel)}: ${JSON.stringify(issue)}`)
1512
- )
1513
- );
1703
+ );
1704
+ });
1514
1705
  if (problems.length > 0) return { ok: false, problems };
1515
1706
  const themes = ordered.map((definition) => ({
1516
1707
  definition,