@css-hooks/core 1.8.1 → 2.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
@@ -15,55 +15,87 @@
15
15
 
16
16
  ## Overview
17
17
 
18
- Hooks bring CSS features to native inline styles, enabling you to target various
19
- states such as hover, focus, and active, all without leaving the `style` prop.
20
- For example, hooks can easily solve the common use case of applying state-driven
21
- styles to a link:
18
+ Hooks add CSS features to native inline styles, enabling you to apply styles
19
+ conditionally based on pseudo-classes, custom selectors, media queries, and
20
+ more—all without leaving the `style` prop. By exploiting the hidden
21
+ programmability of CSS Variables, CSS Hooks delivers a flexible CSS-in-JS
22
+ experience without runtime style injection or build steps.
23
+
24
+ ## Feature highlights
25
+
26
+ ### Pseudo-classes
22
27
 
23
28
  ```jsx
24
- <a
25
- href="https://css-hooks.com/"
29
+ <button
26
30
  style={css({
27
- color: "#03f",
28
- fontSize: "1rem",
29
- "&:hover": {
30
- color: "#09f",
31
- },
32
- "&:active": {
33
- color: "#e33",
34
- },
35
- "@media (1000px <= width)": {
36
- fontSize: "1.25rem",
37
- },
31
+ background: "#004982",
32
+ color: "#eeeff0",
33
+ on: $ => [
34
+ $("&:hover", {
35
+ background: "#1b659c",
36
+ }),
37
+ $("&:active", {
38
+ background: "#9f3131",
39
+ }),
40
+ ],
38
41
  })}
39
42
  >
40
- Hooks
41
- </a>
43
+ Save changes
44
+ </button>
45
+ ```
46
+
47
+ ### Selectors
48
+
49
+ ```jsx
50
+ <label>
51
+ <input type="checkbox" checked />
52
+ <span
53
+ style={css({
54
+ on: $ => [
55
+ $(":checked + &", {
56
+ textDecoration: "line-through",
57
+ }),
58
+ ],
59
+ })}
60
+ >
61
+ Simplify CSS architecture
62
+ </span>
63
+ </label>
42
64
  ```
43
65
 
44
- Notably, the `css` function is pure. It simply returns a flat style object that
45
- is compatible with the `style` prop, creating dynamic property values that
46
- change under various conditions through CSS variables.
66
+ ### Responsive design
67
+
68
+ ```jsx
69
+ <>
70
+ <span
71
+ style={css({
72
+ on: ($, { not }) => [
73
+ $(not("@container sm"), {
74
+ display: "none",
75
+ }),
76
+ ],
77
+ })}
78
+ >
79
+ sm
80
+ </span>
81
+ <span
82
+ style={css({
83
+ on: ($, { not }) => [
84
+ $(not("@container lg"), {
85
+ display: "none",
86
+ }),
87
+ ],
88
+ })}
89
+ >
90
+ lg
91
+ </span>
92
+ </>
93
+ ```
47
94
 
48
95
  ## Documentation
49
96
 
