Files
freeCodeCamp/client/src/utils/css-help.ts
Shaun Hamilton af2a7658b4 feat(client): remove whitespace from calculated values (#42400)
* feat(client?): remove whitespace for calculated values

* feat(client): remove whitespace from styledeclaration

* do not automatically strip

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

* fix: include all properties of CSSStyleDeclaration

* fix test for getPropVal

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
2021-06-10 14:59:02 -07:00

120 lines
3.9 KiB
TypeScript

export interface ExtendedStyleRule extends CSSStyleRule {
isDeclaredAfter: (selector: string) => boolean;
}
interface ExtendedStyleDeclaration extends CSSStyleDeclaration {
getPropVal: (prop: string, strip?: boolean) => string;
}
const getIsDeclaredAfter = (styleRule: CSSStyleRule) => (selector: string) => {
const cssStyleRules = Array.from(
styleRule.parentStyleSheet?.cssRules || []
)?.filter(ele => ele.type === CSSRule.STYLE_RULE) as CSSStyleRule[];
const previousStyleRule = cssStyleRules.find(
ele => ele?.selectorText === selector
);
if (!previousStyleRule) return false;
const currPosition = Array.from(
styleRule.parentStyleSheet?.cssRules || []
).indexOf(styleRule);
const prevPosition = Array.from(
previousStyleRule?.parentStyleSheet?.cssRules || []
).indexOf(previousStyleRule);
return currPosition > prevPosition;
};
class CSSHelp {
doc: HTMLDocument;
constructor(doc: HTMLDocument) {
this.doc = doc;
}
private _getStyleRules() {
const styleSheet = this.getStyleSheet();
return this.styleSheetToCssRulesArray(styleSheet).filter(
ele => ele.type === CSSRule.STYLE_RULE
) as CSSStyleRule[];
}
getStyleDeclarations(selector: string): CSSStyleDeclaration[] {
return this._getStyleRules()
?.filter(ele => ele?.selectorText === selector)
.map(x => x.style);
}
getStyle(selector: string): ExtendedStyleDeclaration | null {
const style = this._getStyleRules().find(
ele => ele?.selectorText === selector
)?.style as ExtendedStyleDeclaration | undefined;
if (!style) return null;
style.getPropVal = (prop: string, strip = false) => {
return strip
? style.getPropertyValue(prop).replace(/\s+/g, '')
: style.getPropertyValue(prop);
};
return style;
}
getStyleRule(selector: string): ExtendedStyleRule | null {
const styleRule = this._getStyleRules()?.find(
ele => ele?.selectorText === selector
);
if (styleRule) {
return {
...styleRule,
isDeclaredAfter: (selector: string) =>
getIsDeclaredAfter(styleRule)(selector)
};
} else {
return null;
}
}
getCSSRules(element?: string): CSSRule[] {
const styleSheet = this.getStyleSheet();
const cssRules = this.styleSheetToCssRulesArray(styleSheet);
switch (element) {
case 'media':
return cssRules.filter(ele => ele.type === CSSRule.MEDIA_RULE);
case 'fontface':
return cssRules.filter(ele => ele.type === CSSRule.FONT_FACE_RULE);
case 'import':
return cssRules.filter(ele => ele.type === CSSRule.IMPORT_RULE);
case 'keyframes':
return cssRules.filter(ele => ele.type === CSSRule.KEYFRAMES_RULE);
default:
return cssRules;
}
}
isPropertyUsed(property: string): boolean {
return this._getStyleRules().some(ele =>
ele.style?.getPropertyValue(property)
);
}
getRuleListsWithinMedia(mediaText: string): CSSStyleRule[] {
const medias = this.getCSSRules('media') as CSSMediaRule[];
const cond = medias?.find(x => x?.media?.mediaText === mediaText);
const cssRules = cond?.cssRules;
return Array.from(cssRules || []) as CSSStyleRule[];
}
getStyleSheet(): CSSStyleSheet | null {
// TODO: Change selector to match exactly 'styles.css'
const link: HTMLLinkElement | null = this.doc?.querySelector(
"link[href*='styles']"
);
// Most* browser extensions inject styles with class/media attributes
const style: HTMLStyleElement | null = this.doc?.querySelector(
'style:not([class]):not([media])'
);
if (link?.sheet?.cssRules?.length) {
return link.sheet;
} else if (style) {
return style.sheet;
} else {
return null;
}
}
styleSheetToCssRulesArray(
styleSheet: ReturnType<CSSHelp['getStyleSheet']>
): CSSRule[] {
return Array.from(styleSheet?.cssRules || []);
}
}
export default CSSHelp;