UNPKG

snabbdom-jsx-lite

Version:

Write snabbdom templates in .jsx or .tsx (JSX for TypeScript)

94 lines (84 loc) 3.71 kB
import {vnode, VNode, VNodeData} from 'snabbdom'; // for conditional rendering we support boolean child element e.g cond && <tag /> export type JsxVNode = VNode; export type JsxVNodeChild = VNode | string | number | boolean | undefined | null; export type JsxVNodeChildren = JsxVNodeChild | JsxVNodeChild[]; export interface JsxVNodeProps extends VNodeData { /** css selector shorthand e.g <div sel="#id.class1.class1" /> */ sel?: string; } export type FunctionComponent = (props: {[prop: string]: any} | null, children?: VNode[]) => VNode; /** Equivalent of <> (React.Fragment) that that wraps children without a containing dom element */ export function Fragment(_props: {}, children?: Array<string | VNode>): VNode { return vnode(undefined, undefined, children, undefined, undefined); } function flattenAndFilterFalsey(children: JsxVNodeChildren[], flattened: VNode[]): VNode[] { for (const child of children) { // filter out falsey children, except 0 since zero can be a valid value e.g inside a chart if (child !== undefined && child !== null && child !== false && child !== ``) { if (Array.isArray(child)) { flattenAndFilterFalsey(child, flattened); } else if (typeof child === `string` || typeof child === `number` || typeof child === `boolean`) { flattened.push(vnode(undefined, undefined, undefined, String(child), undefined)); } else if (child.sel === undefined && child.text === undefined) { // vnode from Fragment if (Array.isArray(child.children)) { flattenAndFilterFalsey(child.children, flattened); } } else { flattened.push(child); } } } return flattened; } function addSvgNs(sel: string, data: VNodeData | undefined, children: VNode[] | undefined): void { data.ns = `http://www.w3.org/2000/svg`; if (sel !== `foreignObject` && children !== undefined) { for (const child of children) { if (child.data !== undefined) { addSvgNs(child.sel, child.data, child.children as VNode[]); } } } } /** * jsx/tsx + hyperscript compatible vnode factory function * see: https://www.typescriptlang.org/docs/handbook/jsx.html#factory-functions */ export function jsx( tag: string | FunctionComponent | null, data: JsxVNodeProps | null, ...children: JsxVNodeChildren[] ): JsxVNode { // don't call flattenAndFilterFalsey if no children const flattenedChildren = children.length > 0 ? flattenAndFilterFalsey(children, []) : (children as []); if (typeof tag === `function`) { // tag is a function component return tag(data, flattenedChildren); } else { if (tag === null) { // Fragment via jsxFragmentFactory: 'null' compiler option // change to undefined since snabbdom expects `tag: string | undefined` tag = undefined as string; // `as string` so we don't get TS typeguard error } else if (tag === `svg`) { // svg elements need recursive namespace addSvgNs(tag, data, flattenedChildren); } // append sel css selector to tag to support equivalent of h('div.foo.bar') data = data ?? {}; if (data.sel) { tag += data.sel; data.sel = undefined; } const numFlattenedChildren = flattenedChildren.length; if (numFlattenedChildren === 0) { return vnode(tag, data, undefined, undefined, undefined); } else if (numFlattenedChildren === 1 && flattenedChildren[0].sel === undefined && flattenedChildren[0].text) { // only child is a simple text node, return as simple text node return vnode(tag, data, undefined, flattenedChildren[0].text, undefined); } else { return vnode(tag, data, flattenedChildren, undefined, undefined); } } }