purse-styles 0.1.2 → 0.2.1

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
@@ -50,6 +50,50 @@ Wrap your app in `PurseProvider`:
50
50
 
51
51
  That's all! No build step.
52
52
 
53
+ ## Variables
54
+
55
+ Define typed CSS custom properties with `defineVars`. Defaults are injected on `:root` when a `PurseProvider` is mounted.
56
+
57
+ ```tsx
58
+ import { PurseProvider, defineVars, style, useStyles } from "purse-styles"
59
+
60
+ const DARK_THEME = ':root[data-theme="dark"]'
61
+ const DARK_MEDIA = "@media (prefers-color-scheme: dark)"
62
+
63
+ export const colors = defineVars({
64
+ text: {
65
+ default: "black",
66
+ [DARK_THEME]: "white",
67
+ [DARK_MEDIA]: "white",
68
+ },
69
+ background: { default: "white", [DARK_THEME]: "black" },
70
+ accent: "blue",
71
+ })
72
+
73
+ const container = style({
74
+ color: colors.text,
75
+ backgroundColor: colors.background,
76
+ borderColor: colors.accent,
77
+ })
78
+
79
+ function App() {
80
+ const className = useStyles(container)
81
+ return <main className={className}>...</main>
82
+ }
83
+
84
+ ;<PurseProvider>
85
+ <App />
86
+ </PurseProvider>
87
+ ```
88
+
89
+ Notes:
90
+
91
+ - Token names are generated (`var(--purse-<hash>-<key>)`). Identical definition objects share the same names.
92
+ - Keys cannot start with `--`; Purse always generates the custom property name.
93
+ - Conditional values use a required `default` plus CSS at-rule or selector keys. At-rules wrap a `:root` rule (for example, `@media ...{:root{...}}`), while selector conditions receive the declarations directly (for example, `:root[data-theme="dark"]{...}`).
94
+ - Set `data-theme="dark"` on the root `<html>` element to activate the selector condition above.
95
+ - Defaults are root-level, and there is no dedicated `createTheme` / subtree theme API.
96
+
53
97
  ## Exports
54
98
 
55
99
  ```ts
@@ -66,10 +110,15 @@ export declare function useStyles(
66
110
  // Inject global styles into the app, you may want to do this as an escape hatch
67
111
  export declare function useInjectGlobalStyles(
68
112
  selector: string,
69
- cssProperties: BaseCSSProperties & CSSVarDeclarations,
113
+ cssProperties: BaseCSSProperties & CustomProperties,
70
114
  deps: DependencyList,
71
115
  ): void
72
116
 
117
+ // Define a typed variable group; defaults are registered on :root via PurseProvider
118
+ export declare function defineVars<
119
+ const T extends Record<string, VariableDefinition>,
120
+ >(definitions: T): VariableGroup<T>
121
+
73
122
  // Wrap your app in this so Purse can inject styles into the app
74
123
  export declare function PurseProvider(props: {
75
124
  children?: React.ReactNode
@@ -82,11 +131,12 @@ export type InMemoryStyleApi = {
82
131
  }
83
132
  addStyleElement: StyleApi["addStyleElement"]
84
133
  addGlobalStyle: StyleApi["addGlobalStyle"]
134
+ detachVariableGroups: () => void
85
135
  }
86
136
  export declare function createInMemoryStyleApi(): InMemoryStyleApi
87
137
  ```
88
138
 
89
139
  ## TODO
90
140
 
91
- - [ ] E2E tests with Playwright
141
+ - [x] E2E tests with Playwright
92
142
  - [ ] Consider exporting a simple `styled` alternative
package/dist/cssVar.d.ts CHANGED
@@ -1,2 +1,31 @@
1
+ import { Destructor } from "./destructors";
1
2
  export type CSSVar = `var(--${string})`;
2
- export declare function createCssVar(name: string): CSSVar;
3
+ export type VariablePrimitive = string | number;
4
+ export type VariableCondition = string;
5
+ export type ConditionalVariableValue<T extends VariablePrimitive = VariablePrimitive> = {
6
+ default: T;
7
+ } & Partial<Record<VariableCondition, T>>;
8
+ export type VariableDefinition = VariablePrimitive | ConditionalVariableValue;
9
+ type NormalizedTokenDefinition = {
10
+ default: VariablePrimitive;
11
+ conditions: Record<string, VariablePrimitive>;
12
+ };
13
+ type NormalizedDefinitions = Record<string, NormalizedTokenDefinition>;
14
+ export type VariableGroupMetadata = {
15
+ groupId: string;
16
+ definitions: NormalizedDefinitions;
17
+ names: Record<string, `--${string}`>;
18
+ };
19
+ export type VariableGroup<T extends Record<string, unknown>> = {
20
+ readonly [K in keyof T]: CSSVar;
21
+ };
22
+ export type AnyVariableGroup = VariableGroup<Record<string, unknown>>;
23
+ export type VariableStyleSink = {
24
+ addGlobalStyle: (styleRule: string) => Destructor;
25
+ };
26
+ export declare function compileVariableGroupRules(metadata: VariableGroupMetadata): string[];
27
+ export declare function subscribeVariableGroups(listener: (metadata: VariableGroupMetadata) => void): Destructor;
28
+ export declare function attachVariableGroups(styleApi: VariableStyleSink): Destructor;
29
+ export declare function defineVars<const T extends Record<string, VariableDefinition>>(definitions: T): VariableGroup<T>;
30
+ export declare function getVariableGroupMetadata(group: AnyVariableGroup): VariableGroupMetadata;
31
+ export {};
package/dist/index.d.ts CHANGED
@@ -1 +1,4 @@
1
- export { CSSProperties, InMemoryStyleApi, PurseProvider, StyleApi, StyleArgument, StyleElement, createInMemoryStyleApi, style, useInjectGlobalStyles, useStyles, } from "./purse";
1
+ export { PurseProvider, createInMemoryStyleApi, style, useInjectGlobalStyles, useStyles, } from "./purse";
2
+ export type { CSSProperties, InMemoryStyleApi, StyleApi, StyleArgument, StyleElement, } from "./purse";
3
+ export { defineVars, getVariableGroupMetadata, } from "./cssVar";
4
+ export type { AnyVariableGroup, CSSVar, ConditionalVariableValue, VariableCondition, VariableDefinition, VariableGroup, VariableGroupMetadata, VariablePrimitive, } from "./cssVar";
package/dist/index.js CHANGED
@@ -40,6 +40,135 @@ function toPhash(h, x) {
40
40
  }
