Skip to content

Commit a575e27

Browse files
committed
fix: correct assignCSSVar, improve tests, better tsdocs
1 parent a429a7f commit a575e27

File tree

8 files changed

+135
-51
lines changed

8 files changed

+135
-51
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ const textColor = createCSSVar('text-color', { fallback: '#000' })
4343
// Fallback chain
4444
const color = fallbackCSSVar('--primary', '--secondary', '#default')
4545
// Result: var(--primary, var(--secondary, #default))
46+
47+
// Assign a value to a CSS variable
48+
const cssVar = assignCSSVar(bgColor, "#ffffff");
49+
// Result: { '--background-color': '#ffffff' }
50+
// Use case: style={{ ...assignCSSVar(bgColor, "#ffffff") }}
4651
```
4752

4853
[Read more about CSS Variables Utility](src/vars/README.md)
@@ -97,6 +102,7 @@ Both utilities include robust error handling:
97102
## Browser Support
98103

99104
This library is designed for modern browsers that support:
105+
100106
- CSS Custom Properties (CSS Variables)
101107
- CSS `calc()` function
102108

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@builtbyfield/css-utils",
3-
"version": "0.0.1",
3+
"version": "0.1.0",
44
"description": "CSS utility functions",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/calc/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,21 @@
88
* @see https://github.com/vanilla-extract-css/vanilla-extract/tree/master/packages/utils
99
*/
1010

11+
/**
12+
* The operator to use in a CSS calc expression.
13+
*
14+
* @example
15+
* '+', '-', '*', '/'
16+
*/
1117
export type Operator = "+" | "-" | "*" | "/";
18+
19+
/**
20+
* The operand to use in a CSS calc expression.
21+
* Can be a string, number, or a CalcChain.
22+
*
23+
* @example
24+
* '1px', 2, calc.add('1px', '2rem')
25+
*/
1226
export type Operand = string | number | CalcChain;
1327

1428
/**
@@ -88,6 +102,13 @@ const negate = (x: Operand) => multiply(x, -1);
88102

89103
/**
90104
* A chainable interface for creating CSS calc expressions.
105+
*
106+
* @example
107+
* calc('10px')
108+
* .add('2rem')
109+
* .multiply(2)
110+
* .divide(3)
111+
* .toString()
91112
*/
92113
export type CalcChain = {
93114
add: (...operands: Array<Operand>) => CalcChain;

src/vars/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ const fallbackColor = fallbackCSSVar(primary, secondary, '#default')
5252
```typescript
5353
import { assignCSSVar, createCSSVar } from '@builtbyfield/css-utils'
5454
const bgColor = createCSSVar('background-color')
55-
const cssVar = assignCSSVar(bgColor, '#ffffff')
56-
// Result: { name: '--background-color', value: '#ffffff' }
55+
const cssVar = assignCSSVar(bgColor, "#ffffff");
56+
// Result: { '--background-color': '#ffffff' }
5757
```
5858

5959
### Validation Functions

src/vars/browser.test.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ function TestComponent({
2626
describe("CSS Variables Browser Tests", () => {
2727
it("should apply a basic CSS variable", () => {
2828
const colorVar = createCSSVar("color");
29-
const { name, value } = assignCSSVar(colorVar, "red");
29+
const { "--color": value } = assignCSSVar(colorVar, "red");
3030

3131
// Set the CSS variable at root level
32-
document.documentElement.style.setProperty(name, value);
32+
document.documentElement.style.setProperty("--color", value);
3333

3434
render(<TestComponent cssVar={colorVar} />);
3535

@@ -39,9 +39,12 @@ describe("CSS Variables Browser Tests", () => {
3939

4040
it("should handle complex values like rgba", () => {
4141
const colorVar = createCSSVar("complex-color");
42-
const { name, value } = assignCSSVar(colorVar, "rgba(255, 0, 0, 0.5)");
42+
const { "--complex-color": value } = assignCSSVar(
43+
colorVar,
44+
"rgba(255, 0, 0, 0.5)"
45+
);
4346

44-
document.documentElement.style.setProperty(name, value);
47+
document.documentElement.style.setProperty("--complex-color", value);
4548

4649
render(<TestComponent cssVar={colorVar} />);
4750

@@ -52,9 +55,9 @@ describe("CSS Variables Browser Tests", () => {
5255

5356
it("should handle calc expressions", () => {
5457
const widthVar = createCSSVar("width");
55-
const { name, value } = assignCSSVar(widthVar, "calc(100% - 20px)");
58+
const { "--width": value } = assignCSSVar(widthVar, "calc(100% - 20px)");
5659

57-
document.documentElement.style.setProperty(name, value);
60+
document.documentElement.style.setProperty("--width", value);
5861

5962
render(
6063
<div style={{ width: "200px" }}>

src/vars/index.test.ts

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe("CSS Variable Name Validation", () => {
3131
});
3232

3333
it("should reject names with spaces", () => {
34+
expect(isValidCSSVarName(" ")).toBe(false);
3435
expect(isValidCSSVarName("foo bar")).toBe(false);
3536
expect(isValidCSSVarName(" foo")).toBe(false);
3637
expect(isValidCSSVarName("foo ")).toBe(false);
@@ -122,11 +123,24 @@ describe("fallbackCSSVar", () => {
122123
expect(fallback).toBe("var(--foo, var(--baz, 12))");
123124
});
124125

126+
it("all but last must be valid CSS variable functions", () => {
127+
const fooVar = createCSSVar("foo");
128+
const bazVar = createCSSVar("baz");
129+
expect(() => fallbackCSSVar(fooVar, "12", bazVar)).toThrow();
130+
});
131+
125132
it("should handle calc expressions", () => {
126133
const widthVar = createCSSVar("width");
127134
const fallback = fallbackCSSVar(widthVar, "calc(100% - 20px)");
128135
expect(fallback).toBe("var(--width, calc(100% - 20px))");
129136
});
137+
138+
it("should throw when creating invalid CSS variable function", () => {
139+
const invalidVar = "not-a-var-function";
140+
expect(() => fallbackCSSVar(invalidVar, "red")).toThrow(
141+
"All values except the last must be valid CSS variable functions"
142+
);
143+
});
130144
});
131145

132146
describe("getCSSVarName", () => {
@@ -147,32 +161,28 @@ describe("assignCSSVar", () => {
147161
it("should assign string value", () => {
148162
const fooVar = createCSSVar("foo");
149163
expect(assignCSSVar(fooVar, "baz")).toEqual({
150-
name: "--foo",
151-
value: "baz",
164+
"--foo": "baz",
152165
});
153166
});
154167

155168
it("should assign number value", () => {
156169
const numberVar = createCSSVar("spacing");
157170
expect(assignCSSVar(numberVar, "12")).toEqual({
158-
name: "--spacing",
159-
value: "12",
171+
"--spacing": "12",
160172
});
161173
});
162174

163175
it("should handle CSS functions", () => {
164176
const colorVar = createCSSVar("overlay-color");
165177
expect(assignCSSVar(colorVar, "rgba(0, 0, 0, 0.5)")).toEqual({
166-
name: "--overlay-color",
167-
value: "rgba(0, 0, 0, 0.5)",
178+
"--overlay-color": "rgba(0, 0, 0, 0.5)",
168179
});
169180
});
170181

171182
it("should handle calc expressions", () => {
172183
const widthVar = createCSSVar("container-width");
173184
expect(assignCSSVar(widthVar, "calc(100% - 2rem)")).toEqual({
174-
name: "--container-width",
175-
value: "calc(100% - 2rem)",
185+
"--container-width": "calc(100% - 2rem)",
176186
});
177187
});
178188

@@ -181,4 +191,25 @@ describe("assignCSSVar", () => {
181191
// @ts-expect-error - testing undefined value
182192
expect(() => assignCSSVar(testVar, undefined)).toThrow();
183193
});
194+
195+
it("should throw on invalid variable name format", () => {
196+
// @ts-expect-error - testing invalid input
197+
expect(() => assignCSSVar("invalid-var", "value")).toThrow(
198+
"Invalid CSS variable name"
199+
);
200+
});
201+
202+
it("should throw on malformed var function", () => {
203+
// @ts-expect-error - testing invalid input
204+
expect(() => assignCSSVar("var(invalid)", "value")).toThrow(
205+
"Invalid CSS variable name"
206+
);
207+
});
208+
209+
it("should handle null value", () => {
210+
const testVar = createCSSVar("test");
211+
expect(assignCSSVar(testVar, null)).toEqual({
212+
"--test": null,
213+
});
214+
});
184215
});

src/vars/index.ts

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,37 +22,32 @@ const CSS_VAR_NAME_EXTRACTOR = /^var\((.*)\)$/;
2222
function validateCSSCustomPropertyName(name: string): boolean {
2323
if (!name) return false;
2424

25-
try {
26-
// Remove leading -- if present for validation
27-
const propertyName = name.startsWith("--") ? name.slice(2) : name;
28-
29-
// First check if it's a valid CSS identifier pattern
30-
if (!/^[a-zA-Z_\-\\][a-zA-Z0-9_\-\\]*$/.test(propertyName)) {
31-
return false;
32-
}
33-
34-
// Process the string to validate escape sequences
35-
const result = propertyName;
36-
37-
// Only reject literal spaces, not escaped ones
38-
if (result.includes(" ")) {
39-
return false;
40-
}
41-
42-
// Validate non-escaped parts
43-
const parts = result.split(/\\[0-9a-fA-F]{1,6}\s?/);
44-
return parts.every((part) => {
45-
if (!part) return true; // Empty parts are OK between escape sequences
46-
const escaped = cssesc(part, { isIdentifier: true });
47-
return escaped === part;
48-
});
49-
} catch {
25+
// Remove leading -- if present for validation
26+
const propertyName = name.startsWith("--") ? name.slice(2) : name;
27+
28+
// First check if it's a valid CSS identifier pattern
29+
if (!/^[a-zA-Z_\-\\][a-zA-Z0-9_\-\\]*$/.test(propertyName)) {
5030
return false;
5131
}
32+
33+
// Validate non-escaped parts
34+
const parts = propertyName.split(/\\[0-9a-fA-F]{1,6}\s?/);
35+
return parts.every((part) => {
36+
if (!part) return true; // Empty parts are OK between escape sequences
37+
const escaped = cssesc(part, { isIdentifier: true });
38+
return escaped === part;
39+
});
5240
}
5341

5442
/**
5543
* Checks if a name is a valid CSS variable name without -- prefix.
44+
*
45+
* @param name - The name to validate
46+
* @returns True if the name is a valid CSS variable name, false otherwise
47+
*
48+
* @example
49+
* isValidCSSVarName('my-var') // true
50+
* isValidCSSVarName('--my-var') // false
5651
*/
5752
export function isValidCSSVarName(name: string): boolean {
5853
return validateCSSCustomPropertyName(name);
@@ -65,6 +60,13 @@ export function isValidCSSVarName(name: string): boolean {
6560
* - Are case-sensitive
6661
* - Can contain letters, numbers, underscores, and hyphens
6762
* - Cannot start with a digit after the dashes
63+
*
64+
* @param value - The value to validate
65+
* @returns True if the value is a valid CSS variable name (including --), false otherwise
66+
*
67+
* @example
68+
* isCSSVarName('--my-var') // true
69+
* isCSSVarName('my-var') // false
6870
*/
6971
export function isCSSVarName(value: string): value is CSSVarName {
7072
if (!value.startsWith("--")) return false;
@@ -75,6 +77,14 @@ export function isCSSVarName(value: string): value is CSSVarName {
7577

7678
/**
7779
* Creates a CSS variable function with optional fallback.
80+
*
81+
* @param name - The name of the CSS variable
82+
* @param options - Optional options for the CSS variable
83+
* @returns A CSS variable function
84+
*
85+
* @example
86+
* createCSSVar('my-var') // var(--my-var)
87+
* createCSSVar('my-var', { fallback: '#fff' }) // var(--my-var, #fff)
7888
*/
7989
export function createCSSVar(
8090
name: string,
@@ -108,6 +118,12 @@ function isCSSVarFunction(value: string): value is CSSVarFunction {
108118

109119
/**
110120
* Creates a fallback CSS variable function.
121+
*
122+
* @param values - The values to fallback to
123+
* @returns A CSS variable function with fallback
124+
*
125+
* @example
126+
* fallbackCSSVar('var(--my-var)', '#fff') // var(--my-var, #fff)
111127
*/
112128
export function fallbackCSSVar(
113129
...values: [string, ...Array<string>]
@@ -138,6 +154,12 @@ export function fallbackCSSVar(
138154

139155
/**
140156
* Returns the variable name from a CSS variable function.
157+
*
158+
* @param variable - The CSS variable function
159+
* @returns The variable name
160+
*
161+
* @example
162+
* getCSSVarName('var(--my-var)') // --my-var
141163
*/
142164
export function getCSSVarName(variable: string): string {
143165
const matches = variable.match(CSS_VAR_NAME_EXTRACTOR);
@@ -146,6 +168,13 @@ export function getCSSVarName(variable: string): string {
146168

147169
/**
148170
* Assigns a value to a CSS variable.
171+
*
172+
* @param variable - The CSS variable function
173+
* @param value - The value to assign to the CSS variable
174+
* @returns The CSS variable definition
175+
*
176+
* @example
177+
* assignCSSVar('var(--my-var)', '#fff') // { '--my-var': '#fff' }
149178
*/
150179
export function assignCSSVar(
151180
variable: CSSVarFunction,
@@ -164,8 +193,5 @@ export function assignCSSVar(
164193
throw new Error("Invalid CSS variable name");
165194
}
166195

167-
return {
168-
name: varName,
169-
value,
170-
};
196+
return { [varName]: value };
171197
}

src/vars/types.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ export type CSSVarFunction =
2424

2525
/**
2626
* CSS variable definition object
27+
* @example { '--my-var': '#fff', '--color-primary': 'red' }
2728
*/
28-
export interface CSSVarDefinition {
29-
name: CSSVarName;
30-
value: string | null;
31-
fallback?: string;
32-
}
29+
export type CSSVarDefinition = Record<CSSVarName, string | null>;

0 commit comments

Comments
 (0)