1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
import { Dependencies, evalExpr } from "./eval";
import { parse } from "./parser";
const UNSET_PROPERTY_VALUE = '<unset>';
const EVENT_HANDLERS = {
click: '--cssx-on-click',
load: '--cssx-on-load',
}
const injectStyles = () => {
const STYLE_TAG_CLASS = 'cssx-style-root';
if (document.querySelector(`.${STYLE_TAG_CLASS}`)) return;
const $style = document.createElement('style');
$style.className = STYLE_TAG_CLASS;
const properties = [
'--cssx-children',
...Object.values(EVENT_HANDLERS),
];
$style.textContent = `.cssx-layer {
${properties.map(p => `${p}: ${UNSET_PROPERTY_VALUE};`).join(' ')}
}`;
document.body.appendChild($style);
}
const getPropertyValue = ($element: HTMLElement, prop: string) => {
const value = `${getComputedStyle($element).getPropertyValue(prop)}`.trim()
return !value || value === UNSET_PROPERTY_VALUE ? '' : value;
};
const getChildrenIds = ($element: HTMLElement) => {
const value = getPropertyValue($element, '--cssx-children')
return value.split(/(\s*,\s*)|\s+/g).filter(Boolean)
}
const evalDeps = (_el: HTMLElement): Dependencies => ({
addClass: async (id, cls) => document.getElementById(id)?.classList.add(cls),
removeClass: async (id, cls) => document.getElementById(id)?.classList.remove(cls),
delay: delay => new Promise((res) => setTimeout(res, delay)),
jsEval: async js => (0, eval)(js),
})
const handleEvents = async ($element: HTMLElement) => {
for (const [event, property] of Object.entries(EVENT_HANDLERS)) {
const handlerExpr = getPropertyValue($element, property);
if (handlerExpr) {
($element as any)[`on${event}`] = async () => {
console.log(`Triggered event: ${event}`)
const exprs = parse(handlerExpr)
for (const expr of exprs) {
await evalExpr(expr, evalDeps($element))
}
};
}
}
};
let iters = 0;
const manageElement = async ($element: HTMLElement) => {
if (iters++ > 100) return; // NOTE: Temporary. To prevent infinite rec
await handleEvents($element);
const $childrenRoot = Object.assign(document.createElement('div'), {
className: 'cssx-layer',
});
$element.appendChild($childrenRoot);
const childrenIds = getChildrenIds($element);
for (const id of childrenIds) {
const $child = Object.assign(document.createElement('div'), { id });
$childrenRoot.appendChild($child);
await manageElement($child);
}
}
interface Options {
root?: HTMLElement;
}
const render = async ({ root = document.body }: Options = {}) => {
injectStyles();
await manageElement(root);
}
render();
|