50
97
  Please visit [css-hooks.com](https://css-hooks.com) to get started.
51
98
 
52
- ## Packages
53
-
54
- - [@css-hooks/recommended](packages/recommended): Recommended hook configuration
55
- with sensible defaults
56
- - [@css-hooks/react](https://github.com/css-hooks/css-hooks/tree/master/packages/react):
57
- React framework integration
58
- - [@css-hooks/preact](https://github.com/css-hooks/css-hooks/tree/master/packages/preact):
59
- Preact framework integration
60
- - [@css-hooks/solid](https://github.com/css-hooks/css-hooks/tree/master/packages/solid):
61
- Solid framework integration
62
- - [@css-hooks/qwik](https://github.com/css-hooks/css-hooks/tree/master/packages/qwik):
63
- Qwik framework integration
64
- - [@css-hooks/core](https://github.com/css-hooks/css-hooks/tree/master/packages/core):
65
- Core package (internal / advanced use cases)
66
-
67
99
  ## Contributing
68
100
 
69
101
  Contributions are welcome. Please see the
package/cjs/index.js CHANGED
@@ -1,273 +1,289 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.recommended = exports.buildHooksSystem = exports.genericStringify = void 0;
4
- function isHookSpec(x) {
5
- if (!x) {
6
- return false;
7
- }
8
- if (typeof x === "string") {
9
- return (x.startsWith(":") ||
10
- x.startsWith("@media ") ||
11
- x.startsWith("@container ") ||
12
- x.startsWith("@supports ") ||
13
- x.includes("&"));
14
- }
15
- if (typeof x === "object") {
16
- if ("or" in x && x.or instanceof Array) {
17
- return !x.or.some(xx => !isHookSpec(xx));
18
- }
19
- if ("and" in x && x.and instanceof Array) {
20
- return !x.and.some(xx => !isHookSpec(xx));
21
- }
22
- }
23
- return false;
24
- }
25
- /** @internal */
1
+ 'use strict';
2
+ // @ts-nocheck
3
+
4
+ const helpers = {
5
+ and: (...and) => ({ and }),
6
+ or: (...or) => ({ or }),
7
+ not: not => ({ not }),
8
+ };
9
+
26
10
  function genericStringify(_, value) {
27
- if (typeof value === "string") {
28
- return value;
29
- }
30
- if (typeof value === "number") {
31
- return `${value}`;
32
- }
33
- return null;
11
+ if (typeof value === "string") {
12
+ return value;
13
+ }
14
+
15
+ if (typeof value === "number") {
16
+ return `${value}`;
17
+ }
18
+
19
+ return null;
34
20
  }
35
- exports.genericStringify = genericStringify;
21
+
36
22
  function hash(obj) {
37
- const jsonString = JSON.stringify(obj);
38
- let hashValue = 0;
39
- for (let i = 0; i < jsonString.length; i++) {
40
- const charCode = jsonString.charCodeAt(i);
41
- hashValue = (hashValue << 5) - hashValue + charCode;
42
- hashValue &= 0x7fffffff;
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;
43
49
  }
44
- return hashValue.toString(36);
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 })] };
45
68
  }
69
+
46
70
  function buildHooksSystem(stringify = genericStringify) {
47
- return function createHooks(config, options) {
48
- const fallbackKeyword = (options === null || options === void 0 ? void 0 : options.fallback) || "unset";
49
- const stringifyImpl = (propertyName, value) => {
50
- return typeof value === "string" && value.startsWith("var(")
51
- ? value
52
- : stringify(propertyName, value);
53
- };
54
- const hookId = (options === null || options === void 0 ? void 0 : options.hookNameToId) ||
55
- ((hookName) => {
56
- const specHash = hash(config[hookName]);
57
- return (options === null || options === void 0 ? void 0 : options.debug)
58
- ? `${hookName.replace(/[^A-Za-z0-9-]/g, "_")}-${specHash}`
59
- : specHash;
60
- });
61
- const hooks = Object.entries(config)
62
- .map(([name, definition]) => {
63
- function nest(input) {
64
- if (typeof input === "object") {
65
- if ("and" in input) {
66
- if (input.and.length > 2) {
67
- const [left, ...rest] = input.and;
68
- return { and: [left, nest({ and: rest })] };
69
- }
70
- return {
71
- and: input.and.map(item => typeof item === "string" ? item : nest(item)),
72
- };
73
- }
74
- if ("or" in input) {
75
- if (input.or.length > 2) {
76
- const [left, ...rest] = input.or;
77
- return { or: [left, nest({ or: rest })] };
78
- }
79
- return {
80
- or: input.or.map(item => typeof item === "string" ? item : nest(item)),
81
- };
82
- }
83
- }
84
- return input;
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 styleSheet() {
99
+ function variablePair({ id, initial, indents }) {
100
+ return [0, 1]
101
+ .map(
102
+ i =>
103
+ `${Array(indents).fill(indent).join("")}--${id}-${i}:${space}${
104
+ initial === i ? "initial" : space ? "" : " "
105
+ };${newline}`,
106
+ )
107
+ .join("");
108
+ }
109
+
110
+ let sheet = `*${space}{${newline}`;
111
+
112
+ const hooks = Object.entries(hooksConfig)
113
+ .map(([hookName, hookCondition]) => [
114
+ hookName,
115
+ normalizeCondition(hookCondition),
116
+ ])
117
+ .filter(([, hookCondition]) => hookCondition);
118
+
119
+ for (const [hookName, hookCondition] of hooks) {
120
+ (function it(id, hookCondition) {
121
+ if (hookCondition && typeof hookCondition === "object") {
122
+ if (hookCondition.not) {
123
+ it(`${id}X`, hookCondition.not);
124
+ sheet += `${indent}--${id}-0:${space}var(--${id}X-1);${newline}`;
125
+ sheet += `${indent}--${id}-1:${space}var(--${id}X-0);${newline}`;
126
+ return;
85
127
  }
86
- if (!isHookSpec(definition) || typeof definition !== "object") {
87
- return [name, definition];
128
+
129
+ if ("and" in hookCondition || "or" in hookCondition) {
130
+ const operator = hookCondition.and ? "and" : "or";
131
+ it(`${id}A`, hookCondition[operator][0]);
132
+ it(`${id}B`, hookCondition[operator][1]);
133
+ if (operator === "and") {
134
+ sheet += `${indent}--${id}-0:${space}var(--${id}A-0)${space}var(--${id}B-0);${newline}`;
135
+ sheet += `${indent}--${id}-1:${space}var(--${id}A-1,${space}var(--${id}B-1));${newline}`;
136
+ } else {
137
+ sheet += `${indent}--${id}-0:${space}var(--${id}A-0,${space}var(--${id}B-0));${newline}`;
138
+ sheet += `${indent}--${id}-1:${space}var(--${id}A-1)${space}var(--${id}B-1);${newline}`;
139
+ }
140
+ return;
88
141
  }
89
- return [name, nest(definition)];
90
- })
91
- .flatMap(([name, definition]) => (function hooksCSS(name, definition) {
92
- if (!isHookSpec(definition)) {
93
- return [];
142
+ }
143
+ sheet += variablePair({ id, initial: 0, indents: 1 });
144
+ })(hookNameToId(hookName), hookCondition);
145
+ }
146
+
147
+ sheet += `}${newline}`;
148
+
149
+ for (const [hookName, hookCondition] of hooks) {
150
+ (function it(id, hookCondition) {
151
+ if (hookCondition && typeof hookCondition === "object") {
152
+ if (hookCondition.not) {
153
+ return it(`${id}X`, hookCondition.not);
94
154
  }
95
- if (typeof definition === "object") {
96
- let a, operator, b, extraHooksCSS = [];
97
- if ("or" in definition) {
98
- operator = "or";
99
- [a, b] = definition.or;
100
- if (!a) {
101
- return [];
102
- }
103
- if (!b) {
104
- return hooksCSS(name, a);
105
- }
106
- extraHooksCSS = [
107
- {
108
- init: (function aorb(x) {
109
- const a = `${x}A`;
110
- const b = `${x}B`;
111
- return [
112
- `--${x}-0:var(--${a}-0,var(--${b}-0));`,
113
- `--${x}-1:var(--${a}-1) var(--${b}-1);`,
114
- ].join("");
115
- })(name),
116
- },
117
- ];
118
- }
119
- else if ("and" in definition) {
120
- operator = "and";
121
- [a, b] = definition.and;
122
- if (!a) {
123
- return [];
124
- }
125
- if (!b) {
126
- return hooksCSS(name, a);
127
- }
128
- extraHooksCSS = [
129
- {
130
- init: (function aandb(x) {
131
- const a = `${x}A`;
132
- const b = `${x}B`;
133
- return [
134
- `--${x}-0:var(--${a}-0) var(--${b}-0);`,
135
- `--${x}-1:var(--${a}-1,var(--${b}-1));`,
136
- ].join("");
137
- })(name),
138
- },
139
- ];
140
- }
141
- if (operator) {
142
- return [
143
- ...hooksCSS(`${name}A`, a),
144
- ...hooksCSS(`${name}B`, b),
145
- ...extraHooksCSS,
146
- ];
147
- }
155
+
156
+ if ("and" in hookCondition || "or" in hookCondition) {
157
+ const operator = hookCondition.and ? "and" : "or";
158
+ it(`${id}A`, hookCondition[operator][0]);
159
+ it(`${id}B`, hookCondition[operator][1]);
160
+ return;
148
161
  }
149
- const init = `--${name}-0:initial;--${name}-1: ;`;
150
- let rule;
151
- if (typeof definition === "string") {
152
- if (definition.includes("&")) {
153
- rule = `${definition.replace(/&/g, "*")}{--${name}-0: ;--${name}-1:initial;}`;
154
- }
155
- else if (definition.startsWith(":")) {
156
- rule = `${definition}{--${name}-0: ;--${name}-1:initial;}`;
157
- }
158
- else if (definition.startsWith("@")) {
159
- rule = `${definition}{*{--${name}-0: ;--${name}-1:initial;}}`;
160
- }
162
+ }
163
+
164
+ if (typeof hookCondition === "string") {
165
+ if (hookCondition[0] === "@") {
166
+ sheet += [
167
+ `${hookCondition}${space}{${newline}`,
168
+ `${indent}*${space}{${newline}`,
169
+ variablePair({
170
+ id,
171
+ initial: 1,
172
+ indents: 2,
173
+ }),
174
+ `${indent}}${newline}`,
175
+ `}${newline}`,
176
+ ].join("");
177
+ } else {
178
+ sheet += [
179
+ `${hookCondition.replace(/&/g, "*")}${space}{${newline}`,
180
+ variablePair({
181
+ id,
182
+ initial: 1,
183
+ indents: 1,
184
+ }),
185
+ `}${newline}`,
186
+ ].join("");
161
187
  }
162
- return rule === undefined ? [] : [{ init, rule }];
163
- })(hookId(name), definition))
164
- .reduce((acc, { init = "", rule = "" }) => ({
165
- init: acc.init + init,
166
- rule: acc.rule + rule,
167
- }), {
168
- init: "",
169
- rule: "",
170
- });
171
- function cssImpl(properties, fallback = propertyName => stringifyImpl(propertyName, properties[propertyName])) {
172
- const sort = options === null || options === void 0 ? void 0 : options.sort;
173
- const keys = sort ? Object.keys(properties) : [];
174
- for (const k in properties) {
175
- const key = k;
176
- const value = properties[key];
177
- if (key in config) {
178
- const hookName = key;
179
- const innerProperties = value;
180
- cssImpl(innerProperties, propertyName => {
181
- let v = stringifyImpl(propertyName, innerProperties[propertyName]);
182
- if (v === null) {
183
- v = fallback(propertyName);
184
- }
185
- if (v === null) {
186
- v = fallbackKeyword;
187
- }
188
- return v;
189
- });
190
- for (const propertyNameStr in innerProperties) {
191
- if (keys.indexOf(propertyNameStr) > keys.indexOf(hookName)) {
192
- continue;
193
- }
194
- const propertyName = propertyNameStr;
195
- const v1 = stringifyImpl(propertyName, innerProperties[propertyName]);
196
- if (v1 !== null) {
197
- let v0 = fallback(propertyName);
198
- if (v0 === null) {
199
- v0 = fallbackKeyword;
200
- }
201
- if (sort) {
202
- delete properties[propertyName];
203
- }
204
- properties[propertyName] = `var(--${hookId(hookName)}-1, ${v1}) var(--${hookId(hookName)}-0, ${v0})`;
205
- }
206
- }
207
- delete properties[hookName];
208
- }
209
- else if (sort) {
210
- delete properties[key];
211
- properties[key] =
212
- value;
213
- }
188
+ }
189
+ })(hookNameToId(hookName), hookCondition);
190
+ }
191
+
192
+ return sheet;
193
+ }
194
+
195
+ function css(...args) {
196
+ const style = {};
197
+ let conditionCount = 0;
198
+ const rules = args
199
+ .filter(rule => rule)
200
+ .reduce(
201
+ ([baseStyles, conditionalStyles], rule) => {
202
+ if (rule.on) {
203
+ baseStyles.push(rule);
204
+ (sortConditionalStyles ? conditionalStyles : baseStyles).push(
205
+ ...(typeof rule.on === "function"
206
+ ? rule.on((condition, styles) => [condition, styles], helpers)
207
+ : rule.on),
208
+ );
209
+ } else {
210
+ baseStyles.push(rule);
214
211
  }
215
- return properties;
212
+ delete rule.on;
213
+ return [baseStyles, conditionalStyles];
214
+ },
215
+ [[], []],
216
+ )
217
+ .reduce((a, b) => {
218
+ return a.concat(b);
219
+ }, []);
220
+ for (const rule of rules) {
221
+ if (!rule || typeof rule !== "object") {
222
+ continue;
216
223
  }
217
- function css(...styles) {
218
- do {
219
- const current = styles[0];
220
- const next = styles[1] || {};
221
- for (const k in next) {
222
- if (options === null || options === void 0 ? void 0 : options.sort) {
223
- if (k in current) {
224
- delete current[k];
225
- }
226
- }
227
- current[k] = next[k];
224
+ if (rule instanceof Array && rule.length === 2) {
225
+ let conditionId = normalizeCondition(rule[0]);
226
+ if (!conditionId) {
227
+ continue;
228
+ }
229
+ if (typeof conditionId === "string") {
230
+ conditionId = hookNameToId(conditionId);
231
+ } else if (typeof conditionId === "object") {
232
+ conditionId = (function it(name, cond) {
233
+ if (typeof cond === "string") {
234
+ return hookNameToId(cond);
235
+ }
236
+ if (cond.not) {
237
+ const inner = it(`${name}X`, cond.not);
238
+ style[`--${name}-0`] = `var(--${inner}-1)`;
239
+ style[`--${name}-1`] = `var(--${inner}-0)`;
240
+ }
241
+ if (cond.and || cond.or) {
242
+ const operator = cond.and ? "and" : "or";
243
+ const a = it(`${name}A`, cond[operator][0]);
244
+ const b = it(`${name}B`, cond[operator][1]);
245
+ if (operator === "and") {
246
+ style[`--${name}-0`] = `var(--${a}-0)${space}var(--${b}-0)`;
247
+ style[`--${name}-1`] = `var(--${a}-1,${space}var(--${b}-1))`;
248
+ } else {
249
+ style[`--${name}-0`] = `var(--${a}-0,${space}var(--${b}-0))`;
250
+ style[`--${name}-1`] = `var(--${a}-1)${space}var(--${b}-1)`;
228
251
  }
229
- styles.splice(1, 1);
230
- } while (styles[1]);
231
- return cssImpl(styles[0]);
252
+ }
253
+ return name;
254
+ })(`cond${conditionCount++}`, conditionId);
255
+ }
256
+ for (const [property, value] of Object.entries(rule[1])) {
257
+ const stringifiedValue = stringify(property, value);
258
+ if (stringifiedValue === null) {
259
+ continue;
260
+ }
261
+ const fallbackValue = (() => {
262
+ if (!(property in style)) {
263
+ return fallback;
264
+ }
265
+ const stringifiedValue = stringify(property, style[property]);
266
+ return stringifiedValue === null ? fallback : stringifiedValue;
267
+ })();
268
+ if (sortProperties) {
269
+ delete style[property];
270
+ }
271
+ style[property] =
272
+ `var(--${conditionId}-1,${space}${stringifiedValue})${space}var(--${conditionId}-0,${space}${fallbackValue})`;
273
+ }
274
+ continue;
232
275
  }
233
- return [`*{${hooks.init}}${hooks.rule}`, css];
234
- };
276
+ for (const [property, value] of Object.entries(rule)) {
277
+ if (sortProperties) {
278
+ delete style[property];
279
+ }
280
+ style[property] = value;
281
+ }
282
+ }
283
+ return style;
284
+ }
285
+
286
+ return { styleSheet, css };
287
+ };
235
288
  }
236
- exports.buildHooksSystem = buildHooksSystem;
237
- /**
238
- * A list of hooks offered as a "sensible default" to solve the most common use cases.
239
- *
240
- * @deprecated Use the `@css-hooks/recommended` package instead.
241
- */
242
- exports.recommended = {
243
- active: ":active",
244
- autofill: { or: [":autofill", ":-webkit-autofill"] },
245
- checked: ":checked",
246
- default: ":default",
247
- disabled: ":disabled",
248
- empty: ":empty",
249
- enabled: ":enabled",
250
- evenChild: ":nth-child(even)",
251
- firstChild: ":first-child",
252
- firstOfType: ":first-of-type",
253
- focus: ":focus",
254
- focusVisible: ":focus-visible",
255
- focusWithin: ":focus-within",
256
- hover: ":hover",
257
- inRange: ":in-range",
258
- indeterminate: ":indeterminate",
259
- invalid: ":invalid",
260
- lastChild: ":last-child",
261
- lastOfType: ":last-of-type",
262
- oddChild: ":nth-child(odd)",
263
- onlyChild: ":only-child",
264
- onlyOfType: ":only-of-type",
265
- outOfRange: ":out-of-range",
266
- placeholderShown: { or: [":placeholder-shown", ":-moz-placeholder-shown"] },
267
- readOnly: { or: [":read-only", ":-moz-read-only"] },
268
- readWrite: { or: [":read-write", ":-moz-read-write"] },
269
- required: ":required",
270
- target: ":target",
271
- valid: ":valid",
272
- visited: ":visited",
273
- };
289
+ exports.buildHooksSystem = buildHooksSystem