@stigmer/theme 3.35.0 → 3.36.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/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAChC,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACvE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAChC,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACvE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAEhC,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAEhC,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/theme",
3
- "version": "3.35.0",
3
+ "version": "3.36.0",
4
4
  "description": "Design tokens, CSS custom properties, and utility functions for Stigmer UI components",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/presets/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2CiB,CAAC;AAE5C,4CAA4C;AAC5C,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AAEjE;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,aAAa,GAAG,MAAM,CAY5D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/presets/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2CiB,CAAC;AAE5C,4CAA4C;AAC5C,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AAEjE;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,aAAa,GAAG,MAAM,CAY5D"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/presets/index.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B;QACE,EAAE,EAAE,SAAS;QACb,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,EAAE;QACb,WAAW,EAAE,iCAAiC;QAC9C,MAAM,EAAE,sBAAsB;KAC/B;IACD;QACE,EAAE,EAAE,WAAW;QACf,IAAI,EAAE,WAAW;QACjB,SAAS,EAAE,sBAAsB;QACjC,WAAW,EAAE,gEAAgE;QAC7E,MAAM,EAAE,sBAAsB;KAC/B;IACD;QACE,EAAE,EAAE,SAAS;QACb,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,oBAAoB;QAC/B,WAAW,EAAE,8DAA8D;QAC3E,MAAM,EAAE,sBAAsB;KAC/B;IACD;QACE,EAAE,EAAE,UAAU;QACd,IAAI,EAAE,UAAU;QAChB,SAAS,EAAE,qBAAqB;QAChC,WAAW,EAAE,0DAA0D;QACvE,MAAM,EAAE,qBAAqB;KAC9B;IACD;QACE,EAAE,EAAE,SAAS;QACb,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,oBAAoB;QAC/B,WAAW,EAAE,gEAAgE;QAC7E,MAAM,EAAE,sBAAsB;KAC/B;IACD;QACE,EAAE,EAAE,YAAY;QAChB,IAAI,EAAE,YAAY;QAClB,SAAS,EAAE,uBAAuB;QAClC,WAAW,EAAE,6DAA6D;QAC1E,MAAM,EAAE,iBAAiB;KAC1B;CACwC,CAAC;AAK5C;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAiB;IAClD,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;YAC1C,OAAO,CAAC,IAAI,CACV,oCAAoC,MAAM,CAAC,EAAE,CAAC,KAAK;gBACjD,cAAc,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC5D,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,MAAM,CAAC,SAAS,CAAC;AAC1B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/presets/index.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B;QACE,EAAE,EAAE,SAAS;QACb,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,EAAE;QACb,WAAW,EAAE,iCAAiC;QAC9C,MAAM,EAAE,sBAAsB;KAC/B;IACD;QACE,EAAE,EAAE,WAAW;QACf,IAAI,EAAE,WAAW;QACjB,SAAS,EAAE,sBAAsB;QACjC,WAAW,EAAE,gEAAgE;QAC7E,MAAM,EAAE,sBAAsB;KAC/B;IACD;QACE,EAAE,EAAE,SAAS;QACb,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,oBAAoB;QAC/B,WAAW,EAAE,8DAA8D;QAC3E,MAAM,EAAE,sBAAsB;KAC/B;IACD;QACE,EAAE,EAAE,UAAU;QACd,IAAI,EAAE,UAAU;QAChB,SAAS,EAAE,qBAAqB;QAChC,WAAW,EAAE,0DAA0D;QACvE,MAAM,EAAE,qBAAqB;KAC9B;IACD;QACE,EAAE,EAAE,SAAS;QACb,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,oBAAoB;QAC/B,WAAW,EAAE,gEAAgE;QAC7E,MAAM,EAAE,sBAAsB;KAC/B;IACD;QACE,EAAE,EAAE,YAAY;QAChB,IAAI,EAAE,YAAY;QAClB,SAAS,EAAE,uBAAuB;QAClC,WAAW,EAAE,6DAA6D;QAC1E,MAAM,EAAE,iBAAiB;KAC1B;CACwC,CAAC;AAK5C;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAiB;IAClD,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;YAC1C,OAAO,CAAC,IAAI,CACV,oCAAoC,MAAM,CAAC,EAAE,CAAC,KAAK;gBACjD,cAAc,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC5D,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,MAAM,CAAC,SAAS,CAAC;AAC1B,CAAC"}
