Skip to content

Commit 6f129fc

Browse files
committed
fix(tasty): style check
1 parent d9b6643 commit 6f129fc

File tree

1 file changed

+129
-30
lines changed

1 file changed

+129
-30
lines changed

src/tasty/injector/injector.ts

Lines changed: 129 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)