vlens
Version:
Data Centric Routing & Rendering Mini-Framework
122 lines (108 loc) • 3.41 kB
text/typescript
import type * as csstype from "csstype";
import * as tester from "./tester";
// based on cxs
interface ISheet {
insertRule(rule: string): void;
}
class Sheet {
domSheet: CSSStyleSheet | null;
rulesCount = 0;
constructor() {
let el = document.createElement("style");
el = document.head.appendChild(el);
if (!el.sheet) {
console.error("failed to created style element");
}
this.domSheet = el.sheet;
}
insertRule(rule: string) {
this.domSheet!.insertRule(rule, this.rulesCount);
this.rulesCount++;
}
}
class FakeSheet {
insertRule(rule: string) {
}
}
let sheet: ISheet = BROWSER ? new Sheet() : new FakeSheet();
export type RuleDefinition = csstype.StandardProperties<string, string> & {
[key: string]: string | number | RuleDefinition;
}
function combineSelectorForNestedRule(selector: string, child: string): string {
let result: string[] = [];
for (let p of selector.split(',')) {
p = p.trim();
for (let c of child.split(',')) {
c = c.trim();
if (c.startsWith(':')) {
result.push(p + c);
} else if (c.startsWith("&")) {
result.push(selector + c.substring(1))
} else if (c.startsWith('@')) {
result.push(c + ' ' + p)
} else {
result.push(p + ' ' + c)
}
}
}
return result.join(', ');
}
export function block(body: string) {
sheet.insertRule(body);
}
export function rule(sel: string, body: RuleDefinition) {
let subrules: [string, RuleDefinition][] = [];
let bodyLines: string[] = [];
for (const [key, value] of Object.entries(body)) {
if (typeof value === 'object') {
subrules.push([combineSelectorForNestedRule(sel, key), value]);
continue;
} else {
let property = key.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
bodyLines.push(`${property}: ${value}`);
}
}
sheet.insertRule(sel + "{" + bodyLines.join(";") + "}\n");
for (let [sel1, body1] of subrules) {
rule(sel1, body1);
}
}
const usedNames = new Set<string>();
export function cls(name: string, body: RuleDefinition) {
if (usedNames.has(name)) {
// find a new name
for (let i = 0; i < 100; i++) {
let newName = name + '-' + i;
if (!usedNames.has(newName)) {
name = newName;
break;
}
}
}
usedNames.add(name);
rule("." + name, body);
return name;
}
export function tests() {
tester.test('css rule combination', (t: tester.Test) => {
{
let sel = ".abc";
let child = "p";
let expected = ".abc p";
t.equal(expected, combineSelectorForNestedRule(sel, child), "expected", "actual");
}
{
let sel = ".abc";
let child = "p, b";
let expected = ".abc p, .abc b";
t.equal(expected, combineSelectorForNestedRule(sel, child), "expected", "actual");
}
{
// pseudo selectors
let sel = ".abc";
let child = ":hover";
let expected = ".abc:hover";
t.equal(expected, combineSelectorForNestedRule(sel, child), "expected", "actual");
}
})
}