41
41
  var hashObject = (value) => toAlphabeticName(toPhash(5381, JSON.stringify(value)) >>> 0);
42
42
 
43
+ // src/cssVar.ts
44
+ var VARIABLE_GROUP_META = /* @__PURE__ */ Symbol("purse.variableGroup");
45
+ var registeredGroups = /* @__PURE__ */ new Map();
46
+ var listeners = /* @__PURE__ */ new Set();
47
+ function normalizeDefinition(value) {
48
+ if (typeof value !== "object" || value === null) {
49
+ return { default: value, conditions: {} };
50
+ }
51
+ const conditions = {};
52
+ for (const key of Object.keys(value).sort()) {
53
+ if (key === "default") continue;
54
+ conditions[key] = value[key];
55
+ }
56
+ return {
57
+ default: value.default,
58
+ conditions
59
+ };
60
+ }
61
+ function canonicalizeDefinitions(definitions) {
62
+ const canonical = {};
63
+ for (const key of Object.keys(definitions).sort()) {
64
+ canonical[key] = normalizeDefinition(definitions[key]);
65
+ }
66
+ return canonical;
67
+ }
68
+ function assertValidTokenKeys(definitions) {
69
+ for (const key of Object.keys(definitions)) {
70
+ if (key.startsWith("--")) {
71
+ throw new Error(
72
+ `defineVars keys cannot start with "--" (received "${key}"). Purse generates custom property names automatically.`
73
+ );
74
+ }
75
+ }
76
+ }
77
+ function registerVariableGroup(metadata) {
78
+ if (registeredGroups.has(metadata.groupId)) {
79
+ return;
80
+ }
81
+ registeredGroups.set(metadata.groupId, metadata);
82
+ for (const listener of listeners) {
83
+ listener(metadata);
84
+ }
85
+ }
86
+ function compileVariableGroupRules(metadata) {
87
+ let rootDeclarations = "";
88
+ const declarationsByCondition = /* @__PURE__ */ new Map();
89
+ for (const key of Object.keys(metadata.definitions)) {
90
+ const definition = metadata.definitions[key];
91
+ const customProperty = metadata.names[key];
92
+ rootDeclarations += `${customProperty}:${definition.default};`;
93
+ for (const condition of Object.keys(definition.conditions).sort()) {
94
+ const existing = declarationsByCondition.get(condition) ?? "";
95
+ declarationsByCondition.set(
96
+ condition,
97
+ `${existing}${customProperty}:${definition.conditions[condition]};`
98
+ );
99
+ }
100
+ }
101
+ const rules = [`:root{${rootDeclarations}}`];
102
+ for (const condition of [...declarationsByCondition.keys()].sort()) {
103
+ const declarations = declarationsByCondition.get(condition);
104
+ rules.push(
105
+ condition.startsWith("@") ? `${condition}{:root{${declarations}}}` : `${condition}{${declarations}}`
106
+ );
107
+ }
108
+ return rules;
109
+ }
110
+ function subscribeVariableGroups(listener) {
111
+ listeners.add(listener);
112
+ for (const metadata of registeredGroups.values()) {
113
+ listener(metadata);
114
+ }
115
+ return () => {
116
+ listeners.delete(listener);
117
+ };
118
+ }
119
+ function attachVariableGroups(styleApi) {
120
+ const insertedGroups = /* @__PURE__ */ new Map();
121
+ function ensureGroup(metadata) {
122
+ if (insertedGroups.has(metadata.groupId)) {
123
+ return;
124
+ }
125
+ const destructor = joinDestructors(
126
+ compileVariableGroupRules(metadata).map(
127
+ (rule) => styleApi.addGlobalStyle(rule)
128
+ )
129
+ );
130
+ insertedGroups.set(metadata.groupId, destructor);
131
+ }
132
+ const unsubscribe = subscribeVariableGroups(ensureGroup);
133
+ return () => {
134
+ unsubscribe();
135
+ for (const destructor of insertedGroups.values()) {
136
+ destructor();
137
+ }
138
+ insertedGroups.clear();
139
+ };
140
+ }
141
+ function defineVars(definitions) {
142
+ assertValidTokenKeys(definitions);
143
+ const canonicalDefinitions = canonicalizeDefinitions(definitions);
144
+ const groupId = hashObject(canonicalDefinitions);
145
+ const names = {};
146
+ const references = {};
147
+ for (const key of Object.keys(canonicalDefinitions)) {
148
+ const customProperty = `--purse-${groupId}-${key}`;
149
+ names[key] = customProperty;
150
+ references[key] = `var(${customProperty})`;
151
+ }
152
+ const metadata = {
153
+ groupId,
154
+ definitions: canonicalDefinitions,
155
+ names
156
+ };
157
+ Object.defineProperty(references, VARIABLE_GROUP_META, {
158
+ value: metadata,
159
+ enumerable: false
160
+ });
161
+ registerVariableGroup(metadata);
162
+ return references;
163
+ }
164
+ function getVariableGroupMetadata(group) {
165
+ const metadata = group[VARIABLE_GROUP_META];
166
+ if (!metadata) {
167
+ throw new Error("Expected a variable group created by defineVars");
168
+ }
169
+ return metadata;
170
+ }
171
+
43
172
  // src/hyphenateStyleName.ts
