@css-hooks/core 2.0.3 → 3.0.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/esm/index.js CHANGED
@@ -1,308 +1,132 @@
1
- // @ts-nocheck
2
-
3
- const helpers = {
4
- and: (...and) => ({ and }),
5
- or: (...or) => ({ or }),
6
- not: not => ({ not }),
7
- };
8
-
9
- function genericStringify(_, value) {
10
- if (typeof value === "string") {
11
- return value;
12
- }
13
-
14
- if (typeof value === "number") {
15
- return `${value}`;
16
- }
17
-
18
- return null;
1
+ /**
2
+ * CSS Hooks core library
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ /**
7
+ * Creates a flavor of CSS Hooks tailored to a specific app framework.
8
+ *
9
+ * @param stringify - The function used to stringify values when merging
10
+ * conditional styles.
11
+ *
12
+ * @returns The `createHooks` function used to bootstrap CSS Hooks within an app
13
+ * or component library.
14
+ *
15
+ * @remarks
16
+ * Primarily for internal use, advanced use cases, or when an appropriate
17
+ * framework integration is not provided.
18
+ *
19
+ * @public
20
+ *
21
+ */
22
+ export function buildHooksSystem(stringify = String) {
23
+ return (...selectors) => {
24
+ const [space, newline] =
25
+ // @ts-expect-error bundler expected to replace `process.env.NODE_ENV` expression
26
+ process.env.NODE_ENV === "development" ? [" ", "\n"] : ["", ""];
27
+ return {
28
+ styleSheet() {
29
+ const indent = Array(2).fill(space).join("");
30
+ return `*${space}{${newline}${selectors
31
+ .flatMap(selector => [
32
+ `${indent}--${createHash(selector)}-0:${space}initial;`,
33
+ `${indent}--${createHash(selector)}-1:${space};`,
34
+ ])
35
+ .join(newline)}${newline}}${newline}${selectors
36
+ .flatMap(def => {
37
+ if (def.startsWith("@")) {
38
+ return [
39
+ `${def} {`,
40
+ `${indent}* {`,
41
+ `${indent}${indent}--${createHash(def)}-0:${space};`,
42
+ `${indent}${indent}--${createHash(def)}-1:${space}initial;`,
43
+ `${indent}}`,
44
+ "}",
45
+ ];
46
+ }
47
+ return [
48
+ `${def.replace(/&/g, "*")}${space}{`,
49
+ `${indent}--${createHash(def)}-0:${space};`,
50
+ `${indent}--${createHash(def)}-1:${space}initial;`,
51
+ "}",
52
+ ];
53
+ })
54
+ .join(newline)}`;
55
+ },
56
+ and: (...and) => ({ and }),
57
+ or: (...or) => ({ or }),
58
+ not: not => ({ not }),
59
+ on(condition, conditionalStyle) {
60
+ return fallbackStyle => {
61
+ const style = { ...fallbackStyle };
62
+ for (const property in conditionalStyle) {
63
+ const conditionalValue = stringify(conditionalStyle[property], property);
64
+ if (conditionalValue === null) {
65
+ continue;
66
+ }
67
+ let fallbackValue = "revert-layer";
68
+ if (property in style) {
69
+ const fv = stringify(style[property], property);
70
+ if (fv !== null) {
71
+ fallbackValue = fv;
72
+ }
73
+ }
74
+ const [value, extraDecls] = buildExpression(condition, conditionalValue, fallbackValue);
75
+ Object.assign(style, { [property]: value }, extraDecls);
76
+ }
77
+ return style;
78
+ function buildExpression(condition, valueIfTrue, valueIfFalse) {
79
+ if (typeof condition === "string") {
80
+ let valTrue = valueIfTrue, valFalse = valueIfFalse;
81
+ const extraDecls = {};
82
+ if (valTrue.length > 32) {
83
+ const hash = createHash(valTrue);
84
+ extraDecls[`--${hash}`] = valTrue;
85
+ valTrue = `var(--${hash})`;
86
+ }
87
+ if (valFalse.length > 32) {
88
+ const hash = createHash(valFalse);
89
+ extraDecls[`--${hash}`] = valFalse;
90
+ valFalse = `var(--${hash})`;
91
+ }
92
+ return [
93
+ `var(--${createHash(condition)}-1,${space}${valTrue})${space}var(--${createHash(condition)}-0,${space}${valFalse})`,
94
+ extraDecls,
95
+ ];
96
+ }
97
+ if ("and" in condition) {
98
+ const [head, ...tail] = condition.and;
99
+ if (!head) {
100
+ return [valueIfTrue, {}];
101
+ }
102
+ if (tail.length === 0) {
103
+ return buildExpression(head, valueIfTrue, valueIfFalse);
104
+ }
105
+ const [tailExpr, tailDecls] = buildExpression({ and: tail }, valueIfTrue, valueIfFalse);
106
+ const [expr, decls] = buildExpression(head, tailExpr, valueIfFalse);
107
+ return [expr, { ...decls, ...tailDecls }];
108
+ }
109
+ if ("or" in condition) {
110
+ return buildExpression({ and: condition.or.map(not => ({ not })) }, valueIfFalse, valueIfTrue);
111
+ }
112
+ if (condition.not) {
113
+ return buildExpression(condition.not, valueIfFalse, valueIfTrue);
114
+ }
115
+ throw new Error(`Invalid condition: ${JSON.stringify(condition)}`);
116
+ }
117
+ };
118
+ },
119
+ };
120
+ };
19
121
  }
20
-
21
- function hash(obj) {
22
- const jsonString = JSON.stringify(obj);
23
-
24
- let hashValue = 0;
25
-
26
- for (let i = 0; i < jsonString.length; i++) {
27
- const charCode = jsonString.charCodeAt(i);
28
- hashValue = (hashValue << 5) - hashValue + charCode;
29
- hashValue &= 0x7fffffff;
30
- }
31
-
32
- return hashValue.toString(36);
33
- }
34
-
35
- function normalizeCondition(cond) {
36
- if (!cond) {
37
- return undefined;
38
- }
39
- if (typeof cond === "string") {
40
- return cond;
41
- }
42
- if (typeof cond !== "object") {
43
- return undefined;
44
- }
45
- if ("not" in cond) {
46
- if (!cond.not) {
47
- return undefined;
48
- }
49
- if (cond.not.not) {
50
- return normalizeCondition(cond.not.not);
51
- }
52
- const inner = normalizeCondition(cond.not);
53
- return inner ? { not: inner } : undefined;
54
- }
55
- const [operator] = Object.keys(cond);
56
- const [head, ...tail] = cond[operator].map(normalizeCondition).filter(x => x);
57
- if (!head) {
58
- return undefined;
59
- }
60
- if (tail.length === 0) {
61
- return head;
62
- }
63
- if (tail.length === 1) {
64
- return { [operator]: [head, tail[0]] };
65
- }
66
- return { [operator]: [head, normalizeCondition({ [operator]: tail })] };
67
- }
68
-
69
- export function buildHooksSystem(stringify = genericStringify) {
70
- return function createHooks({
71
- hooks: hooksConfigUnresolved,
72
- fallback = "revert-layer",
73
- debug,
74
- sort: {
75
- properties: sortProperties = true,
76
- conditionalStyles: sortConditionalStyles = true,
77
- } = {},
78
- hookNameToId: customHookNameToId,
79
- }) {
80
- const hooksConfig =
81
- typeof hooksConfigUnresolved === "function"
82
- ? hooksConfigUnresolved(helpers)
83
- : hooksConfigUnresolved;
84
-
85
- const [space, newline] = debug ? [" ", "\n"] : ["", ""];
86
- const indent = `${space}${space}`;
87
-
88
- const hookNameToId =
89
- customHookNameToId ||
90
- (hookName => {
91
- const specHash = hash(hooksConfig[hookName]);
92
- return debug
93
- ? `${hookName.replace(/[^A-Za-z0-9-]/g, "_")}-${specHash}`
94
- : specHash;
95
- });
96
-
97
- function conditionToId(condition) {
98
- if (condition) {
99
- if (typeof condition === "string") {
100
- return hookNameToId(condition);
101
- }
102
- if (typeof condition === "object") {
103
- if (condition.and) {
104
- return `_${condition.and.map(conditionToId).join("-and-")}_`;
105
- }
106
- if (condition.or) {
107
- return `_${condition.or.map(conditionToId).join("-or-")}_`;
108
- }
109
- if (condition.not) {
110
- return `_-not-${conditionToId(condition.not)}_`;
111
- }
112
- }
113
- }
114
- return `${condition}`;
115
- }
116
-
117
- function styleSheet() {
118
- function variablePair({ id, initial, indents }) {
119
- return [0, 1]
120
- .map(
121
- i =>
122
- `${Array(indents).fill(indent).join("")}--${id}-${i}:${space}${
123
- initial === i ? "initial" : space ? "" : " "
124
- };${newline}`,
125
- )
126
- .join("");
127
- }
128
-
129
- let sheet = `*${space}{${newline}`;
130
-
131
- const hooks = Object.entries(hooksConfig)
132
- .map(([hookName, hookCondition]) => [
133
- hookName,
134
- normalizeCondition(hookCondition),
135
- ])
136
- .filter(([, hookCondition]) => hookCondition);
137
-
138
- for (const [hookName, hookCondition] of hooks) {
139
- (function it(id, hookCondition) {
140
- if (hookCondition && typeof hookCondition === "object") {
141
- if (hookCondition.not) {
142
- it(`${id}X`, hookCondition.not);
143
- sheet += `${indent}--${id}-0:${space}var(--${id}X-1);${newline}`;
144
- sheet += `${indent}--${id}-1:${space}var(--${id}X-0);${newline}`;
145
- return;
146
- }
147
-
148
- if ("and" in hookCondition || "or" in hookCondition) {
149
- const operator = hookCondition.and ? "and" : "or";
150
- it(`${id}A`, hookCondition[operator][0]);
151
- it(`${id}B`, hookCondition[operator][1]);
152
- if (operator === "and") {
153
- sheet += `${indent}--${id}-0:${space}var(--${id}A-0)${space}var(--${id}B-0);${newline}`;
154
- sheet += `${indent}--${id}-1:${space}var(--${id}A-1,${space}var(--${id}B-1));${newline}`;
155
- } else {
156
- sheet += `${indent}--${id}-0:${space}var(--${id}A-0,${space}var(--${id}B-0));${newline}`;
157
- sheet += `${indent}--${id}-1:${space}var(--${id}A-1)${space}var(--${id}B-1);${newline}`;
158
- }
159
- return;
160
- }
161
- }
162
- sheet += variablePair({ id, initial: 0, indents: 1 });
163
- })(hookNameToId(hookName), hookCondition);
164
- }
165
-
166
- sheet += `}${newline}`;
167
-
168
- for (const [hookName, hookCondition] of hooks) {
169
- (function it(id, hookCondition) {
170
- if (hookCondition && typeof hookCondition === "object") {
171
- if (hookCondition.not) {
172
- return it(`${id}X`, hookCondition.not);
173
- }
174
-
175
- if ("and" in hookCondition || "or" in hookCondition) {
176
- const operator = hookCondition.and ? "and" : "or";
177
- it(`${id}A`, hookCondition[operator][0]);
178
- it(`${id}B`, hookCondition[operator][1]);
179
- return;
180
- }
181
- }
182
-
183
- if (typeof hookCondition === "string") {
184
- if (hookCondition[0] === "@") {
185
- sheet += [
186
- `${hookCondition}${space}{${newline}`,
187
- `${indent}*${space}{${newline}`,
188
- variablePair({
189
- id,
190
- initial: 1,
191
- indents: 2,
192
- }),
193
- `${indent}}${newline}`,
194
- `}${newline}`,
195
- ].join("");
196
- } else {
197
- sheet += [
198
- `${hookCondition.replace(/&/g, "*")}${space}{${newline}`,
199
- variablePair({
200
- id,
201
- initial: 1,
202
- indents: 1,
203
- }),
204
- `}${newline}`,
205
- ].join("");
206
- }
207
- }
208
- })(hookNameToId(hookName), hookCondition);
209
- }
210
-
211
- return sheet;
212
- }
213
-
214
- function css(...args) {
215
- const style = {};
216
- const rules = args
217
- .filter(rule => rule)
218
- .reduce(
219
- ([baseStyles, conditionalStyles], rule) => {
220
- if (rule.on) {
221
- baseStyles.push(rule);
222
- (sortConditionalStyles ? conditionalStyles : baseStyles).push(
223
- ...(typeof rule.on === "function"
224
- ? rule.on((condition, styles) => [condition, styles], helpers)
225
- : rule.on),
226
- );
227
- } else {
228
- baseStyles.push(rule);
229
- }
230
- return [baseStyles, conditionalStyles];
231
- },
232
- [[], []],
233
- )
234
- .reduce((a, b) => {
235
- return a.concat(b);
236
- }, []);
237
- for (const rule of rules) {
238
- if (!rule || typeof rule !== "object") {
239
- continue;
240
- }
241
- if (rule instanceof Array && rule.length === 2) {
242
- let conditionId = normalizeCondition(rule[0]);
243
- if (!conditionId) {
244
- continue;
245
- }
246
- if (typeof conditionId === "string") {
247
- conditionId = hookNameToId(conditionId);
248
- } else if (typeof conditionId === "object") {
249
- conditionId = (function it(name, cond) {
250
- if (typeof cond === "string") {
251
- return hookNameToId(cond);
252
- }
253
- if (cond.not) {
254
- const inner = it(`${name}X`, cond.not);
255
- style[`--${name}-0`] = `var(--${inner}-1)`;
256
- style[`--${name}-1`] = `var(--${inner}-0)`;
257
- }
258
- if (cond.and || cond.or) {
259
- const operator = cond.and ? "and" : "or";
260
- const a = it(`${name}A`, cond[operator][0]);
261
- const b = it(`${name}B`, cond[operator][1]);
262
- if (operator === "and") {
263
- style[`--${name}-0`] = `var(--${a}-0)${space}var(--${b}-0)`;
264
- style[`--${name}-1`] = `var(--${a}-1,${space}var(--${b}-1))`;
265
- } else {
266
- style[`--${name}-0`] = `var(--${a}-0,${space}var(--${b}-0))`;
267
- style[`--${name}-1`] = `var(--${a}-1)${space}var(--${b}-1)`;
268
- }
269
- }
270
- return name;
271
- })(`cond-${hash(conditionToId(conditionId))}`, conditionId);
272
- }
273
- for (const [property, value] of Object.entries(rule[1])) {
274
- const stringifiedValue = stringify(property, value);
275
- if (stringifiedValue === null) {
276
- continue;
277
- }
278
- const fallbackValue = (() => {
279
- if (!(property in style)) {
280
- return fallback;
281
- }
282
- const stringifiedValue = stringify(property, style[property]);
283
- return stringifiedValue === null ? fallback : stringifiedValue;
284
- })();
285
- if (sortProperties) {
286
- delete style[property];
287
- }
288
- style[property] =
289
- `var(--${conditionId}-1,${space}${stringifiedValue})${space}var(--${conditionId}-0,${space}${fallbackValue})`;
290
- }
291
- continue;
292
- }
293
- for (const [property, value] of Object.entries(rule)) {
294
- if (property === "on") {
295
- continue;
296
- }
297
- if (sortProperties) {
298
- delete style[property];
299
- }
300
- style[property] = value;
301
- }
302
- }
303
- return style;
122
+ function createHash(obj) {
123
+ const jsonString = JSON.stringify(obj);
124
+ let hashValue = 0;
125
+ for (let i = 0; i < jsonString.length; i++) {
126
+ const charCode = jsonString.charCodeAt(i);
127
+ hashValue = (hashValue << 5) - hashValue + charCode;
128
+ hashValue &= 0x7fffffff;
304
129
  }
305
-
306
- return { styleSheet, css };
307
- };
130
+ const str = hashValue.toString(36);
131
+ return /^[0-9]/.test(str) ? `a${str}` : str;
308
132
  }
package/package.json CHANGED
@@ -1,30 +1,26 @@
1
1
  {
2
2
  "name": "@css-hooks/core",
3
3
  "description": "CSS Hooks core library",
4
- "version": "2.0.3",
4
+ "version": "3.0.0",
5
5
  "author": "Nick Saunders",
6
6
  "devDependencies": {
7
7
  "@microsoft/api-extractor": "^7.39.4",
8
- "@tsconfig/strictest": "^2.0.1",
9
8
  "@types/color": "^3.0.6",
10
- "@typescript-eslint/eslint-plugin": "^7.0.0",
11
- "@typescript-eslint/parser": "^7.0.0",
12
- "ascjs": "^6.0.3",
13
9
  "color": "^4.2.3",
14
10
  "csstype": "^3.1.3",
15
- "eslint": "^8.47.0",
16
- "eslint-plugin-compat": "^4.2.0",
17
11
  "lightningcss": "^1.23.0",
18
12
  "puppeteer": "^22.0.0",
13
+ "remeda": "^2.14.0",
19
14
  "rimraf": "^5.0.1",
20
- "tsc-watch": "^6.0.4",
21
- "typescript": "^5.1.6"
15
+ "tsx": "^4.19.1",
16
+ "typescript": "^5.4.2"
22
17
  },
23
18
  "files": [
24
19
  "cjs",
25
20
  "esm",
26
21
  "types",
27
- "tsdoc-metadata.json"
22
+ "tsdoc-metadata.json",
23
+ "!**/*.test.*"
28
24
  ],
29
25
  "license": "MIT",
30
26
  "main": "cjs",
@@ -36,23 +32,17 @@
36
32
  "url": "https://github.com/css-hooks/css-hooks.git",
37
33
  "directory": "packages/core"
38
34
  },
39
- "scripts": {
40
- "api": "node -e \"var path=require('path').resolve,fs=require('fs'),cp=fs.cpSync;cp(path('src', 'index.d.ts'),path('types','index.d.ts'))\" && api-extractor run",
41
- "clean": "rimraf cjs esm out types",
42
- "lint": "eslint src .*.*js *.*js",
43
- "prepublishOnly": "node -e \"var path=require('path').resolve,fs=require('fs'),cp=fs.cpSync,mkdir=fs.mkdirSync;cp(path('src', 'index.d.ts'),path('types','index.d.ts'));cp(path('src','index.js'),path('esm','index.js'));mkdir(path('cjs'),{recursive:true})\" && ascjs src/index.js cjs/index.js",
44
- "test": "tsc && node --test",
45
- "test.watch": "tsc-watch --onSuccess 'node --test'"
46
- },
47
35
  "types": "types",
48
36
  "exports": {
49
37
  ".": {
38
+ "@css-hooks/source": "./src/index.ts",
50
39
  "import": "./esm/index.js",
51
40
  "require": "./cjs/index.js",
52
41
  "types": "./types/index.d.ts"
53
42
  }
54
43
  },
55
- "browserslist": [
56
- "supports css-variables and supports object-entries"
57
- ]
58
- }
44
+ "scripts": {
45
+ "clean": "rimraf cjs esm types",
46
+ "test": "node --import tsx --conditions @css-hooks/source --test src/index.test.ts"
47
+ }
48
+ }
@@ -0,0 +1,11 @@
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.43.1"
9
+ }
10
+ ]
11
+ }