@master/css-runtime 2.0.0-rc.4 → 2.0.0-rc.40
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 +3 -2
- package/dist/global.min.js +1 -0
- package/dist/index.cjs +399 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +393 -0
- package/dist/init-css-runtime.d.ts +3 -3
- package/dist/types/global.d.ts +4 -4
- 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/{iife.d.ts → global.min.d.ts} +0 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var css = require('@master/css');
|
|
6
|
+
|
|
7
|
+
class RuntimeCSS extends css.MasterCSS {
|
|
8
|
+
root;
|
|
9
|
+
customConfig;
|
|
10
|
+
host;
|
|
11
|
+
observing;
|
|
12
|
+
progressive;
|
|
13
|
+
container;
|
|
14
|
+
observer;
|
|
15
|
+
constructor(root = document, customConfig = css.config){
|
|
16
|
+
super(customConfig);
|
|
17
|
+
this.root = root;
|
|
18
|
+
this.customConfig = customConfig;
|
|
19
|
+
this.observing = false;
|
|
20
|
+
this.progressive = false;
|
|
21
|
+
if (!root) this.root = document;
|
|
22
|
+
if (this.root?.constructor.name === 'HTMLDocument') {
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
this.root.defaultView.globalThis.runtimeCSS = this;
|
|
25
|
+
this.container = this.root.head;
|
|
26
|
+
this.host = this.root.documentElement;
|
|
27
|
+
} else {
|
|
28
|
+
this.container = this.root;
|
|
29
|
+
this.host = this.root.host;
|
|
30
|
+
}
|
|
31
|
+
runtimeCSSs.push(this);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Observe the DOM for changes and update the running stylesheet. (browser only)
|
|
35
|
+
* @param options mutation observer options
|
|
36
|
+
* @returns this
|
|
37
|
+
*/ observe(options = {
|
|
38
|
+
subtree: true,
|
|
39
|
+
childList: true
|
|
40
|
+
}) {
|
|
41
|
+
if (this.observing || !this.root) return this;
|
|
42
|
+
if (runtimeCSSs.find((eachRuntimeCSS)=>eachRuntimeCSS !== this && eachRuntimeCSS.root === this.root)) {
|
|
43
|
+
console.warn('Cannot observe the same root element repeatedly.');
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
if (this.root?.styleSheets) for (const sheet of this.root.styleSheets){
|
|
47
|
+
const { ownerNode } = sheet;
|
|
48
|
+
if (ownerNode && ownerNode.id === 'master') {
|
|
49
|
+
// @ts-ignore
|
|
50
|
+
this.style = ownerNode;
|
|
51
|
+
// @ts-ignore
|
|
52
|
+
this.progressive = true;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (this.style?.sheet) {
|
|
57
|
+
let index = 0;
|
|
58
|
+
for(; index < this.style.sheet.cssRules.length; index++){
|
|
59
|
+
const eachCSSRule = this.style.sheet.cssRules[index];
|
|
60
|
+
switch(eachCSSRule.constructor.name){
|
|
61
|
+
case 'CSSKeyframesRule':
|
|
62
|
+
continue;
|
|
63
|
+
case 'CSSMeidaRule':
|
|
64
|
+
const result = /\(prefers-color-scheme: (.*?)\)/.exec(eachCSSRule.conditionText);
|
|
65
|
+
if (result) {
|
|
66
|
+
const firstCSSRule = eachCSSRule.cssRules[0];
|
|
67
|
+
if (firstCSSRule?.constructor.name === 'CSSStyleRule' && firstCSSRule.selectorText === ':root') {
|
|
68
|
+
this.pushVariableNativeRule(result[1], firstCSSRule);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
case 'CSSStyleRule':
|
|
74
|
+
// eslint-disable-next-line no-case-declarations
|
|
75
|
+
const selectorText = eachCSSRule.selectorText;
|
|
76
|
+
if (eachCSSRule.style.length) {
|
|
77
|
+
let isVariablesRule = true;
|
|
78
|
+
for(let i = 0; i < eachCSSRule.style.length; i++){
|
|
79
|
+
if (!eachCSSRule.style[i]?.startsWith('--')) {
|
|
80
|
+
isVariablesRule = false;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (isVariablesRule) {
|
|
85
|
+
if (selectorText === ':root') {
|
|
86
|
+
this.pushVariableNativeRule('', eachCSSRule);
|
|
87
|
+
continue;
|
|
88
|
+
} else {
|
|
89
|
+
const result = /:host(.*?)/.exec(selectorText);
|
|
90
|
+
if (result) {
|
|
91
|
+
this.pushVariableNativeRule(result[1], eachCSSRule);
|
|
92
|
+
continue;
|
|
93
|
+
} else if (!selectorText.startsWith('.\\$')) {
|
|
94
|
+
this.pushVariableNativeRule(selectorText.slice(1), eachCSSRule);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
for(; index < this.style.sheet.cssRules.length; index++){
|
|
105
|
+
const getRule = (cssRule)=>{
|
|
106
|
+
if (cssRule.selectorText) {
|
|
107
|
+
const selectorTexts = cssRule.selectorText.split(', ');
|
|
108
|
+
const escapedClassNames = selectorTexts[0].split(' ');
|
|
109
|
+
for(let i = 0; i < escapedClassNames.length; i++){
|
|
110
|
+
const eachSelectorText = escapedClassNames[i];
|
|
111
|
+
if (eachSelectorText[0] === '.') {
|
|
112
|
+
const escapedClassName = eachSelectorText.slice(1);
|
|
113
|
+
let className = '';
|
|
114
|
+
for(let j = 0; j < escapedClassName.length; j++){
|
|
115
|
+
const char = escapedClassName[j];
|
|
116
|
+
const nextChar = escapedClassName[j + 1];
|
|
117
|
+
if (char === '\\') {
|
|
118
|
+
j++;
|
|
119
|
+
if (nextChar !== '\\') {
|
|
120
|
+
className += nextChar;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
} else if ([
|
|
124
|
+
',',
|
|
125
|
+
'.',
|
|
126
|
+
'#',
|
|
127
|
+
'[',
|
|
128
|
+
'!',
|
|
129
|
+
'*',
|
|
130
|
+
'>',
|
|
131
|
+
'+',
|
|
132
|
+
'~',
|
|
133
|
+
':',
|
|
134
|
+
'@'
|
|
135
|
+
].includes(char)) {
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
className += char;
|
|
139
|
+
}
|
|
140
|
+
if (!Object.prototype.hasOwnProperty.call(this.ruleBy, className) && !Object.prototype.hasOwnProperty.call(this.styles, className)) {
|
|
141
|
+
const currentRule = this.generate(className)[0];
|
|
142
|
+
if (currentRule) return currentRule;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else if (cssRule.cssRules) {
|
|
147
|
+
for(let index = 0; index < cssRule.cssRules.length; index++){
|
|
148
|
+
const currentRule = getRule(cssRule.cssRules[index]);
|
|
149
|
+
if (currentRule) return currentRule;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const rule = getRule(this.style.sheet.cssRules[index]);
|
|
154
|
+
if (rule) {
|
|
155
|
+
this.rules.push(rule);
|
|
156
|
+
this.ruleBy[rule.className] = rule;
|
|
157
|
+
for(let i = 0; i < rule.natives.length; i++){
|
|
158
|
+
rule.natives[i].cssRule = this.style.sheet.cssRules[index + i];
|
|
159
|
+
}
|
|
160
|
+
index += rule.natives.length - 1;
|
|
161
|
+
// variables
|
|
162
|
+
this.handleRuleWithVariableNames(rule, true);
|
|
163
|
+
// animations
|
|
164
|
+
this.handleRuleWithAnimationNames(rule, true);
|
|
165
|
+
rule.definition.insert?.call(rule);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
// @ts-ignore
|
|
170
|
+
this.style = document.createElement('style');
|
|
171
|
+
this.style.id = 'master';
|
|
172
|
+
this.container.append(this.style);
|
|
173
|
+
}
|
|
174
|
+
const handleClassList = (classList)=>{
|
|
175
|
+
classList.forEach((className)=>{
|
|
176
|
+
if (Object.prototype.hasOwnProperty.call(this.classesUsage, className)) {
|
|
177
|
+
this.classesUsage[className]++;
|
|
178
|
+
} else {
|
|
179
|
+
this.classesUsage[className] = 1;
|
|
180
|
+
this.add(className);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
handleClassList(this.host.classList);
|
|
185
|
+
if (options.subtree) {
|
|
186
|
+
/**
|
|
187
|
+
* 待所有 DOM 結構完成解析後,開始繪製 Rule 樣式
|
|
188
|
+
*/ (this.root.constructor.name === 'HTMLDocument' ? this.host : this.container).querySelectorAll('[class]').forEach((element)=>handleClassList(element.classList));
|
|
189
|
+
}
|
|
190
|
+
this.observer = new MutationObserver((mutationRecords)=>{
|
|
191
|
+
// console.time('css engine');
|
|
192
|
+
const correctionOfClassName = {};
|
|
193
|
+
const attributeMutationRecords = [];
|
|
194
|
+
const updatedElements = [];
|
|
195
|
+
const unchangedElements = [];
|
|
196
|
+
/**
|
|
197
|
+
* 取得所有深層後代的 class names
|
|
198
|
+
*/ const handleClassNameDeeply = (element, remove)=>{
|
|
199
|
+
if (remove) {
|
|
200
|
+
element.classList.forEach(removeClassName);
|
|
201
|
+
} else {
|
|
202
|
+
element.classList.forEach(addClassName);
|
|
203
|
+
}
|
|
204
|
+
const children = element.children;
|
|
205
|
+
for(let i = 0; i < children.length; i++){
|
|
206
|
+
const eachChildren = children[i];
|
|
207
|
+
if (eachChildren.classList && !updatedElements.includes(eachChildren)) {
|
|
208
|
+
updatedElements.push(eachChildren);
|
|
209
|
+
handleClassNameDeeply(eachChildren, remove);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const addClassName = (className)=>{
|
|
214
|
+
if (Object.prototype.hasOwnProperty.call(correctionOfClassName, className)) {
|
|
215
|
+
correctionOfClassName[className]++;
|
|
216
|
+
} else {
|
|
217
|
+
correctionOfClassName[className] = 1;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const removeClassName = (className)=>{
|
|
221
|
+
if (Object.prototype.hasOwnProperty.call(correctionOfClassName, className)) {
|
|
222
|
+
correctionOfClassName[className]--;
|
|
223
|
+
} else if (Object.prototype.hasOwnProperty.call(this.classesUsage, className)) {
|
|
224
|
+
correctionOfClassName[className] = -1;
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
const handleNodes = (nodes, remove)=>{
|
|
228
|
+
for(let i = 0; i < nodes.length; i++){
|
|
229
|
+
const eachNode = nodes[i];
|
|
230
|
+
if (eachNode.classList && !updatedElements.includes(eachNode) && !unchangedElements.includes(eachNode)) {
|
|
231
|
+
if (eachNode.isConnected !== remove) {
|
|
232
|
+
updatedElements.push(eachNode);
|
|
233
|
+
handleClassNameDeeply(eachNode, remove);
|
|
234
|
+
} else {
|
|
235
|
+
unchangedElements.push(eachNode);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
for(let i = 0; i < mutationRecords.length; i++){
|
|
241
|
+
const mutationRecord = mutationRecords[i];
|
|
242
|
+
const { addedNodes, removedNodes, type, target } = mutationRecord;
|
|
243
|
+
if (type === 'attributes') {
|
|
244
|
+
/**
|
|
245
|
+
* 防止同樣的 MutationRecord 重複執行
|
|
246
|
+
* According to this history,
|
|
247
|
+
* MutationObserver was designed to work that way.
|
|
248
|
+
* Any call to setAttribute triggers a mutation,
|
|
249
|
+
* regardless of whether the value is being changed or set to the current value
|
|
250
|
+
*/ if (attributeMutationRecords.find((eachAttributeMutationRecord)=>eachAttributeMutationRecord.target === target)) {
|
|
251
|
+
continue;
|
|
252
|
+
} else {
|
|
253
|
+
/**
|
|
254
|
+
* 第一個匹配到的 oldValue 一定是該批變動前的原始狀態值
|
|
255
|
+
*/ attributeMutationRecords.push(mutationRecord);
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
// 先判斷節點新增或移除
|
|
259
|
+
handleNodes(addedNodes, false);
|
|
260
|
+
// 忽略處理新元素的已刪除子節點
|
|
261
|
+
if (!target.isConnected || !updatedElements.includes(target)) {
|
|
262
|
+
handleNodes(removedNodes, true);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (!attributeMutationRecords.length && !Object.keys(correctionOfClassName).length) {
|
|
267
|
+
// console.timeEnd('css engine');
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
for (const { oldValue, target } of attributeMutationRecords){
|
|
271
|
+
/**
|
|
272
|
+
* 如果被操作的元素中包含了屬性變更的目標,
|
|
273
|
+
* 則將該目標從 existedAttributeMutationTargets 中移除,
|
|
274
|
+
* 以防止執行接下來的屬性變更處理
|
|
275
|
+
*
|
|
276
|
+
* 該批 mutationRecords 中,某個 target 同時有 attribute 及 childList 的變更,
|
|
277
|
+
* 則以 childList 節點插入及移除的 target.className 為主
|
|
278
|
+
*/ const updated = updatedElements.includes(target);
|
|
279
|
+
const classNames = target.classList;
|
|
280
|
+
const oldClassNames = oldValue ? oldValue.split(' ') : [];
|
|
281
|
+
if (updated) {
|
|
282
|
+
if (target.isConnected) {
|
|
283
|
+
continue;
|
|
284
|
+
} else {
|
|
285
|
+
for (const oldClassName of oldClassNames){
|
|
286
|
+
if (!classNames.contains(oldClassName)) {
|
|
287
|
+
removeClassName(oldClassName);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} else if (target.isConnected) {
|
|
292
|
+
classNames.forEach((className)=>{
|
|
293
|
+
if (!oldClassNames.includes(className)) {
|
|
294
|
+
addClassName(className);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
for (const oldClassName of oldClassNames){
|
|
298
|
+
if (!classNames.contains(oldClassName)) {
|
|
299
|
+
removeClassName(oldClassName);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
for(const className in correctionOfClassName){
|
|
305
|
+
const correction = correctionOfClassName[className];
|
|
306
|
+
const count = (this.classesUsage[className] || 0) + correction;
|
|
307
|
+
if (count === 0) {
|
|
308
|
+
// remove
|
|
309
|
+
delete this.classesUsage[className];
|
|
310
|
+
/**
|
|
311
|
+
* class name 從 DOM tree 中被移除,
|
|
312
|
+
* 匹配並刪除對應的 rule
|
|
313
|
+
*/ this.delete(className);
|
|
314
|
+
} else {
|
|
315
|
+
if (!Object.prototype.hasOwnProperty.call(this.classesUsage, className)) {
|
|
316
|
+
// add
|
|
317
|
+
/**
|
|
318
|
+
* 新 class name 被 connected 至 DOM tree,
|
|
319
|
+
* 匹配並創建對應的 Rule
|
|
320
|
+
*/ this.add(className);
|
|
321
|
+
}
|
|
322
|
+
this.classesUsage[className] = count;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
this.observer.observe(this.root, {
|
|
327
|
+
...options,
|
|
328
|
+
attributes: true,
|
|
329
|
+
attributeOldValue: true,
|
|
330
|
+
attributeFilter: [
|
|
331
|
+
'class'
|
|
332
|
+
]
|
|
333
|
+
});
|
|
334
|
+
this.host.style.removeProperty('display');
|
|
335
|
+
// @ts-ignore
|
|
336
|
+
this.observing = true;
|
|
337
|
+
return this;
|
|
338
|
+
}
|
|
339
|
+
refresh(customConfig = this.customConfig) {
|
|
340
|
+
// remove all CSS rules
|
|
341
|
+
// todo: e2e test
|
|
342
|
+
const sheet = this.style?.sheet;
|
|
343
|
+
if (sheet?.cssRules) {
|
|
344
|
+
for(let i = sheet.cssRules.length - 1; i >= 0; i--){
|
|
345
|
+
sheet.deleteRule(i);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
super.refresh(customConfig);
|
|
349
|
+
return this;
|
|
350
|
+
}
|
|
351
|
+
reset() {
|
|
352
|
+
super.reset();
|
|
353
|
+
const sheet = this.style?.sheet;
|
|
354
|
+
if (sheet?.cssRules) {
|
|
355
|
+
for(let i = sheet.cssRules.length - 1; i >= 0; i--){
|
|
356
|
+
sheet.deleteRule(i);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (!this.progressive) {
|
|
360
|
+
this.style?.remove();
|
|
361
|
+
// @ts-ignore
|
|
362
|
+
this.style = null;
|
|
363
|
+
}
|
|
364
|
+
return this;
|
|
365
|
+
}
|
|
366
|
+
disconnect() {
|
|
367
|
+
if (this.observer) {
|
|
368
|
+
this.observer.disconnect();
|
|
369
|
+
this.observer = null;
|
|
370
|
+
}
|
|
371
|
+
// @ts-ignore
|
|
372
|
+
this.observing = false;
|
|
373
|
+
this.reset();
|
|
374
|
+
return this;
|
|
375
|
+
}
|
|
376
|
+
destroy() {
|
|
377
|
+
this.disconnect();
|
|
378
|
+
runtimeCSSs.splice(runtimeCSSs.indexOf(this), 1);
|
|
379
|
+
return this;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const runtimeCSSs = [];
|
|
383
|
+
(()=>{
|
|
384
|
+
globalThis.RuntimeCSS = RuntimeCSS;
|
|
385
|
+
globalThis.runtimeCSSs = runtimeCSSs;
|
|
386
|
+
})();
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Initialize a new RuntimeCSS instance and observe the target root
|
|
390
|
+
* @param config master css config
|
|
391
|
+
* @param targetRoot target root to observe
|
|
392
|
+
* @returns master css instance
|
|
393
|
+
*/ function initCSSRuntime(config) {
|
|
394
|
+
return new RuntimeCSS(document, config).observe();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
exports.RuntimeCSS = RuntimeCSS;
|
|
398
|
+
exports.default = RuntimeCSS;
|
|
399
|
+
exports.initCSSRuntime = initCSSRuntime;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { RuntimeCSS, RuntimeCSS as default } from './core';
|
|
2
2
|
export { default as initCSSRuntime } from './init-css-runtime';
|