diff --git a/src/jsx-dom.ts b/src/jsx-dom.ts index 4fa5208..16fe22c 100644 --- a/src/jsx-dom.ts +++ b/src/jsx-dom.ts @@ -2,7 +2,9 @@ import { isRef } from "./ref" import { forEach, isArrayLike, + isAttributeHook, isBoolean, + isChildrenHook, isComponentClass, isElement, isFunction, @@ -219,6 +221,8 @@ function appendChild( const shadowRoot = (node as HTMLElement).attachShadow(child.attr) appendChild(child.children, shadowRoot) attachRef(child.ref, shadowRoot) + } else if (isChildrenHook(child)) { + appendChild(child.jsxDomChildrenHook.call(child, node), node) } } @@ -406,7 +410,11 @@ function attrNS(node: Element, namespace: string, key: string, value: string | n function attributes(attr: object, node: HTMLElement | SVGElement) { for (const key of keys(attr)) { - attribute(key, attr[key], node) + let value: any = attr[key] + if (isAttributeHook(value)) { + value = value.jsxDomAttributeHook(node, key) + } + attribute(key, value, node) } return node } diff --git a/src/util.ts b/src/util.ts index c3ff94a..e351376 100644 --- a/src/util.ts +++ b/src/util.ts @@ -40,6 +40,14 @@ export function isArrayLike(obj: any): obj is ArrayLike { return isObject(obj) && typeof obj.length === "number" && typeof obj.nodeType !== "number" } +export function isAttributeHook(val: any): val is { jsxDomAttributeHook: (node: HTMLElement | SVGElement, attr: string) => any } { + return isObject(val) && isFunction(val.jsxDomAttributeHook) +} + +export function isChildrenHook(val: any): val is { jsxDomChildrenHook: (parent: HTMLElement | SVGElement) => any } { + return isObject(val) && isFunction(val.jsxDomChildrenHook) +} + export function forEach(value: { [key: string]: V }, fn: (value: V, key: string) => void) { if (!value) return for (const key of keys(value)) { diff --git a/test/hooks.test.tsx b/test/hooks.test.tsx index 5b5df99..fbacbf5 100644 --- a/test/hooks.test.tsx +++ b/test/hooks.test.tsx @@ -85,4 +85,52 @@ describe("hooks", () => { cls.toggle("container", false) expect(cls.contains("container")).toBe(false) }) -}) + + it("supports jsxDomAttributeHook", () => { + let callValue: any, callNode: HTMLElement | SVGElement, callTimes: number = 0 + function hook(node: HTMLElement | SVGElement, attr: string) { + callNode = node; + callValue = this; + callTimes++ + return `hooked:${attr}` + } + const hookedValue = { jsxDomAttributeHook: hook } + const div =
+ expect(callTimes).toBe(1) + expect(callValue).toBe(hookedValue) + expect(callNode).toBe(div) + expect(div.getAttribute("title")).toBe("hooked:title") + }) + + it("supports jsxDomChildrenHook as sole content", () => { + let callValue: any, callParentNode: HTMLElement | SVGElement, callTimes: number = 0 + function hook(parentNode: HTMLElement | SVGElement) { + callParentNode = parentNode + callValue = this + callTimes++ + return "test" + } + const hookedValue = { jsxDomChildrenHook: hook } + const div =
{hookedValue}
+ expect(callTimes).toBe(1) + expect(callValue).toBe(hookedValue) + expect(callParentNode).toBe(div) + expect(div.textContent).toBe("test") + }) + + it("supports jsxDomChildrenHook inside a fragment", () => { + let callValue: any, callParentNode: HTMLElement | SVGElement, callTimes: number = 0 + function hook(parentNode: HTMLElement | SVGElement) { + callParentNode = parentNode + callValue = this + callTimes++ + return "test" + } + const hookedValue = { jsxDomChildrenHook: hook } + const div =
<>{hookedValue}
+ expect(callTimes).toBe(1) + expect(callValue).toBe(hookedValue) + expect(callParentNode instanceof DocumentFragment).toBe(true) + expect(div.textContent).toBe("test") + }) +}) \ No newline at end of file