@@ -39,6 +39,40 @@ export class StyleInjector {
3939 this . sheetManager = new SheetManager ( config ) ;
4040 }
4141
42+ /**
43+ * Check if a class has live rules in the CSSOM
44+ */
45+ private hasLiveRules ( registry : any , className : string ) : boolean {
46+ const info = registry . rules . get ( className ) ;
47+ if ( ! info ) return false ;
48+
49+ try {
50+ const sheetInfo = registry . sheets [ info . sheetIndex ] ;
51+ const styleSheet = sheetInfo ?. sheet ?. sheet as CSSStyleSheet | undefined ;
52+ if ( ! styleSheet ) return false ;
53+
54+ const start = Math . max ( 0 , info . ruleIndex ) ;
55+ const end = Math . min (
56+ styleSheet . cssRules . length - 1 ,
57+ ( info . endRuleIndex as number ) ?? info . ruleIndex ,
58+ ) ;
59+
60+ if ( start > end || start >= styleSheet . cssRules . length ) return false ;
61+
62+ // Check if any rule in the range contains the expected class name
63+ for ( let i = start ; i <= end ; i ++ ) {
64+ const rule = styleSheet . cssRules [ i ] as CSSRule | undefined ;
65+ if ( rule && ( rule . cssText || '' ) . includes ( '.' + className ) ) {
66+ return true ;
67+ }
68+ }
69+ } catch {
70+ // Ignore CSSOM access errors and treat as missing
71+ }
72+
73+ return false ;
74+ }
75+
4276 /**
4377 * Inject styles from StyleResult objects
4478 */
@@ -52,7 +86,7 @@ export class StyleInjector {
5286 if ( rules . length === 0 ) {
5387 return {
5488 className : '' ,
55- dispose : ( ) => { } ,
89+ dispose : ( ) => { } ,
5690 } ;
5791 }
5892
@@ -67,6 +101,39 @@ export class StyleInjector {
67101 : null ;
68102
69103 if ( generatedClass && registry . rules . has ( generatedClass ) ) {
104+ // Verify that the rules still exist in the live CSSOM
105+ const hasLiveCSS = this . hasLiveRules ( registry , generatedClass ) ;
106+ if ( ! hasLiveCSS ) {
107+ // Rules were deleted from CSSOM but registry still has the entry
108+ // Reinsert the rules using the same class name
109+ const className = generatedClass ;
110+ const rulesToInsert = rules . map ( ( r ) => {
111+ // Increase specificity for class-based selectors by duplicating the class
112+ if ( r . selector . startsWith ( '.' ) && / ^ \. t \d + / . test ( r . selector ) ) {
113+ const classMatch = r . selector . match ( / ^ \. t \d + / ) ;
114+ if ( classMatch ) {
115+ const baseClass = classMatch [ 0 ] ;
116+ return {
117+ ...r ,
118+ selector : baseClass + r . selector ,
119+ } as StyleRule ;
120+ }
121+ }
122+ return r ;
123+ } ) ;
124+
125+ const ruleInfo = this . sheetManager . insertRule (
126+ registry ,
127+ rulesToInsert ,
128+ className ,
129+ root ,
130+ ) ;
131+
132+ if ( ruleInfo ) {
133+ registry . rules . set ( className , ruleInfo ) ;
134+ }
135+ }
136+
70137 const currentRefCount = registry . refCounts . get ( generatedClass ) || 0 ;
71138 registry . refCounts . set ( generatedClass , currentRefCount + 1 ) ;
72139 // If this class was previously marked as unused, clear that state now
@@ -100,6 +167,38 @@ export class StyleInjector {
100167 generatedClass ,
101168 ) ;
102169 if ( restored ) {
170+ // Verify that the rules still exist in the live CSSOM after restoration
171+ const hasLiveCSS = this . hasLiveRules ( registry , generatedClass ) ;
172+ if ( ! hasLiveCSS ) {
173+ // Rules were deleted from CSSOM, reinsert them
174+ const className = generatedClass ;
175+ const rulesToInsert = rules . map ( ( r ) => {
176+ // Increase specificity for class-based selectors by duplicating the class
177+ if ( r . selector . startsWith ( '.' ) && / ^ \. t \d + / . test ( r . selector ) ) {
178+ const classMatch = r . selector . match ( / ^ \. t \d + / ) ;
179+ if ( classMatch ) {
180+ const baseClass = classMatch [ 0 ] ;
181+ return {
182+ ...r ,
183+ selector : baseClass + r . selector ,
184+ } as StyleRule ;
185+ }
186+ }
187+ return r ;
188+ } ) ;
189+
190+ const ruleInfo = this . sheetManager . insertRule (
191+ registry ,
192+ rulesToInsert ,
193+ className ,
194+ root ,
195+ ) ;
196+
197+ if ( ruleInfo ) {
198+ registry . rules . set ( className , ruleInfo ) ;
199+ }
200+ }
201+
103202 // Update metrics
104203 if ( registry . metrics ) {
105204 registry . metrics . hits ++ ;
@@ -124,36 +223,36 @@ export class StyleInjector {
124223 const rulesToInsert =
125224 generatedClass && generatedClass !== className
126225 ? rules . map ( ( r ) => {
127- if ( r . selector . startsWith ( '.' + generatedClass ) ) {
128- const newSelector =
129- '.' + className + r . selector . slice ( generatedClass . length + 1 ) ;
130- // Increase specificity by duplicating the class for class-based selectors
131- const specificSelector =
132- newSelector . startsWith ( '.' + className ) &&
226+ if ( r . selector . startsWith ( '.' + generatedClass ) ) {
227+ const newSelector =
228+ '.' + className + r . selector . slice ( generatedClass . length + 1 ) ;
229+ // Increase specificity by duplicating the class for class-based selectors
230+ const specificSelector =
231+ newSelector . startsWith ( '.' + className ) &&
133232 / ^ \. t \d + / . test ( newSelector )
134- ? '.' + className + newSelector
135- : newSelector ;
233+ ? '.' + className + newSelector
234+ : newSelector ;
235+ return {
236+ ...r ,
237+ selector : specificSelector ,
238+ } as StyleRule ;
239+ }
240+ return r ;
241+ } )
242+ : rules . map ( ( r ) => {
243+ // Increase specificity for class-based selectors by duplicating the class
244+ if ( r . selector . startsWith ( '.' ) && / ^ \. t \d + / . test ( r . selector ) ) {
245+ const classMatch = r . selector . match ( / ^ \. t \d + / ) ;
246+ if ( classMatch ) {
247+ const baseClass = classMatch [ 0 ] ;
136248 return {
137249 ...r ,
138- selector : specificSelector ,
250+ selector : baseClass + r . selector ,
139251 } as StyleRule ;
140252 }
141- return r ;
142- } )
143- : rules . map ( ( r ) => {
144- // Increase specificity for class-based selectors by duplicating the class
145- if ( r . selector . startsWith ( '.' ) && / ^ \. t \d + / . test ( r . selector ) ) {
146- const classMatch = r . selector . match ( / ^ \. t \d + / ) ;
147- if ( classMatch ) {
148- const baseClass = classMatch [ 0 ] ;
149- return {
150- ...r ,
151- selector : baseClass + r . selector ,
152- } as StyleRule ;
153- }
154- }
155- return r ;
156- } ) ;
253+ }
254+ return r ;
255+ } ) ;
157256
158257 // Before inserting, auto-register @property for any color custom properties being defined.
159258 // Fast parse: split declarations by ';' and match "--*-color:"
@@ -196,7 +295,7 @@ export class StyleInjector {
196295
197296 return {
198297 className,
199- dispose : ( ) => { } ,
298+ dispose : ( ) => { } ,
200299 } ;
201300 }
202301
@@ -229,7 +328,7 @@ export class StyleInjector {
229328 const registry = this . sheetManager . getRegistry ( root ) ;
230329
231330 if ( ! rules || rules . length === 0 ) {
232- return { dispose : ( ) => { } } ;
331+ return { dispose : ( ) => { } } ;
233332 }
234333
235334 // Use a non-tasty identifier to avoid any collisions with .t{number} classes
@@ -447,7 +546,7 @@ export class StyleInjector {
447546 if ( Object . keys ( steps ) . length === 0 ) {
448547 return {
449548 toString : ( ) => '' ,
450- dispose : ( ) => { } ,
549+ dispose : ( ) => { } ,
451550 } ;
452551 }
453552
@@ -474,7 +573,7 @@ export class StyleInjector {
474573 if ( ! info ) {
475574 return {
476575 toString : ( ) => '' ,
477- dispose : ( ) => { } ,
576+ dispose : ( ) => { } ,
478577 } ;
479578 }
480579
0 commit comments