@master/css-runtime 2.0.0-rc.54 → 2.0.0-rc.56

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.
@@ -0,0 +1,440 @@
1
+ import { 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 (console[type]) {
7
+ console[type](`%cMaster CSS%c ${message}`, 'color: gray;', 'color: inherit;', ...args);
8
+ }
9
+ };
10
+ };
11
+ const loggers = {
12
+ debug: createLogger('debug'),
13
+ info: createLogger('info'),
14
+ warn: createLogger('warn'),
15
+ error: createLogger('error'),
16
+ trace: createLogger('trace'),
17
+ table: console.table,
18
+ group: console.group,
19
+ groupCollapsed: console.group,
20
+ groupEnd: console.groupEnd,
21
+ time: console.time,
22
+ timeEnd: console.timeEnd
23
+ };
24
+ const log = createLogger('log');
25
+ Object.assign(log, loggers);
26
+
27
+ function startDebug() {
28
+ log('debug mode is enabled by default in development.');
29
+ const hook = installHook();
30
+ hook.on('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(`Count mismatch for`, {
49
+ class: className,
50
+ expected: eachActualCount,
51
+ received: eachCount
52
+ });
53
+ errored = true;
54
+ }
55
+ }
56
+ cssRuntime.classCounts.forEach((eachCount, className)=>{
57
+ if (!Object.prototype.hasOwnProperty.call(actualClassCounts, className)) {
58
+ log.error(`Count mismatch for`, {
59
+ class: className,
60
+ expected: 0,
61
+ received: eachCount
62
+ });
63
+ errored = true;
64
+ }
65
+ });
66
+ if (errored) {
67
+ console.debug('Records:', records);
68
+ console.debug('Counts:', classCounts);
69
+ }
70
+ });
71
+ }
72
+
73
+ function registerGlobal(CSSRuntime) {
74
+ if (!globalThis.CSSRuntime) globalThis.CSSRuntime = CSSRuntime;
75
+ installHook();
76
+ {
77
+ startDebug();
78
+ }
79
+ }
80
+
81
+ class CSSRuntime extends MasterCSS {
82
+ root;
83
+ customConfig;
84
+ static instances = new WeakMap();
85
+ host;
86
+ observing;
87
+ progressive;
88
+ container;
89
+ observer;
90
+ constructor(root = document, customConfig = config){
91
+ super(customConfig), this.root = root, this.customConfig = customConfig, this.observing = false, this.progressive = false;
92
+ const rootConstructorName = root?.constructor.name;
93
+ if (rootConstructorName === 'HTMLDocument' || rootConstructorName === 'Document') {
94
+ this.root.defaultView.globalThis.cssRuntime = this;
95
+ this.container = this.root.head;
96
+ this.host = this.root.documentElement;
97
+ } else {
98
+ this.container = this.root;
99
+ this.host = this.root.host;
100
+ }
101
+ this.supportVendors = new Set();
102
+ const styleDeclaration = document.documentElement.style;
103
+ if ('webkitTransform' in styleDeclaration) this.supportVendors.add('webkit');
104
+ if ('MozTransform' in styleDeclaration) this.supportVendors.add('moz');
105
+ if ('msTransform' in styleDeclaration) this.supportVendors.add('ms');
106
+ if ('OTransform' in styleDeclaration) this.supportVendors.add('o');
107
+ globalThis.CSSRuntime.instances.set(this.root, this);
108
+ }
109
+ /**
110
+ * Observe the DOM for changes and update the running stylesheet. (browser only)
111
+ * @param options mutation observer options
112
+ * @returns this
113
+ */ observe() {
114
+ if (this.observing) return this;
115
+ if (this.root.styleSheets) for (const sheet of this.root.styleSheets){
116
+ const { ownerNode } = sheet;
117
+ if (ownerNode && ownerNode.id === 'master') {
118
+ this.style = ownerNode;
119
+ // @ts-ignore
120
+ this.progressive = true;
121
+ break;
122
+ }
123
+ }
124
+ if (this.progressive) {
125
+ this.hydrate(this.style.sheet.cssRules);
126
+ } else {
127
+ this.style = document.createElement('style');
128
+ this.style.id = 'master';
129
+ this.container.append(this.style);
130
+ this.style.sheet.insertRule(this.layerStatementRule.text);
131
+ this.layerStatementRule.nodes[0].native = this.style.sheet.cssRules.item(0);
132
+ }
133
+ const firstConnectedElementsClasses = new Map();
134
+ const addClasses = (element)=>{
135
+ element.classList.forEach((className)=>{
136
+ if (this.classCounts.has(className)) {
137
+ this.classCounts.set(className, this.classCounts.get(className) + 1);
138
+ } else {
139
+ this.classCounts.set(className, 1);
140
+ this.add(className);
141
+ }
142
+ const classes = firstConnectedElementsClasses.get(element);
143
+ if (classes) {
144
+ classes.push(className);
145
+ } else {
146
+ firstConnectedElementsClasses.set(element, [
147
+ className
148
+ ]);
149
+ }
150
+ });
151
+ };
152
+ addClasses(this.host);
153
+ /**
154
+ * 待所有 DOM 結構完成解析後,開始繪製 Rule 樣式
155
+ */ (this.root.constructor.name === 'HTMLDocument' ? this.host : this.container).querySelectorAll('[class]').forEach((element)=>{
156
+ addClasses(element);
157
+ });
158
+ // @ts-expect-error readonly
159
+ this.observer = new MutationObserver((mutationRecords)=>{
160
+ // console.clear()
161
+ // const test = ''
162
+ // if (test) {
163
+ // console.log('')
164
+ // console.log(`${test}: ${this.classCounts.get(test)}`)
165
+ // }
166
+ const eachClassCounts = new Map();
167
+ const targetFirstAttrMutationRecord = new Map();
168
+ const updateClassCount = (classes, isAdding = false)=>{
169
+ const usage = isAdding ? 1 : -1;
170
+ classes.forEach((className)=>{
171
+ eachClassCounts.set(className, (eachClassCounts.get(className) || 0) + usage);
172
+ });
173
+ };
174
+ const connectedStatusMap = new Map();
175
+ const disconnectedStatusMap = new Map();
176
+ const recordStatus = (target, mutationRecord, map, adding)=>{
177
+ const status = map.get(target);
178
+ if (status) {
179
+ status.change += adding ? 1 : -1;
180
+ status.mutationRecord = mutationRecord;
181
+ } else {
182
+ map.set(target, {
183
+ change: adding ? 1 : -1,
184
+ mutationRecord
185
+ });
186
+ }
187
+ };
188
+ mutationRecords.forEach((mutationRecord)=>{
189
+ const target = mutationRecord.target;
190
+ switch(mutationRecord.type){
191
+ case 'attributes':
192
+ if (!targetFirstAttrMutationRecord.has(target)) {
193
+ targetFirstAttrMutationRecord.set(target, mutationRecord);
194
+ }
195
+ break;
196
+ case 'childList':
197
+ const targetStatusMap = target.isConnected ? connectedStatusMap : disconnectedStatusMap;
198
+ mutationRecord.addedNodes.forEach((node)=>'classList' in node && recordStatus(node, mutationRecord, targetStatusMap, true));
199
+ mutationRecord.removedNodes.forEach((node)=>'classList' in node && recordStatus(node, mutationRecord, targetStatusMap, false));
200
+ break;
201
+ }
202
+ });
203
+ const updatedTargetChangeMap = new Map();
204
+ const updateTarget = (target, adding)=>{
205
+ const change = updatedTargetChangeMap.get(target) || 0;
206
+ const newChange = change + (adding ? 1 : -1);
207
+ if (newChange >= -1 && newChange <= 1) {
208
+ updatedTargetChangeMap.set(target, newChange);
209
+ const firstAttrMutationRecord = targetFirstAttrMutationRecord.get(target);
210
+ if (firstAttrMutationRecord) {
211
+ targetFirstAttrMutationRecord.delete(target);
212
+ }
213
+ if (adding) {
214
+ updateClassCount(target.classList, adding);
215
+ } else {
216
+ if (firstAttrMutationRecord) {
217
+ updateClassCount(firstAttrMutationRecord.oldValue ? firstAttrMutationRecord.oldValue.split(/\s+/) : [], adding);
218
+ } else {
219
+ updateClassCount(target.classList, adding);
220
+ }
221
+ disconnectedStatusMap.forEach((disconnectedTargetStatus, disconnectedTarget)=>{
222
+ if (disconnectedTargetStatus.mutationRecord.target === target && disconnectedTargetStatus.change !== 0) {
223
+ updateTarget(disconnectedTarget, disconnectedTargetStatus.change > 0);
224
+ }
225
+ });
226
+ }
227
+ for (const child of target.children){
228
+ updateTarget(child, adding);
229
+ }
230
+ }
231
+ };
232
+ connectedStatusMap.forEach(({ change }, target)=>change !== 0 && updateTarget(target, change > 0));
233
+ targetFirstAttrMutationRecord.forEach((mutation, target)=>{
234
+ if (!target.isConnected) return;
235
+ const oldClassList = mutation.oldValue ? mutation.oldValue.split(/\s+/) : [];
236
+ const newClassList = target.classList;
237
+ const addedClasses = [];
238
+ newClassList.forEach((c)=>{
239
+ if (!oldClassList.includes(c)) addedClasses.push(c);
240
+ });
241
+ const removedClasses = oldClassList.filter((c)=>!newClassList.contains(c));
242
+ if (addedClasses.length) {
243
+ // console.log('[attribute]', '[add] ', addedClasses, target)
244
+ updateClassCount(addedClasses, true);
245
+ }
246
+ if (removedClasses.length) {
247
+ // console.log('[attribute]', '[remove]', removedClasses, target)
248
+ updateClassCount(removedClasses, false);
249
+ }
250
+ });
251
+ /**
252
+ * Merge the class usage changes into the current class usage map.
253
+ */ eachClassCounts.forEach((countChange, className)=>{
254
+ let currentCount = this.classCounts.get(className) || 0;
255
+ let newCount = currentCount + countChange;
256
+ if (newCount > 0) {
257
+ this.classCounts.set(className, newCount);
258
+ if (currentCount === 0) {
259
+ this.add(className);
260
+ }
261
+ } else {
262
+ this.classCounts.delete(className);
263
+ this.remove(className);
264
+ }
265
+ });
266
+ globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('mutated', {
267
+ records: mutationRecords,
268
+ classCounts: eachClassCounts,
269
+ cssRuntime: this
270
+ });
271
+ });
272
+ this.observer.observe(this.root, {
273
+ attributes: true,
274
+ attributeOldValue: true,
275
+ attributeFilter: [
276
+ 'class'
277
+ ],
278
+ childList: true,
279
+ subtree: true
280
+ });
281
+ if (!this.progressive) {
282
+ this.host.removeAttribute('hidden');
283
+ }
284
+ // @ts-ignore
285
+ this.observing = true;
286
+ return this;
287
+ }
288
+ hydrate(nativeLayerRules) {
289
+ const cssLayerRules = [];
290
+ const checkSheet = new CSSStyleSheet();
291
+ for(let i = 0; i < nativeLayerRules.length; i++){
292
+ const eachNativeCSSRule = nativeLayerRules[i];
293
+ if (eachNativeCSSRule.constructor.name === 'CSSLayerBlockRule') {
294
+ const eachCSSLayerRule = eachNativeCSSRule;
295
+ if (eachNativeCSSRule.name === 'theme') {
296
+ this.themeLayer.native = eachCSSLayerRule;
297
+ let variableRule;
298
+ let lastVariableName;
299
+ for(let j = 0; j < eachCSSLayerRule.cssRules.length; j++){
300
+ const cssRule = eachCSSLayerRule.cssRules[j];
301
+ const variableCSSRule = cssRule.constructor.name === 'CSSMediaRule' ? cssRule.cssRules[0] : cssRule;
302
+ const variableName = variableCSSRule.style[0].slice(2);
303
+ if (variableName !== lastVariableName) {
304
+ lastVariableName = variableName;
305
+ variableRule = new Rule(variableName);
306
+ this.themeLayer.rules.push(variableRule);
307
+ this.themeLayer.usages[variableRule.name] = 0;
308
+ }
309
+ variableRule?.nodes.push({
310
+ native: cssRule,
311
+ text: cssRule.cssText
312
+ });
313
+ }
314
+ if (this.themeLayer.rules.length) this.rules.push(this.themeLayer);
315
+ } else {
316
+ cssLayerRules.push(eachCSSLayerRule);
317
+ }
318
+ } else if (eachNativeCSSRule.constructor.name === 'CSSLayerStatementRule') {
319
+ this.layerStatementRule.nodes[0].native = eachNativeCSSRule;
320
+ } else if (eachNativeCSSRule.constructor.name === 'CSSKeyframesRule') {
321
+ const keyframsRule = eachNativeCSSRule;
322
+ const animationRule = new Rule(keyframsRule.name, [
323
+ {
324
+ native: keyframsRule,
325
+ text: keyframsRule.cssText
326
+ }
327
+ ]);
328
+ this.animationsNonLayer.rules.push(animationRule);
329
+ this.rules.push(animationRule);
330
+ this.animationsNonLayer.usages[animationRule.name] = 0;
331
+ }
332
+ }
333
+ for (const eachCSSLayerRule of cssLayerRules){
334
+ const nativeLayerRules = Array.from(eachCSSLayerRule.cssRules);
335
+ let layer;
336
+ switch(eachCSSLayerRule.name){
337
+ case 'base':
338
+ layer = this.baseLayer;
339
+ break;
340
+ case 'preset':
341
+ layer = this.presetLayer;
342
+ break;
343
+ case 'components':
344
+ layer = this.componentsLayer;
345
+ break;
346
+ default:
347
+ layer = this.generalLayer;
348
+ break;
349
+ }
350
+ layer.native = eachCSSLayerRule;
351
+ for (const eachNativeRule of nativeLayerRules){
352
+ const selectorText = this.getSelectorText(eachNativeRule);
353
+ if (!selectorText) {
354
+ console.error(`Cannot get the selector text from \`${eachNativeRule.cssText}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
355
+ continue;
356
+ }
357
+ const createdRules = this.createFromSelectorText(selectorText);
358
+ if (createdRules) {
359
+ for (const createdRule of createdRules){
360
+ layer.rules.push(createdRule);
361
+ layer.insertVariables(createdRule);
362
+ layer.insertAnimations(createdRule);
363
+ createdRule.nodes.forEach((node)=>{
364
+ const createdNodeNativeRule = checkSheet.cssRules.item(checkSheet.insertRule(node.text));
365
+ if (createdNodeNativeRule) {
366
+ for (const eachFindingNativeRule of nativeLayerRules){
367
+ if (createdNodeNativeRule.cssText === eachFindingNativeRule.cssText) {
368
+ node.native = eachFindingNativeRule;
369
+ nativeLayerRules.splice(nativeLayerRules.indexOf(eachFindingNativeRule), 1);
370
+ return;
371
+ }
372
+ }
373
+ }
374
+ console.error(`Cannot retrieve CSS rule for \`${node.selectorText}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
375
+ });
376
+ }
377
+ } else {
378
+ console.error(`Cannot recognize \`${eachNativeRule.cssText}\`. (${layer.name}) (https://rc.css.master.co/messages/hydration-errors)`);
379
+ }
380
+ }
381
+ if (layer.rules.length) this.rules.push(layer);
382
+ }
383
+ }
384
+ getSelectorText(cssRule) {
385
+ if ('selectorText' in cssRule) {
386
+ return cssRule.selectorText;
387
+ } else if ('cssRules' in cssRule) {
388
+ return this.getSelectorText(cssRule.cssRules.item(0));
389
+ }
390
+ }
391
+ disconnect() {
392
+ if (!this.observing) return;
393
+ if (this.observer) {
394
+ this.observer.disconnect();
395
+ // @ts-expect-error readonly
396
+ this.observer = undefined;
397
+ }
398
+ // @ts-ignore
399
+ this.observing = false;
400
+ this.reset();
401
+ this.classCounts.clear();
402
+ if (!this.progressive) {
403
+ this.style?.remove();
404
+ this.style = null;
405
+ }
406
+ return this;
407
+ }
408
+ refresh(customConfig = this.customConfig) {
409
+ if (!this.observing || !this.style.sheet) return this;
410
+ for(let i = 1; i <= this.style.sheet.cssRules.length - 1; i++){
411
+ this.style.sheet.deleteRule(i);
412
+ }
413
+ super.refresh(customConfig);
414
+ return this;
415
+ }
416
+ destroy() {
417
+ this.disconnect();
418
+ globalThis.CSSRuntime.instances.delete(this.root);
419
+ return this;
420
+ }
421
+ }
422
+ (function(CSSRuntime) {
423
+ registerGlobal(CSSRuntime);
424
+ })(CSSRuntime);
425
+
426
+ /**
427
+ * Initialize a new CSSRuntime instance and observe the target root
428
+ * @param config master css config
429
+ * @param root target root to observe
430
+ * @param autoObserve auto observe the target root
431
+ * @returns master css instance
432
+ */ function initCSSRuntime(config, root = document, autoObserve = true) {
433
+ let cssRuntime = globalThis.CSSRuntime.instances.get(root);
434
+ if (cssRuntime) return cssRuntime;
435
+ cssRuntime = new CSSRuntime(document, config);
436
+ if (autoObserve) cssRuntime.observe();
437
+ return cssRuntime;
438
+ }
439
+
440
+ export { CSSRuntime, initCSSRuntime };
package/dist/index.mjs CHANGED
@@ -1,43 +1,38 @@
1
1
  import { MasterCSS, config, Rule } from '@master/css';
