@master/css-runtime 2.0.0-rc.62 → 2.0.0-rc.64

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/index.cjs CHANGED
@@ -45,6 +45,10 @@ class RuntimeLayer extends css.Layer {
45
45
  return this.native.cssRules.item(insertedIndex);
46
46
  } catch (error) {
47
47
  console.error(error, rule);
48
+ /**
49
+ * If the rule is invalid, remove it from the rules array.
50
+ * It's important to note that the rule may break the entire CSS runtime,
51
+ */ this.rules.splice(insertedIndex, 1);
48
52
  return;
49
53
  }
50
54
  };
@@ -110,20 +114,19 @@ class CSSRuntime extends css.MasterCSS {
110
114
  customConfig;
111
115
  static instances = new WeakMap();
112
116
  host;
113
- observing;
114
- progressive;
115
117
  container;
116
- observer;
117
118
  baseLayer;
118
119
  themeLayer;
119
120
  presetLayer;
120
121
  componentsLayer;
121
122
  generalLayer;
122
123
  classCounts;
124
+ observer;
125
+ progressive;
126
+ observing;
123
127
  constructor(root = document, customConfig = css.config){
124
- 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();
125
- const rootConstructorName = root?.constructor.name;
126
- if (rootConstructorName === 'HTMLDocument' || rootConstructorName === 'Document') {
128
+ super(customConfig), this.root = root, this.customConfig = customConfig, 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(), this.progressive = false, this.observing = false;
129
+ if (this.root instanceof Document || this.root instanceof HTMLDocument) {
127
130
  this.root.defaultView.globalThis.cssRuntime = this;
128
131
  this.container = this.root.head;
129
132
  this.host = this.root.documentElement;
@@ -142,36 +145,44 @@ class CSSRuntime extends css.MasterCSS {
142
145
  * @returns this
143
146
  */ observe() {
144
147
  if (this.observing) return this;
145
- // 1. Check if the stylesheet is progressive.
146
- if (this.root.styleSheets) for (const sheet of this.root.styleSheets){
147
- const { ownerNode } = sheet;
148
- if (ownerNode && ownerNode.id === 'master') {
149
- this.style = ownerNode;
150
- if (this.style.sheet?.cssRules.length) {
151
- // @ts-ignore
152
- this.progressive = true;
148
+ // Detect prerendered stylesheet
149
+ if (this.root.styleSheets) {
150
+ for (const sheet of this.root.styleSheets){
151
+ const { ownerNode } = sheet;
152
+ if (ownerNode instanceof HTMLStyleElement && ownerNode.id === 'master') {
153
+ this.style = ownerNode;
154
+ if (this.style.sheet?.cssRules.length) {
155
+ this.progressive = true;
156
+ }
157
+ break;
153
158
  }
154
- break;
155
159
  }
156
160
  }
157
- // 2. Count the classes in the host and its children.
161
+ // Prepare snapshot map
162
+ const elementClasses = new WeakMap();
163
+ // Initial scan and populate counts + snapshot
158
164
  const connectedNames = new Set();
159
165
  const increaseClassCount = (className)=>{
160
166
  const count = this.classCounts.get(className) || 0;
161
- if (!count) {
162
- connectedNames.add(className);
163
- }
167
+ if (!count) connectedNames.add(className);
164
168
  this.classCounts.set(className, count + 1);
165
169
  };
166
- this.host.classList.forEach(increaseClassCount);
167
- (this.root.constructor.name === 'HTMLDocument' ? this.host : this.container).querySelectorAll('[class]').forEach((element)=>element.classList.forEach(increaseClassCount));
168
- // 3. Hydrate the existing CSS rules or create a new one.
170
+ const rootEl = this.root instanceof Document || this.root instanceof HTMLDocument ? this.root : this.container;
171
+ const elementsWithClass = rootEl.querySelectorAll('[class]');
172
+ elementsWithClass.forEach((el)=>{
173
+ const clsList = el.classList;
174
+ if (clsList) {
175
+ el.classList.forEach(increaseClassCount);
176
+ }
177
+ elementClasses.set(el, new Set(clsList));
178
+ });
179
+ // Hydration or style creation
169
180
  if (this.progressive) {
170
181
  const hydrateResult = this.hydrate(this.style.sheet.cssRules);
171
- // Add the connected class names that are not hydrated yet.
172
- for (const eachConnectedName of connectedNames){
173
- if (hydrateResult.allSyntaxRules.find((rule)=>(rule.fixedClass || rule.name) === eachConnectedName)) continue;
174
- this.add(eachConnectedName);
182
+ for (const cls of connectedNames){
183
+ if (!hydrateResult.allSyntaxRules.find((r)=>(r.fixedClass || r.name) === cls)) {
184
+ this.add(cls);
185
+ }
175
186
  }
176
187
  } else {
177
188
  this.style = document.createElement('style');
@@ -179,125 +190,81 @@ class CSSRuntime extends css.MasterCSS {
179
190
  this.container.append(this.style);
180
191
  this.style.sheet.insertRule(this.layerStatementRule.text);
181
192
  this.layerStatementRule.native = this.style.sheet.cssRules.item(0);
182
- for (const eachConnectedName of connectedNames){
183
- this.add(eachConnectedName);
184
- }
193
+ connectedNames.forEach((cls)=>this.add(cls));
185
194
  }
186
- // @ts-expect-error readonly
187
- this.observer = new MutationObserver((mutationRecords)=>{
188
- const eachClassCounts = new Map();
189
- const targetFirstAttrMutationRecord = new Map();
190
- const updateClassCount = (classes, isAdding = false)=>{
191
- const usage = isAdding ? 1 : -1;
192
- classes.forEach((className)=>{
193
- eachClassCounts.set(className, (eachClassCounts.get(className) || 0) + usage);
194
- });
195
+ this.observer = new MutationObserver((records)=>{
196
+ const deltaCounts = new Map();
197
+ const nodeMap = new Map();
198
+ const attrRecords = new Set();
199
+ const visited = new WeakSet();
200
+ const updateDelta = (cls, delta)=>deltaCounts.set(cls, (deltaCounts.get(cls) || 0) + delta);
201
+ const diffAndSnapshot = (el)=>{
202
+ const prev = new Set(elementClasses.get(el) || []);
203
+ const next = new Set(el.classList);
204
+ for (const c of next)if (!prev.has(c)) updateDelta(c, 1);
205
+ for (const c of prev)if (!next.has(c)) updateDelta(c, -1);
206
+ elementClasses.set(el, next);
195
207
  };
196
- const connectedStatusMap = new Map();
197
- const disconnectedStatusMap = new Map();
198
- const recordStatus = (target, mutationRecord, map, adding)=>{
199
- const status = map.get(target);
200
- if (status) {
201
- status.change += adding ? 1 : -1;
202
- status.mutationRecord = mutationRecord;
203
- } else {
204
- map.set(target, {
205
- change: adding ? 1 : -1,
206
- mutationRecord
207
- });
208
- }
209
- for (const child of target.children){
210
- if ('classList' in child) {
211
- recordStatus(child, mutationRecord, map, adding);
212
- }
213
- }
208
+ const removeSnapshot = (el)=>{
209
+ for (const c of elementClasses.get(el) || [])updateDelta(c, -1);
210
+ elementClasses.delete(el);
214
211
  };
215
- mutationRecords.forEach((mutationRecord)=>{
216
- const target = mutationRecord.target;
217
- switch(mutationRecord.type){
218
- case 'attributes':
219
- if (!targetFirstAttrMutationRecord.has(target)) {
220
- targetFirstAttrMutationRecord.set(target, mutationRecord);
221
- }
222
- break;
223
- case 'childList':
224
- const targetStatusMap = target.isConnected ? connectedStatusMap : disconnectedStatusMap;
225
- mutationRecord.addedNodes.forEach((node)=>'classList' in node && recordStatus(node, mutationRecord, targetStatusMap, true));
226
- mutationRecord.removedNodes.forEach((node)=>'classList' in node && recordStatus(node, mutationRecord, targetStatusMap, false));
227
- break;
228
- }
229
- });
230
- const updateTarget = (target, adding)=>{
231
- const firstAttrMutationRecord = targetFirstAttrMutationRecord.get(target);
232
- if (firstAttrMutationRecord) {
233
- targetFirstAttrMutationRecord.delete(target);
212
+ for (const record of records){
213
+ if (record.type === 'childList') {
214
+ for (const node of record.addedNodes)if (node instanceof Element && node.isConnected) nodeMap.set(node, (nodeMap.get(node) || 0) + 1);
215
+ for (const node of record.removedNodes)if (node instanceof Element && !node.isConnected) nodeMap.set(node, (nodeMap.get(node) || 0) - 1);
216
+ } else if (record.type === 'attributes' && record.attributeName === 'class') {
217
+ attrRecords.add(record.target);
234
218
  }
235
- if (adding) {
236
- updateClassCount(target.classList, adding);
237
- } else {
238
- if (firstAttrMutationRecord) {
239
- updateClassCount(firstAttrMutationRecord.oldValue ? firstAttrMutationRecord.oldValue.split(/\s+/) : [], adding);
240
- } else {
241
- updateClassCount(target.classList, adding);
219
+ }
220
+ const traverseIncludingSelf = (el, fn, visited)=>{
221
+ if (!visited.has(el)) {
222
+ visited.add(el);
223
+ fn(el);
224
+ for (const child of el.children){
225
+ traverseIncludingSelf(child, fn, visited);
242
226
  }
243
- disconnectedStatusMap.forEach((disconnectedTargetStatus, disconnectedTarget)=>{
244
- if (disconnectedTargetStatus.mutationRecord.target === target && disconnectedTargetStatus.change !== 0) {
245
- updateTarget(disconnectedTarget, disconnectedTargetStatus.change > 0);
246
- }
247
- });
227
+ } else {
228
+ nodeMap.delete(el);
248
229
  }
249
230
  };
250
- connectedStatusMap.forEach(({ change }, target)=>change !== 0 && updateTarget(target, change > 0));
251
- targetFirstAttrMutationRecord.forEach((mutation, target)=>{
252
- if (!target.isConnected) return;
253
- const oldClassList = mutation.oldValue ? mutation.oldValue.split(/\s+/) : [];
254
- const newClassList = target.classList;
255
- const addedClasses = [];
256
- newClassList.forEach((c)=>{
257
- if (!oldClassList.includes(c)) addedClasses.push(c);
258
- });
259
- const removedClasses = oldClassList.filter((c)=>!newClassList.contains(c));
260
- if (addedClasses.length) {
261
- updateClassCount(addedClasses, true);
231
+ for (const [node, count] of nodeMap){
232
+ if (count > 0) {
233
+ traverseIncludingSelf(node, diffAndSnapshot, visited);
234
+ } else if (count < 0 && !node.isConnected) {
235
+ traverseIncludingSelf(node, removeSnapshot, visited);
262
236
  }
263
- if (removedClasses.length) {
264
- updateClassCount(removedClasses, false);
265
- }
266
- });
267
- /**
268
- * Merge the class usage changes into the current class usage map.
269
- */ eachClassCounts.forEach((countChange, className)=>{
270
- let currentCount = this.classCounts.get(className) || 0;
271
- let newCount = currentCount + countChange;
272
- if (newCount > 0) {
273
- this.classCounts.set(className, newCount);
274
- if (currentCount === 0) {
275
- this.add(className);
276
- }
237
+ }
238
+ for (const el of attrRecords){
239
+ if (!visited.has(el)) diffAndSnapshot(el);
240
+ }
241
+ for (const [cls, change] of deltaCounts){
242
+ const current = this.classCounts.get(cls) || 0;
243
+ const next = current + change;
244
+ if (next > 0) {
245
+ this.classCounts.set(cls, next);
246
+ if (current === 0) this.add(cls);
277
247
  } else {
278
- this.classCounts.delete(className);
279
- this.remove(className);
248
+ this.classCounts.delete(cls);
249
+ this.remove(cls);
280
250
  }
281
- });
251
+ }
282
252
  globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:mutated', {
283
- records: mutationRecords,
284
- classCounts: eachClassCounts,
253
+ records,
254
+ classCounts: deltaCounts,
285
255
  cssRuntime: this
286
256
  });
287
257
  });
288
258
  this.observer.observe(this.root, {
259
+ childList: true,
289
260
  attributes: true,
290
- attributeOldValue: true,
291
261
  attributeFilter: [
292
262
  'class'
293
263
  ],
294
- childList: true,
264
+ attributeOldValue: true,
295
265
  subtree: true
296
266
  });
297
- if (!this.progressive) {
298
- this.host.removeAttribute('hidden');
299
- }
300
- // @ts-ignore
267
+ if (!this.progressive) this.host.removeAttribute('hidden');
301
268
  this.observing = true;
302
269
  globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:observed', {
303
270
  cssRuntime: this
@@ -440,7 +407,6 @@ class CSSRuntime extends css.MasterCSS {
440
407
  if (!this.observing) return;
441
408
  if (this.observer) {
442
409
  this.observer.disconnect();
443
- // @ts-expect-error readonly
444
410
  this.observer = undefined;
445
411
  }
446
412
  // @ts-ignore
@@ -59,10 +59,36 @@ function registerClassCountDebugger() {
59
59
  });
60
60
  if (errored) {
61
61
  log.debug('Records:', records);
62
+ log.debug('Serialized Records:', serializeMutations(records));
62
63
  log.debug('Counts:', classCounts);
63
64
  }
64
65
  });
65
66
  }
67
+ function serializeMutations(mutations) {
68
+ return mutations.map((m)=>{
69
+ const target = m.target;
70
+ return {
71
+ type: m.type,
72
+ attributeName: m.attributeName,
73
+ oldValue: m.oldValue,
74
+ target: {
75
+ tagName: target.tagName,
76
+ id: target.id,
77
+ classList: Array.from(target.classList)
78
+ },
79
+ added: Array.from(m.addedNodes).filter((n)=>n.nodeType === 1).map((el)=>({
80
+ tagName: el.tagName,
81
+ id: el.id,
82
+ classList: Array.from(el.classList)
83
+ })),
84
+ removed: Array.from(m.removedNodes).filter((n)=>n.nodeType === 1).map((el)=>({
85
+ tagName: el.tagName,
86
+ id: el.id,
87
+ classList: Array.from(el.classList)
88
+ }))
89
+ };
90
+ });
91
+ }
66
92
 
67
93
  function registerLifecycleDebugger() {
68
94
  const hook = globalThis.__MASTER_CSS_DEVTOOLS_HOOK__;
@@ -141,6 +167,10 @@ class RuntimeLayer extends css.Layer {
141
167
  return this.native.cssRules.item(insertedIndex);
142
168
  } catch (error) {
143
169
  console.error(error, rule);
170
+ /**
171
+ * If the rule is invalid, remove it from the rules array.
172
+ * It's important to note that the rule may break the entire CSS runtime,
173
+ */ this.rules.splice(insertedIndex, 1);
144
174
  return;
145
175
  }
146
176
  };
@@ -206,20 +236,19 @@ class CSSRuntime extends css.MasterCSS {
206
236
  customConfig;
207
237
  static instances = new WeakMap();
208
238
  host;
209
- observing;
210
- progressive;
211
239
  container;
212
- observer;
213
240
  baseLayer;
214
241
  themeLayer;
215
242
  presetLayer;
216
243
  componentsLayer;
217
244
  generalLayer;
218
245
  classCounts;
246
+ observer;
247
+ progressive;
248
+ observing;
219
249
  constructor(root = document, customConfig = css.config){
220
- 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();
221
- const rootConstructorName = root?.constructor.name;
222
- if (rootConstructorName === 'HTMLDocument' || rootConstructorName === 'Document') {
250
+ super(customConfig), this.root = root, this.customConfig = customConfig, 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(), this.progressive = false, this.observing = false;
251
+ if (this.root instanceof Document || this.root instanceof HTMLDocument) {
223
252
  this.root.defaultView.globalThis.cssRuntime = this;
224
253
  this.container = this.root.head;
225
254
  this.host = this.root.documentElement;
@@ -238,39 +267,46 @@ class CSSRuntime extends css.MasterCSS {
238
267
  * @returns this
239
268
  */ observe() {
240
269
  if (this.observing) return this;
241
- // 1. Check if the stylesheet is progressive.
242
- if (this.root.styleSheets) for (const sheet of this.root.styleSheets){
243
- const { ownerNode } = sheet;
244
- if (ownerNode && ownerNode.id === 'master') {
245
- this.style = ownerNode;
246
- if (this.style.sheet?.cssRules.length) {
247
- // @ts-ignore
248
- this.progressive = true;
270
+ // Detect prerendered stylesheet
271
+ if (this.root.styleSheets) {
272
+ for (const sheet of this.root.styleSheets){
273
+ const { ownerNode } = sheet;
274
+ if (ownerNode instanceof HTMLStyleElement && ownerNode.id === 'master') {
275
+ this.style = ownerNode;
276
+ if (this.style.sheet?.cssRules.length) {
277
+ this.progressive = true;
278
+ }
279
+ break;
249
280
  }
250
- break;
251
281
  }
252
282
  }
253
- // 2. Count the classes in the host and its children.
283
+ // Prepare snapshot map
284
+ const elementClasses = new WeakMap();
285
+ // Initial scan and populate counts + snapshot
254
286
  const connectedNames = new Set();
255
287
  const increaseClassCount = (className)=>{
256
288
  const count = this.classCounts.get(className) || 0;
257
- if (!count) {
258
- connectedNames.add(className);
259
- }
289
+ if (!count) connectedNames.add(className);
260
290
  this.classCounts.set(className, count + 1);
261
291
  };
262
- this.host.classList.forEach(increaseClassCount);
263
- (this.root.constructor.name === 'HTMLDocument' ? this.host : this.container).querySelectorAll('[class]').forEach((element)=>element.classList.forEach(increaseClassCount));
264
- // 3. Hydrate the existing CSS rules or create a new one.
292
+ const rootEl = this.root instanceof Document || this.root instanceof HTMLDocument ? this.root : this.container;
293
+ const elementsWithClass = rootEl.querySelectorAll('[class]');
294
+ elementsWithClass.forEach((el)=>{
295
+ const clsList = el.classList;
296
+ if (clsList) {
297
+ el.classList.forEach(increaseClassCount);
298
+ }
299
+ elementClasses.set(el, new Set(clsList));
300
+ });
301
+ // Hydration or style creation
265
302
  if (this.progressive) {
266
303
  const hydrateResult = this.hydrate(this.style.sheet.cssRules);
267
- // Add the connected class names that are not hydrated yet.
268
- for (const eachConnectedName of connectedNames){
269
- if (hydrateResult.allSyntaxRules.find((rule)=>(rule.fixedClass || rule.name) === eachConnectedName)) continue;
270
- this.add(eachConnectedName);
271
- {
272
- // Basically, the style#master should have all the prerendered CSS rules that are connected in the DOM.
273
- console.debug(`The class \`${eachConnectedName}\` was added via script before calling hydrate, or the corresponding CSS rule were not properly pre-rendered.`);
304
+ for (const cls of connectedNames){
305
+ if (!hydrateResult.allSyntaxRules.find((r)=>(r.fixedClass || r.name) === cls)) {
306
+ this.add(cls);
307
+ {
308
+ console.debug(`Missing prerendered rule for class \`${cls}\``);
309
+ }
274
310
  }
275
311
  }
276
312
  } else {
@@ -279,125 +315,81 @@ class CSSRuntime extends css.MasterCSS {
279
315
  this.container.append(this.style);
280
316
  this.style.sheet.insertRule(this.layerStatementRule.text);
281
317
  this.layerStatementRule.native = this.style.sheet.cssRules.item(0);
282
- for (const eachConnectedName of connectedNames){
283
- this.add(eachConnectedName);
284
- }
318
+ connectedNames.forEach((cls)=>this.add(cls));
285
319
  }
286
- // @ts-expect-error readonly
287
- this.observer = new MutationObserver((mutationRecords)=>{
288
- const eachClassCounts = new Map();
289
- const targetFirstAttrMutationRecord = new Map();
290
- const updateClassCount = (classes, isAdding = false)=>{
291
- const usage = isAdding ? 1 : -1;
292
- classes.forEach((className)=>{
293
- eachClassCounts.set(className, (eachClassCounts.get(className) || 0) + usage);
294
- });
320
+ this.observer = new MutationObserver((records)=>{
321
+ const deltaCounts = new Map();
322
+ const nodeMap = new Map();
323
+ const attrRecords = new Set();
324
+ const visited = new WeakSet();
325
+ const updateDelta = (cls, delta)=>deltaCounts.set(cls, (deltaCounts.get(cls) || 0) + delta);
326
+ const diffAndSnapshot = (el)=>{
327
+ const prev = new Set(elementClasses.get(el) || []);
328
+ const next = new Set(el.classList);
329
+ for (const c of next)if (!prev.has(c)) updateDelta(c, 1);
330
+ for (const c of prev)if (!next.has(c)) updateDelta(c, -1);
331
+ elementClasses.set(el, next);
295
332
  };
296
- const connectedStatusMap = new Map();
297
- const disconnectedStatusMap = new Map();
298
- const recordStatus = (target, mutationRecord, map, adding)=>{
299
- const status = map.get(target);
300
- if (status) {
301
- status.change += adding ? 1 : -1;
302
- status.mutationRecord = mutationRecord;
303
- } else {
304
- map.set(target, {
305
- change: adding ? 1 : -1,
306
- mutationRecord
307
- });
308
- }
309
- for (const child of target.children){
310
- if ('classList' in child) {
311
- recordStatus(child, mutationRecord, map, adding);
312
- }
313
- }
333
+ const removeSnapshot = (el)=>{
334
+ for (const c of elementClasses.get(el) || [])updateDelta(c, -1);
335
+ elementClasses.delete(el);
314
336
  };
315
- mutationRecords.forEach((mutationRecord)=>{
316
- const target = mutationRecord.target;
317
- switch(mutationRecord.type){
318
- case 'attributes':
319
- if (!targetFirstAttrMutationRecord.has(target)) {
320
- targetFirstAttrMutationRecord.set(target, mutationRecord);
321
- }
322
- break;
323
- case 'childList':
324
- const targetStatusMap = target.isConnected ? connectedStatusMap : disconnectedStatusMap;
325
- mutationRecord.addedNodes.forEach((node)=>'classList' in node && recordStatus(node, mutationRecord, targetStatusMap, true));
326
- mutationRecord.removedNodes.forEach((node)=>'classList' in node && recordStatus(node, mutationRecord, targetStatusMap, false));
327
- break;
337
+ for (const record of records){
338
+ if (record.type === 'childList') {
339
+ for (const node of record.addedNodes)if (node instanceof Element && node.isConnected) nodeMap.set(node, (nodeMap.get(node) || 0) + 1);
340
+ for (const node of record.removedNodes)if (node instanceof Element && !node.isConnected) nodeMap.set(node, (nodeMap.get(node) || 0) - 1);
341
+ } else if (record.type === 'attributes' && record.attributeName === 'class') {
342
+ attrRecords.add(record.target);
328
343
  }
329
- });
330
- const updateTarget = (target, adding)=>{
331
- const firstAttrMutationRecord = targetFirstAttrMutationRecord.get(target);
332
- if (firstAttrMutationRecord) {
333
- targetFirstAttrMutationRecord.delete(target);
334
- }
335
- if (adding) {
336
- updateClassCount(target.classList, adding);
337
- } else {
338
- if (firstAttrMutationRecord) {
339
- updateClassCount(firstAttrMutationRecord.oldValue ? firstAttrMutationRecord.oldValue.split(/\s+/) : [], adding);
340
- } else {
341
- updateClassCount(target.classList, adding);
344
+ }
345
+ const traverseIncludingSelf = (el, fn, visited)=>{
346
+ if (!visited.has(el)) {
347
+ visited.add(el);
348
+ fn(el);
349
+ for (const child of el.children){
350
+ traverseIncludingSelf(child, fn, visited);
342
351
  }
343
- disconnectedStatusMap.forEach((disconnectedTargetStatus, disconnectedTarget)=>{
344
- if (disconnectedTargetStatus.mutationRecord.target === target && disconnectedTargetStatus.change !== 0) {
345
- updateTarget(disconnectedTarget, disconnectedTargetStatus.change > 0);
346
- }
347
- });
352
+ } else {
353
+ nodeMap.delete(el);
348
354
  }
349
355
  };
350
- connectedStatusMap.forEach(({ change }, target)=>change !== 0 && updateTarget(target, change > 0));
351
- targetFirstAttrMutationRecord.forEach((mutation, target)=>{
352
- if (!target.isConnected) return;
353
- const oldClassList = mutation.oldValue ? mutation.oldValue.split(/\s+/) : [];
354
- const newClassList = target.classList;
355
- const addedClasses = [];
356
- newClassList.forEach((c)=>{
357
- if (!oldClassList.includes(c)) addedClasses.push(c);
358
- });
359
- const removedClasses = oldClassList.filter((c)=>!newClassList.contains(c));
360
- if (addedClasses.length) {
361
- updateClassCount(addedClasses, true);
362
- }
363
- if (removedClasses.length) {
364
- updateClassCount(removedClasses, false);
356
+ for (const [node, count] of nodeMap){
357
+ if (count > 0) {
358
+ traverseIncludingSelf(node, diffAndSnapshot, visited);
359
+ } else if (count < 0 && !node.isConnected) {
360
+ traverseIncludingSelf(node, removeSnapshot, visited);
365
361
  }
366
- });
367
- /**
368
- * Merge the class usage changes into the current class usage map.
369
- */ eachClassCounts.forEach((countChange, className)=>{
370
- let currentCount = this.classCounts.get(className) || 0;
371
- let newCount = currentCount + countChange;
372
- if (newCount > 0) {
373
- this.classCounts.set(className, newCount);
374
- if (currentCount === 0) {
375
- this.add(className);
376
- }
362
+ }
363
+ for (const el of attrRecords){
364
+ if (!visited.has(el)) diffAndSnapshot(el);
365
+ }
366
+ for (const [cls, change] of deltaCounts){
367
+ const current = this.classCounts.get(cls) || 0;
368
+ const next = current + change;
369
+ if (next > 0) {
370
+ this.classCounts.set(cls, next);
371
+ if (current === 0) this.add(cls);
377
372
  } else {
378
- this.classCounts.delete(className);
379
- this.remove(className);
373
+ this.classCounts.delete(cls);
374
+ this.remove(cls);
380
375
  }
381
- });
376
+ }
382
377
  globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:mutated', {
383
- records: mutationRecords,
384
- classCounts: eachClassCounts,
378
+ records,
379
+ classCounts: deltaCounts,
385
380
  cssRuntime: this
386
381
  });
387
382
  });
388
383
  this.observer.observe(this.root, {
384
+ childList: true,
389
385
  attributes: true,
390
- attributeOldValue: true,
391
386
  attributeFilter: [
392
387
  'class'
393
388
  ],
394
- childList: true,
389
+ attributeOldValue: true,
395
390
  subtree: true
396
391
  });
397
- if (!this.progressive) {
398
- this.host.removeAttribute('hidden');
399
- }
400
- // @ts-ignore
392
+ if (!this.progressive) this.host.removeAttribute('hidden');
401
393
  this.observing = true;
402
394
  globalThis.__MASTER_CSS_DEVTOOLS_HOOK__?.emit('runtime:observed', {
403
395
  cssRuntime: this
@@ -543,7 +535,6 @@ class CSSRuntime extends css.MasterCSS {
543
535
  if (!this.observing) return;
544
536
  if (this.observer) {
545
537
  this.observer.disconnect();
546
- // @ts-expect-error readonly
547
538
  this.observer = undefined;
548
539
  }
549
540
  // @ts-ignore