summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--examples/todo-list/style.css80
-rw-r--r--src/eval.ts13
-rw-r--r--src/index.ts27
3 files changed, 100 insertions, 20 deletions
diff --git a/examples/todo-list/style.css b/examples/todo-list/style.css
index d4cd391..35e44fd 100644
--- a/examples/todo-list/style.css
+++ b/examples/todo-list/style.css
@@ -3,6 +3,7 @@
--color-accent: #5180e9;
font-size: 16px;
+ font-family: sans-serif;
color: #555;
--cssx-children: main#container;
@@ -80,47 +81,102 @@ body * { box-sizing: border-box; }
[data-instance=task-item] {
--text: "default text";
--done: "false";
+ --is-editing: "false";
padding: 1rem;
- cursor: pointer;
display: flex;
width: 100%;
align-items: center;
--cssx-on-mount: update(--task-item-id, attr(data-element));
- --cssx-on-click: update(--done, if(get-var(--done), "false", "true"));
- --cssx-on-update: update(":scope [data-element=checkbox]", --checked, if(get-var(--done), true, false));
+ --cssx-on-update:
+ update(
+ ':scope [data-element=edit-task-form]',
+ display,
+ if(get-var(--is-editing), 'block', 'none')
+ )
+ update(
+ ':scope [data-element=task-text]',
+ display,
+ if(get-var(--is-editing), 'none', 'block')
+ )
+ if(get-var(--is-editing),
+ set-attr(':scope [data-element=edit-task-input]', value, get-var(--text)),
+ "")
+ if(get-var(--is-editing),
+ call(':scope [data-element=edit-task-input]', focus),
+ "")
+ ;
+
+ --cssx-children:
+ div#checkbox
+ div#task-text
+ form#edit-task-form
+ button#delete-task
+ ;
+}
- --cssx-children: div#checkbox div#task-text button#delete-task;
+[data-instance=task-item]:not(:first-child) {
+ content: "";
+ border-top: 1px solid var(--color-gray);
}
-[data-instance=task-item] [data-element=task-text] {
+[data-element=task-text] {
flex: 2;
+
+ --cssx-on-click: update(get-var(--task-item-id), --is-editing, "true");
}
-[data-instance=task-item] [data-element=task-text]::after {
+[data-element=task-text]::after {
content: var(--text);
padding-left: 0.8rem;
}
-[data-instance=task-item]:not(:first-child) {
- content: "";
- border-top: 1px solid var(--color-gray);
+[data-element=edit-task-form] {
+ display: none;
+ width: 100%;
+ padding: 0 .8rem;
+
+ --cssx-children: input#edit-task-input;
+ --cssx-on-submit:
+ prevent-default()
+ update(
+ get-var(--task-item-id),
+ --text,
+ attr(':scope [data-element=edit-task-input]', value)
+ )
+ update(get-var(--task-item-id), --is-editing, "false")
+ ;
+}
+[data-element=edit-task-input] {
+ display: block;
+ width: 100%;
+ border: none;
+ border-bottom: 1px solid gray;
+ font-size: 1rem;
+}
+[data-element=edit-task-input]:focus {
+ outline: 1px solid #aaa;
}
-[data-instance=task-item] [data-element=checkbox] {
+[data-element=checkbox] {
--checked: false;
width: 18px;
height: 18px;
border: 2px solid gray;
background-color: transparent;
+ cursor: pointer;
+
+ --cssx-on-click: update(--checked, if(get-var(--checked), false, true));
--cssx-on-update:
- update(background-color, if(get-var(--checked), get-var(--color-accent), transparent));
+ update(get-var(--task-item-id), --done, get-var(--checked))
+ update(background-color, if(get-var(--checked), get-var(--color-accent), transparent))
+ ;
}
-[data-instance=task-item] [data-element=delete-task] {
+[data-element=delete-task] {
--cssx-text: 'Delete';
--cssx-on-click: remove-element(get-var(--task-item-id));
}
diff --git a/src/eval.ts b/src/eval.ts
index 445cb8f..aba31df 100644
--- a/src/eval.ts
+++ b/src/eval.ts
@@ -31,6 +31,11 @@ export interface EvalActions {
}): Promise<void>
addChildren(id: string, children: Expr[]): Promise<void>
removeElement(id: string | undefined): Promise<void>
+ callMethod(
+ id: string | undefined,
+ method: string,
+ args: EvalValue[],
+ ): Promise<void>
// calculate ??
}
@@ -154,6 +159,14 @@ const getFunctions = (name: string, args: Expr[], actions: EvalActions) => {
actions.removeElement(
(args[0] && (await evalExpr(args[0], actions))) ?? undefined,
),
+ call: async () => {
+ const [id, method, ...methodArgs] = await Promise.all(
+ args.map(a => evalExpr(a, actions)),
+ )
+ if (id && method) {
+ actions.callMethod(id, method, methodArgs)
+ }
+ },
_: () => Promise.reject(new Error('not supposed to be here')),
})
diff --git a/src/index.ts b/src/index.ts
index 2392c9e..ec22b70 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -52,7 +52,8 @@ export const injectStyles = () => {
}
export const getPropertyValue = ($element: HTMLElement, prop: string) => {
- const value = `${getComputedStyle($element).getPropertyValue(prop)}`.trim()
+ let value = `${getComputedStyle($element).getPropertyValue(prop)}`.trim()
+ value = value.replace(/(^['"])|(['"]$)/gi, '')
return !value || value === UNSET_PROPERTY_VALUE ? '' : value
}
@@ -68,13 +69,19 @@ const getElement = (
id: string,
$node: HTMLElement | Document = document,
): HTMLElement | null => {
- // TODO: Please no ternary
- const [$element, selector] = /^('|")?[a-z0-9_-]+\1$/gi.test(id)
- ? [document, `[data-element=${id}]`]
- : /^:scope/i.test(id)
- ? [$node, id]
- : [document, id]
- return $element.querySelector<HTMLElement>(selector)
+ let $element: Node | null = document,
+ selector: string = id
+
+ if (/^('|")?[a-z0-9_-]+\1$/gi.test(id)) {
+ selector = `[data-element=${id}]`
+ } else if (/^:scope/i.test(id)) {
+ $element = $node
+ } else if (/^:parent\s+/i.test(id)) {
+ $element = $node.parentNode
+ selector = id.replace(/^:parent\s+/i, '')
+ }
+
+ return ($element as Element)?.querySelector<HTMLElement>(selector)
}
const getEvalActions = (
@@ -159,6 +166,10 @@ const getEvalActions = (
const $el = id ? getElement(id, $element) : $element
$el?.parentNode?.removeChild($el)
},
+ callMethod: async (id, method, args) => {
+ const $el = id ? getElement(id, $element) : $element
+ ;($el as any)[method].call($el, args)
+ },
}
return actions
}