2
+ import { installHook } from '@master/css-devtools-hook';
2
3
 
3
- class RuntimeCSS extends MasterCSS {
4
+ function registerGlobal(CSSRuntime) {
5
+ if (!globalThis.CSSRuntime) globalThis.CSSRuntime = CSSRuntime;
6
+ installHook();
7
+ }
8
+
9
+ class CSSRuntime extends MasterCSS {
4
10
  root;
5
11
  customConfig;
6
- // @ts-expect-error
12
+ static instances = new WeakMap();
7
13
  host;
8
14
  observing;
9
15
  progressive;
10
- // @ts-expect-error
11
16
  container;
12
17
  observer;
13
18
  constructor(root = document, customConfig = config){
14
19
  super(customConfig), this.root = root, this.customConfig = customConfig, this.observing = false, this.progressive = false;
15
- this.init();
16
- this.supportVendors = new Set();
17
- const styleDeclaration = document.documentElement.style;
18
- if ('webkitTransform' in styleDeclaration) this.supportVendors.add('webkit');
19
- if ('MozTransform' in styleDeclaration) this.supportVendors.add('moz');
20
- if ('msTransform' in styleDeclaration) this.supportVendors.add('ms');
21
- if ('OTransform' in styleDeclaration) this.supportVendors.add('o');
22
- }
23
- init() {
24
- const existingRuntimeCSS = globalThis.runtimeCSSs.find((eachCSS)=>eachCSS.root === this.root);
25
- if (existingRuntimeCSS) throw new Error('Cannot create multiple RuntimeCSS instances for the same root element.');
26
- const rootConstructorName = this.root?.constructor.name;
20
+ const rootConstructorName = root?.constructor.name;
27
21
  if (rootConstructorName === 'HTMLDocument' || rootConstructorName === 'Document') {
28
- // @ts-ignore
29
- this.root.defaultView.globalThis.runtimeCSS = this;
30
- // @ts-ignore readonly
22
+ this.root.defaultView.globalThis.cssRuntime = this;
31
23
  this.container = this.root.head;
32
- // @ts-ignore readonly
33
24
  this.host = this.root.documentElement;
34
25
  } else {
35
- // @ts-ignore readonly
36
26
  this.container = this.root;
37
- // @ts-ignore readonly
38
27
  this.host = this.root.host;
39
28
  }
40
- runtimeCSSs.push(this);
29
+ this.supportVendors = new Set();
30
+ const styleDeclaration = document.documentElement.style;
31
+ if ('webkitTransform' in styleDeclaration) this.supportVendors.add('webkit');
32
+ if ('MozTransform' in styleDeclaration) this.supportVendors.add('moz');
33
+ if ('msTransform' in styleDeclaration) this.supportVendors.add('ms');
34
+ if ('OTransform' in styleDeclaration) this.supportVendors.add('o');
35
+ globalThis.CSSRuntime.instances.set(this.root, this);
41
36
  }
42
37
  /**
43
38
  * Observe the DOM for changes and update the running stylesheet. (browser only)
@@ -66,10 +61,10 @@ class RuntimeCSS extends MasterCSS {
66
61
  const firstConnectedElementsClasses = new Map();
67
62
  const addClasses = (element)=>{
68
63
  element.classList.forEach((className)=>{
69
- if (this.classUsages.has(className)) {
70
- this.classUsages.set(className, this.classUsages.get(className) + 1);
64
+ if (this.classCounts.has(className)) {
65
+ this.classCounts.set(className, this.classCounts.get(className) + 1);
71
66
  } else {
72
- this.classUsages.set(className, 1);
67
+ this.classCounts.set(className, 1);
73
68
  this.add(className);
74
69
  }
75
70
  const classes = firstConnectedElementsClasses.get(element);
@@ -94,14 +89,14 @@ class RuntimeCSS extends MasterCSS {
94
89
  // const test = ''
95
90
  // if (test) {
96
91
  // console.log('')
97
- // console.log(`${test}: ${this.classUsages.get(test)}`)
92
+ // console.log(`${test}: ${this.classCounts.get(test)}`)
98
93
  // }
99
- const eachClassUsages = new Map();
94
+ const eachClassCounts = new Map();
100
95
  const targetFirstAttrMutationRecord = new Map();
101
- const updateClassUsage = (classes, isAdding = false)=>{
96
+ const updateClassCount = (classes, isAdding = false)=>{
102
97
  const usage = isAdding ? 1 : -1;
103
98
  classes.forEach((className)=>{
104
- eachClassUsages.set(className, (eachClassUsages.get(className) || 0) + usage);
99
+ eachClassCounts.set(className, (eachClassCounts.get(className) || 0) + usage);
105
100
  });
106
101
  };
107
102
  const connectedStatusMap = new Map();
@@ -144,12 +139,12 @@ class RuntimeCSS extends MasterCSS {
144
139
  targetFirstAttrMutationRecord.delete(target);
145
140
  }
146
141
  if (adding) {
147
- updateClassUsage(target.classList, adding);
142
+ updateClassCount(target.classList, adding);
148
143
  } else {
149
144
  if (firstAttrMutationRecord) {
150
- updateClassUsage(firstAttrMutationRecord.oldValue ? firstAttrMutationRecord.oldValue.split(/\s+/) : [], adding);
145
+ updateClassCount(firstAttrMutationRecord.oldValue ? firstAttrMutationRecord.oldValue.split(/\s+/) : [], adding);
151
146
  } else {
152
- updateClassUsage(target.classList, adding);
147
+ updateClassCount(target.classList, adding);
153
148
  }
154
149
  disconnectedStatusMap.forEach((disconnectedTargetStatus, disconnectedTarget)=>{
155
150
  if (disconnectedTargetStatus.mutationRecord.target === target && disconnectedTargetStatus.change !== 0) {
@@ -174,59 +169,33 @@ class RuntimeCSS extends MasterCSS {
174
169
  const removedClasses = oldClassList.filter((c)=>!newClassList.contains(c));
175
170
  if (addedClasses.length) {
176
171
  // console.log('[attribute]', '[add] ', addedClasses, target)
177
- updateClassUsage(addedClasses, true);
172
+ updateClassCount(addedClasses, true);
178
173
  }
179
174
  if (removedClasses.length) {
180
175
  // console.log('[attribute]', '[remove]', removedClasses, target)
181
- updateClassUsage(removedClasses, false);
176
+ updateClassCount(removedClasses, false);
182
177
  }
183
178
  });
184
179
  /**
185
180
  * Merge the class usage changes into the current class usage map.
186
- */ eachClassUsages.forEach((countChange, className)=>{
187
- let currentCount = this.classUsages.get(className) || 0;
181
+ */ eachClassCounts.forEach((countChange, className)=>{
182
+ let currentCount = this.classCounts.get(className) || 0;
188
183
  let newCount = currentCount + countChange;
189
184
  if (newCount > 0) {
190
- this.classUsages.set(className, newCount);
185
+ this.classCounts.set(className, newCount);
191
186
  if (currentCount === 0) {
192
187
  this.add(className);
193
188
  }
194
189
  } else {
195
- this.classUsages.delete(className);
190
+ this.classCounts.delete(className);
196
191
  this.remove(className);
197
192
  }
198
193
  });
199
- // start: debug
200
- // const safeClassUsages: any = {};
201
- // ((this.root.constructor.name === 'HTMLDocument') ? this.host : this.container)
202
- // .querySelectorAll('[class]')
203
- // .forEach((element) => {
204
- // element.classList.forEach((className) => {
205
- // if (Object.prototype.hasOwnProperty.call(safeClassUsages, className)) {
206
- // safeClassUsages[className]++
207
- // } else {
208
- // safeClassUsages[className] = 1
209
- // }
210
- // })
211
- // })
212
- // this.host.classList.forEach((className) => {
213
- // if (Object.prototype.hasOwnProperty.call(safeClassUsages, className)) {
214
- // safeClassUsages[className]++
215
- // } else {
216
- // safeClassUsages[className] = 1
217
- // }
218
- // })
219
- // for (const className in safeClassUsages) {
220
- // if (this.classUsages.get(className) !== safeClassUsages[className]) {
221
- // throw new Error(`[css] ${className} ${this.classUsages.get(className)} (correct: ${safeClassUsages[className]})`)
222
- // }
223
- // }
224
- // this.classUsages.forEach((count, className) => {
225
- // if (!Object.prototype.hasOwnProperty.call(safeClassUsages, className)) {
226
- // throw new Error(`[css] ${className} ${count} (correct: 0)`)
227
- // }
228
- // })
229
- // end: debug
194
+ globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('mutated', {
195
+ records: mutationRecords,
196
+ classCounts: eachClassCounts,
197
+ cssRuntime: this
198
+ });
230
199
  });
231
200
  this.observer.observe(this.root, {
232
201
  attributes: true,
@@ -238,7 +207,7 @@ class RuntimeCSS extends MasterCSS {
238
207
  subtree: true
239
208
  });
240
209
  if (!this.progressive) {
241
- this.host.style.removeProperty('display');
210
+ this.host.removeAttribute('hidden');
242
211
  }
243
212
  // @ts-ignore
244
213
  this.observing = true;
@@ -357,7 +326,7 @@ class RuntimeCSS extends MasterCSS {
357
326
  // @ts-ignore
358
327
  this.observing = false;
359
328
  this.reset();
360
- this.classUsages.clear();
329
+ this.classCounts.clear();
361
330
  if (!this.progressive) {
362
331
  this.style?.remove();
363
332
  this.style = null;
@@ -374,23 +343,26 @@ class RuntimeCSS extends MasterCSS {
374
343
  }
375
344
  destroy() {
376
345
  this.disconnect();
377
- runtimeCSSs.splice(runtimeCSSs.indexOf(this), 1);
346
+ globalThis.CSSRuntime.instances.delete(this.root);
378
347
  return this;
379
348
  }
380
349
  }
381
- const runtimeCSSs = [];
382
- (()=>{
383
- globalThis.RuntimeCSS = RuntimeCSS;
384
- globalThis.runtimeCSSs = runtimeCSSs;
385
- })();
350
+ (function(CSSRuntime) {
351
+ registerGlobal(CSSRuntime);
352
+ })(CSSRuntime);
386
353
 
387
354
  /**
388
- * Initialize a new RuntimeCSS instance and observe the target root
355
+ * Initialize a new CSSRuntime instance and observe the target root
389
356
  * @param config master css config
390
- * @param targetRoot target root to observe
357
+ * @param root target root to observe
358
+ * @param autoObserve auto observe the target root
391
359
  * @returns master css instance
392
- */ function initCSSRuntime(config) {
393
- return new RuntimeCSS(document, config).observe();
360
+ */ function initCSSRuntime(config, root = document, autoObserve = true) {
361
+ let cssRuntime = globalThis.CSSRuntime.instances.get(root);
362
+ if (cssRuntime) return cssRuntime;
363
+ cssRuntime = new CSSRuntime(document, config);
364
+ if (autoObserve) cssRuntime.observe();
365
+ return cssRuntime;
394
366
  }
395
367
 
396
- export { RuntimeCSS, RuntimeCSS as default, initCSSRuntime };
368
+ export { CSSRuntime, CSSRuntime as default, initCSSRuntime };