From a3ad7d8a5407635b876869ba5b9ab097a0f6835e Mon Sep 17 00:00:00 2001 From: Valerii <92913245+krulod@users.noreply.github.com> Date: Fri, 20 Oct 2023 10:54:44 +0300 Subject: [PATCH] feat(jsx): support 'style' and 'styles' props (#674) * feat(jsx): support 'style' and 'styles' props * feat(jsx): add $attrs attribute --- packages/jsx/src/index.ts | 81 +++++++++++++++++++++++---------------- packages/jsx/src/jsx.d.ts | 1 + 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/packages/jsx/src/index.ts b/packages/jsx/src/index.ts index 9b91e9e8c..2ec835393 100644 --- a/packages/jsx/src/index.ts +++ b/packages/jsx/src/index.ts @@ -1,4 +1,5 @@ import { + Action, atom, createCtx, Ctx, @@ -13,7 +14,7 @@ import { parseAtoms } from '@reatom/lens' declare type JSXElement = JSX.Element export type { JSXElement, JSX } -type ElementTag = keyof JSX.HTMLElementTags | keyof JSX.SVGElementTags +export type ElementTag = keyof JSX.HTMLElementTags | keyof JSX.SVGElementTags export type Component = (props: Props) => JSXElement @@ -42,6 +43,47 @@ export const reatomJsx = (ctx: Ctx) => { }) } + let create = (tag: string, props: Rec) => { + let element = + tag === 'svg' + ? document.createElementNS('http://www.w3.org/2000/svg', tag) + : document.createElement(tag) + + for (let k in props) { + if (k === 'children') continue + bindAttr(element, k, props[k]) + } + + render(element, props.children ?? []) + + return element + } + + const bindAttr = (element: any, key: any, val: any) => { + if (key === '$attrs') { + const recs = Array.isArray(val) ? val : [val] + for (const rec of recs) { + for (const k in rec) bindAttr(element, k, rec[k]) + } + } else if (isAtom(val)) { + if (val.__reatom.isAction) { + element[key] = (...args: any) => (val as Action)(ctx, ...args) + } else { + // TODO handle unsubscribe! + var un: undefined | Unsubscribe = ctx.subscribe(val, (val) => + !un || element.isConnected ? renderAttr(element, key, val) : un(), + ) + unlink(element, un) + } + } else renderAttr(element, key, val) + } + + const renderAttr = (element: any, key: any, val: any) => { + if (key === 'style') { + for (const style in val) element.style.setProperty(style, val[style]) + } else element[key] = val + } + let render = (parent: Element, children: JSXElement[]) => { // TODO support Atom let walk = (child: any) => { @@ -82,47 +124,22 @@ export const reatomJsx = (ctx: Ctx) => { children.forEach(walk) } - let h = ((tag: any, props: Rec, ...children: any[]) => { + let h = ((tag: any, props: Rec, ...children: JSXElement[]) => { if (tag === hf) return children + props.children = children + if (typeof tag === 'function') { - ;(props ??= {}).children = children return tag(props) } - let element = - tag === 'svg' - ? document.createElementNS('http://www.w3.org/2000/svg', tag) - : document.createElement(tag) - - for (let k in props) { - let prop = props[k] - if (isAtom(prop)) { - if (prop.__reatom.isAction) { - element[k] = (...a: any[]) => prop(ctx, ...a) - } else { - // TODO handle unsubscribe! - var un: undefined | Unsubscribe = ctx.subscribe(prop, (v) => - !un || element.isConnected ? (element[k] = v) : un(), - ) - - unlink(element, un) - } - } else { - element[k] = prop - } - } - - render(element, children) - - return element + return create(tag, props) }) as ( tag: T, props: InferProps, ...children: JSXElement[] ) => Element - /** Fragment */ let hf = noop let mount = (target: Element, child: JSXElement) => { @@ -144,8 +161,8 @@ export const reatomJsx = (ctx: Ctx) => { }) } - return { h, hf, mount } + return { h, hf, mount, create } } export const ctx = createCtx() -export const { h, hf, mount } = reatomJsx(ctx) +export const { h, hf, mount, create } = reatomJsx(ctx) diff --git a/packages/jsx/src/jsx.d.ts b/packages/jsx/src/jsx.d.ts index 1897343b7..375660a2f 100644 --- a/packages/jsx/src/jsx.d.ts +++ b/packages/jsx/src/jsx.d.ts @@ -110,6 +110,7 @@ export namespace JSX { OnAttributes, OnCaptureAttributes, */ extends CustomEventHandlers { + $attrs?: Atom children?: Element innerHTML?: string innerText?: string | number