@pre.style/core 3.1.0-alpha.2 → 3.1.0-alpha.4
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 +409 -3
- package/dist/cn.d.ts +2 -1
- package/dist/cn.js +4 -2
- package/dist/compiler/block.d.ts +3 -0
- package/dist/compiler/block.js +17 -4
- package/dist/compiler/compiler.d.ts +17 -0
- package/dist/compiler/compiler.js +90 -22
- package/dist/compiler/config.d.ts +1 -0
- package/dist/compiler/config.js +4 -0
- package/dist/compiler/custom-properties.d.ts +5 -0
- package/dist/compiler/custom-properties.js +150 -0
- package/dist/compiler/index.d.ts +3 -2
- package/dist/compiler/index.js +1 -1
- package/dist/compiler/interpolate.d.ts +11 -0
- package/dist/compiler/interpolate.js +318 -0
- package/dist/compiler/libraries.d.ts +2 -0
- package/dist/compiler/libraries.js +9 -2
- package/dist/compiler/mapper.js +3 -1
- package/dist/compiler/stylesheet.js +9 -0
- package/dist/compiler/variables.d.ts +13 -0
- package/dist/compiler/variables.js +272 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -2
- package/dist/runtime-dev.d.ts +2 -0
- package/dist/runtime-dev.js +9 -0
- package/dist/runtime.d.ts +16 -0
- package/dist/runtime.js +105 -0
- package/package.json +11 -1
- package/package.schema.json +8 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { transform } from 'lightningcss';
|
|
3
|
+
import { key, overrideKey, PreStyleError, valueOf } from './block.js';
|
|
4
|
+
import { atRuleInfo } from './at-rules.js';
|
|
5
|
+
import { SLOT_PREFIX } from './interpolate.js';
|
|
6
|
+
import { print } from './print.js';
|
|
7
|
+
import { withClass } from './selectors.js';
|
|
8
|
+
// A variable a block's runtime values set. In parsed rules, a generated
|
|
9
|
+
// variable's name is this prefix and a hash of what it's for, and the compiler
|
|
10
|
+
// gives it a name when printing, like localized keyframes.
|
|
11
|
+
export const VARIABLE_PREFIX = '--\0pre.style-variable:';
|
|
12
|
+
const isSlot = (node) => {
|
|
13
|
+
const value = node;
|
|
14
|
+
return value?.type === 'var' && value.value.name.ident.startsWith(SLOT_PREFIX);
|
|
15
|
+
};
|
|
16
|
+
// A placeholder's slot, and its type from `type()`, which is in its name (in
|
|
17
|
+
// hex) so that blocks differing only by type don't share a parse.
|
|
18
|
+
const parseSlot = (node) => {
|
|
19
|
+
const [, slot, type] = /^(\d+)(?:-t([\da-f]+))?/.exec(node.value.name.ident.slice(SLOT_PREFIX.length));
|
|
20
|
+
return {
|
|
21
|
+
slot: Number(slot),
|
|
22
|
+
syntax: type === undefined ? null : Buffer.from(type, 'hex').toString(),
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
// Calls `visit` for each slot in a value, in the order they're written.
|
|
26
|
+
function eachSlot(value, visit) {
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
for (const item of value)
|
|
29
|
+
eachSlot(item, visit);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (value === null || typeof value !== 'object')
|
|
33
|
+
return;
|
|
34
|
+
if (isSlot(value))
|
|
35
|
+
visit(value);
|
|
36
|
+
for (const child of Object.values(value))
|
|
37
|
+
eachSlot(child, visit);
|
|
38
|
+
}
|
|
39
|
+
const IGNORED = (token) => token.type === 'token' && token.value?.type === 'white-space';
|
|
40
|
+
// The slot a declaration is entirely made of, for `--brand: ${color}` directly
|
|
41
|
+
// on the element, which can set `--brand` itself. Under at-rules or a selector
|
|
42
|
+
// variant, the generated class would have to be too, so those use a variable.
|
|
43
|
+
function directSlot(atom) {
|
|
44
|
+
if (atom.atRules.length > 0 || atom.variant !== null || atom.declaration.important)
|
|
45
|
+
return null;
|
|
46
|
+
const declaration = atom.declaration.value;
|
|
47
|
+
// Lightning CSS also uses `custom` for properties it doesn't parse, such as
|
|
48
|
+
// `content` with a `var()`.
|
|
49
|
+
if (declaration.property !== 'custom' || !atom.property.startsWith('--'))
|
|
50
|
+
return null;
|
|
51
|
+
const tokens = declaration.value.value.filter((t) => !IGNORED(t));
|
|
52
|
+
const [token] = tokens;
|
|
53
|
+
if (tokens.length !== 1 || !isSlot(token))
|
|
54
|
+
return null;
|
|
55
|
+
return !token.value.fallback || parseSlot(token).syntax !== null ? token : null;
|
|
56
|
+
}
|
|
57
|
+
const hash = (identity) => createHash('sha256').update(JSON.stringify(identity)).digest('base64url').slice(0, 16);
|
|
58
|
+
const SELF = '__pre-style-self__';
|
|
59
|
+
// A declaration's selector, for where to set its variables. Null when it's the
|
|
60
|
+
// element itself, as for `&:hover` and `.dark &`.
|
|
61
|
+
function selectorOf(atom) {
|
|
62
|
+
if (atom.variant === null)
|
|
63
|
+
return null;
|
|
64
|
+
const selectors = withClass(atom.variant.selectors, SELF);
|
|
65
|
+
const onElement = selectors.every((selector) => {
|
|
66
|
+
const compound = selector.slice(selector.findLastIndex((c) => c.type === 'combinator') + 1);
|
|
67
|
+
return (compound.some((c) => c.type === 'class' && c.name === SELF) &&
|
|
68
|
+
!compound.some((c) => c.type === 'pseudo-element'));
|
|
69
|
+
});
|
|
70
|
+
if (onElement)
|
|
71
|
+
return null;
|
|
72
|
+
const rule = {
|
|
73
|
+
type: 'style',
|
|
74
|
+
value: {
|
|
75
|
+
selectors,
|
|
76
|
+
// Empty rules aren't printed.
|
|
77
|
+
declarations: {
|
|
78
|
+
declarations: [
|
|
79
|
+
{
|
|
80
|
+
property: 'custom',
|
|
81
|
+
value: {
|
|
82
|
+
name: '--z',
|
|
83
|
+
value: [{ type: 'token', value: { type: 'ident', value: 'z' } }],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
rules: [],
|
|
89
|
+
loc: { source_index: 0, line: 0, column: 1 },
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
const { code } = print([rule], [], {}, false);
|
|
93
|
+
return code
|
|
94
|
+
.slice(0, code.indexOf('{'))
|
|
95
|
+
.trim()
|
|
96
|
+
.replace(/\s*\n\s*/g, ' ')
|
|
97
|
+
.split(`.${SELF}`);
|
|
98
|
+
}
|
|
99
|
+
const atomIdentity = (atom, index) => [
|
|
100
|
+
atom.atRules.map((segment) => segment.key),
|
|
101
|
+
atom.variant?.key ?? '',
|
|
102
|
+
atom.property,
|
|
103
|
+
index,
|
|
104
|
+
];
|
|
105
|
+
// `@property --a { syntax: "*"; inherits: false; }`. Without it, an element
|
|
106
|
+
// with no value of its own takes its parent's instead of its default.
|
|
107
|
+
const registration = (name) => {
|
|
108
|
+
const rule = {
|
|
109
|
+
type: 'property',
|
|
110
|
+
value: {
|
|
111
|
+
name,
|
|
112
|
+
syntax: { type: 'universal' },
|
|
113
|
+
inherits: false,
|
|
114
|
+
initialValue: null,
|
|
115
|
+
loc: { source_index: 0, line: 0, column: 1 },
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
return {
|
|
119
|
+
key: key(rule),
|
|
120
|
+
rule,
|
|
121
|
+
hoist: atRuleInfo('property').hoist,
|
|
122
|
+
overrideKey: null,
|
|
123
|
+
position: null,
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
// Units and functions that depend on where they're used. Browsers drop a
|
|
127
|
+
// typed `@property` whose `initial-value` has them (tested in Chromium), and
|
|
128
|
+
// Lightning CSS doesn't report them.
|
|
129
|
+
const NOT_INDEPENDENT = /(?<![\w-])[+-]?[\d.]+(?:e[+-]?\d+)?(?:em|rem|ex|rex|ch|rch|lh|rlh|cap|rcap|ic|ric|cqw|cqh|cqi|cqb|cqmin|cqmax)\b|\b(?:env|attr)\(/i;
|
|
130
|
+
const QUOTED = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g;
|
|
131
|
+
// A slot's default, as CSS.
|
|
132
|
+
function defaultText(node) {
|
|
133
|
+
const rule = {
|
|
134
|
+
type: 'style',
|
|
135
|
+
value: {
|
|
136
|
+
selectors: [[{ type: 'class', name: 'z' }]],
|
|
137
|
+
declarations: {
|
|
138
|
+
declarations: [
|
|
139
|
+
{ property: 'custom', value: { name: '--z', value: node.value.fallback ?? [] } },
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
rules: [],
|
|
143
|
+
loc: { source_index: 0, line: 0, column: 1 },
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
const { code } = print([rule], [], {}, false);
|
|
147
|
+
return /--z:\s*(.*?);?\s*\}/s.exec(code)[1];
|
|
148
|
+
}
|
|
149
|
+
// `@property --a { syntax: "<length>"; inherits: false; initial-value: 1px; }`.
|
|
150
|
+
function typedRegistration(name, syntax, initial, position) {
|
|
151
|
+
const invalid = (reason, cause) => new PreStyleError(`\`${initial}\` can't be the default of \`type(${syntax})\`: ${reason}`, position, { cause });
|
|
152
|
+
if (NOT_INDEPENDENT.test(initial.replace(QUOTED, '')))
|
|
153
|
+
throw invalid("a typed variable's default can't depend on where it's used, such as `em`, `rem`, container units, `env()`, or `attr()`, or browsers drop its `@property` rule. Use an absolute value, or leave out `type()`.");
|
|
154
|
+
let parsed;
|
|
155
|
+
try {
|
|
156
|
+
transform({
|
|
157
|
+
filename: 'type.css',
|
|
158
|
+
code: Buffer.from(`@property --z{syntax:${JSON.stringify(syntax)};inherits:false;initial-value:${initial}}`),
|
|
159
|
+
visitor: {
|
|
160
|
+
StyleSheetExit(sheet) {
|
|
161
|
+
[parsed] = sheet.rules;
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
throw invalid(`it isn't valid for that type (${error.message}).`, error);
|
|
168
|
+
}
|
|
169
|
+
const rule = { ...parsed, value: { ...valueOf(parsed), name } };
|
|
170
|
+
return {
|
|
171
|
+
key: key(rule),
|
|
172
|
+
rule,
|
|
173
|
+
hoist: atRuleInfo('property').hoist,
|
|
174
|
+
overrideKey: null,
|
|
175
|
+
position,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const SLOT_MESSAGE = "`${…}` can only be used in a declaration's value, because it becomes a `var()`, which isn't valid here.";
|
|
179
|
+
// Points a slot at its variable, and registers the variable. A typed
|
|
180
|
+
// variable's default moves to its `initial-value`, so each type and default
|
|
181
|
+
// has its own variable.
|
|
182
|
+
function useVariable(node, identity, slot, syntax, variables, added, position, selector = null) {
|
|
183
|
+
const initial = syntax === null ? null : defaultText(node);
|
|
184
|
+
const variable = hash([identity, syntax, initial]);
|
|
185
|
+
const name = VARIABLE_PREFIX + variable;
|
|
186
|
+
variables.push({ key: variable, property: null, slot, selector });
|
|
187
|
+
node.value.name.ident = name;
|
|
188
|
+
if (syntax !== null)
|
|
189
|
+
node.value.fallback = null;
|
|
190
|
+
if (added.has(variable))
|
|
191
|
+
return;
|
|
192
|
+
added.set(variable, syntax === null ? registration(name) : typedRegistration(name, syntax, initial, position));
|
|
193
|
+
}
|
|
194
|
+
// Replaces the slots in a global rule, such as a `@keyframes`, with variables
|
|
195
|
+
// identified by the rule and their place in it, so the same rule in any block
|
|
196
|
+
// shares them.
|
|
197
|
+
function globalVariables(global, variables, added) {
|
|
198
|
+
const rule = structuredClone(global.rule);
|
|
199
|
+
const slots = [];
|
|
200
|
+
eachSlot(rule, (node) => slots.push(node));
|
|
201
|
+
const parsed = slots.map(parseSlot);
|
|
202
|
+
slots.forEach((node, index) => {
|
|
203
|
+
const type = parsed[index].syntax === null ? '' : `:${parsed[index].syntax}`;
|
|
204
|
+
node.value.name.ident = `${SLOT_PREFIX}${index}${type}`;
|
|
205
|
+
});
|
|
206
|
+
const identity = key(rule);
|
|
207
|
+
slots.forEach((node, index) => {
|
|
208
|
+
const { slot, syntax } = parsed[index];
|
|
209
|
+
useVariable(node, [identity, index], slot, syntax, variables, added, global.position);
|
|
210
|
+
});
|
|
211
|
+
const json = key(rule);
|
|
212
|
+
// Anywhere else, such as a selector Sass built, it isn't a `var()` node.
|
|
213
|
+
if (json.includes(SLOT_PREFIX))
|
|
214
|
+
throw new PreStyleError(SLOT_MESSAGE, global.position);
|
|
215
|
+
return { ...global, key: json, rule };
|
|
216
|
+
}
|
|
217
|
+
// Turns a parsed block's placeholder variables into its real ones. Runs after
|
|
218
|
+
// overridden atoms are dropped, so their slots are left out. Variables are
|
|
219
|
+
// identified by what they're for, so `width: ${w}px` in any block shares one
|
|
220
|
+
// variable and class.
|
|
221
|
+
export function extractVariables(atoms, globals) {
|
|
222
|
+
const variables = [];
|
|
223
|
+
const added = new Map();
|
|
224
|
+
const kept = globals.map((global) => global.key.includes(SLOT_PREFIX) ? globalVariables(global, variables, added) : global);
|
|
225
|
+
const keptAtoms = [];
|
|
226
|
+
for (const atom of atoms) {
|
|
227
|
+
const prefix = atom.atRules.map((segment) => segment.key).join() + (atom.variant?.key ?? '');
|
|
228
|
+
// Sass can move a placeholder, so its position is checked again here.
|
|
229
|
+
if (prefix.includes(SLOT_PREFIX))
|
|
230
|
+
throw new PreStyleError(SLOT_MESSAGE, atom.position);
|
|
231
|
+
if (!atom.declaration.key.includes(SLOT_PREFIX)) {
|
|
232
|
+
keptAtoms.push(atom);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const direct = directSlot(atom);
|
|
236
|
+
if (direct !== null) {
|
|
237
|
+
const { slot, syntax } = parseSlot(direct);
|
|
238
|
+
variables.push({
|
|
239
|
+
key: `direct:${atom.property}`,
|
|
240
|
+
property: atom.property,
|
|
241
|
+
slot,
|
|
242
|
+
selector: null,
|
|
243
|
+
});
|
|
244
|
+
// A typed custom property registers itself, under its own name.
|
|
245
|
+
if (syntax !== null) {
|
|
246
|
+
const registered = typedRegistration(atom.property, syntax, defaultText(direct), atom.position);
|
|
247
|
+
added.set(`direct:${atom.property}:${registered.key}`, {
|
|
248
|
+
...registered,
|
|
249
|
+
overrideKey: overrideKey(registered.rule),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const value = structuredClone(atom.declaration.value);
|
|
255
|
+
const selector = selectorOf(atom);
|
|
256
|
+
let index = 0;
|
|
257
|
+
eachSlot(value, (node) => {
|
|
258
|
+
const { slot, syntax } = parseSlot(node);
|
|
259
|
+
const identity = atomIdentity(atom, index++);
|
|
260
|
+
useVariable(node, identity, slot, syntax, variables, added, atom.position, selector);
|
|
261
|
+
});
|
|
262
|
+
keptAtoms.push({
|
|
263
|
+
...atom,
|
|
264
|
+
declaration: {
|
|
265
|
+
...atom.declaration,
|
|
266
|
+
key: key(value) + (atom.declaration.important ? '!' : ''),
|
|
267
|
+
value,
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
return { atoms: keptAtoms, globals: [...kept, ...added.values()], variables };
|
|
272
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export
|
|
2
|
-
export declare function
|
|
1
|
+
export type RuntimeValue = string | number | null | undefined;
|
|
2
|
+
export declare function css(_strings: TemplateStringsArray, ..._values: RuntimeValue[]): string;
|
|
3
|
+
export declare function scss(_strings: TemplateStringsArray, ..._values: RuntimeValue[]): string;
|
|
3
4
|
import { createCn } from './cn.ts';
|
|
4
5
|
export declare const cn: (...inputs: import("./cn.ts").ClassValue[]) => string;
|
|
5
6
|
export { createCn };
|
|
6
|
-
export type { ClassDictionary, ClassValue, CnTable } from './cn.ts';
|
|
7
|
+
export type { ClassDictionary, ClassValue, CnLookup, CnTable } from './cn.ts';
|
package/dist/index.js
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
function notTransformed() {
|
|
5
5
|
throw new Error('pre.style transform not applied. css and scss blocks must be compiled by a pre.style bundler plugin, such as @pre.style/vite.');
|
|
6
6
|
}
|
|
7
|
-
export function css(_strings) {
|
|
7
|
+
export function css(_strings, ..._values) {
|
|
8
8
|
return notTransformed();
|
|
9
9
|
}
|
|
10
|
-
export function scss(_strings) {
|
|
10
|
+
export function scss(_strings, ..._values) {
|
|
11
11
|
return notTransformed();
|
|
12
12
|
}
|
|
13
13
|
import { createCn } from './cn.js';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const dynamic: (classes: string, values: readonly unknown[], ...variables: import("./runtime.ts").RuntimeVariable[]) => string;
|
|
2
|
+
export declare const dynamicLowercase: (classes: string, values: readonly unknown[], ...variables: import("./runtime.ts").RuntimeVariable[]) => string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CASE_INSENSITIVE, CASE_SENSITIVE, create } from './runtime.js';
|
|
2
|
+
// `./runtime.ts` with full error messages, which the bundler plugin uses
|
|
3
|
+
// outside production builds.
|
|
4
|
+
const invalidValue = (value, strings) => {
|
|
5
|
+
const got = typeof value === 'number' ? String(value) : typeof value;
|
|
6
|
+
throw new TypeError(`pre.style: \`\${…}\` values must be strings or numbers, or null or undefined to use the default. Got ${got} in \`${strings.join('${…}')}\`. For a condition, write \`\${open ? width : null}\` rather than \`\${open && width}\`.`);
|
|
7
|
+
};
|
|
8
|
+
export const dynamic = /* @__PURE__ */ create(CASE_SENSITIVE, invalidValue);
|
|
9
|
+
export const dynamicLowercase = /* @__PURE__ */ create(CASE_INSENSITIVE, invalidValue);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CnLookup } from './cn.ts';
|
|
2
|
+
export type RuntimeVariable = [
|
|
3
|
+
name: string,
|
|
4
|
+
strings: readonly string[],
|
|
5
|
+
indexes: readonly number[],
|
|
6
|
+
property?: string | 0,
|
|
7
|
+
selector?: readonly string[]
|
|
8
|
+
];
|
|
9
|
+
export declare const CASE_SENSITIVE: string;
|
|
10
|
+
export declare const CASE_INSENSITIVE: string;
|
|
11
|
+
export type InvalidValue = (value: unknown, strings: readonly string[]) => never;
|
|
12
|
+
export declare const create: (alphabet: string, invalid: InvalidValue) => (classes: string, values: readonly unknown[], ...variables: RuntimeVariable[]) => string;
|
|
13
|
+
export declare const dynamic: (classes: string, values: readonly unknown[], ...variables: RuntimeVariable[]) => string;
|
|
14
|
+
export declare const dynamicLowercase: (classes: string, values: readonly unknown[], ...variables: RuntimeVariable[]) => string;
|
|
15
|
+
export type CnVariables = Record<string, string>;
|
|
16
|
+
export declare const generatedClasses: (variables: CnVariables) => CnLookup;
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const DIGITS = '0123456789';
|
|
2
|
+
const LOWERCASE = 'abcdefghijklmnopqrstuvwxyz';
|
|
3
|
+
export const CASE_SENSITIVE = DIGITS + LOWERCASE + LOWERCASE.toUpperCase();
|
|
4
|
+
export const CASE_INSENSITIVE = DIGITS + LOWERCASE;
|
|
5
|
+
const HASH_LENGTH = 4;
|
|
6
|
+
// A hash of a value that's the same on the server and in the browser, since
|
|
7
|
+
// the class names it's part of have to match for hydration. FNV-1a.
|
|
8
|
+
function hash(value, alphabet) {
|
|
9
|
+
let h = 0x811c9dc5;
|
|
10
|
+
for (let i = 0; i < value.length; i++) {
|
|
11
|
+
h ^= value.charCodeAt(i);
|
|
12
|
+
h = Math.imul(h, 0x01000193);
|
|
13
|
+
}
|
|
14
|
+
h >>>= 0;
|
|
15
|
+
let result = '';
|
|
16
|
+
for (let i = 0; i < HASH_LENGTH; i++) {
|
|
17
|
+
result += alphabet[h % alphabet.length];
|
|
18
|
+
h = Math.floor(h / alphabet.length);
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
// What can't be written as it is in a quoted CSS string.
|
|
23
|
+
const UNSAFE_IN_STRING = /["'\\\n\r\f]/g;
|
|
24
|
+
// The variable's value, or null when any of its values is null or undefined,
|
|
25
|
+
// so its default applies. Anything else is almost always a bug, which falling
|
|
26
|
+
// back would hide.
|
|
27
|
+
function text(strings, indexes, values, invalid) {
|
|
28
|
+
let result = strings[0];
|
|
29
|
+
let missing = false;
|
|
30
|
+
for (let i = 0; i < indexes.length; i++) {
|
|
31
|
+
const index = indexes[i];
|
|
32
|
+
const value = values[index < 0 ? ~index : index];
|
|
33
|
+
if (value === null || value === undefined)
|
|
34
|
+
missing = true;
|
|
35
|
+
else if (typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value)))
|
|
36
|
+
result +=
|
|
37
|
+
(index < 0
|
|
38
|
+
? String(value).replace(UNSAFE_IN_STRING, (c) => `\\${c.charCodeAt(0).toString(16)} `)
|
|
39
|
+
: value) + strings[i + 1];
|
|
40
|
+
else
|
|
41
|
+
invalid(value, strings);
|
|
42
|
+
}
|
|
43
|
+
return missing ? null : result;
|
|
44
|
+
}
|
|
45
|
+
let sheet;
|
|
46
|
+
const inserted = new Set();
|
|
47
|
+
// Inserts a generated class's rule once. `setProperty` parses the value alone,
|
|
48
|
+
// so a value can't add rules or declarations, even if it comes from a user.
|
|
49
|
+
function insert(selector, className, property, value) {
|
|
50
|
+
if (inserted.has(className))
|
|
51
|
+
return;
|
|
52
|
+
inserted.add(className);
|
|
53
|
+
// An unknown selector is ignored, as it is in the CSS file. Browsers without
|
|
54
|
+
// constructable stylesheets, or where the list can't be changed (Chrome before
|
|
55
|
+
// 99, Firefox before 101, Safari before 16.4), keep the defaults rather than
|
|
56
|
+
// failing the render.
|
|
57
|
+
try {
|
|
58
|
+
if (sheet === undefined) {
|
|
59
|
+
sheet = new CSSStyleSheet();
|
|
60
|
+
document.adoptedStyleSheets.push(sheet);
|
|
61
|
+
}
|
|
62
|
+
const index = sheet.insertRule(`${selector}{}`, sheet.cssRules.length);
|
|
63
|
+
sheet.cssRules[index].style.setProperty(property, value);
|
|
64
|
+
}
|
|
65
|
+
catch { }
|
|
66
|
+
}
|
|
67
|
+
export const create = (alphabet, invalid) => (classes, values, ...variables) => {
|
|
68
|
+
let result = classes;
|
|
69
|
+
for (const [name, strings, indexes, property, selector] of variables) {
|
|
70
|
+
const value = text(strings, indexes, values, invalid);
|
|
71
|
+
if (value === null)
|
|
72
|
+
continue;
|
|
73
|
+
const className = `_${name}${hash(value, alphabet)}`;
|
|
74
|
+
// The server returns the same class strings, without the rules, so
|
|
75
|
+
// hydration matches and the page uses its defaults until this runs.
|
|
76
|
+
if (typeof document !== 'undefined')
|
|
77
|
+
insert(selector ? selector.join(`.${className}`) : `.${className}`, className, property || `--${name}`, value);
|
|
78
|
+
result += result === '' ? className : ` ${className}`;
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
};
|
|
82
|
+
// Production errors are numbered, to keep the runtime small. The site's
|
|
83
|
+
// `/errors` pages explain them, and `./runtime-dev.ts` has the full messages.
|
|
84
|
+
const invalidValue = (value) => {
|
|
85
|
+
throw new TypeError(`pre.style error 1 (${typeof value}): https://pre.style/errors/1`);
|
|
86
|
+
};
|
|
87
|
+
// What a block with `${…}` compiles to. It takes the block's class string, its
|
|
88
|
+
// expressions' values, so each runs once and in order, and its variables, and
|
|
89
|
+
// returns the class string with a generated class for each value.
|
|
90
|
+
export const dynamic = /* @__PURE__ */ create(CASE_SENSITIVE, invalidValue);
|
|
91
|
+
// For the `quirksMode` setting, where class selectors ignore case.
|
|
92
|
+
export const dynamicLowercase = /* @__PURE__ */ create(CASE_INSENSITIVE, invalidValue);
|
|
93
|
+
// A `cn` lookup that also recognizes generated classes, which aren't in its
|
|
94
|
+
// table: `_`, a variable's name, and four characters. Each sets its variable on
|
|
95
|
+
// the element. It's here rather than in `cn`, so apps without `${…}` don't ship
|
|
96
|
+
// it.
|
|
97
|
+
export const generatedClasses = (variables) => new (class extends Map {
|
|
98
|
+
get(className) {
|
|
99
|
+
const name = className.slice(1, -4);
|
|
100
|
+
return (super.get(className) ??
|
|
101
|
+
(className[0] === '_' && Object.hasOwn(variables, name)
|
|
102
|
+
? ['', variables[name] || `--${name}`]
|
|
103
|
+
: undefined));
|
|
104
|
+
}
|
|
105
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pre.style/core",
|
|
3
|
-
"version": "3.1.0-alpha.
|
|
3
|
+
"version": "3.1.0-alpha.4",
|
|
4
4
|
"description": "Zero-runtime CSS-in-JS that compiles css and scss blocks into atomic CSS classes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atomic-css",
|
|
@@ -37,6 +37,16 @@
|
|
|
37
37
|
"types": "./dist/index.d.ts",
|
|
38
38
|
"default": "./dist/index.js"
|
|
39
39
|
},
|
|
40
|
+
"./runtime": {
|
|
41
|
+
"pre.style-source": "./src/runtime.ts",
|
|
42
|
+
"types": "./dist/runtime.d.ts",
|
|
43
|
+
"default": "./dist/runtime.js"
|
|
44
|
+
},
|
|
45
|
+
"./runtime/dev": {
|
|
46
|
+
"pre.style-source": "./src/runtime-dev.ts",
|
|
47
|
+
"types": "./dist/runtime-dev.d.ts",
|
|
48
|
+
"default": "./dist/runtime-dev.js"
|
|
49
|
+
},
|
|
40
50
|
"./compiler": {
|
|
41
51
|
"pre.style-source": "./src/compiler/index.ts",
|
|
42
52
|
"types": "./dist/compiler/index.d.ts",
|
package/package.schema.json
CHANGED
|
@@ -61,6 +61,10 @@
|
|
|
61
61
|
"prependFiles": {
|
|
62
62
|
"$ref": "#/definitions/prependFiles"
|
|
63
63
|
},
|
|
64
|
+
"customPropertyLocalization": {
|
|
65
|
+
"description": "Give each custom property declared in the library's blocks and stylesheet files a generated name, used only in the block or file that declares it. Names it only reads, and names from prepended files, stay global. The app's setting doesn't apply to libraries.",
|
|
66
|
+
"type": "boolean"
|
|
67
|
+
},
|
|
64
68
|
"keyframeLocalization": {
|
|
65
69
|
"description": "Give each @keyframes in the library's blocks and stylesheet files a generated name, used only in the block or file that defines it, like styled-components' `keyframes`. The app's setting doesn't apply to libraries.",
|
|
66
70
|
"type": "boolean"
|
|
@@ -88,6 +92,10 @@
|
|
|
88
92
|
"prependFiles": {
|
|
89
93
|
"$ref": "#/definitions/prependFiles"
|
|
90
94
|
},
|
|
95
|
+
"customPropertyLocalization": {
|
|
96
|
+
"description": "Give each custom property declared in the app's blocks and stylesheet files a generated name, used only in the block or file that declares it. Names it only reads, and names from prepended files, stay global. Libraries use their own setting.",
|
|
97
|
+
"type": "boolean"
|
|
98
|
+
},
|
|
91
99
|
"keyframeLocalization": {
|
|
92
100
|
"description": "Give each @keyframes in the app's blocks and stylesheet files a generated name, used only in the block or file that defines it (including keyframes from prepended files), like styled-components' `keyframes`. Libraries use their own setting.",
|
|
93
101
|
"type": "boolean"
|