@css-hooks/core 2.0.4 → 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/README.md CHANGED
@@ -27,18 +27,18 @@ experience without runtime style injection or build steps.
27
27
 
28
28
  ```jsx
29
29
  <button
30
- style={css({
31
- background: "#004982",
32
- color: "#eeeff0",
33
- on: $ => [
34
- $("&:hover", {
35
- background: "#1b659c",
36
- }),
37
- $("&:active", {
38
- background: "#9f3131",
39
- }),
40
- ],
41
- })}
30
+ style={pipe(
31
+ {
32
+ background: "#004982",
33
+ color: "#eeeff0",
34
+ },
35
+ on("&:hover", {
36
+ background: "#1b659c",
37
+ }),
38
+ on("&:active", {
39
+ background: "#9f3131",
40
+ }),
41
+ )}
42
42
  >
43
43
  Save changes
44
44
  </button>
@@ -50,13 +50,12 @@ experience without runtime style injection or build steps.
50
50
  <label>
51
51
  <input type="checkbox" checked />
52
52
  <span
53
- style={css({
54
- on: $ => [
55
- $(":checked + &", {
56
- textDecoration: "line-through",
57
- }),
58
- ],
59
- })}
53
+ style={pipe(
54
+ {},
55
+ on(":checked + &", {
56
+ textDecoration: "line-through",
57
+ }),
58
+ )}
60
59
  >
61
60
  Simplify CSS architecture
62
61
  </span>
@@ -68,24 +67,22 @@ experience without runtime style injection or build steps.
68
67
  ```jsx
69
68
  <>
70
69
  <span
71
- style={css({
72
- on: ($, { not }) => [
73
- $(not("@container sm"), {
74
- display: "none",
75
- }),
76
- ],
77
- })}
70
+ style={pipe(
71
+ {},
72
+ on(not("@container (width < 400px)"), {
73
+ display: "none",
74
+ }),
75
+ )}
78
76
  >
79
77
  sm
80
78
  </span>
81
79
  <span
82
- style={css({
83
- on: ($, { not }) => [
84
- $(not("@container lg"), {
85
- display: "none",
86
- }),
87
- ],
88
- })}
80
+ style={pipe(
81
+ {},
82
+ on("@container (width < 400px)", {
83
+ display: "none",
84
+ }),
85
+ )}
89
86
  >
90
87
  lg
91
88
  </span>
