import { TraceSource } from "@implab/core-amd/log/TraceSource"; import { isPromise } from "@implab/core-amd/safe"; import { id as mid } from "module"; import { IScope, Scope } from "./Scope"; import { isNode, isRendition, isWidget } from "./traits"; const trace = TraceSource.get(mid); interface Context { scope: IScope; hooks?: (() => void)[]; } let _context: Context = { scope: Scope.dummy }; const guard = (cb: () => unknown) => { try { const result = cb(); if (isPromise(result)) { const warn = (ret: unknown) => trace.error("The callback {0} competed asynchronously. result = {1}", cb, ret); result.then(warn, warn); } } catch (e) { trace.error(e); } }; export const beginRender = (scope: IScope = getScope()) => { const prev = _context; _context = { scope, hooks: [] }; return endRender(prev); }; /** * Completes render operation */ const endRender = (prev: Context) => () => { const { hooks } = _context; if (hooks) hooks.forEach(guard); _context = prev; }; export const renderHook = (hook: () => void) => { const { hooks } = _context; if (hooks) hooks.push(hook); else guard(hook); }; export const refHook = (value: T, ref: JSX.Ref) => { const { hooks, scope } = _context; if (hooks) hooks.push(() => ref(value)); else guard(() => ref(value)); scope.own(() => ref(undefined)); }; /** Returns the current scope */ export const getScope = () => _context.scope; /** Schedules the rendition to be rendered to the DOM Node * @param rendition The rendition to be rendered * @param scope The scope */ export const render = (rendition: unknown, scope = Scope.dummy) => { const complete = beginRender(scope); try { return getItemDom(rendition); } finally { complete(); } }; /** Renders DOM element for different types of the argument. */ export const getItemDom = (v: unknown) => { if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) { // primitive types converted to the text nodes return document.createTextNode(v.toString()); } else if (isNode(v)) { // nodes are kept as is return v; } else if (isRendition(v)) { // renditions are instantiated return v.getDomNode(); } else if (isWidget(v)) { // widgets are converted to it's markup return v.domNode; } else if (typeof v === "boolean" || v === null || v === undefined) { // null | undefined | boolean are removed return document.createDocumentFragment(); } else if (v instanceof Array) { // arrays will be translated to document fragments const fragment = document.createDocumentFragment(); v.map(item => getItemDom(item)) .forEach(node => fragment.appendChild(node)); return fragment; } else { // bug: explicit error otherwise throw new Error(`Invalid parameter: ${String(v)}`); } };