44
173
  var uppercasePattern = /[A-Z]/g;
45
174
  var msPattern = /^ms-/;
@@ -126,7 +255,7 @@ function isUnitlessNumberProperty(kebabProperty) {
126
255
  return false;
127
256
  }
128
257
  var PurseContext = createContext(void 0);
129
- var __style__ = Symbol("style-element");
258
+ var __style__ = /* @__PURE__ */ Symbol("style-element");
130
259
  function isStyleElement(o) {
131
260
  return o != null && typeof o === "object" && "__style__" in o && o["__style__"] === __style__;
132
261
  }
@@ -186,9 +315,13 @@ function createInMemoryStyleApi() {
186
315
  }
187
316
  return () => removeStyleElement(styleElement);
188
317
  }
189
- return {
318
+ const styleApi = {
190
319
  addStyleElement,
191
- addGlobalStyle: addStyleRule,
320
+ addGlobalStyle: addStyleRule
321
+ };
322
+ return {
323
+ ...styleApi,
324
+ detachVariableGroups: attachVariableGroups(styleApi),
192
325
  styleRulesRef: {
193
326
  get current() {
194
327
  return styleRules;
@@ -285,16 +418,23 @@ function PurseProvider(props) {
285
418
  }
286
419
  return { addStyleElement, addGlobalStyle: addStyleRule };
287
420
  }, []);
421
+ useInsertionEffect(() => attachVariableGroups(styleApi), [styleApi]);
288
422
  return /* @__PURE__ */ jsx(PurseContext.Provider, { value: styleApi, children: props.children });
289
423
  }
424
+ function isCustomProperty(name) {
425
+ return name.startsWith("--");
426
+ }
290
427
  function compileDeclarations(declarations) {
291
- return entries(declarations).map(([camelCaseProperty, value]) => {
292
- const kebabProperty = hyphenateStyleName(camelCaseProperty);
293
- const needToAddPixelsUnit = typeof value === "number" && !isUnitlessNumberProperty(kebabProperty);
428
+ return entries(declarations).map(([name, value]) => {
429
+ if (value === void 0 || value === null) {
430
+ return "";
431
+ }
432
+ const property = isCustomProperty(name) ? name : hyphenateStyleName(name);
433
+ const needToAddPixelsUnit = typeof value === "number" && !isCustomProperty(name) && !isUnitlessNumberProperty(property);
294
434
  if (needToAddPixelsUnit) {
295
- return `${kebabProperty}:${value}px;`;
435
+ return `${property}:${value}px;`;
296
436
  } else {
297
- return `${kebabProperty}:${value};`;
437
+ return `${property}:${value};`;
298
438
  }
299
439
  }).join("");
300
440
  }
@@ -421,6 +561,8 @@ function useStyles(...styleElementsOrCSS) {
421
561
  export {
422
562
  PurseProvider,
423
563
  createInMemoryStyleApi,
564
+ defineVars,
565
+ getVariableGroupMetadata,
424
566
  style,
425
567
  useInjectGlobalStyles,
426
568
  useStyles
package/dist/index.js.map CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/purse.tsx", "../src/clsx.ts", "../src/destructors.ts", "../src/hashObject.ts", "../src/hyphenateStyleName.ts", "../src/useRequiredContext.tsx"],
4
- "mappings": "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",
3
+ "sources": ["../src/purse.tsx", "../src/clsx.ts", "../src/destructors.ts", "../src/hashObject.ts", "../src/cssVar.ts", "../src/hyphenateStyleName.ts", "../src/useRequiredContext.tsx"],
4
+ "mappings": "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",
5
5
  "names": ["rule", "styleRules", "value", "composed"]
6
6
  }
package/dist/purse.d.ts CHANGED
@@ -1,14 +1,16 @@
1
1
  import CSS from "csstype";
2
2
  import React, { DependencyList } from "react";
3
- import { CSSVar } from "./cssVar";
4
3
  import { Destructor } from "./destructors";
5
4
  type BaseCSSProperties = CSS.Properties<number | string, string & {}>;
6
- type CSSVarDeclarations = Record<CSSVar, string>;
5
+ type CustomProperties = {
6
+ [name: `--${string}`]: string | number | undefined;
7
+ };
8
+ type Declarations = BaseCSSProperties & CustomProperties;
7
9
  type NestedRuleKey = `&${CSS.SimplePseudos}` | `${CSS.AtRules}${string}` | `.${string}` | `&${string}`;
8
10
  export type CSSProperties = {
9
11
  [Key in keyof BaseCSSProperties]?: BaseCSSProperties[Key];
10
- } & {
11
- [Key in NestedRuleKey]?: BaseCSSProperties;
12
+ } & CustomProperties & {
13
+ [Key in NestedRuleKey]?: Declarations;
12
14
  };
13
15
  export type StyleApi = {
14
16
  addStyleElement: (styleElement: StyleElement) => Destructor;
@@ -31,6 +33,7 @@ export type InMemoryStyleApi = {
31
33
  };
32
34
  addStyleElement: StyleApi["addStyleElement"];
33
35
  addGlobalStyle: StyleApi["addGlobalStyle"];
36
+ detachVariableGroups: Destructor;
34
37
  };
35
38
  export declare function createInMemoryStyleApi(): InMemoryStyleApi;
36
39
  export declare function PurseProvider(props: {
@@ -38,6 +41,6 @@ export declare function PurseProvider(props: {
38
41
  }): import("react/jsx-runtime").JSX.Element;
39
42
  export type StyleArgument = CSSProperties | StyleElement | false | null | undefined;
40
43
  export declare function style(...styleElementsOrCSS: StyleArgument[]): StyleElement;
41
- export declare function useInjectGlobalStyles(selector: string, cssProperties: BaseCSSProperties & CSSVarDeclarations, deps: DependencyList): void;
44
+ export declare function useInjectGlobalStyles(selector: string, cssProperties: Declarations, deps: DependencyList): void;
42
45
  export declare function useStyles(...styleElementsOrCSS: StyleArgument[]): string;
43
46
  export {};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "purse-styles",
3
3
  "type": "module",
4
4
  "main": "./dist/index.js",
5
- "version": "0.1.2",
5
+ "version": "0.2.1",
6
6
  "author": "Tanishq Kancharla <tanishqkancharla3@gmail.com>",
7
7
  "description": "A basic styling solution for web apps with an emphasis on simplicity and purity.",
8
8
  "keywords": [],
@@ -18,20 +18,28 @@
18
18
  },
19
19
  "scripts": {
20
20
  "build": "tsx scripts/build.ts",
21
- "check": "tsc --noEmit",
21
+ "check": "tsc --noEmit && tsc -p e2e/tsconfig.json",
22
22
  "test": "vitest",
23
+ "test:run": "vitest run",
24
+ "test:e2e": "vitest run --project browser",
23
25
  "release": "npm run build; npm publish",
24
26
  "bundlesize": "esbuild ./dist/index.js --minify --outfile=bundled; rm bundled"
25
27
  },
26
28
  "devDependencies": {
27
29
  "@types/lodash-es": "^4.17.12",
28
- "@types/node": "^20.14.2",
30
+ "@types/node": "^22.20.1",
29
31
  "@types/react": "^18.3.3",
32
+ "@types/react-dom": "^18.3.7",
33
+ "@vitejs/plugin-react": "^6.0.3",
34
+ "@vitest/browser-playwright": "^4.1.10",
30
35
  "ansi-colors": "^4.1.3",
31
- "esbuild": "^0.21.5",
32
- "tsx": "^4.15.5",
36
+ "esbuild": "^0.28.1",
37
+ "playwright": "^1.61.1",
38
+ "react-dom": "^18.3.1",
39
+ "tsx": "^4.23.1",
33
40
  "typescript": "^5.4.5",
34
- "vitest": "^1.6.0"
41
+ "vitest": "^4.1.10",
42
+ "vitest-browser-react": "^2.2.0"
35
43
  },
36
44
  "peerDependencies": {
37
45
  "react": "^18.3.1"
@@ -0,0 +1,238 @@
1
+ import assert from "assert"
2
+ import { describe, expect, expectTypeOf, it } from "vitest"
3
+ import {
4
+ CSSVar,
5
+ compileVariableGroupRules,
6
+ defineVars,
7
+ getVariableGroupMetadata,
8
+ } from "./cssVar"
9
+ import { createInMemoryStyleApi, style } from "./purse"
10
+
11
+ function expectInjectedRules(
12
+ api: ReturnType<typeof createInMemoryStyleApi>,
13
+ metadata: ReturnType<typeof getVariableGroupMetadata>,
14
+ ) {
15
+ for (const rule of compileVariableGroupRules(metadata)) {
16
+ expect(api.styleRulesRef.current).toContain(rule)
17
+ }
18
+ }
19
+
20
+ describe("defineVars", () => {
21
+ it("returns typed var() references for each key", () => {
22
+ const colors = defineVars({
23
+ text: "black",
24
+ accent: "blue",
25
+ })
26
+
27
+ expectTypeOf(colors.text).toEqualTypeOf<CSSVar>()
28
+ expectTypeOf(colors.accent).toEqualTypeOf<CSSVar>()
29
+ expect(colors.text).toMatch(/^var\(--purse-[A-Za-z]+-text\)$/)
30
+ expect(colors.accent).toMatch(/^var\(--purse-[A-Za-z]+-accent\)$/)
31
+ })
32
+
33
+ it("generates deterministic names for the same definitions", () => {
34
+ const first = defineVars({
35
+ text: "black",
36
+ accent: "blue",
37
+ })
38
+ const second = defineVars({
39
+ text: "black",
40
+ accent: "blue",
41
+ })
42
+
43
+ expect(first.text).toBe(second.text)
44
+ expect(first.accent).toBe(second.accent)
45
+ expect(getVariableGroupMetadata(first).groupId).toBe(
46
+ getVariableGroupMetadata(second).groupId,
47
+ )
48
+ })
49
+
50
+ it("ignores object key insertion order when hashing", () => {
51
+ const first = defineVars({
52
+ text: "black",
53
+ accent: "blue",
54
+ })
55
+ const second = defineVars({
56
+ accent: "blue",
57
+ text: "black",
58
+ })
59
+
60
+ expect(first.text).toBe(second.text)
61
+ expect(first.accent).toBe(second.accent)
62
+ })
63
+
64
+ it("rejects keys that start with --", () => {
65
+ expect(() =>
66
+ defineVars({
67
+ "--brand": "blue",
68
+ }),
69
+ ).toThrow(/cannot start with "--"/)
70
+ })
71
+
72
+ it("can be used as style values", () => {
73
+ const colors = defineVars({
74
+ text: "black",
75
+ })
76
+
77
+ const test = style({
78
+ color: colors.text,
79
+ })
80
+
81
+ assert(test.owned !== undefined)
82
+ expect(test.owned.styleRules).toContain(
83
+ `.${test.className}{color:${colors.text};}`,
84
+ )
85
+ })
86
+
87
+ it("stores canonical metadata for later registration", () => {
88
+ const colors = defineVars({
89
+ accent: "blue",
90
+ text: "black",
91
+ })
92
+
93
+ const metadata = getVariableGroupMetadata(colors)
94
+ expect(Object.keys(metadata.definitions)).toEqual(["accent", "text"])
95
+ expect(metadata.names.text).toBe(
96
+ colors.text.slice("var(".length, -1),
97
+ )
98
+ })
99
+ })
100
+
101
+ describe("variable group injection", () => {
102
+ it("compiles scalar defaults onto :root", () => {
103
+ const colors = defineVars({
104
+ accent: "blue",
105
+ text: "black",
106
+ space: 2,
107
+ })
108
+ const metadata = getVariableGroupMetadata(colors)
109
+
110
+ expect(compileVariableGroupRules(metadata)).toEqual([
111
+ `:root{${metadata.names.accent}:blue;${metadata.names.space}:2;${metadata.names.text}:black;}`,
112
+ ])
113
+ })
114
+
115
+ it("injects already-registered groups when a style api attaches", () => {
116
+ const colors = defineVars({
117
+ phase3Existing: "navy",
118
+ })
119
+
120
+ const api = createInMemoryStyleApi()
121
+ expectInjectedRules(api, getVariableGroupMetadata(colors))
122
+ })
123
+
124
+ it("injects groups registered after a style api attaches", () => {
125
+ const api = createInMemoryStyleApi()
126
+ const colors = defineVars({
127
+ phase3Late: "teal",
128
+ })
129
+
130
+ expectInjectedRules(api, getVariableGroupMetadata(colors))
131
+ })
132
+
133
+ it("deduplicates identical variable groups into one root rule", () => {
134
+ const api = createInMemoryStyleApi()
135
+ const first = defineVars({
136
+ phase3Deduped: "purple",
137
+ })
138
+ defineVars({
139
+ phase3Deduped: "purple",
140
+ })
141
+
142
+ const [expectedRule] = compileVariableGroupRules(
143
+ getVariableGroupMetadata(first),
144
+ )
145
+ expect(
146
+ api.styleRulesRef.current.filter((rule) => rule === expectedRule),
147
+ ).toHaveLength(1)
148
+ })
149
+
150
+ it("removes injected variable rules when detached", () => {
151
+ const colors = defineVars({
152
+ phase3Cleanup: "orange",
153
+ })
154
+ const expectedRules = compileVariableGroupRules(
155
+ getVariableGroupMetadata(colors),
156
+ )
157
+ const api = createInMemoryStyleApi()
158
+
159
+ expectInjectedRules(api, getVariableGroupMetadata(colors))
160
+ api.detachVariableGroups()
161
+ for (const rule of expectedRules) {
162
+ expect(api.styleRulesRef.current).not.toContain(rule)
163
+ }
164
+ })
165
+ })
166
+
167
+ describe("conditional variable values", () => {
168
+ const DARK = "@media (prefers-color-scheme: dark)"
169
+
170
+ it("compiles defaults and at-rule overrides", () => {
171
+ const colors = defineVars({
172
+ accent: "blue",
173
+ text: {
174
+ default: "black",
175
+ [DARK]: "white",
176
+ },
177
+ })
178
+ const metadata = getVariableGroupMetadata(colors)
179
+
180
+ expect(compileVariableGroupRules(metadata)).toEqual([
181
+ `:root{${metadata.names.accent}:blue;${metadata.names.text}:black;}`,
182
+ `${DARK}{:root{${metadata.names.text}:white;}}`,
183
+ ])
184
+ })
185
+
186
+ it("groups multiple tokens that share a condition", () => {
187
+ const colors = defineVars({
188
+ background: {
189
+ default: "white",
190
+ [DARK]: "black",
191
+ },
192
+ text: {
193
+ default: "black",
194
+ [DARK]: "white",
195
+ },
196
+ })
197
+ const metadata = getVariableGroupMetadata(colors)
198
+
199
+ expect(compileVariableGroupRules(metadata)).toEqual([
200
+ `:root{${metadata.names.background}:white;${metadata.names.text}:black;}`,
201
+ `${DARK}{:root{${metadata.names.background}:black;${metadata.names.text}:white;}}`,
202
+ ])
203
+ })
204
+
205
+ it("ignores condition key order when hashing", () => {
206
+ const first = defineVars({
207
+ text: {
208
+ default: "black",
209
+ "@supports (color: color(display-p3 1 1 1))": "red",
210
+ [DARK]: "white",
211
+ },
212
+ })
213
+ const second = defineVars({
214
+ text: {
215
+ [DARK]: "white",
216
+ "@supports (color: color(display-p3 1 1 1))": "red",
217
+ default: "black",
218
+ },
219
+ })
220
+
221
+ expect(first.text).toBe(second.text)
222
+ expect(getVariableGroupMetadata(first).groupId).toBe(
223
+ getVariableGroupMetadata(second).groupId,
224
+ )
225
+ })
226
+
227
+ it("injects conditional rules through the style api", () => {
228
+ const api = createInMemoryStyleApi()
229
+ const colors = defineVars({
230
+ phase4Conditional: {
231
+ default: "black",
232
+ [DARK]: "white",
233
+ },
234
+ })
235
+
236
+ expectInjectedRules(api, getVariableGroupMetadata(colors))
237
+ })
238
+ })
package/src/cssVar.ts CHANGED
@@ -1,5 +1,221 @@
1
+ import { Destructor, joinDestructors } from "./destructors"
2
+ import { hashObject } from "./hashObject"
3
+
1
4
  export type CSSVar = `var(--${string})`
2
5
 
3
- export function createCssVar(name: string): CSSVar {
4
- return `var(--${name})`
6
+ export type VariablePrimitive = string | number
7
+
8
+ export type VariableCondition = string
9
+
10
+ export type ConditionalVariableValue<
11
+ T extends VariablePrimitive = VariablePrimitive,
12
+ > = {
13
+ default: T
14
+ } & Partial<Record<VariableCondition, T>>
15
+
16
+ export type VariableDefinition =
17
+ | VariablePrimitive
18
+ | ConditionalVariableValue
19
+
20
+ type NormalizedTokenDefinition = {
21
+ default: VariablePrimitive
22
+ conditions: Record<string, VariablePrimitive>
23
+ }
24
+
25
+ type NormalizedDefinitions = Record<string, NormalizedTokenDefinition>
26
+
27
+ export type VariableGroupMetadata = {
28
+ groupId: string
29
+ definitions: NormalizedDefinitions
30
+ names: Record<string, `--${string}`>
31
+ }
32
+
33
+ export type VariableGroup<T extends Record<string, unknown>> = {
34
+ readonly [K in keyof T]: CSSVar
35
+ }
36
+
37
+ export type AnyVariableGroup = VariableGroup<Record<string, unknown>>
38
+
39
+ export type VariableStyleSink = {
40
+ addGlobalStyle: (styleRule: string) => Destructor
41
+ }
42
+
43
+ const VARIABLE_GROUP_META = Symbol("purse.variableGroup")
44
+
45
+ type VariableGroupWithMeta<T extends Record<string, unknown>> =
46
+ VariableGroup<T> & {
47
+ readonly [VARIABLE_GROUP_META]: VariableGroupMetadata
48
+ }
49
+
50
+ const registeredGroups = new Map<string, VariableGroupMetadata>()
51
+ const listeners = new Set<(metadata: VariableGroupMetadata) => void>()
52
+
53
+ function normalizeDefinition(
54
+ value: VariableDefinition,
55
+ ): NormalizedTokenDefinition {
56
+ if (typeof value !== "object" || value === null) {
57
+ return { default: value, conditions: {} }
58
+ }
59
+
60
+ const conditions: Record<string, VariablePrimitive> = {}
61
+ for (const key of Object.keys(value).sort()) {
62
+ if (key === "default") continue
63
+ conditions[key] = value[key as VariableCondition]!
64
+ }
65
+
66
+ return {
67
+ default: value.default,
68
+ conditions,
69
+ }
70
+ }
71
+
72
+ function canonicalizeDefinitions(
73
+ definitions: Record<string, VariableDefinition>,
74
+ ): NormalizedDefinitions {
75
+ const canonical: NormalizedDefinitions = {}
76
+ for (const key of Object.keys(definitions).sort()) {
77
+ canonical[key] = normalizeDefinition(definitions[key]!)
78
+ }
79
+ return canonical
80
+ }
81
+
82
+ function assertValidTokenKeys(definitions: Record<string, VariableDefinition>) {
83
+ for (const key of Object.keys(definitions)) {
84
+ if (key.startsWith("--")) {
85
+ throw new Error(
86
+ `defineVars keys cannot start with "--" (received "${key}"). Purse generates custom property names automatically.`,
87
+ )
88
+ }
89
+ }
90
+ }
91
+
92
+ function registerVariableGroup(metadata: VariableGroupMetadata) {
93
+ if (registeredGroups.has(metadata.groupId)) {
94
+ return
95
+ }
96
+
97
+ registeredGroups.set(metadata.groupId, metadata)
98
+ for (const listener of listeners) {
99
+ listener(metadata)
100
+ }
101
+ }
102
+
103
+ export function compileVariableGroupRules(
104
+ metadata: VariableGroupMetadata,
105
+ ): string[] {
106
+ let rootDeclarations = ""
107
+ const declarationsByCondition = new Map<string, string>()
108
+
109
+ for (const key of Object.keys(metadata.definitions)) {
110
+ const definition = metadata.definitions[key]!
111
+ const customProperty = metadata.names[key]!
112
+ rootDeclarations += `${customProperty}:${definition.default};`
113
+
114
+ for (const condition of Object.keys(definition.conditions).sort()) {
115
+ const existing = declarationsByCondition.get(condition) ?? ""
116
+ declarationsByCondition.set(
117
+ condition,
118
+ `${existing}${customProperty}:${definition.conditions[condition]};`,
119
+ )
120
+ }
121
+ }
122
+
123
+ const rules = [`:root{${rootDeclarations}}`]
124
+ for (const condition of [...declarationsByCondition.keys()].sort()) {
125
+ const declarations = declarationsByCondition.get(condition)
126
+ rules.push(
127
+ condition.startsWith("@")
128
+ ? `${condition}{:root{${declarations}}}`
129
+ : `${condition}{${declarations}}`,
130
+ )
131
+ }
132
+ return rules
133
+ }
134
+
135
+ export function subscribeVariableGroups(
136
+ listener: (metadata: VariableGroupMetadata) => void,
137
+ ): Destructor {
138
+ listeners.add(listener)
139
+
140
+ for (const metadata of registeredGroups.values()) {
141
+ listener(metadata)
142
+ }
143
+
144
+ return () => {
145
+ listeners.delete(listener)
146
+ }
147
+ }
148
+
149
+ export function attachVariableGroups(
150
+ styleApi: VariableStyleSink,
151
+ ): Destructor {
152
+ const insertedGroups = new Map<string, Destructor>()
153
+
154
+ function ensureGroup(metadata: VariableGroupMetadata) {
155
+ if (insertedGroups.has(metadata.groupId)) {
156
+ return
157
+ }
158
+
159
+ const destructor = joinDestructors(
160
+ compileVariableGroupRules(metadata).map((rule) =>
161
+ styleApi.addGlobalStyle(rule),
162
+ ),
163
+ )
164
+ insertedGroups.set(metadata.groupId, destructor)
165
+ }
166
+
167
+ const unsubscribe = subscribeVariableGroups(ensureGroup)
168
+
169
+ return () => {
170
+ unsubscribe()
171
+ for (const destructor of insertedGroups.values()) {
172
+ destructor()
173
+ }
174
+ insertedGroups.clear()
175
+ }
176
+ }
177
+
178
+ export function defineVars<
179
+ const T extends Record<string, VariableDefinition>,
180
+ >(definitions: T): VariableGroup<T> {
181
+ assertValidTokenKeys(definitions)
182
+
183
+ const canonicalDefinitions = canonicalizeDefinitions(definitions)
184
+ const groupId = hashObject(canonicalDefinitions)
185
+ const names: Record<string, `--${string}`> = {}
186
+ const references = {} as VariableGroupWithMeta<T>
187
+
188
+ for (const key of Object.keys(canonicalDefinitions)) {
189
+ const customProperty = `--purse-${groupId}-${key}` as const
190
+ names[key] = customProperty
191
+ ;(references as Record<string, CSSVar>)[key] =
192
+ `var(${customProperty})`
193
+ }
194
+
195
+ const metadata = {
196
+ groupId,
197
+ definitions: canonicalDefinitions,
198
+ names,
199
+ } satisfies VariableGroupMetadata
200
+
201
+ Object.defineProperty(references, VARIABLE_GROUP_META, {
202
+ value: metadata,
203
+ enumerable: false,
204
+ })
205
+
206
+ registerVariableGroup(metadata)
207
+
208
+ return references
209
+ }
210
+
211
+ export function getVariableGroupMetadata(
212
+ group: AnyVariableGroup,
213
+ ): VariableGroupMetadata {
214
+ const metadata = (group as VariableGroupWithMeta<Record<string, unknown>>)[
215
+ VARIABLE_GROUP_META
216
+ ]
217
+ if (!metadata) {
218
+ throw new Error("Expected a variable group created by defineVars")
219
+ }
220
+ return metadata
5
221
  }
package/src/index.ts CHANGED
@@ -1,12 +1,28 @@
1
1
  export {
2
- CSSProperties,
3
- InMemoryStyleApi,
4
2
  PurseProvider,
5
- StyleApi,
6
- StyleArgument,
7
- StyleElement,
8
3
  createInMemoryStyleApi,
9
4
  style,
10
5
  useInjectGlobalStyles,
11
6
  useStyles,
12
7
  } from "./purse"
8
+ export type {
9
+ CSSProperties,
10
+ InMemoryStyleApi,
11
+ StyleApi,
12
+ StyleArgument,
13
+ StyleElement,
14
+ } from "./purse"
15
+ export {
16
+ defineVars,
17
+ getVariableGroupMetadata,
18
+ } from "./cssVar"
19
+ export type {
20
+ AnyVariableGroup,
21
+ CSSVar,
22
+ ConditionalVariableValue,
23
+ VariableCondition,
24
+ VariableDefinition,
25
+ VariableGroup,
26
+ VariableGroupMetadata,
27
+ VariablePrimitive,
28
+ } from "./cssVar"
package/src/purse.test.ts CHANGED
@@ -125,6 +125,28 @@ describe("Purse", () => {
125
125
  )
126
126
  })
127
127
 
128
+ it("Compiles custom property declarations without adding px", () => {
129
+ const test = style({
130
+ "--space": 2,
131
+ })
132
+
133
+ assert(test.owned !== undefined)
134
+ expect(test.owned.styleRules).toContain(
135
+ `.${test.className}{--space:2;}`,
136
+ )
137
+ })
138
+
139
+ it("Compiles custom property references as ordinary values", () => {
140
+ const test = style({
141
+ color: "var(--color)",
142
+ })
143
+
144
+ assert(test.owned !== undefined)
145
+ expect(test.owned.styleRules).toContain(
146
+ `.${test.className}{color:var(--color);}`,
147
+ )
148
+ })
149
+
128
150
  it.skip("Inserts into given style element correctly", () => {})
129
151
  it.skip("Works if you pass style rule or object to useStyles", () => {})
130
152
  it.skip("Works if you pass multiple style rule or object to useStyles", () => {})
package/src/purse.tsx CHANGED
@@ -8,7 +8,7 @@ import React, {
8
8
  useMemo,
9
9
  } from "react"
10
10
  import { clsx } from "./clsx"
11
- import { CSSVar } from "./cssVar"
11
+ import { attachVariableGroups } from "./cssVar"
12
12
  import { Destructor, joinDestructors } from "./destructors"
13
13
  import { hashObject } from "./hashObject"
14
14
  import { hyphenateStyleName } from "./hyphenateStyleName"
@@ -80,7 +80,12 @@ function isUnitlessNumberProperty(kebabProperty: string): boolean {
80
80
  }
81
81
 
82
82
  type BaseCSSProperties = CSS.Properties<number | string, string & {}>
83
- type CSSVarDeclarations = Record<CSSVar, string>
83
+
84
+ type CustomProperties = {
85
+ [name: `--${string}`]: string | number | undefined
86
+ }
87
+
88
+ type Declarations = BaseCSSProperties & CustomProperties
84
89
 
85
90
  type NestedRuleKey =
86
91
  | `&${CSS.SimplePseudos}`
@@ -90,8 +95,8 @@ type NestedRuleKey =
90
95
 
91
96
  export type CSSProperties = {
92
97
  [Key in keyof BaseCSSProperties]?: BaseCSSProperties[Key]
93
- } & {
94
- [Key in NestedRuleKey]?: BaseCSSProperties
98
+ } & CustomProperties & {
99
+ [Key in NestedRuleKey]?: Declarations
95
100
  }
96
101
 
97
102
  const PurseContext = createContext<StyleApi | undefined>(undefined)
@@ -130,6 +135,7 @@ export type InMemoryStyleApi = {
130
135
  styleRulesRef: { current: string[] }
131
136
  addStyleElement: StyleApi["addStyleElement"]
132
137
  addGlobalStyle: StyleApi["addGlobalStyle"]
138
+ detachVariableGroups: Destructor
133
139
  }
134
140
 
135
141
  export function createInMemoryStyleApi(): InMemoryStyleApi {
@@ -208,9 +214,14 @@ export function createInMemoryStyleApi(): InMemoryStyleApi {
208
214
  return () => removeStyleElement(styleElement)
209
215
  }
210
216
 
211
- return {
217
+ const styleApi = {
212
218
  addStyleElement,
213
219
  addGlobalStyle: addStyleRule,
220
+ }
221
+
222
+ return {
223
+ ...styleApi,
224
+ detachVariableGroups: attachVariableGroups(styleApi),
214
225
  styleRulesRef: {
215
226
  get current() {
216
227
  return styleRules
@@ -344,6 +355,8 @@ export function PurseProvider(props: { children?: React.ReactNode }) {
344
355
  return { addStyleElement, addGlobalStyle: addStyleRule }
345
356
  }, [])
346
357
 
358
+ useInsertionEffect(() => attachVariableGroups(styleApi), [styleApi])
359
+
347
360
  return (
348
361
  <PurseContext.Provider value={styleApi}>
349
362
  {props.children}
@@ -351,19 +364,30 @@ export function PurseProvider(props: { children?: React.ReactNode }) {
351
364
  )
352
365
  }
353
366
 
354
- function compileDeclarations(declarations: BaseCSSProperties) {
367
+ function isCustomProperty(name: string): name is `--${string}` {
368
+ return name.startsWith("--")
369
+ }
370
+
371
+ function compileDeclarations(declarations: Declarations) {
355
372
  // return entries(prefix(declarations))
356
373
  return entries(declarations)
357
- .map(([camelCaseProperty, value]) => {
358
- const kebabProperty = hyphenateStyleName(camelCaseProperty)
374
+ .map(([name, value]) => {
375
+ if (value === undefined || value === null) {
376
+ return ""
377
+ }
378
+
379
+ const property = isCustomProperty(name)
380
+ ? name
381
+ : hyphenateStyleName(name)
359
382
  const needToAddPixelsUnit =
360
383
  typeof value === "number" &&
361
- !isUnitlessNumberProperty(kebabProperty)
384
+ !isCustomProperty(name) &&
385
+ !isUnitlessNumberProperty(property)
362
386
 
363
387
  if (needToAddPixelsUnit) {
364
- return `${kebabProperty}:${value}px;`
388
+ return `${property}:${value}px;`
365
389
  } else {
366
- return `${kebabProperty}:${value};`
390
+ return `${property}:${value};`
367
391
  }
368
392
  })
369
393
  .join("")
@@ -394,9 +418,9 @@ function isObjectEmpty(obj: {}): boolean {
394
418
  }
395
419
 
396
420
  type CSSPropertiesGroup = {
397
- "": BaseCSSProperties
421
+ "": Declarations
398
422
  } & {
399
- [Key in NestedRuleKey]?: BaseCSSProperties
423
+ [Key in NestedRuleKey]?: Declarations
400
424
  }
401
425
 
402
426
  function mergeCSSProperties(...groups: CSSProperties[]): CSSProperties {
@@ -535,7 +559,7 @@ function styleElementToRules({ owned, composed }: StyleElement): StyleRule[] {
535
559
 
536
560
  export function useInjectGlobalStyles(
537
561
  selector: string,
538
- cssProperties: BaseCSSProperties & CSSVarDeclarations,
562
+ cssProperties: Declarations,
539
563
  deps: DependencyList,
540
564
  ) {
541
565
  const styleApi = useRequiredContext(PurseContext)