From e4d70a54e97551f974a04379817e8baf152fa8d3 Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Sun, 13 Aug 2023 17:05:06 +0530 Subject: feat: adds applying css properties for instances + adds --cssx-text eval + fixes issues --- TODO.norg | 6 +- examples/counter/style.css | 1 - src/declarations.ts | 5 +- src/eval.ts | 2 +- src/index.ts | 158 ++++++++++++++++++++----------------- tests/fixtures/todo-app/index.html | 23 ++---- tests/signup.spec.ts | 9 ++- tests/todo-app.spec.ts | 7 +- 8 files changed, 110 insertions(+), 101 deletions(-) diff --git a/TODO.norg b/TODO.norg index cd74df4..5148115 100644 --- a/TODO.norg +++ b/TODO.norg @@ -15,9 +15,9 @@ - (x) `selector` parsing - (x) `map` data structure - (x) component system (with variables. `instance(button#my-btn)`) - - ( ) instance access - - ( ) More complex selector support for cssx-children - - ( ) `add-element` & `remove-element` + - (x) More complex selector support for cssx-children + - (x) `add-element` & `remove-element` + - ( ) access an instance of component - ( ) string concatenation - ( ) `request` error handling - ( ) keyboard events diff --git a/examples/counter/style.css b/examples/counter/style.css index b60785f..b193f23 100644 --- a/examples/counter/style.css +++ b/examples/counter/style.css @@ -1,6 +1,5 @@ body { --cssx-children: container todo-container; - --cssx-on-load: js(console.log('what have we done?!')); } #container { diff --git a/src/declarations.ts b/src/declarations.ts index 78c9801..17306bd 100644 --- a/src/declarations.ts +++ b/src/declarations.ts @@ -50,12 +50,15 @@ export const toDeclaration = (expr: Expr): Declaration | undefined => { instance: () => { isInstance = true const [sel, map] = args + + // Selector match(sel, { Selector: sel => { selector = sel }, _: _ => {}, }) + match(map, { Call: ({ name, args }) => { if (name !== 'map') return @@ -90,7 +93,7 @@ export const toDeclaration = (expr: Expr): Declaration | undefined => { export const expressionsToDeclrs = async ( exprs: Array, actions: EvalActions, -) => { +): Promise> => { const declrs = await Promise.all( exprs .map(toDeclaration) diff --git a/src/eval.ts b/src/eval.ts index 3d6734f..79eeb79 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -85,7 +85,7 @@ const getFunctions = (name: string, args: Expr[], actions: EvalActions) => var: async () => { const varName = await evalExpr(args[0], actions) - const defaultValue = await evalExpr(args[1], actions) + const defaultValue = args[1] && (await evalExpr(args[1], actions)) return varName && (actions.getVariable(varName) ?? defaultValue) }, update: async () => { diff --git a/src/index.ts b/src/index.ts index 81e5b88..c90588b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -51,72 +51,75 @@ export const getDeclarations = ( return extractDeclaration(value, actions) } -const getEvalActions = ($element: Element, event: any): EvalActions => ({ - 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), - loadCssx: async (id, url) => - new Promise((resolve, reject) => { - const $link = Object.assign(document.createElement('link'), { - href: url, - rel: 'stylesheet', - }) - $link.onload = () => { - const $el = document.getElementById(id) - // NOTE: Maybe create and append to body if no root? - if ($el) { - manageElement($el) - resolve(id) - } else { - console.error(`[CSSX] Unable to find root for ${id}`) - reject(`[CSSX] Unable to find root for ${id}`) +const getEvalActions = ( + $element: Element, + { event = null, pure = false }: { event?: any; pure?: boolean }, +): EvalActions => { + const actions: EvalActions = { + 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), + loadCssx: async (id, url) => + new Promise((resolve, reject) => { + const $link = Object.assign(document.createElement('link'), { + href: url, + rel: 'stylesheet', + }) + $link.onload = () => { + const $el = document.getElementById(id) + if ($el) { + manageElement($el) + resolve(id) + } else { + console.error(`[CSSX] Unable to find root for ${id}`) + reject(`[CSSX] Unable to find root for ${id}`) + } } + document.body.appendChild($link) + }), + getVariable: async varName => getPropertyValue($element, varName), + updateVariable: async (targetId, varName, value) => { + const $el = document.getElementById(targetId) + if ($el) { + $el.style.setProperty(varName, JSON.stringify(value)) } - document.body.appendChild($link) - }), - getVariable: async varName => getPropertyValue($element, varName), - updateVariable: async (targetId, varName, value) => { - const $el = document.getElementById(targetId) - if ($el) { - $el.style.setProperty(varName, JSON.stringify(value)) - } - }, - setAttribute: async (id, name, value) => { - const $el = id ? document.getElementById(id) : $element - if (value) { - $el?.setAttribute(name, value) - } else { - $el?.removeAttribute(name) - } - }, - getAttribute: async (id, name) => { - const $el = id ? document.getElementById(id) : $element - return $el?.getAttribute(name) ?? undefined - }, - withEvent: async fn => fn(event), - getFormData: async () => - $element.nodeName === 'FORM' - ? new FormData($element as HTMLFormElement) - : undefined, - sendRequest: async ({ url, method, data }) => { - await fetch(url, { method, body: data }) - // TODO: Handle response? - }, - addChildren: async (id, children) => { - const $el = document.getElementById(id) - const declarations = await expressionsToDeclrs( - children, - getEvalActions($element, event), - ) - $el && createLayer(declarations, $el) - }, - removeElement: async id => { - const $el = id ? document.getElementById(id) : $element - $el?.parentNode?.removeChild($el) - }, -}) + }, + setAttribute: async (id, name, value) => { + const $el = id ? document.getElementById(id) : $element + if (value) { + $el?.setAttribute(name, value) + } else { + $el?.removeAttribute(name) + } + }, + getAttribute: async (id, name) => { + const $el = id ? document.getElementById(id) : $element + return $el?.getAttribute(name) ?? undefined + }, + withEvent: async fn => event && fn(event), + getFormData: async () => + $element.nodeName === 'FORM' + ? new FormData($element as HTMLFormElement) + : undefined, + sendRequest: async ({ url, method, data }) => { + await fetch(url, { method, body: data }) + // TODO: Handle response? + }, + addChildren: async (id, children) => { + const $el = document.getElementById(id) + const declarations = await expressionsToDeclrs(children, actions) + $el && createLayer(declarations, $el) + }, + removeElement: async id => { + const $el = id ? document.getElementById(id) : $element + $el?.parentNode?.removeChild($el) + }, + } + return actions +} export const handleEvents = async ( $element: Element, @@ -129,7 +132,7 @@ export const handleEvents = async ( const eventHandler = async (event: any) => { const exprs = parse(handlerExpr) for (const expr of exprs) { - await evalExpr(expr, getEvalActions($element, event)) + await evalExpr(expr, getEvalActions($element, { event })) } } @@ -150,6 +153,7 @@ const declarationToElement = ( const tagName = tag || 'div' let $child = $parent?.querySelector(`:scope > #${id}`) + const isNewElement = !$child if (!$child) { $child = Object.assign(document.createElement(tagName), { id }) } @@ -163,7 +167,11 @@ const declarationToElement = ( }) } - return { node: $child, isNewElement: !$child } + for (const [key, value] of declaration.properties) { + ;($child as HTMLElement)?.style.setProperty(key, value) + } + + return { node: $child, isNewElement } } const createLayer = async ( @@ -193,16 +201,24 @@ export const manageElement = async ( ) => { await handleEvents($element, isNewElement) + const actions = getEvalActions($element, { pure: true }) + const text = getPropertyValue($element, '--cssx-text') - if (text) $element.textContent = text + if (text) { + const exprs = parse(text) + try { + $element.textContent = + (exprs[0] ? await evalExpr(exprs[0], actions) : text) ?? text + } catch (e) { + console.log(e, exprs) + $element.textContent = text + } + } const html = getPropertyValue($element, '--cssx-disgustingly-set-innerhtml') if (html) $element.innerHTML = html.replace(/(^'|")|('|"$)/g, '') - const declarations = await getDeclarations( - $element, - getEvalActions($element, null), - ) + const declarations = await getDeclarations($element, actions) if (declarations.length > 0) { await createLayer(declarations, $element) } diff --git a/tests/fixtures/todo-app/index.html b/tests/fixtures/todo-app/index.html index 66a9798..55996f3 100644 --- a/tests/fixtures/todo-app/index.html +++ b/tests/fixtures/todo-app/index.html @@ -10,7 +10,11 @@ #task-input-form { --cssx-on-submit: prevent-default() - js-eval('console.log("todo: implement add new task")') + add-children(task-list, + instance(li#task-item, map( + --text: "Hello world", + )) + ) ; --cssx-children: @@ -21,24 +25,13 @@ #text-input {} #create-task-btn { - --cssx-text: Submit; + --cssx-text: "Submit"; } - #task-list { - --cssx-children: - instance(li#task-item, map( - --text: "hello world", - --checked: 0 - )) - instance(li#task-item, map( - --text: "coolio stuff", - --checked: 0 - )) - ; - } + #task-list { } [data-instance="task-item"] { - --text: "default text"; + --text: default text; --checked: 0; --cssx-text: var(--text); diff --git a/tests/signup.spec.ts b/tests/signup.spec.ts index 1317049..4764d7f 100644 --- a/tests/signup.spec.ts +++ b/tests/signup.spec.ts @@ -3,6 +3,7 @@ import { waitFor, getByText, getByTestId, + prettyDOM, } from '@testing-library/dom' import '@testing-library/jest-dom' import { delay, loadHTMLFixture } from './util' @@ -33,9 +34,9 @@ describe('signup example', () => { beforeEach(async () => { const $showFormBtn = document.getElementById('show-form-btn')! fireEvent.click($showFormBtn) - await waitFor(() => expect($showFormBtn).not.toBeVisible()) - const $form = document.getElementById('signup-form')! - expect($form).toBeVisible() + await waitFor(() => + expect(document.getElementById('signup-form')).toBeVisible(), + ) await delay(100) // Wait for mounting }) @@ -48,7 +49,7 @@ describe('signup example', () => { const $password = getByTestId(document.body, 'password') $password.value = 'password' - await delay(2000) + await delay(100) // Submit form const $submitBtn = getByText(document.body, 'Submit') diff --git a/tests/todo-app.spec.ts b/tests/todo-app.spec.ts index 1e0cd72..2bdcdee 100644 --- a/tests/todo-app.spec.ts +++ b/tests/todo-app.spec.ts @@ -23,11 +23,8 @@ describe('todo-app example', () => { await delay(100) - console.log(prettyDOM(document.body)) - console.log() - console.log() - console.log() - console.log() + console.log(prettyDOM(document.getElementById('task-list')!)) + console.log('-------------------') }) }) }) -- cgit v1.3.1