package/utils.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAQ,MAAM,MAAM,CAAC;AAG7C,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,SAAS,CAAC;AAqBrC;;;;;;;;GAQG;AACH,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAQ,MAAM,MAAM,CAAC;AAG7C,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,SAAS,CAAC;AAqBrC;;;;;;;;GAQG;AACH,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
package/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,IAAI,EAAE,MAAM,MAAM,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAIrD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC;AAErC;;;;;;;;;GASG;AACH,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;IAC5C,0BAA0B,EAAE,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,EAAE,EAAE,CAC5D,cAAc,CACZ,SAAS,CAAC,UAAU,CAAC,cAAc,CAAC;QAClC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC;QACxC,CAAC,CAAC,SAAS,CACd;CACJ,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,UAAU,EAAE,CAAC,GAAG,MAAoB;IACxC,OAAO,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,IAAI,EAAE,MAAM,MAAM,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAIrD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC;AAErC;;;;;;;;;GASG;AACH,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;IAC5C,0BAA0B,EAAE,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,EAAE,EAAE,CAC5D,cAAc,CACZ,SAAS,CAAC,UAAU,CAAC,cAAc,CAAC;QAClC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC;QACxC,CAAC,CAAC,SAAS,CACd;CACJ,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,UAAU,EAAE,CAAC,GAAG,MAAoB;IACxC,OAAO,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AACzC,CAAC"}
@@ -1,49 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { cn } from "../utils";
3
-
4
- /**
5
- * Pins the #454 override contract: the `stg:` utility prefix must be
6
- * TRANSPARENT for tailwind-merge conflict grouping, so a host's unprefixed
7
- * `className` utilities still displace the SDK's prefixed defaults. If these
8
- * pins fail, `cn("stg:p-2", hostClassName)` keeps both classes and the SDK's
9
- * higher cascade layer silently wins — the documented override channel
10
- * (DD-019) breaks without any visible error.
11
- */
12
- describe("cn: stg: prefix is transparent for conflict grouping", () => {
13
- it("host unprefixed utility displaces the SDK's prefixed default", () => {
14
- expect(cn("stg:p-2", "p-4")).toBe("p-4");
15
- });
16
-
17
- it("SDK-internal merges keep the prefixed spelling", () => {
18
- expect(cn("stg:p-2", "stg:p-4")).toBe("stg:p-4");
19
- });
20
-
21
- it("non-conflicting utilities from both spellings all survive", () => {
22
- expect(cn("stg:flex stg:gap-2", "p-4")).toBe("stg:flex stg:gap-2 p-4");
23
- });
24
-
25
- it("variant conflicts resolve across spellings", () => {
26
- expect(cn("stg:hover:bg-red-500", "hover:bg-blue-500")).toBe(
27
- "hover:bg-blue-500",
28
- );
29
- });
30
-
31
- it("SDK prefixed utility displaces an earlier host utility", () => {
32
- // SDK call sites put the host className LAST (cn(base, className)), but
33
- // the grouping must be symmetric.
34
- expect(cn("p-4", "stg:p-2")).toBe("stg:p-2");
35
- });
36
-
37
- it("non-Tailwind classes pass through untouched", () => {
38
- expect(cn("stg:p-2", "custom-class")).toBe("stg:p-2 custom-class");
39
- expect(cn("stgm-thread-item-enter", "stg:opacity-50")).toBe(
40
- "stgm-thread-item-enter stg:opacity-50",
41
- );
42
- });
43
-
44
- it("conditional falsy inputs are dropped (clsx semantics)", () => {
45
- expect(cn("stg:flex", false, undefined, null, "stg:gap-2")).toBe(
46
- "stg:flex stg:gap-2",
47
- );
48
- });
49
- });
@@ -1,72 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { compositeOver, contrastRatio, isColorValue, lightnessDelta, parseColor } from "../color.js";
3
-
4
- describe("contrastRatio", () => {
5
- it("computes the canonical black-on-white ratio", () => {
6
- expect(contrastRatio("black", "white")).toBeCloseTo(21, 5);
7
- expect(contrastRatio("white", "white")).toBeCloseTo(1, 5);
8
- });
9
-
10
- it("parses OKLCH token values", () => {
11
- // Dark-mode foreground on background from tokens.css — a high-contrast pair.
12
- const ratio = contrastRatio("oklch(0.985 0 0)", "oklch(0.145 0 0)");
13
- expect(ratio).toBeGreaterThan(15);
14
- });
15
-
16
- it("composites a translucent foreground over the background first", () => {
17
- // 50% white over black is a grey; contrast must be far below solid white's.
18
- const solid = contrastRatio("white", "black");
19
- const translucent = contrastRatio("rgb(255 255 255 / 50%)", "black");
20
- expect(translucent).toBeLessThan(solid);
21
- expect(translucent).toBeGreaterThan(1);
22
- });
23
-
24
- it("requires a backdrop for a translucent background", () => {
25
- expect(() => contrastRatio("white", "oklch(1 0 0 / 14%)")).toThrow(/backdropValue/);
26
- const ratio = contrastRatio("white", "oklch(1 0 0 / 14%)", "oklch(0.145 0 0)");
27
- expect(ratio).toBeGreaterThan(1);
28
- });
29
- });
30
-
31
- describe("compositeOver", () => {
32
- it("returns the foreground unchanged when opaque", () => {
33
- const fg = parseColor("rgb(10 20 30)");
34
- expect(compositeOver(fg, parseColor("white"))).toBe(fg);
35
- });
36
-
37
- it("blends channels linearly by alpha in sRGB", () => {
38
- const result = compositeOver(parseColor("rgb(255 255 255 / 50%)"), parseColor("black"));
39
- expect(result.r).toBeCloseTo(0.5, 5);
40
- expect(result.g).toBeCloseTo(0.5, 5);
41
- expect(result.b).toBeCloseTo(0.5, 5);
42
- expect(result.alpha).toBe(1);
43
- });
44
- });
45
-
46
- describe("lightnessDelta", () => {
47
- it("measures OKLCH lightness separation", () => {
48
- expect(lightnessDelta("oklch(0.21 0 0)", "oklch(0.145 0 0)")).toBeCloseTo(0.065, 3);
49
- expect(lightnessDelta("white", "white")).toBeCloseTo(0, 5);
50
- });
51
-
52
- it("composites a translucent fill over the surface first", () => {
53
- // The default dark --stgm-input (20% white) over the popover surface:
54
- // raw white would report a delta near 0.73; the painted fill is far
55
- // dimmer but still clearly separated.
56
- const delta = lightnessDelta("oklch(1 0 0 / 20%)", "oklch(0.269 0 0)");
57
- expect(delta).toBeGreaterThan(0.045);
58
- expect(delta).toBeLessThan(0.5);
59
- });
60
-
61
- it("rejects a translucent surface (nothing to composite onto)", () => {
62
- expect(() => lightnessDelta("white", "oklch(1 0 0 / 14%)")).toThrow(/opaque surface/);
63
- });
64
- });
65
-
66
- describe("isColorValue", () => {
67
- it("distinguishes colors from other token values", () => {
68
- expect(isColorValue("oklch(0.5 0.1 190)")).toBe(true);
69
- expect(isColorValue("0.625rem")).toBe(false);
70
- expect(isColorValue("var(--font-geist-sans, system-ui, sans-serif)")).toBe(false);
71
- });
72
- });
@@ -1,81 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { runContrastAudit, resultId } from "../audit.js";
3
-
4
- /**
5
- * The permanent contrast gate for the token contract (stigmer/stigmer#187).
6
- *
7
- * Every pair declared in `pairs.ts` must meet its threshold in every preset
8
- * × color mode, resolved through the real cascade. A regression here means
9
- * shipped chrome text or surfaces became unreadable in someone's theme.
10
- *
11
- * EXEMPTIONS is the explicit, reviewed list of accepted misses. Adding to
12
- * it is a design decision, not a fix — every entry needs a rationale, and
13
- * the exemption is pinned to a measured floor so an exempted pair can still
14
- * never silently get *worse*.
15
- */
16
-
17
- interface Exemption {
18
- /** `resultId()` of the accepted miss. */
19
- readonly id: string;
20
- /** Why this miss is acceptable. */
21
- readonly rationale: string;
22
- /** The measured value at the time of exemption; regressions below fail. */
23
- readonly measuredFloor: number;
24
- }
25
-
26
- const EXEMPTIONS: readonly Exemption[] = [];
27
-
28
- const exemptionById = new Map(EXEMPTIONS.map((e) => [e.id, e]));
29
-
30
- describe("token contract contrast audit", () => {
31
- const { results, leaks } = runContrastAudit();
32
-
33
- it("declared pairs resolve for every preset and mode", () => {
34
- // 6 presets × 2 modes × (text+supporting+surface pairs); exact count is
35
- // asserted loosely so adding a pair does not require touching this test.
36
- expect(results.length).toBeGreaterThan(400);
37
- });
38
-
39
- it("no preset leaks a light-only token value into dark mode", () => {
40
- const formatted = leaks.map(
41
- (leak) => `${leak.preset}: ${leak.tokens.join(", ")}`,
42
- );
43
- expect(formatted, "preset tokens defined light-only override the default dark value (see resolve.ts)").toEqual([]);
44
- });
45
-
46
- const failures = results.filter(
47
- (r) => r.enforced && !r.passes && !exemptionById.has(resultId(r)),
48
- );
49
-
50
- it("every non-exempt pair meets its threshold", () => {
51
- const formatted = failures.map(
52
- (r) =>
53
- `${resultId(r)} — ${r.pair.kind} ${r.measured.toFixed(2)} < ${r.threshold} ` +
54
- `(fg ${r.foregroundValue} on bg ${r.backgroundValue}; used by: ${r.pair.usage})`,
55
- );
56
- expect(formatted).toEqual([]);
57
- });
58
-
59
- it("exempted pairs never regress below their recorded floor", () => {
60
- const regressions: string[] = [];
61
- const staleExemptions: string[] = [];
62
- for (const exemption of EXEMPTIONS) {
63
- const result = results.find((r) => resultId(r) === exemption.id);
64
- if (!result) {
65
- staleExemptions.push(exemption.id);
66
- continue;
67
- }
68
- if (result.passes) {
69
- // The pair now meets the real threshold — the exemption must be
70
- // removed so the full gate applies again.
71
- staleExemptions.push(exemption.id);
72
- } else if (result.measured < exemption.measuredFloor - 1e-6) {
73
- regressions.push(
74
- `${exemption.id}: ${result.measured.toFixed(3)} fell below exempted floor ${exemption.measuredFloor}`,
75
- );
76
- }
77
- }
78
- expect(regressions).toEqual([]);
79
- expect(staleExemptions, "exemption no longer needed or references a removed pair — delete it").toEqual([]);
80
- });
81
- });
@@ -1,106 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { parseThemeCss } from "../parse.js";
3
-
4
- describe("parseThemeCss", () => {
5
- it("splits :root and dark-attribute blocks into light and dark maps", () => {
6
- const css = `
7
- :root {
8
- --stgm-background: oklch(0.98 0 0);
9
- --stgm-foreground: oklch(0.145 0 0);
10
- }
11
-
12
- [data-stgm-color-mode="dark"] {
13
- --stgm-background: oklch(0.145 0 0);
14
- }
15
- `;
16
- const { light, dark } = parseThemeCss(css);
17
- expect(light.get("--stgm-background")?.value).toBe("oklch(0.98 0 0)");
18
- expect(light.get("--stgm-foreground")?.value).toBe("oklch(0.145 0 0)");
19
- expect(dark.get("--stgm-background")?.value).toBe("oklch(0.145 0 0)");
20
- expect(dark.has("--stgm-foreground")).toBe(false);
21
- });
22
-
23
- it("classifies the preset dual dark selector spanning multiple lines", () => {
24
- const css = `
25
- .stgm-theme-x {
26
- --stgm-primary: oklch(0.5 0.2 250);
27
- }
28
-
29
- [data-stgm-color-mode="dark"] .stgm-theme-x,
30
- .stgm-theme-x[data-stgm-color-mode="dark"] {
31
- --stgm-primary: oklch(0.7 0.2 250);
32
- }
33
- `;
34
- const { light, dark } = parseThemeCss(css);
35
- expect(light.get("--stgm-primary")?.value).toBe("oklch(0.5 0.2 250)");
36
- expect(dark.get("--stgm-primary")?.value).toBe("oklch(0.7 0.2 250)");
37
- });
38
-
39
- it("keeps values containing semicolons-free complex expressions intact", () => {
40
- const css = `
41
- :root {
42
- --stgm-shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
43
- --stgm-font-sans: var(--font-geist-sans, system-ui, sans-serif);
44
- --stgm-backdrop: oklch(0.98 0 0 / 80%);
45
- }
46
- `;
47
- const { light } = parseThemeCss(css);
48
- expect(light.get("--stgm-shadow-sm")?.value).toBe(
49
- "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
50
- );
51
- expect(light.get("--stgm-font-sans")?.value).toBe(
52
- "var(--font-geist-sans, system-ui, sans-serif)",
53
- );
54
- expect(light.get("--stgm-backdrop")?.value).toBe("oklch(0.98 0 0 / 80%)");
55
- });
56
-
57
- it("attaches @group headers and per-token descriptions", () => {
58
- const css = `
59
- :root {
60
- /* @group Core colors */
61
- /* Page background behind all content. */
62
- --stgm-background: oklch(0.98 0 0);
63
- --stgm-foreground: oklch(0.145 0 0);
64
-
65
- /* @group Shape */
66
- --stgm-radius: 0.625rem;
67
- }
68
- `;
69
- const { light } = parseThemeCss(css);
70
- expect(light.get("--stgm-background")).toMatchObject({
71
- group: "Core colors",
72
- description: "Page background behind all content.",
73
- });
74
- // The description belongs only to the token directly beneath it.
75
- expect(light.get("--stgm-foreground")).toMatchObject({ group: "Core colors" });
76
- expect(light.get("--stgm-foreground")?.description).toBeUndefined();
77
- expect(light.get("--stgm-radius")).toMatchObject({ group: "Shape" });
78
- });
79
-
80
- it("ignores multi-line file header comments", () => {
81
- const css = `
82
- /* Corporate — Enterprise SaaS design language
83
- Tight radius, corporate blue */
84
-
85
- .stgm-theme-corporate {
86
- --stgm-radius: 0.375rem;
87
- }
88
- `;
89
- const { light } = parseThemeCss(css);
90
- expect(light.get("--stgm-radius")?.value).toBe("0.375rem");
91
- expect(light.get("--stgm-radius")?.description).toBeUndefined();
92
- });
93
-
94
- it("ignores non-stgm declarations", () => {
95
- const css = `
96
- :root {
97
- --other-token: red;
98
- color: blue;
99
- --stgm-primary: oklch(0.5 0.1 190);
100
- }
101
- `;
102
- const { light } = parseThemeCss(css);
103
- expect(light.size).toBe(1);
104
- expect(light.has("--stgm-primary")).toBe(true);
105
- });
106
- });
@@ -1,85 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { parseThemeCss } from "../parse.js";
3
- import { presetLightLeaksIntoDark, resolveTokens } from "../resolve.js";
4
-
5
- const DEFAULTS = parseThemeCss(`
6
- :root {
7
- --stgm-background: light-default;
8
- --stgm-foreground: light-default;
9
- --stgm-primary: light-default;
10
- }
11
- [data-stgm-color-mode="dark"] {
12
- --stgm-background: dark-default;
13
- --stgm-primary: dark-default;
14
- }
15
- `);
16
-
17
- const PRESET = parseThemeCss(`
18
- .stgm-theme-x {
19
- --stgm-background: light-preset;
20
- --stgm-primary: light-preset;
21
- }
22
- [data-stgm-color-mode="dark"] .stgm-theme-x,
23
- .stgm-theme-x[data-stgm-color-mode="dark"] {
24
- --stgm-background: dark-preset;
25
- }
26
- `);
27
-
28
- describe("resolveTokens", () => {
29
- it("light mode without preset uses :root values", () => {
30
- const resolved = resolveTokens(DEFAULTS, undefined, "light");
31
- expect(resolved.get("--stgm-background")).toMatchObject({
32
- value: "light-default",
33
- source: "default-light",
34
- });
35
- });
36
-
37
- it("dark mode without preset overlays the dark block, falling back to light", () => {
38
- const resolved = resolveTokens(DEFAULTS, undefined, "dark");
39
- expect(resolved.get("--stgm-background")?.value).toBe("dark-default");
40
- // --stgm-foreground has no dark declaration: inherits the light value.
41
- expect(resolved.get("--stgm-foreground")).toMatchObject({
42
- value: "light-default",
43
- source: "default-light",
44
- });
45
- });
46
-
47
- it("light mode with preset prefers preset-light over defaults", () => {
48
- const resolved = resolveTokens(DEFAULTS, PRESET, "light");
49
- expect(resolved.get("--stgm-background")?.value).toBe("light-preset");
50
- expect(resolved.get("--stgm-foreground")?.value).toBe("light-default");
51
- });
52
-
53
- it("dark mode with preset resolves preset-dark ?? preset-light ?? default-dark ?? default-light", () => {
54
- const resolved = resolveTokens(DEFAULTS, PRESET, "dark");
55
- // Defined in the preset dark block: preset-dark wins.
56
- expect(resolved.get("--stgm-background")).toMatchObject({
57
- value: "dark-preset",
58
- source: "preset-dark",
59
- });
60
- // Defined in preset light only: the preset-light value leaks into dark
61
- // (higher cascade priority than the default dark block).
62
- expect(resolved.get("--stgm-primary")).toMatchObject({
63
- value: "light-preset",
64
- source: "preset-light",
65
- });
66
- // Untouched by the preset: default dark... which doesn't exist for
67
- // foreground, so default light.
68
- expect(resolved.get("--stgm-foreground")?.value).toBe("light-default");
69
- });
70
- });
71
-
72
- describe("presetLightLeaksIntoDark", () => {
73
- it("flags tokens the preset defines light-only while defaults have a dark value", () => {
74
- expect(presetLightLeaksIntoDark(DEFAULTS, PRESET)).toEqual(["--stgm-primary"]);
75
- });
76
-
77
- it("does not flag tokens with no default dark value (nothing to leak over)", () => {
78
- const preset = parseThemeCss(`
79
- .stgm-theme-y {
80
- --stgm-foreground: light-preset;
81
- }
82
- `);
83
- expect(presetLightLeaksIntoDark(DEFAULTS, preset)).toEqual([]);
84
- });
85
- });