@master/css-runtime 2.0.0-rc.6 → 2.0.0-rc.61
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 +4 -4
- package/dist/core.d.ts +71 -10
- package/dist/debuggers/class-count.d.ts +1 -0
- package/dist/debuggers/index.d.ts +1 -0
- package/dist/debuggers/lifecycle.d.ts +1 -0
- package/dist/global.min.js +1 -0
- package/dist/index.cjs +508 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.development.cjs +610 -0
- package/dist/index.development.d.ts +1 -0
- package/dist/index.development.mjs +606 -0
- package/dist/index.mjs +501 -0
- package/dist/init.d.ts +10 -0
- package/dist/layer.d.ts +15 -0
- package/dist/register-global.d.ts +9 -0
- package/dist/syntax-layer.d.ts +19 -0
- package/dist/types/hydrate-result.d.ts +4 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
- package/dist/iife.bundle.js +0 -1
- package/dist/index.bundle.js +0 -1
- package/dist/index.bundle.mjs +0 -1
- package/dist/init-css-runtime.d.ts +0 -9
- package/dist/types/global.d.ts +0 -8
- /package/dist/{iife.d.ts → global.min.d.ts} +0 -0
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
import { Layer, withSyntaxLayer, MasterCSS, config, Rule } from '@master/css';
|
|
2
|
+
import { installHook } from '@master/css-devtools-hook';
|
|
3
|
+
|
|
4
|
+
const createLogger = (type)=>{
|
|
5
|
+
return (message, ...args)=>{
|
|
6
|
+
if (!type) return console.log(message, ...args);
|
|
7
|
+
if (console[type]) {
|
|
8
|
+
console[type](`%c[Master CSS]%c ${message}`, 'color: gray;', 'color: inherit;', ...args);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
const loggers = {
|
|
13
|
+
info: createLogger('info'),
|
|
14
|
+
debug: createLogger('debug'),
|
|
15
|
+
warn: console.warn,
|
|
16
|
+
error: console.error,
|
|
17
|
+
trace: console.trace,
|
|
18
|
+
table: console.table,
|
|
19
|
+
group: console.group,
|
|
20
|
+
groupCollapsed: console.group,
|
|
21
|
+
groupEnd: console.groupEnd,
|
|
22
|
+
time: console.time,
|
|
23
|
+
timeEnd: console.timeEnd
|
|
24
|
+
};
|
|
25
|
+
const log = createLogger();
|
|
26
|
+
Object.assign(log, loggers);
|
|
27
|
+
|
|
28
|
+
function registerClassCountDebugger() {
|
|
29
|
+
const hook = globalThis.__MASTER_CSS_DEVTOOLS_HOOK__;
|
|
30
|
+
hook.on('runtime:mutated', ({ records, classCounts, cssRuntime })=>{
|
|
31
|
+
const actualClassCounts = {};
|
|
32
|
+
let errored = false;
|
|
33
|
+
const resolveClass = (className)=>{
|
|
34
|
+
if (Object.prototype.hasOwnProperty.call(actualClassCounts, className)) {
|
|
35
|
+
actualClassCounts[className]++;
|
|
36
|
+
} else {
|
|
37
|
+
actualClassCounts[className] = 1;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
(cssRuntime.root.constructor.name === 'HTMLDocument' ? cssRuntime.host : cssRuntime.container).querySelectorAll('[class]').forEach((element)=>{
|
|
41
|
+
element.classList.forEach(resolveClass);
|
|
42
|
+
});
|
|
43
|
+
cssRuntime.host.classList.forEach(resolveClass);
|
|
44
|
+
for(const className in actualClassCounts){
|
|
45
|
+
const eachCount = cssRuntime.classCounts.get(className);
|
|
46
|
+
const eachActualCount = actualClassCounts[className];
|
|
47
|
+
if (eachCount !== eachActualCount) {
|
|
48
|
+
log.error(`Class count mismatch for '${className}' (expected ${eachActualCount}) (received ${eachCount})`);
|
|
49
|
+
errored = true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
cssRuntime.classCounts.forEach((eachCount, className)=>{
|
|
53
|
+
if (!Object.prototype.hasOwnProperty.call(actualClassCounts, className)) {
|
|
54
|
+
log.error(`Class count mismatch for '${className}' (expected ${0}) (received ${eachCount})`);
|
|
55
|
+
errored = true;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
if (errored) {
|
|
59
|
+
log.debug('Records:', records);
|
|
60
|
+
log.debug('Counts:', classCounts);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function registerLifecycleDebugger() {
|
|
66
|
+
const hook = globalThis.__MASTER_CSS_DEVTOOLS_HOOK__;
|
|
67
|
+
hook.on('runtime:created', ({ cssRuntime })=>{
|
|
68
|
+
log.debug('created', cssRuntime);
|
|
69
|
+
});
|
|
70
|
+
hook.on('runtime:hydrated', ({ cssRuntime })=>{
|
|
71
|
+
log.debug('hydrated', cssRuntime);
|
|
72
|
+
});
|
|
73
|
+
hook.on('runtime:observed', ({ cssRuntime })=>{
|
|
74
|
+
log.debug('observed', cssRuntime);
|
|
75
|
+
if (cssRuntime.progressive) {
|
|
76
|
+
log.info('Progressive rendering is adopted.');
|
|
77
|
+
log.debug('pre-rendered CSS', cssRuntime.style);
|
|
78
|
+
} else {
|
|
79
|
+
log.info('Runtime rendering is adopted.');
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
hook.on('runtime:refreshed', ({ cssRuntime, customConfig })=>{
|
|
83
|
+
log.debug('refreshed', cssRuntime, customConfig);
|
|
84
|
+
});
|
|
85
|
+
hook.on('runtime:disconnected', ({ cssRuntime })=>{
|
|
86
|
+
log.debug('disconnected', cssRuntime);
|
|
87
|
+
});
|
|
88
|
+
hook.on('runtime:destroyed', ({ cssRuntime })=>{
|
|
89
|
+
log.debug('destroyed', cssRuntime);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function startDebuggers() {
|
|
94
|
+
log.info('Debug mode is enabled by default in development.');
|
|
95
|
+
registerClassCountDebugger();
|
|
96
|
+
registerLifecycleDebugger();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function registerGlobal(CSSRuntime) {
|
|
100
|
+
if (!globalThis.CSSRuntime) globalThis.CSSRuntime = CSSRuntime;
|
|
101
|
+
installHook();
|
|
102
|
+
{
|
|
103
|
+
startDebuggers();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function findNativeCSSRuleIndex(cssRules, nativeRule) {
|
|
108
|
+
for(let i = 0; i < cssRules.length; i++){
|
|
109
|
+
if (cssRules[i] === nativeRule) {
|
|
110
|
+
return i;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return -1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
class RuntimeLayer extends Layer {
|
|
117
|
+
name;
|
|
118
|
+
cssRuntime;
|
|
119
|
+
rules;
|
|
120
|
+
tokenCounts;
|
|
121
|
+
native;
|
|
122
|
+
constructor(name, cssRuntime){
|
|
123
|
+
super(name, cssRuntime), this.name = name, this.cssRuntime = cssRuntime, this.rules = [], this.tokenCounts = new Map(), this.native = null;
|
|
124
|
+
}
|
|
125
|
+
attach() {
|
|
126
|
+
super.attach();
|
|
127
|
+
const nativeSheet = this.cssRuntime.style?.sheet;
|
|
128
|
+
if (nativeSheet && !this.native?.parentStyleSheet) {
|
|
129
|
+
const insertedIndex = nativeSheet.insertRule(this.text, nativeSheet.cssRules.length);
|
|
130
|
+
this.native = nativeSheet.cssRules.item(insertedIndex);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
insert(rule, index = this.rules.length) {
|
|
134
|
+
const insertedIndex = super.insert(rule, index);
|
|
135
|
+
if (insertedIndex === undefined) return;
|
|
136
|
+
if (this.native) {
|
|
137
|
+
let cssRuleIndex = 0;
|
|
138
|
+
const lastCssRule = function getLastCssRule(layer, index) {
|
|
139
|
+
let lastCssRule;
|
|
140
|
+
const previouRule = layer.rules[index];
|
|
141
|
+
if (previouRule && 'nodes' in previouRule) {
|
|
142
|
+
if (!previouRule.nodes.length) return getLastCssRule(layer, index - 1);
|
|
143
|
+
const lastNativeRule = previouRule.nodes[previouRule.nodes.length - 1];
|
|
144
|
+
lastCssRule = lastNativeRule.native;
|
|
145
|
+
}
|
|
146
|
+
return lastCssRule;
|
|
147
|
+
}(this, insertedIndex - 1);
|
|
148
|
+
if (lastCssRule) {
|
|
149
|
+
for(let i = 0; i < this.native.cssRules.length; i++){
|
|
150
|
+
if (this.native.cssRules[i] === lastCssRule) {
|
|
151
|
+
cssRuleIndex = i + 1;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for(let i = 0; i < rule.nodes.length;){
|
|
157
|
+
const node = rule.nodes[i];
|
|
158
|
+
try {
|
|
159
|
+
const insertedIndex = this.native.insertRule(node.text, cssRuleIndex);
|
|
160
|
+
node.native = this.native.cssRules.item(insertedIndex);
|
|
161
|
+
cssRuleIndex++;
|
|
162
|
+
i++;
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error(error, node);
|
|
165
|
+
rule.nodes.splice(i, 1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return insertedIndex;
|
|
170
|
+
}
|
|
171
|
+
detach() {
|
|
172
|
+
const nativeSheet = this.cssRuntime.style?.sheet;
|
|
173
|
+
if (nativeSheet && this.native?.parentStyleSheet) {
|
|
174
|
+
const foundIndex = findNativeCSSRuleIndex(nativeSheet.cssRules, this.native);
|
|
175
|
+
if (foundIndex !== -1) {
|
|
176
|
+
nativeSheet.deleteRule(foundIndex);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
delete(key) {
|
|
181
|
+
const deletedRule = super.delete(key);
|
|
182
|
+
if (!deletedRule) return;
|
|
183
|
+
if (this.native?.cssRules && 'nodes' in deletedRule) {
|
|
184
|
+
for (const node of deletedRule.nodes){
|
|
185
|
+
if (node.native) {
|
|
186
|
+
const foundIndex = findNativeCSSRuleIndex(this.native.cssRules, node.native);
|
|
187
|
+
if (foundIndex !== -1) {
|
|
188
|
+
// todo: Firefox throw "Uncaught NS_ERROR_FAILURE". Reproduce: Add '@fade|1s @fade|2s' and remove '@fade|1s @fade|2s'
|
|
189
|
+
this.native.deleteRule(foundIndex);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return deletedRule;
|
|
195
|
+
}
|
|
196
|
+
reset() {
|
|
197
|
+
super.reset();
|
|
198
|
+
const nativeSheet = this.cssRuntime.style?.sheet;
|
|
199
|
+
if (this.native && nativeSheet) {
|
|
200
|
+
const foundIndex = findNativeCSSRuleIndex(nativeSheet.cssRules, this.native);
|
|
201
|
+
if (foundIndex !== -1) {
|
|
202
|
+
nativeSheet.deleteRule(foundIndex);
|
|
203
|
+
}
|
|
204
|
+
this.native = null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const RuntimeSyntaxLayer = withSyntaxLayer(RuntimeLayer);
|
|
210
|
+
|
|
211
|
+
class CSSRuntime extends MasterCSS {
|
|
212
|
+
root;
|
|
213
|
+
customConfig;
|
|
214
|
+
static instances = new WeakMap();
|
|
215
|
+
host;
|
|
216
|
+
observing;
|
|
217
|
+
progressive;
|
|
218
|
+
container;
|
|
219
|
+
observer;
|
|
220
|
+
baseLayer;
|
|
221
|
+
themeLayer;
|
|
222
|
+
presetLayer;
|
|
223
|
+
componentsLayer;
|
|
224
|
+
generalLayer;
|
|
225
|
+
classCounts;
|
|
226
|
+
constructor(root = document, customConfig = config){
|
|
227
|
+
super(customConfig), this.root = root, this.customConfig = customConfig, this.observing = false, this.progressive = false, this.baseLayer = new RuntimeSyntaxLayer('base', this), this.themeLayer = new RuntimeLayer('theme', this), this.presetLayer = new RuntimeSyntaxLayer('preset', this), this.componentsLayer = new RuntimeSyntaxLayer('components', this), this.generalLayer = new RuntimeSyntaxLayer('general', this), this.classCounts = new Map();
|
|
228
|
+
const rootConstructorName = root?.constructor.name;
|
|
229
|
+
if (rootConstructorName === 'HTMLDocument' || rootConstructorName === 'Document') {
|
|
230
|
+
this.root.defaultView.globalThis.cssRuntime = this;
|
|
231
|
+
this.container = this.root.head;
|
|
232
|
+
this.host = this.root.documentElement;
|
|
233
|
+
} else {
|
|
234
|
+
this.container = this.root;
|
|
235
|
+
this.host = this.root.host;
|
|
236
|
+
}
|
|
237
|
+
globalThis.CSSRuntime.instances.set(this.root, this);
|
|
238
|
+
__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:created', {
|
|
239
|
+
cssRuntime: this
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Observe the DOM for changes and update the running stylesheet. (browser only)
|
|
244
|
+
* @param options mutation observer options
|
|
245
|
+
* @returns this
|
|
246
|
+
*/ observe() {
|
|
247
|
+
if (this.observing) return this;
|
|
248
|
+
// 1. Check if the stylesheet is progressive.
|
|
249
|
+
if (this.root.styleSheets) for (const sheet of this.root.styleSheets){
|
|
250
|
+
const { ownerNode } = sheet;
|
|
251
|
+
if (ownerNode && ownerNode.id === 'master') {
|
|
252
|
+
this.style = ownerNode;
|
|
253
|
+
if (this.style.sheet?.cssRules.length) {
|
|
254
|
+
// @ts-ignore
|
|
255
|
+
this.progressive = true;
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// 2. Count the classes in the host and its children.
|
|
261
|
+
const connectedNames = new Set();
|
|
262
|
+
const increaseClassCount = (className)=>{
|
|
263
|
+
const count = this.classCounts.get(className) || 0;
|
|
264
|
+
if (!count) {
|
|
265
|
+
connectedNames.add(className);
|
|
266
|
+
}
|
|
267
|
+
this.classCounts.set(className, count + 1);
|
|
268
|
+
};
|
|
269
|
+
this.host.classList.forEach(increaseClassCount);
|
|
270
|
+
(this.root.constructor.name === 'HTMLDocument' ? this.host : this.container).querySelectorAll('[class]').forEach((element)=>element.classList.forEach(increaseClassCount));
|
|
271
|
+
// 3. Hydrate the existing CSS rules or create a new one.
|
|
272
|
+
if (this.progressive) {
|
|
273
|
+
const hydrateResult = this.hydrate(this.style.sheet.cssRules);
|
|
274
|
+
// Add the connected class names that are not hydrated yet.
|
|
275
|
+
for (const eachConnectedName of connectedNames){
|
|
276
|
+
if (hydrateResult.allSyntaxRules.find((rule)=>(rule.fixedClass || rule.name) === eachConnectedName)) continue;
|
|
277
|
+
this.add(eachConnectedName);
|
|
278
|
+
{
|
|
279
|
+
// Basically, the style#master should have all the prerendered CSS rules that are connected in the DOM.
|
|
280
|
+
console.debug(`The class \`${eachConnectedName}\` was added via script before calling hydrate, or the corresponding CSS rule were not properly pre-rendered.`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
this.style = document.createElement('style');
|
|
285
|
+
this.style.id = 'master';
|
|
286
|
+
this.container.append(this.style);
|
|
287
|
+
this.style.sheet.insertRule(this.layerStatementRule.text);
|
|
288
|
+
this.layerStatementRule.nodes[0].native = this.style.sheet.cssRules.item(0);
|
|
289
|
+
for (const eachConnectedName of connectedNames){
|
|
290
|
+
this.add(eachConnectedName);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// @ts-expect-error readonly
|
|
294
|
+
this.observer = new MutationObserver((mutationRecords)=>{
|
|
295
|
+
const eachClassCounts = new Map();
|
|
296
|
+
const targetFirstAttrMutationRecord = new Map();
|
|
297
|
+
const updateClassCount = (classes, isAdding = false)=>{
|
|
298
|
+
const usage = isAdding ? 1 : -1;
|
|
299
|
+
classes.forEach((className)=>{
|
|
300
|
+
eachClassCounts.set(className, (eachClassCounts.get(className) || 0) + usage);
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
const connectedStatusMap = new Map();
|
|
304
|
+
const disconnectedStatusMap = new Map();
|
|
305
|
+
const recordStatus = (target, mutationRecord, map, adding)=>{
|
|
306
|
+
const status = map.get(target);
|
|
307
|
+
if (status) {
|
|
308
|
+
status.change += adding ? 1 : -1;
|
|
309
|
+
status.mutationRecord = mutationRecord;
|
|
310
|
+
} else {
|
|
311
|
+
map.set(target, {
|
|
312
|
+
change: adding ? 1 : -1,
|
|
313
|
+
mutationRecord
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
for (const child of target.children){
|
|
317
|
+
if ('classList' in child) {
|
|
318
|
+
recordStatus(child, mutationRecord, map, adding);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
mutationRecords.forEach((mutationRecord)=>{
|
|
323
|
+
const target = mutationRecord.target;
|
|
324
|
+
switch(mutationRecord.type){
|
|
325
|
+
case 'attributes':
|
|
326
|
+
if (!targetFirstAttrMutationRecord.has(target)) {
|
|
327
|
+
targetFirstAttrMutationRecord.set(target, mutationRecord);
|
|
328
|
+
}
|
|
329
|
+
break;
|
|
330
|
+
case 'childList':
|
|
331
|
+
const targetStatusMap = target.isConnected ? connectedStatusMap : disconnectedStatusMap;
|
|
332
|
+
mutationRecord.addedNodes.forEach((node)=>'classList' in node && recordStatus(node, mutationRecord, targetStatusMap, true));
|
|
333
|
+
mutationRecord.removedNodes.forEach((node)=>'classList' in node && recordStatus(node, mutationRecord, targetStatusMap, false));
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
const updateTarget = (target, adding)=>{
|
|
338
|
+
const firstAttrMutationRecord = targetFirstAttrMutationRecord.get(target);
|
|
339
|
+
if (firstAttrMutationRecord) {
|
|
340
|
+
targetFirstAttrMutationRecord.delete(target);
|
|
341
|
+
}
|
|
342
|
+
if (adding) {
|
|
343
|
+
updateClassCount(target.classList, adding);
|
|
344
|
+
} else {
|
|
345
|
+
if (firstAttrMutationRecord) {
|
|
346
|
+
updateClassCount(firstAttrMutationRecord.oldValue ? firstAttrMutationRecord.oldValue.split(/\s+/) : [], adding);
|
|
347
|
+
} else {
|
|
348
|
+
updateClassCount(target.classList, adding);
|
|
349
|
+
}
|
|
350
|
+
disconnectedStatusMap.forEach((disconnectedTargetStatus, disconnectedTarget)=>{
|
|
351
|
+
if (disconnectedTargetStatus.mutationRecord.target === target && disconnectedTargetStatus.change !== 0) {
|
|
352
|
+
updateTarget(disconnectedTarget, disconnectedTargetStatus.change > 0);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
connectedStatusMap.forEach(({ change }, target)=>change !== 0 && updateTarget(target, change > 0));
|
|
358
|
+
targetFirstAttrMutationRecord.forEach((mutation, target)=>{
|
|
359
|
+
if (!target.isConnected) return;
|
|
360
|
+
const oldClassList = mutation.oldValue ? mutation.oldValue.split(/\s+/) : [];
|
|
361
|
+
const newClassList = target.classList;
|
|
362
|
+
const addedClasses = [];
|
|
363
|
+
newClassList.forEach((c)=>{
|
|
364
|
+
if (!oldClassList.includes(c)) addedClasses.push(c);
|
|
365
|
+
});
|
|
366
|
+
const removedClasses = oldClassList.filter((c)=>!newClassList.contains(c));
|
|
367
|
+
if (addedClasses.length) {
|
|
368
|
+
updateClassCount(addedClasses, true);
|
|
369
|
+
}
|
|
370
|
+
if (removedClasses.length) {
|
|
371
|
+
updateClassCount(removedClasses, false);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
/**
|
|
375
|
+
* Merge the class usage changes into the current class usage map.
|
|
376
|
+
*/ eachClassCounts.forEach((countChange, className)=>{
|
|
377
|
+
let currentCount = this.classCounts.get(className) || 0;
|
|
378
|
+
let newCount = currentCount + countChange;
|
|
379
|
+
if (newCount > 0) {
|
|
380
|
+
this.classCounts.set(className, newCount);
|
|
381
|
+
if (currentCount === 0) {
|
|
382
|
+
this.add(className);
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
this.classCounts.delete(className);
|
|
386
|
+
this.remove(className);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:mutated', {
|
|
390
|
+
records: mutationRecords,
|
|
391
|
+
classCounts: eachClassCounts,
|
|
392
|
+
cssRuntime: this
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
this.observer.observe(this.root, {
|
|
396
|
+
attributes: true,
|
|
397
|
+
attributeOldValue: true,
|
|
398
|
+
attributeFilter: [
|
|
399
|
+
'class'
|
|
400
|
+
],
|
|
401
|
+
childList: true,
|
|
402
|
+
subtree: true
|
|
403
|
+
});
|
|
404
|
+
if (!this.progressive) {
|
|
405
|
+
this.host.removeAttribute('hidden');
|
|
406
|
+
}
|
|
407
|
+
// @ts-ignore
|
|
408
|
+
this.observing = true;
|
|
409
|
+
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:observed', {
|
|
410
|
+
cssRuntime: this
|
|
411
|
+
});
|
|
412
|
+
return this;
|
|
413
|
+
}
|
|
414
|
+
hydrate(nativeLayerRules) {
|
|
415
|
+
const cssLayerRules = [];
|
|
416
|
+
const checkSheet = new CSSStyleSheet();
|
|
417
|
+
const result = {
|
|
418
|
+
allSyntaxRules: []
|
|
419
|
+
};
|
|
420
|
+
for(let i = 0; i < nativeLayerRules.length; i++){
|
|
421
|
+
const eachNativeCSSRule = nativeLayerRules[i];
|
|
422
|
+
if (eachNativeCSSRule.constructor.name === 'CSSLayerBlockRule') {
|
|
423
|
+
const eachCSSLayerRule = eachNativeCSSRule;
|
|
424
|
+
if (eachNativeCSSRule.name === 'theme') {
|
|
425
|
+
this.themeLayer.native = eachCSSLayerRule;
|
|
426
|
+
let variableRule;
|
|
427
|
+
let lastVariableName;
|
|
428
|
+
for(let j = 0; j < eachCSSLayerRule.cssRules.length; j++){
|
|
429
|
+
const cssRule = eachCSSLayerRule.cssRules[j];
|
|
430
|
+
const variableCSSRule = cssRule.constructor.name === 'CSSMediaRule' ? cssRule.cssRules[0] : cssRule;
|
|
431
|
+
const variableName = variableCSSRule.style[0].slice(2);
|
|
432
|
+
if (variableName !== lastVariableName) {
|
|
433
|
+
lastVariableName = variableName;
|
|
434
|
+
variableRule = new Rule(variableName);
|
|
435
|
+
this.themeLayer.rules.push(variableRule);
|
|
436
|
+
this.themeLayer.tokenCounts.set(variableRule.name, 0);
|
|
437
|
+
}
|
|
438
|
+
variableRule?.nodes.push({
|
|
439
|
+
native: cssRule,
|
|
440
|
+
text: cssRule.cssText
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
if (this.themeLayer.rules.length) this.rules.push(this.themeLayer);
|
|
444
|
+
} else {
|
|
445
|
+
cssLayerRules.push(eachCSSLayerRule);
|
|
446
|
+
}
|
|
447
|
+
} else if (eachNativeCSSRule.constructor.name === 'CSSLayerStatementRule') {
|
|
448
|
+
this.layerStatementRule.nodes[0].native = eachNativeCSSRule;
|
|
449
|
+
} else if (eachNativeCSSRule.constructor.name === 'CSSKeyframesRule') {
|
|
450
|
+
const keyframsRule = eachNativeCSSRule;
|
|
451
|
+
const animationRule = new Rule(keyframsRule.name, [
|
|
452
|
+
{
|
|
453
|
+
native: keyframsRule,
|
|
454
|
+
text: keyframsRule.cssText
|
|
455
|
+
}
|
|
456
|
+
]);
|
|
457
|
+
this.animationsNonLayer.rules.push(animationRule);
|
|
458
|
+
this.rules.push(animationRule);
|
|
459
|
+
this.animationsNonLayer.tokenCounts.set(animationRule.name, 0);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
for (const eachCSSLayerRule of cssLayerRules){
|
|
463
|
+
let layer;
|
|
464
|
+
switch(eachCSSLayerRule.name){
|
|
465
|
+
case 'base':
|
|
466
|
+
layer = this.baseLayer;
|
|
467
|
+
break;
|
|
468
|
+
case 'preset':
|
|
469
|
+
layer = this.presetLayer;
|
|
470
|
+
break;
|
|
471
|
+
case 'components':
|
|
472
|
+
layer = this.componentsLayer;
|
|
473
|
+
break;
|
|
474
|
+
case 'general':
|
|
475
|
+
layer = this.generalLayer;
|
|
476
|
+
break;
|
|
477
|
+
default:
|
|
478
|
+
console.error(`Cannot recognize the layer \`${eachCSSLayerRule.name}\`. (https://rc.css.master.co/messages/hydration-errors)`);
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
layer.native = eachCSSLayerRule;
|
|
482
|
+
const unresolvedCSSRules = new Map();
|
|
483
|
+
for (const rule of eachCSSLayerRule.cssRules){
|
|
484
|
+
// trim() for fix the firefox bug that the cssText ends with \n\n
|
|
485
|
+
unresolvedCSSRules.set(rule.cssText.trim(), rule);
|
|
486
|
+
}
|
|
487
|
+
for (const eachNativeLayerRule of eachCSSLayerRule.cssRules){
|
|
488
|
+
if (!unresolvedCSSRules.has(eachNativeLayerRule.cssText)) continue;
|
|
489
|
+
const selectorText = this.getSelectorText(eachNativeLayerRule);
|
|
490
|
+
if (!selectorText) {
|
|
491
|
+
console.error(`Cannot get the selector text from \`${eachNativeLayerRule.cssText}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
const createdRules = this.createFromSelectorText(selectorText);
|
|
495
|
+
if (createdRules) {
|
|
496
|
+
for (const createdRule of createdRules){
|
|
497
|
+
layer.rules.push(createdRule);
|
|
498
|
+
layer.insertVariables(createdRule);
|
|
499
|
+
layer.insertAnimations(createdRule);
|
|
500
|
+
result.allSyntaxRules.push(createdRule);
|
|
501
|
+
createdRule.nodes.forEach((node)=>{
|
|
502
|
+
try {
|
|
503
|
+
const checkRuleIndex = checkSheet.insertRule(node.text);
|
|
504
|
+
const checkNodeNativeRule = checkSheet.cssRules.item(checkRuleIndex);
|
|
505
|
+
if (checkNodeNativeRule) {
|
|
506
|
+
const checkNodeNativeRuleText = checkNodeNativeRule.cssText.trim();
|
|
507
|
+
const match = unresolvedCSSRules.get(checkNodeNativeRuleText);
|
|
508
|
+
if (match) {
|
|
509
|
+
node.native = match;
|
|
510
|
+
unresolvedCSSRules.delete(checkNodeNativeRuleText);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
console.error(`Cannot retrieve CSS rule for \`${node.text}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
|
|
515
|
+
} catch (error) {
|
|
516
|
+
{
|
|
517
|
+
console.debug(`Cannot insert CSS rule for \`${node.text}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
} else {
|
|
523
|
+
console.error(`Cannot recognize \`${eachNativeLayerRule.cssText}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (layer.rules.length) this.rules.push(layer);
|
|
527
|
+
}
|
|
528
|
+
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:hydrated', {
|
|
529
|
+
cssRuntime: this,
|
|
530
|
+
result
|
|
531
|
+
});
|
|
532
|
+
return result;
|
|
533
|
+
}
|
|
534
|
+
getSelectorText(cssRule) {
|
|
535
|
+
if (cssRule instanceof CSSStyleRule) {
|
|
536
|
+
return cssRule.selectorText;
|
|
537
|
+
} else if (cssRule instanceof CSSGroupingRule) {
|
|
538
|
+
return this.getSelectorText(cssRule.cssRules.item(0));
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
disconnect() {
|
|
542
|
+
if (!this.observing) return;
|
|
543
|
+
if (this.observer) {
|
|
544
|
+
this.observer.disconnect();
|
|
545
|
+
// @ts-expect-error readonly
|
|
546
|
+
this.observer = undefined;
|
|
547
|
+
}
|
|
548
|
+
// @ts-ignore
|
|
549
|
+
this.observing = false;
|
|
550
|
+
this.reset();
|
|
551
|
+
this.classCounts.clear();
|
|
552
|
+
if (!this.progressive) {
|
|
553
|
+
this.style?.remove();
|
|
554
|
+
this.style = null;
|
|
555
|
+
}
|
|
556
|
+
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:disconnected', {
|
|
557
|
+
cssRuntime: this
|
|
558
|
+
});
|
|
559
|
+
return this;
|
|
560
|
+
}
|
|
561
|
+
refresh(customConfig = this.customConfig) {
|
|
562
|
+
if (!this.observing || !this.style.sheet) return this;
|
|
563
|
+
for(let i = 1; i <= this.style.sheet.cssRules.length - 1; i++){
|
|
564
|
+
this.style.sheet.deleteRule(i);
|
|
565
|
+
}
|
|
566
|
+
super.refresh(customConfig);
|
|
567
|
+
/**
|
|
568
|
+
* 拿當前所有的 classNames 按照最新的 colors, config.rules 匹配並生成新的 style
|
|
569
|
+
* 所以 refresh 過後 rules 可能會變多也可能會變少
|
|
570
|
+
*/ this.classCounts.forEach((_, className)=>{
|
|
571
|
+
this.add(className);
|
|
572
|
+
});
|
|
573
|
+
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:refreshed', {
|
|
574
|
+
cssRuntime: this,
|
|
575
|
+
customConfig
|
|
576
|
+
});
|
|
577
|
+
return this;
|
|
578
|
+
}
|
|
579
|
+
destroy() {
|
|
580
|
+
this.disconnect();
|
|
581
|
+
globalThis.CSSRuntime.instances.delete(this.root);
|
|
582
|
+
globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:destroyed', {
|
|
583
|
+
cssRuntime: this
|
|
584
|
+
});
|
|
585
|
+
return this;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
(function(CSSRuntime) {
|
|
589
|
+
registerGlobal(CSSRuntime);
|
|
590
|
+
})(CSSRuntime);
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Initialize a new CSSRuntime instance and observe the target root
|
|
594
|
+
* @param config master css config
|
|
595
|
+
* @param root target root to observe
|
|
596
|
+
* @param autoObserve auto observe the target root
|
|
597
|
+
* @returns master css instance
|
|
598
|
+
*/ function initCSSRuntime(config, root = document, autoObserve = true) {
|
|
599
|
+
let cssRuntime = globalThis.CSSRuntime.instances.get(root);
|
|
600
|
+
if (cssRuntime) return cssRuntime;
|
|
601
|
+
cssRuntime = new CSSRuntime(root, config);
|
|
602
|
+
if (autoObserve) cssRuntime.observe();
|
|
603
|
+
return cssRuntime;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export { CSSRuntime, RuntimeSyntaxLayer, initCSSRuntime };
|