package/cjs/index.js CHANGED
@@ -1,310 +1,136 @@
1
- 'use strict';
2
- // @ts-nocheck
3
-
4
- const helpers = {
5
- and: (...and) => ({ and }),
6
- or: (...or) => ({ or }),
7
- not: not => ({ not }),
8
- };
9
-
10
- function genericStringify(_, value) {
11
- if (typeof value === "string") {
12
- return value;
13
- }
14
-
15
- if (typeof value === "number") {
16
- return `${value}`;
17
- }
18
-
19
- return null;
1
+ "use strict";
2
+ /**
3
+ * CSS Hooks core library
4
+ *
5
+ * @packageDocumentation
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.buildHooksSystem = void 0;
9
+ /**
10
+ * Creates a flavor of CSS Hooks tailored to a specific app framework.
11
+ *
12
+ * @param stringify - The function used to stringify values when merging
13
+ * conditional styles.
14
+ *
15
+ * @returns The `createHooks` function used to bootstrap CSS Hooks within an app
16
+ * or component library.
17
+ *
18
+ * @remarks
19
+ * Primarily for internal use, advanced use cases, or when an appropriate
20
+ * framework integration is not provided.
21
+ *
22
+ * @public
23
+ *
24
+ */
25
+ function buildHooksSystem(stringify = String) {
26
+ return (...selectors) => {
27
+ const [space, newline] =
28
+ // @ts-expect-error bundler expected to replace `process.env.NODE_ENV` expression
29
+ process.env.NODE_ENV === "development" ? [" ", "\n"] : ["", ""];
30
+ return {
31
+ styleSheet() {
32
+ const indent = Array(2).fill(space).join("");
33
+ return `*${space}{${newline}${selectors
34
+ .flatMap(selector => [
35
+ `${indent}--${createHash(selector)}-0:${space}initial;`,
36
+ `${indent}--${createHash(selector)}-1:${space};`,
37
+ ])
38
+ .join(newline)}${newline}}${newline}${selectors
39
+ .flatMap(def => {
40
+ if (def.startsWith("@")) {
41
+ return [
42
+ `${def} {`,
43
+ `${indent}* {`,
44
+ `${indent}${indent}--${createHash(def)}-0:${space};`,
45
+ `${indent}${indent}--${createHash(def)}-1:${space}initial;`,
46
+ `${indent}}`,
47
+ "}",
48
+ ];
49
+ }
50
+ return [
51
+ `${def.replace(/&/g, "*")}${space}{`,
52
+ `${indent}--${createHash(def)}-0:${space};`,
53
+ `${indent}--${createHash(def)}-1:${space}initial;`,
54
+ "}",
55
+ ];
56
+ })
57
+ .join(newline)}`;
58
+ },
59
+ and: (...and) => ({ and }),
60
+ or: (...or) => ({ or }),
61
+ not: not => ({ not }),
62
+ on(condition, conditionalStyle) {
63
+ return fallbackStyle => {
64
+ const style = { ...fallbackStyle };
65
+ for (const property in conditionalStyle) {
66
+ const conditionalValue = stringify(conditionalStyle[property], property);
67
+ if (conditionalValue === null) {
68
+ continue;
69
+ }
70
+ let fallbackValue = "revert-layer";
71
+ if (property in style) {
72
+ const fv = stringify(style[property], property);
73
+ if (fv !== null) {
74
+ fallbackValue = fv;
75
+ }
76
+ }
77
+ const [value, extraDecls] = buildExpression(condition, conditionalValue, fallbackValue);
78
+ Object.assign(style, { [property]: value }, extraDecls);
79
+ }
80
+ return style;
81
+ function buildExpression(condition, valueIfTrue, valueIfFalse) {
82
+ if (typeof condition === "string") {
83
+ let valTrue = valueIfTrue, valFalse = valueIfFalse;
84
+ const extraDecls = {};
85
+ if (valTrue.length > 32) {
86
+ const hash = createHash(valTrue);
87
+ extraDecls[`--${hash}`] = valTrue;
88
+ valTrue = `var(--${hash})`;
89
+ }
90
+ if (valFalse.length > 32) {
91
+ const hash = createHash(valFalse);
92
+ extraDecls[`--${hash}`] = valFalse;
93
+ valFalse = `var(--${hash})`;
94
+ }
95
+ return [
96
+ `var(--${createHash(condition)}-1,${space}${valTrue})${space}var(--${createHash(condition)}-0,${space}${valFalse})`,
97
+ extraDecls,
98
+ ];
99
+ }
100
+ if ("and" in condition) {
101
+ const [head, ...tail] = condition.and;
102
+ if (!head) {
103
+ return [valueIfTrue, {}];
104
+ }
105
+ if (tail.length === 0) {
106
+ return buildExpression(head, valueIfTrue, valueIfFalse);
107
+ }
108
+ const [tailExpr, tailDecls] = buildExpression({ and: tail }, valueIfTrue, valueIfFalse);
109
+ const [expr, decls] = buildExpression(head, tailExpr, valueIfFalse);
110
+ return [expr, { ...decls, ...tailDecls }];
111
+ }
112
+ if ("or" in condition) {
113
+ return buildExpression({ and: condition.or.map(not => ({ not })) }, valueIfFalse, valueIfTrue);
114
+ }
115
+ if (condition.not) {
116
+ return buildExpression(condition.not, valueIfFalse, valueIfTrue);
117
+ }
118
+ throw new Error(`Invalid condition: ${JSON.stringify(condition)}`);
119
+ }
120
+ };
121
+ },
122
+ };
123
+ };
20
124
  }
21
-
22
- function hash(obj) {
23
- const jsonString = JSON.stringify(obj);
24
-
25
- let hashValue = 0;
26
-
27
- for (let i = 0; i < jsonString.length; i++) {
28
- const charCode = jsonString.charCodeAt(i);
29
- hashValue = (hashValue << 5) - hashValue + charCode;
30
- hashValue &= 0x7fffffff;
31
- }
32
-
33
- return hashValue.toString(36);
34
- }
35
-
36
- function normalizeCondition(cond) {
37
- if (!cond) {
38
- return undefined;
39
- }
40
- if (typeof cond === "string") {
41
- return cond;
42
- }
43
- if (typeof cond !== "object") {
44
- return undefined;
45
- }
46
- if ("not" in cond) {
47
- if (!cond.not) {
48
- return undefined;
49
- }
50
- if (cond.not.not) {
51
- return normalizeCondition(cond.not.not);
52
- }
53
- const inner = normalizeCondition(cond.not);
54
- return inner ? { not: inner } : undefined;
55
- }
56
- const [operator] = Object.keys(cond);
57
- const [head, ...tail] = cond[operator].map(normalizeCondition).filter(x => x);
58
- if (!head) {
59
- return undefined;
60
- }
61
- if (tail.length === 0) {
62
- return head;
63
- }
64
- if (tail.length === 1) {
65
- return { [operator]: [head, tail[0]] };
66
- }
67
- return { [operator]: [head, normalizeCondition({ [operator]: tail })] };
68
- }
69
-
70
- function buildHooksSystem(stringify = genericStringify) {
71
- return function createHooks({
72
- hooks: hooksConfigUnresolved,
73
- fallback = "revert-layer",
74
- debug,
75
- sort: {
76
- properties: sortProperties = true,
77
- conditionalStyles: sortConditionalStyles = true,
78
- } = {},
79
- hookNameToId: customHookNameToId,
80
- }) {
81
- const hooksConfig =
82
- typeof hooksConfigUnresolved === "function"
83
- ? hooksConfigUnresolved(helpers)
84
- : hooksConfigUnresolved;
85
-
86
- const [space, newline] = debug ? [" ", "\n"] : ["", ""];
87
- const indent = `${space}${space}`;
88
-
89
- const hookNameToId =
90
- customHookNameToId ||
91
- (hookName => {
92
- const specHash = hash(hooksConfig[hookName]);
93
- return debug
94
- ? `${hookName.replace(/[^A-Za-z0-9-]/g, "_")}-${specHash}`
95
- : specHash;
96
- });
97
-
98
- function conditionToId(condition) {
99
- if (condition) {
100
- if (typeof condition === "string") {
101
- return hookNameToId(condition);
102
- }
103
- if (typeof condition === "object") {
104
- if (condition.and) {
105
- return `_${condition.and.map(conditionToId).join("-and-")}_`;
106
- }
107
- if (condition.or) {
108
- return `_${condition.or.map(conditionToId).join("-or-")}_`;
109
- }
110
- if (condition.not) {
111
- return `_-not-${conditionToId(condition.not)}_`;
112
- }
113
- }
114
- }
115
- return `${condition}`;
116
- }
117
-
118
- function styleSheet() {
119
- function variablePair({ id, initial, indents }) {
120
- return [0, 1]
121
- .map(
122
- i =>
123
- `${Array(indents).fill(indent).join("")}--${id}-${i}:${space}${
124
- initial === i ? "initial" : space ? "" : " "
125
- };${newline}`,
126
- )
127
- .join("");
128
- }
129
-
130
- let sheet = `*${space}{${newline}`;
131
-
132
- const hooks = Object.entries(hooksConfig)
133
- .map(([hookName, hookCondition]) => [
134
- hookName,
135
- normalizeCondition(hookCondition),
136
- ])
137
- .filter(([, hookCondition]) => hookCondition);
138
-
139
- for (const [hookName, hookCondition] of hooks) {
140
- (function it(id, hookCondition) {
141
- if (hookCondition && typeof hookCondition === "object") {
142
- if (hookCondition.not) {
143
- it(`${id}X`, hookCondition.not);
144
- sheet += `${indent}--${id}-0:${space}var(--${id}X-1);${newline}`;
145
- sheet += `${indent}--${id}-1:${space}var(--${id}X-0);${newline}`;
146
- return;
147
- }
148
-
149
- if ("and" in hookCondition || "or" in hookCondition) {
150
- const operator = hookCondition.and ? "and" : "or";
151
- it(`${id}A`, hookCondition[operator][0]);
152
- it(`${id}B`, hookCondition[operator][1]);
153
- if (operator === "and") {
154
- sheet += `${indent}--${id}-0:${space}var(--${id}A-0)${space}var(--${id}B-0);${newline}`;
155
- sheet += `${indent}--${id}-1:${space}var(--${id}A-1,${space}var(--${id}B-1));${newline}`;
156
- } else {
157
- sheet += `${indent}--${id}-0:${space}var(--${id}A-0,${space}var(--${id}B-0));${newline}`;
158
- sheet += `${indent}--${id}-1:${space}var(--${id}A-1)${space}var(--${id}B-1);${newline}`;
159
- }
160
- return;
161
- }
162
- }
163
- sheet += variablePair({ id, initial: 0, indents: 1 });
164
- })(hookNameToId(hookName), hookCondition);
165
- }
166
-
167
- sheet += `}${newline}`;
168
-
169
- for (const [hookName, hookCondition] of hooks) {
170
- (function it(id, hookCondition) {
171
- if (hookCondition && typeof hookCondition === "object") {
172
- if (hookCondition.not) {
173
- return it(`${id}X`, hookCondition.not);
174
- }
175
-
176
- if ("and" in hookCondition || "or" in hookCondition) {
177
- const operator = hookCondition.and ? "and" : "or";
178
- it(`${id}A`, hookCondition[operator][0]);
179
- it(`${id}B`, hookCondition[operator][1]);
180
- return;
181
- }
182
- }
183
-
184
- if (typeof hookCondition === "string") {
185
- if (hookCondition[0] === "@") {
186
- sheet += [
187
- `${hookCondition}${space}{${newline}`,
188
- `${indent}*${space}{${newline}`,
189
- variablePair({
190
- id,
191
- initial: 1,
192
- indents: 2,
193
- }),
194
- `${indent}}${newline}`,
195
- `}${newline}`,
196
- ].join("");
197
- } else {
198
- sheet += [
199
- `${hookCondition.replace(/&/g, "*")}${space}{${newline}`,
200
- variablePair({
201
- id,
202
- initial: 1,
203
- indents: 1,
204
- }),
205
- `}${newline}`,
206
- ].join("");
207
- }
208
- }
209
- })(hookNameToId(hookName), hookCondition);
210
- }
211
-
212
- return sheet;
213
- }
214
-
215
- function css(...args) {
216
- const style = {};
217
- const rules = args
218
- .filter(rule => rule)
219
- .reduce(
220
- ([baseStyles, conditionalStyles], rule) => {
221
- if (rule.on) {
222
- baseStyles.push(rule);
223
- (sortConditionalStyles ? conditionalStyles : baseStyles).push(
224
- ...(typeof rule.on === "function"
225
- ? rule.on((condition, styles) => [condition, styles], helpers)
226
- : rule.on),
227
- );
228
- } else {
229
- baseStyles.push(rule);
230
- }
231
- return [baseStyles, conditionalStyles];
232
- },
233
- [[], []],
234
- )
235
- .reduce((a, b) => {
236
- return a.concat(b);
237
- }, []);
238
- for (const rule of rules) {
239
- if (!rule || typeof rule !== "object") {
240
- continue;
241
- }
242
- if (rule instanceof Array && rule.length === 2) {
243
- let conditionId = normalizeCondition(rule[0]);
244
- if (!conditionId) {
245
- continue;
246
- }
247
- if (typeof conditionId === "string") {
248
- conditionId = hookNameToId(conditionId);
249
- } else if (typeof conditionId === "object") {
250
- conditionId = (function it(name, cond) {
251
- if (typeof cond === "string") {
252
- return hookNameToId(cond);
253
- }
254
- if (cond.not) {
255
- const inner = it(`${name}X`, cond.not);
256
- style[`--${name}-0`] = `var(--${inner}-1)`;
257
- style[`--${name}-1`] = `var(--${inner}-0)`;
258
- }
259
- if (cond.and || cond.or) {
260
- const operator = cond.and ? "and" : "or";
261
- const a = it(`${name}A`, cond[operator][0]);
262
- const b = it(`${name}B`, cond[operator][1]);
263
- if (operator === "and") {
264
- style[`--${name}-0`] = `var(--${a}-0)${space}var(--${b}-0)`;
265
- style[`--${name}-1`] = `var(--${a}-1,${space}var(--${b}-1))`;
266
- } else {
267
- style[`--${name}-0`] = `var(--${a}-0,${space}var(--${b}-0))`;
268
- style[`--${name}-1`] = `var(--${a}-1)${space}var(--${b}-1)`;
269
- }
270
- }
271
- return name;
272
- })(`cond-${hash(conditionToId(conditionId))}`, conditionId);
273
- }
274
- for (const [property, value] of Object.entries(rule[1])) {
275
- const stringifiedValue = stringify(property, value);
276
- if (stringifiedValue === null) {
277
- continue;
278
- }
279
- const fallbackValue = (() => {
280
- if (!(property in style)) {
281
- return fallback;
282
- }
283
- const stringifiedValue = stringify(property, style[property]);
284
- return stringifiedValue === null ? fallback : stringifiedValue;
285
- })();
286
- if (sortProperties) {
287
- delete style[property];
288
- }
289
- style[property] =
290
- `var(--${conditionId}-1,${space}${stringifiedValue})${space}var(--${conditionId}-0,${space}${fallbackValue})`;
291
- }
292
- continue;
293
- }
294
- for (const [property, value] of Object.entries(rule)) {
295
- if (property === "on") {
296
- continue;
297
- }
298
- if (sortProperties) {
299
- delete style[property];
300
- }
301
- style[property] = value;
302
- }
303
- }
304
- return style;
125
+ exports.buildHooksSystem = buildHooksSystem;
126
+ function createHash(obj) {
127
+ const jsonString = JSON.stringify(obj);
128
+ let hashValue = 0;
129
+ for (let i = 0; i < jsonString.length; i++) {
130
+ const charCode = jsonString.charCodeAt(i);
131
+ hashValue = (hashValue << 5) - hashValue + charCode;
132
+ hashValue &= 0x7fffffff;
305
133
  }
306
-
307
- return { styleSheet, css };
308
- };
134
+ const str = hashValue.toString(36);
135
+ return /^[0-9]/.test(str) ? `a${str}` : str;
309
136
  }
310
- exports.buildHooksSystem = buildHooksSystem