@@ -99,89 +99,102 @@ export class RuntimeCSS extends MasterCSS {
99
99
// @ts -expect-error readonly
100
100
this . observer = new MutationObserver ( ( mutationRecords ) => {
101
101
// console.clear()
102
- // console.log('-----------')
103
- // const test = 'swiper-slide-next'
104
- // console.log(`${test}: ${this.classUsages.get(test)}`)
102
+ // const test = ''
103
+ // if (test) {
104
+ // console.log('')
105
+ // console.log(`${test}: ${this.classUsages.get(test)}`)
106
+ // }
105
107
const eachClassUsages = new Map ( )
106
- const updatedAttrElements = new Set < Element > ( )
107
- const updatedConnectedElements = new Set < Element > ( )
108
- const updatedElements = new Set < Element > ( )
108
+ const targetFirstAttrMutationRecord = new Map < Element , MutationRecord > ( )
109
109
110
110
const updateClassUsage = ( classes : Set < string > | string [ ] | DOMTokenList , isAdding = false ) => {
111
111
const usage = isAdding ? 1 : - 1
112
112
classes . forEach ( ( className ) => {
113
- // if (className === test) console.log(` ${isAdding ? '+' : '-'} ${className} ${(eachClassUsages.get(className) || 0) + usage}`, target)
114
113
eachClassUsages . set ( className , ( eachClassUsages . get ( className ) || 0 ) + usage )
115
114
} )
116
115
}
117
116
118
- const updateElementTree = ( element : Element , adding : boolean ) => {
119
- if ( element . isConnected ) {
120
- updatedConnectedElements . add ( element )
121
- }
122
- updatedElements . add ( element )
123
- updateClassUsage ( element . classList , adding )
124
- for ( const child of element . children ) {
125
- updateElementTree ( child as Element , adding )
117
+ const connectedStatusMap = new Map < Element , { change : number , mutationRecord : MutationRecord } > ( )
118
+ const disconnectedStatusMap = new Map < Element , { change : number , mutationRecord : MutationRecord } > ( )
119
+ const recordStatus = ( target : Element , mutationRecord : MutationRecord , map : Map < Element , { change : number , mutationRecord : MutationRecord } > , adding : boolean ) => {
120
+ const status = map . get ( target )
121
+ if ( status ) {
122
+ status . change += adding ? 1 : - 1
123
+ status . mutationRecord = mutationRecord
124
+ } else {
125
+ map . set ( target , { change : adding ? 1 : - 1 , mutationRecord } )
126
126
}
127
127
}
128
128
129
- // console.log('///')
130
-
131
- mutationRecords . forEach ( ( mutation ) => {
132
- const target = mutation . target as Element
133
- switch ( mutation . type ) {
129
+ mutationRecords . forEach ( ( mutationRecord ) => {
130
+ const target = mutationRecord . target as Element
131
+ switch ( mutationRecord . type ) {
134
132
case 'attributes' :
135
- const oldClassList = mutation . oldValue ? mutation . oldValue . split ( / \s + / ) : [ ]
136
- const newClassList = target . classList
137
- const addedClasses : string [ ] = [ ]
138
- newClassList . forEach ( c => {
139
- if ( ! oldClassList . includes ( c ) ) addedClasses . push ( c )
140
- } )
141
- // const removedClasses = oldClassList.filter(c => !newClassList.contains(c))
142
- // if (addedClasses.length) console.log('[attribute]', '[add]', addedClasses, target)
143
- // if (removedClasses.length) console.log('[attribute]', '[remove]', removedClasses, target)
133
+ if ( ! targetFirstAttrMutationRecord . has ( target ) ) {
134
+ targetFirstAttrMutationRecord . set ( target , mutationRecord )
135
+ }
144
136
break
145
137
case 'childList' :
146
- // if (mutation.addedNodes.length) console.log('[childList]', '[add]', mutation.addedNodes)
147
- // if (mutation.removedNodes.length) console.log('[childList]', '[remove]', mutation.removedNodes)
138
+ const targetStatusMap = target . isConnected ? connectedStatusMap : disconnectedStatusMap
139
+ mutationRecord . addedNodes . forEach ( ( node ) =>
140
+ 'classList' in node && recordStatus ( node as Element , mutationRecord , targetStatusMap , true )
141
+ )
142
+ mutationRecord . removedNodes . forEach ( ( node ) =>
143
+ 'classList' in node && recordStatus ( node as Element , mutationRecord , targetStatusMap , false )
144
+ )
148
145
break
149
146
}
150
147
} )
151
148
152
- // console.log('///' )
149
+ const updatedTargetChangeMap = new Map < Element , number > ( )
153
150
154
- mutationRecords . forEach ( ( mutation ) => {
155
- const target = mutation . target as Element
156
- switch ( mutation . type ) {
157
- case 'attributes' :
158
- /**
159
- * We only need to determine the first attribute record of the same target,
160
- * because the first record has the original old class name.
161
- *
162
- * The target is skipped if it is contained in the unhandled childList.
163
- */
164
- if ( updatedAttrElements . has ( target ) || updatedElements . has ( target ) ) return
165
- updatedAttrElements . add ( target )
166
- const oldClassList = mutation . oldValue ? mutation . oldValue . split ( / \s + / ) : [ ]
167
- const newClassList = target . classList
168
- const addedClasses : string [ ] = [ ]
169
- newClassList . forEach ( c => {
170
- if ( ! oldClassList . includes ( c ) ) addedClasses . push ( c )
151
+ const updateTarget = ( target : Element , adding : boolean ) => {
152
+ const change = updatedTargetChangeMap . get ( target ) || 0
153
+ const newChange = change + ( adding ? 1 : - 1 )
154
+ if ( newChange >= - 1 && newChange <= 1 ) {
155
+ updatedTargetChangeMap . set ( target , newChange )
156
+ const firstAttrMutationRecord = targetFirstAttrMutationRecord . get ( target )
157
+ if ( firstAttrMutationRecord ) {
158
+ targetFirstAttrMutationRecord . delete ( target )
159
+ }
160
+ if ( adding ) {
161
+ updateClassUsage ( target . classList , adding )
162
+ } else {
163
+ if ( firstAttrMutationRecord ) {
164
+ updateClassUsage ( firstAttrMutationRecord . oldValue ? firstAttrMutationRecord . oldValue . split ( / \s + / ) : [ ] , adding )
165
+ } else {
166
+ updateClassUsage ( target . classList , adding )
167
+ }
168
+ disconnectedStatusMap . forEach ( ( disconnectedTargetStatus , disconnectedTarget ) => {
169
+ if ( disconnectedTargetStatus . mutationRecord . target === target && disconnectedTargetStatus . change !== 0 ) {
170
+ updateTarget ( disconnectedTarget , disconnectedTargetStatus . change > 0 )
171
+ }
171
172
} )
172
- const removedClasses = oldClassList . filter ( c => ! newClassList . contains ( c ) )
173
- // if (addedClasses.length) console.log('[attribute]', '[add]', addedClasses, target)
174
- if ( addedClasses . length ) updateClassUsage ( addedClasses , true )
175
- // if (removedClasses.length) console.log('[attribute]', '[remove]', removedClasses, target)
176
- if ( removedClasses . length ) updateClassUsage ( removedClasses , false )
177
- break
178
- case 'childList' :
179
- if ( updatedConnectedElements . has ( target ) ) return
180
- // if (mutation.addedNodes.length) console.log('[childList]', '[add]', mutation.addedNodes)
181
- mutation . addedNodes . forEach ( ( node ) => 'classList' in node && updateElementTree ( node as Element , true ) )
182
- // if (mutation.removedNodes.length) console.log('[childList]', '[remove]', mutation.removedNodes)
183
- mutation . removedNodes . forEach ( ( node ) => 'classList' in node && updateElementTree ( node as Element , false ) )
184
- break
173
+ }
174
+ for ( const child of target . children ) {
175
+ updateTarget ( child as Element , adding )
176
+ }
177
+ }
178
+ }
179
+
180
+ connectedStatusMap . forEach ( ( { change } , target ) => change !== 0 && updateTarget ( target , change > 0 ) )
181
+
182
+ targetFirstAttrMutationRecord . forEach ( ( mutation , target ) => {
183
+ if ( ! target . isConnected ) return
184
+ const oldClassList = mutation . oldValue ? mutation . oldValue . split ( / \s + / ) : [ ]
185
+ const newClassList = target . classList
186
+ const addedClasses : string [ ] = [ ]
187
+ newClassList . forEach ( c => {
188
+ if ( ! oldClassList . includes ( c ) ) addedClasses . push ( c )
189
+ } )
190
+ const removedClasses = oldClassList . filter ( c => ! newClassList . contains ( c ) )
191
+ if ( addedClasses . length ) {
192
+ // console.log('[attribute]', '[add] ', addedClasses, target)
193
+ updateClassUsage ( addedClasses , true )
194
+ }
195
+ if ( removedClasses . length ) {
196
+ // console.log('[attribute]', '[remove]', removedClasses, target)
197
+ updateClassUsage ( removedClasses , false )
185
198
}
186
199
} )
187
200
@@ -224,12 +237,17 @@ export class RuntimeCSS extends MasterCSS {
224
237
// }
225
238
// })
226
239
227
- // // 與 this.classUsages 比較並且打印不同的部分
228
240
// for (const className in safeClassUsages) {
229
241
// if (this.classUsages.get(className) !== safeClassUsages[className]) {
230
242
// throw new Error(`[css] ${className} ${this.classUsages.get(className)} (correct: ${safeClassUsages[className]})`)
231
243
// }
232
244
// }
245
+
246
+ // this.classUsages.forEach((count, className) => {
247
+ // if (!Object.prototype.hasOwnProperty.call(safeClassUsages, className)) {
248
+ // throw new Error(`[css] ${className} ${count} (correct: 0)`)
249
+ // }
250
+ // })
233
251
// end: debug
234
252
} )
235
253
0 commit comments