summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/declarations.ts102
-rw-r--r--src/index.ts35
2 files changed, 129 insertions, 8 deletions
diff --git a/src/declarations.ts b/src/declarations.ts
new file mode 100644
index 0000000..6c33fda
--- /dev/null
+++ b/src/declarations.ts
@@ -0,0 +1,102 @@
+import { EvalActions, evalExpr } from './eval'
+import { Expr, Selector, SelectorComp, parseDeclarations } from './parser'
+import { match, matchString } from './utils/adt'
+
+export interface Declaration {
+ selector: Selector
+ properties: Map<string, Expr>
+}
+
+export interface DeclarationEval {
+ selector: Selector
+ properties: Array<readonly [string, string]>
+}
+
+export const evaluateDeclaration = async (
+ { selector, properties }: Declaration,
+ actions: EvalActions,
+): Promise<DeclarationEval> => {
+ if (properties.size === 0) return { selector, properties: [] }
+
+ const props = await Promise.all(
+ [...properties.entries()].map(async ([key, expr]) => {
+ // Ignore errors?
+ const result = await evalExpr(expr, actions).catch(e => console.warn(e))
+ return [key, result ?? ''] as const
+ }),
+ )
+
+ return { selector, properties: props }
+}
+
+const instanceCountMap = new Map<string, number>()
+const getUniqueInstanceId = (id: string) => {
+ const instanceCount = instanceCountMap.get(id) ?? 0
+ instanceCountMap.set(id, instanceCount + 1)
+ return `${id}--index-${instanceCount}`
+}
+
+export const toDeclaration = (expr: Expr): Declaration | undefined => {
+ let selector: Selector | undefined
+ const properties: Map<string, Expr> = new Map()
+ let isInstance = false
+
+ match(expr, {
+ Selector: sel => {
+ selector = sel
+ },
+ Call: ({ name, args }) => {
+ matchString(name, {
+ instance: () => {
+ isInstance = true
+ const [sel, map] = args
+ match(sel, {
+ Selector: sel => {
+ selector = sel
+ },
+ _: _ => {},
+ })
+ match(map, {
+ Call: ({ name, args }) => {
+ if (name !== 'map') return
+ for (const arg of args) {
+ match(arg, {
+ Pair: ({ key, value }) => properties.set(key, value),
+ _: _ => {},
+ })
+ }
+ },
+ })
+ },
+ _: () => {
+ throw new Error(`weird function in cssx-chi9ldren: ${name}`)
+ },
+ })
+ },
+ _: () => {},
+ })
+
+ if (!selector) return undefined
+
+ if (isInstance) {
+ const baseId = selector.id
+ selector.id = getUniqueInstanceId(selector.id)
+ selector.selectors.push(SelectorComp.Attr(['data-instance', baseId]))
+ }
+
+ return { selector, properties }
+}
+
+export const extractDeclaration = async (
+ input: string,
+ actions: EvalActions,
+): Promise<Array<DeclarationEval>> => {
+ const exprs = parseDeclarations(input)
+ const declrs = await Promise.all(
+ exprs
+ .map(toDeclaration)
+ .filter(declr => !!declr)
+ .map(declr => declr && evaluateDeclaration(declr, actions)),
+ )
+ return declrs.filter(declr => !!declr) as Array<DeclarationEval>
+}
diff --git a/src/index.ts b/src/index.ts
index 1c8184a..80e67d9 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,5 +1,7 @@
import { EvalActions, evalExpr } from './eval'
+import { extractDeclaration, DeclarationEval } from './declarations'
import { parse } from './parser'
+import { match } from './utils/adt'
const UNSET_PROPERTY_VALUE = '<unset>'
const EVENT_HANDLERS = {
@@ -36,9 +38,12 @@ export const getPropertyValue = ($element: Element, prop: string) => {
return !value || value === UNSET_PROPERTY_VALUE ? '' : value
}
-export const getChildrenIds = ($element: Element) => {
+export const getDeclarations = (
+ $element: Element,
+ actions: EvalActions,
+): Promise<Array<DeclarationEval>> => {
const value = getPropertyValue($element, '--cssx-children')
- return value.split(/(\s*,\s*)|\s+/g).filter(Boolean)
+ return extractDeclaration(value, actions)
}
const getEvalActions = ($element: Element, event: any): EvalActions => ({
@@ -132,22 +137,36 @@ export const manageElement = async (
const html = getPropertyValue($element, '--cssx-disgustingly-set-innerhtml')
if (html) $element.innerHTML = html.replace(/(^'|")|('|"$)/g, '')
- const childrenIds = getChildrenIds($element)
- if (childrenIds.length > 0) {
+ const declarations = await getDeclarations(
+ $element,
+ getEvalActions($element, null),
+ )
+ if (declarations.length > 0) {
const LAYER_CLASS = 'cssx-layer'
const $childrenRoot =
$element.querySelector(`:scope > .${LAYER_CLASS}`) ??
Object.assign(document.createElement('div'), { className: LAYER_CLASS })
$element.appendChild($childrenRoot)
- for (const childId of childrenIds) {
- const selector = childId.split('#')
- const [tag, id] = selector.length >= 2 ? selector : ['div', ...selector]
+ for (const declaration of declarations) {
+ const { tag, id, selectors } = declaration.selector
+ const tagName = tag || 'div'
+
let $child = $childrenRoot.querySelector(`:scope > #${id}`)
const isNewElement = !$child
if (!$child) {
- $child = Object.assign(document.createElement(tag || 'div'), { id })
+ $child = Object.assign(document.createElement(tagName), { id })
}
+
+ // Add selectors
+ for (const selector of selectors) {
+ match(selector, {
+ ClassName: cls =>
+ !$child?.classList.contains(cls) && $child?.classList.add(cls),
+ Attr: ([key, val]) => $child?.setAttribute(key, val),
+ })
+ }
+
$childrenRoot.appendChild($child)
await manageElement($child, isNewElement)
}