@aesthetic/style
Version:
A low-level, high-performance, atomic-based CSS-in-JS style engine.
154 lines (131 loc) • 3.38 kB
text/typescript
/* eslint-disable @typescript-eslint/prefer-string-starts-ends-with */
import { AtRule, CSS, Sheet, Value } from '@aesthetic/types';
import { hyphenate } from '@aesthetic/utils';
import { IMPORT_RULE, STYLE_RULE } from './constants';
export function insertRule(sheet: Sheet, rule: CSS, index?: number): number {
try {
return sheet.insertRule(rule, index ?? sheet.cssRules.length);
} catch {
// Vendor prefixed properties, pseudos, etc, that are inserted
// into different vendors will trigger a failure. For example,
// `-moz` or `-ms` being inserted into WebKit.
// There's no easy way around this, so let's just ignore the
// error so that subsequent styles are inserted.
// istanbul ignore next
return -1;
}
}
export function insertAtRule(sheet: Sheet, rule: CSS): number {
const { length } = sheet.cssRules;
let index = 0;
// At-rules must be inserted before normal style rules.
for (let i = 0; i <= length; i += 1) {
index = i;
if (sheet.cssRules[i]?.type === STYLE_RULE) {
break;
}
}
return insertRule(sheet, rule, index);
}
export function insertImportRule(sheet: Sheet, rule: CSS): number {
const { length } = sheet.cssRules;
let index = 0;
// Import rules must be inserted at the top of the style sheet,
// but we also want to persist the existing order.
for (let i = 0; i <= length; i += 1) {
index = i;
if (sheet.cssRules[i]?.type !== IMPORT_RULE) {
break;
}
}
return insertRule(sheet, rule, index);
}
export function isAtRule(value: string): value is AtRule {
return value[0] === '@';
}
export function isImportRule(value: string): boolean {
// eslint-disable-next-line no-magic-numbers
return value.slice(0, 7) === '@import';
}
export function isNestedSelector(value: string): boolean {
const char = value[0];
return (
char === ':' ||
char === '[' ||
char === '>' ||
char === '~' ||
char === '+' ||
char === '*' ||
char === '|'
);
}
const unitlessProperties = new Set<string>();
[
'animationIterationCount',
'borderImage',
'borderImageOutset',
'borderImageSlice',
'borderImageWidth',
'columnCount',
'columns',
'flex',
'flexGrow',
'flexPositive',
'flexShrink',
'flexNegative',
'flexOrder',
'fontWeight',
'gridArea',
'gridRow',
'gridRowEnd',
'gridRowSpan',
'gridRowStart',
'gridColumn',
'gridColumnEnd',
'gridColumnSpan',
'gridColumnStart',
'lineClamp',
'lineHeight',
'maskBorder',
'maskBorderOutset',
'maskBorderSlice',
'maskBorderWidth',
'opacity',
'order',
'orphans',
'tabSize',
'widows',
'zIndex',
'zoom',
// SVG
'fillOpacity',
'floodOpacity',
'stopOpacity',
'strokeDasharray',
'strokeDashoffset',
'strokeMiterlimit',
'strokeOpacity',
'strokeWidth',
].forEach((property) => {
unitlessProperties.add(property);
unitlessProperties.add(hyphenate(property));
});
export function isUnitlessProperty(property: string): boolean {
return unitlessProperties.has(property);
}
export function isValidValue(property: string, value: unknown): value is Value {
if (value === undefined) {
return false;
}
if (value === null || value === true || value === false || value === '') {
if (__DEV__) {
// eslint-disable-next-line no-console
console.warn(`Invalid value "${value}" for "${property}".`);
}
return false;
}
return true;
}
export function isVariable(value: string): boolean {
return value.slice(0, 2) === '--';
}