@master/css-runtime 2.0.0-rc.48 → 2.0.0-rc.49
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/dist/core.d.ts +2 -2
- package/dist/global.min.js +1 -1
- package/dist/index.cjs +141 -148
- package/dist/index.mjs +141 -148
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -32,10 +32,7 @@ class RuntimeCSS extends css.MasterCSS {
|
|
|
32
32
|
* Observe the DOM for changes and update the running stylesheet. (browser only)
|
|
33
33
|
* @param options mutation observer options
|
|
34
34
|
* @returns this
|
|
35
|
-
*/ observe(
|
|
36
|
-
subtree: true,
|
|
37
|
-
childList: true
|
|
38
|
-
}) {
|
|
35
|
+
*/ observe() {
|
|
39
36
|
if (this.observing) return this;
|
|
40
37
|
if (this.root.styleSheets) for (const sheet of this.root.styleSheets){
|
|
41
38
|
const { ownerNode } = sheet;
|
|
@@ -55,164 +52,159 @@ class RuntimeCSS extends css.MasterCSS {
|
|
|
55
52
|
this.style.sheet.insertRule(this.layerStatementRule.text);
|
|
56
53
|
this.layerStatementRule.nodes[0].native = this.style.sheet.cssRules.item(0);
|
|
57
54
|
}
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
const firstConnectedElementsClasses = new Map();
|
|
56
|
+
const addClasses = (element)=>{
|
|
57
|
+
element.classList.forEach((className)=>{
|
|
58
|
+
if (this.classUsages.has(className)) {
|
|
59
|
+
this.classUsages.set(className, this.classUsages.get(className) + 1);
|
|
62
60
|
} else {
|
|
63
|
-
this.
|
|
61
|
+
this.classUsages.set(className, 1);
|
|
64
62
|
this.add(className);
|
|
65
63
|
}
|
|
64
|
+
const classes = firstConnectedElementsClasses.get(element);
|
|
65
|
+
if (classes) {
|
|
66
|
+
classes.push(className);
|
|
67
|
+
} else {
|
|
68
|
+
firstConnectedElementsClasses.set(element, [
|
|
69
|
+
className
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
66
72
|
});
|
|
67
73
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
+
addClasses(this.host);
|
|
75
|
+
/**
|
|
76
|
+
* 待所有 DOM 結構完成解析後,開始繪製 Rule 樣式
|
|
77
|
+
*/ (this.root.constructor.name === 'HTMLDocument' ? this.host : this.container).querySelectorAll('[class]').forEach((element)=>{
|
|
78
|
+
addClasses(element);
|
|
79
|
+
});
|
|
80
|
+
// @ts-expect-error readonly
|
|
74
81
|
this.observer = new MutationObserver((mutationRecords)=>{
|
|
75
|
-
// console.
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
for (const eachChildren of children){
|
|
90
|
-
if (eachChildren.classList && !updatedElements.includes(eachChildren)) {
|
|
91
|
-
updatedElements.push(eachChildren);
|
|
92
|
-
handleClassNameDeeply(eachChildren, remove);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
82
|
+
// console.clear()
|
|
83
|
+
// console.log('-----------')
|
|
84
|
+
// const test = 'swiper-slide-next'
|
|
85
|
+
// console.log(`${test}: ${this.classUsages.get(test)}`)
|
|
86
|
+
const eachClassUsages = new Map();
|
|
87
|
+
const updatedAttrElements = new Set();
|
|
88
|
+
const updatedConnectedElements = new Set();
|
|
89
|
+
const updatedElements = new Set();
|
|
90
|
+
const updateClassUsage = (classes, isAdding = false)=>{
|
|
91
|
+
const usage = isAdding ? 1 : -1;
|
|
92
|
+
classes.forEach((className)=>{
|
|
93
|
+
// if (className === test) console.log(` ${isAdding ? '+' : '-'} ${className} ${(eachClassUsages.get(className) || 0) + usage}`, target)
|
|
94
|
+
eachClassUsages.set(className, (eachClassUsages.get(className) || 0) + usage);
|
|
95
|
+
});
|
|
95
96
|
};
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
} else {
|
|
100
|
-
correctionOfClassName[className] = 1;
|
|
97
|
+
const updateElementTree = (element, adding)=>{
|
|
98
|
+
if (element.isConnected) {
|
|
99
|
+
updatedConnectedElements.add(element);
|
|
101
100
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
} else if (Object.prototype.hasOwnProperty.call(this.classesUsage, className)) {
|
|
107
|
-
correctionOfClassName[className] = -1;
|
|
101
|
+
updatedElements.add(element);
|
|
102
|
+
updateClassUsage(element.classList, adding);
|
|
103
|
+
for (const child of element.children){
|
|
104
|
+
updateElementTree(child, adding);
|
|
108
105
|
}
|
|
109
106
|
};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
107
|
+
// console.log('///')
|
|
108
|
+
mutationRecords.forEach((mutation)=>{
|
|
109
|
+
const target = mutation.target;
|
|
110
|
+
switch(mutation.type){
|
|
111
|
+
case 'attributes':
|
|
112
|
+
const oldClassList = mutation.oldValue ? mutation.oldValue.split(/\s+/) : [];
|
|
113
|
+
const newClassList = target.classList;
|
|
114
|
+
newClassList.forEach((c)=>{
|
|
115
|
+
if (!oldClassList.includes(c)) ;
|
|
116
|
+
});
|
|
117
|
+
break;
|
|
120
118
|
}
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
* According to this history,
|
|
128
|
-
* MutationObserver was designed to work that way.
|
|
129
|
-
* Any call to setAttribute triggers a mutation,
|
|
130
|
-
* regardless of whether the value is being changed or set to the current value
|
|
131
|
-
*/ if (attributeMutationRecords.find((eachAttributeMutationRecord)=>eachAttributeMutationRecord.target === target)) {
|
|
132
|
-
continue;
|
|
133
|
-
} else {
|
|
119
|
+
});
|
|
120
|
+
// console.log('///')
|
|
121
|
+
mutationRecords.forEach((mutation)=>{
|
|
122
|
+
const target = mutation.target;
|
|
123
|
+
switch(mutation.type){
|
|
124
|
+
case 'attributes':
|
|
134
125
|
/**
|
|
135
|
-
*
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
126
|
+
* We only need to determine the first attribute record of the same target,
|
|
127
|
+
* because the first record has the original old class name.
|
|
128
|
+
*
|
|
129
|
+
* The target is skipped if it is contained in the unhandled childList.
|
|
130
|
+
*/ if (updatedAttrElements.has(target) || updatedElements.has(target)) return;
|
|
131
|
+
updatedAttrElements.add(target);
|
|
132
|
+
const oldClassList = mutation.oldValue ? mutation.oldValue.split(/\s+/) : [];
|
|
133
|
+
const newClassList = target.classList;
|
|
134
|
+
const addedClasses = [];
|
|
135
|
+
newClassList.forEach((c)=>{
|
|
136
|
+
if (!oldClassList.includes(c)) addedClasses.push(c);
|
|
137
|
+
});
|
|
138
|
+
const removedClasses = oldClassList.filter((c)=>!newClassList.contains(c));
|
|
139
|
+
// if (addedClasses.length) console.log('[attribute]', '[add]', addedClasses, target)
|
|
140
|
+
if (addedClasses.length) updateClassUsage(addedClasses, true);
|
|
141
|
+
// if (removedClasses.length) console.log('[attribute]', '[remove]', removedClasses, target)
|
|
142
|
+
if (removedClasses.length) updateClassUsage(removedClasses, false);
|
|
143
|
+
break;
|
|
144
|
+
case 'childList':
|
|
145
|
+
if (updatedConnectedElements.has(target)) return;
|
|
146
|
+
// if (mutation.addedNodes.length) console.log('[childList]', '[add]', mutation.addedNodes)
|
|
147
|
+
mutation.addedNodes.forEach((node)=>'classList' in node && updateElementTree(node, true));
|
|
148
|
+
// if (mutation.removedNodes.length) console.log('[childList]', '[remove]', mutation.removedNodes)
|
|
149
|
+
mutation.removedNodes.forEach((node)=>'classList' in node && updateElementTree(node, false));
|
|
150
|
+
break;
|
|
145
151
|
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
*
|
|
157
|
-
* 該批 mutationRecords 中,某個 target 同時有 attribute 及 childList 的變更,
|
|
158
|
-
* 則以 childList 節點插入及移除的 target.className 為主
|
|
159
|
-
*/ const updated = updatedElements.includes(target);
|
|
160
|
-
const classNames = target.classList;
|
|
161
|
-
const oldClassNames = oldValue ? oldValue.split(' ') : [];
|
|
162
|
-
if (updated) {
|
|
163
|
-
if (target.isConnected) {
|
|
164
|
-
continue;
|
|
165
|
-
} else {
|
|
166
|
-
for (const oldClassName of oldClassNames){
|
|
167
|
-
if (!classNames.contains(oldClassName)) {
|
|
168
|
-
removeClassName(oldClassName);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
} else if (target.isConnected) {
|
|
173
|
-
classNames.forEach((className)=>{
|
|
174
|
-
if (!oldClassNames.includes(className)) {
|
|
175
|
-
addClassName(className);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
for (const oldClassName of oldClassNames){
|
|
179
|
-
if (!classNames.contains(oldClassName)) {
|
|
180
|
-
removeClassName(oldClassName);
|
|
181
|
-
}
|
|
152
|
+
});
|
|
153
|
+
/**
|
|
154
|
+
* Merge the class usage changes into the current class usage map.
|
|
155
|
+
*/ eachClassUsages.forEach((countChange, className)=>{
|
|
156
|
+
let currentCount = this.classUsages.get(className) || 0;
|
|
157
|
+
let newCount = currentCount + countChange;
|
|
158
|
+
if (newCount > 0) {
|
|
159
|
+
this.classUsages.set(className, newCount);
|
|
160
|
+
if (currentCount === 0) {
|
|
161
|
+
this.add(className);
|
|
182
162
|
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
for(const className in correctionOfClassName){
|
|
186
|
-
const correction = correctionOfClassName[className];
|
|
187
|
-
const count = (this.classesUsage[className] || 0) + correction;
|
|
188
|
-
if (count === 0) {
|
|
189
|
-
// remove
|
|
190
|
-
delete this.classesUsage[className];
|
|
191
|
-
/**
|
|
192
|
-
* class name 從 DOM tree 中被移除,
|
|
193
|
-
* 匹配並刪除對應的 rule
|
|
194
|
-
*/ this.remove(className);
|
|
195
163
|
} else {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* 新 class name 被 connected 至 DOM tree,
|
|
200
|
-
* 匹配並創建對應的 Rule
|
|
201
|
-
*/ this.add(className);
|
|
202
|
-
}
|
|
203
|
-
this.classesUsage[className] = count;
|
|
164
|
+
this.classUsages.delete(className);
|
|
165
|
+
this.remove(className);
|
|
204
166
|
}
|
|
205
|
-
}
|
|
167
|
+
});
|
|
168
|
+
// start: debug
|
|
169
|
+
// const safeClassUsages: any = {};
|
|
170
|
+
// ((this.root.constructor.name === 'HTMLDocument') ? this.host : this.container)
|
|
171
|
+
// .querySelectorAll('[class]')
|
|
172
|
+
// .forEach((element) => {
|
|
173
|
+
// element.classList.forEach((className) => {
|
|
174
|
+
// if (Object.prototype.hasOwnProperty.call(safeClassUsages, className)) {
|
|
175
|
+
// safeClassUsages[className]++
|
|
176
|
+
// } else {
|
|
177
|
+
// safeClassUsages[className] = 1
|
|
178
|
+
// }
|
|
179
|
+
// })
|
|
180
|
+
// })
|
|
181
|
+
// this.host.classList.forEach((className) => {
|
|
182
|
+
// if (Object.prototype.hasOwnProperty.call(safeClassUsages, className)) {
|
|
183
|
+
// safeClassUsages[className]++
|
|
184
|
+
// } else {
|
|
185
|
+
// safeClassUsages[className] = 1
|
|
186
|
+
// }
|
|
187
|
+
// })
|
|
188
|
+
// // 與 this.classUsages 比較並且打印不同的部分
|
|
189
|
+
// for (const className in safeClassUsages) {
|
|
190
|
+
// if (this.classUsages.get(className) !== safeClassUsages[className]) {
|
|
191
|
+
// throw new Error(`[css] ${className} ${this.classUsages.get(className)} (correct: ${safeClassUsages[className]})`)
|
|
192
|
+
// }
|
|
193
|
+
// }
|
|
194
|
+
// end: debug
|
|
206
195
|
});
|
|
207
196
|
this.observer.observe(this.root, {
|
|
208
|
-
...options,
|
|
209
197
|
attributes: true,
|
|
210
198
|
attributeOldValue: true,
|
|
211
199
|
attributeFilter: [
|
|
212
200
|
'class'
|
|
213
|
-
]
|
|
201
|
+
],
|
|
202
|
+
childList: true,
|
|
203
|
+
subtree: true
|
|
214
204
|
});
|
|
215
|
-
this.
|
|
205
|
+
if (!this.progressive) {
|
|
206
|
+
this.host.style.removeProperty('display');
|
|
207
|
+
}
|
|
216
208
|
// @ts-ignore
|
|
217
209
|
this.observing = true;
|
|
218
210
|
return this;
|
|
@@ -307,7 +299,7 @@ class RuntimeCSS extends css.MasterCSS {
|
|
|
307
299
|
handleSyntaxLayer(this.presetLayer);
|
|
308
300
|
break;
|
|
309
301
|
case 'components':
|
|
310
|
-
this.
|
|
302
|
+
this.componentsLayer.native = cssLayerBlockRule;
|
|
311
303
|
let stylePreText;
|
|
312
304
|
const createSyntaxRules = (cssRule)=>{
|
|
313
305
|
if (cssRule.selectorText) {
|
|
@@ -332,10 +324,10 @@ class RuntimeCSS extends css.MasterCSS {
|
|
|
332
324
|
for (const node of eachSyntaxRule.nodes){
|
|
333
325
|
if (!node.native && node.text.includes(stylePreText)) {
|
|
334
326
|
node.native = cssRule;
|
|
335
|
-
if (!this.
|
|
336
|
-
this.
|
|
337
|
-
this.
|
|
338
|
-
this.
|
|
327
|
+
if (!this.componentsLayer.rules.includes(eachSyntaxRule)) {
|
|
328
|
+
this.componentsLayer.rules.push(eachSyntaxRule);
|
|
329
|
+
this.componentsLayer.insertVariables(eachSyntaxRule);
|
|
330
|
+
this.componentsLayer.insertAnimations(eachSyntaxRule);
|
|
339
331
|
}
|
|
340
332
|
matched = true;
|
|
341
333
|
break;
|
|
@@ -348,14 +340,14 @@ class RuntimeCSS extends css.MasterCSS {
|
|
|
348
340
|
console.error(`Cannot recognize the CSS rule in the components layer. \`${cssRule.cssText}\` (https://rc.css.master.co/messages/hydration-errors)`);
|
|
349
341
|
}
|
|
350
342
|
}
|
|
351
|
-
for (const eachRule of this.
|
|
343
|
+
for (const eachRule of this.componentsLayer.rules){
|
|
352
344
|
for(let k = eachRule.nodes.length - 1; k >= 0; k--){
|
|
353
345
|
if (!eachRule.nodes[k].native) {
|
|
354
346
|
eachRule.nodes.splice(k, 1);
|
|
355
347
|
}
|
|
356
348
|
}
|
|
357
349
|
}
|
|
358
|
-
if (this.
|
|
350
|
+
if (this.componentsLayer.rules.length) this.rules.push(this.componentsLayer);
|
|
359
351
|
break;
|
|
360
352
|
case 'general':
|
|
361
353
|
handleSyntaxLayer(this.generalLayer);
|
|
@@ -367,13 +359,14 @@ class RuntimeCSS extends css.MasterCSS {
|
|
|
367
359
|
if (!this.observing) return;
|
|
368
360
|
if (this.observer) {
|
|
369
361
|
this.observer.disconnect();
|
|
370
|
-
|
|
362
|
+
// @ts-expect-error readonly
|
|
363
|
+
this.observer = undefined;
|
|
371
364
|
}
|
|
372
365
|
// @ts-ignore
|
|
373
366
|
this.observing = false;
|
|
374
367
|
this.reset();
|
|
375
368
|
// @ts-expect-error
|
|
376
|
-
this.
|
|
369
|
+
this.classUsages = {};
|
|
377
370
|
if (!this.progressive) {
|
|
378
371
|
this.style?.remove();
|
|
379
372
|
this.style = null;
|