import { Constructor } from "@implab/core-amd/interfaces"; import { HtmlRendition } from "./tsx/HtmlRendition"; import { WidgetRendition } from "./tsx/WidgetRendition"; import { isElementNode, isWidget, isWidgetConstructor, Rendition } from "./tsx/traits"; import { FunctionRendition } from "./tsx/FunctionRendition"; import Stateful = require("dojo/Stateful"); import _WidgetBase = require("dijit/_WidgetBase"); import { DjxWidgetBase } from "./tsx/DjxWidgetBase"; import { WatchRendition } from "./tsx/WatchRendition"; import { Observable, observe, Subscribable } from "./observable"; import djAttr = require("dojo/dom-attr"); import djClass = require("dojo/dom-class"); import { AnimationAttrs, WatchForRendition } from "./tsx/WatchForRendition"; import { OrderedUpdate } from "./store"; export function createElement Element)>(elementType: T, ...args: unknown[]): Rendition { if (typeof elementType === "string") { const ctx = new HtmlRendition(elementType); if (args) args.forEach(x => ctx.visitNext(x)); return ctx; } else if (isWidgetConstructor(elementType)) { const ctx = new WidgetRendition(elementType); if (args) args.forEach(x => ctx.visitNext(x)); return ctx; } else if (typeof elementType === "function") { const ctx = new FunctionRendition(elementType as (props: unknown) => Element); if (args) args.forEach(x => ctx.visitNext(x)); return ctx; } else { throw new Error(`The element type '${String(elementType)}' is unsupported`); } } export interface EventDetails { detail: T; } export interface EventSelector { selectorTarget: HTMLElement; target: HTMLElement; } export type DojoMouseEvent = MouseEvent & EventSelector & EventDetails; type StatefulProps = T extends Stateful ? A : T extends _WidgetBase ? T : never; /** * Observers the property and calls render callback each change. * * @param target The target object which property will be observed. * @param prop The name of the property. * @param render The callback which will be called every time the value is changed * @returns Rendition which is created instantly */ export function watch( target: W, prop: K, render: (model: W[K]) => unknown ): Rendition; /** * Observers the property and calls render callback each change. * * @param target The target object which property will be observed. * @param prop The name of the property. * @param render The callback which will be called every time the value is changed * @returns Rendition which is created instantly */ export function watch>( target: T, prop: K, render: (model: StatefulProps[K]) => unknown ): Rendition; export function watch(subj: Subscribable, render: (model: V) => unknown): Rendition; export function watch( ...args: [Stateful, string, (model: unknown) => unknown] | [Subscribable, (model: unknown) => unknown] ) { if (args.length === 3) { const [target, prop, render] = args; return new WatchRendition( render, observe(({next}) => { const h = target.watch( prop, (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue) ); next(target.get(prop)); return () => h.remove(); }) ); } else { const [subj, render] = args; return new WatchRendition(render, subj); } } export const watchFor = (source: T[] | Subscribable> | null | undefined, render: (item: T, index: number) => unknown, opts: AnimationAttrs = {}) => { return new WatchForRendition({ ...opts, subject: source, component: render }); }; export const prop: { >(target: T, name: K): Observable[K]>; (target: T, name: K): Observable; } = (target: Stateful, name: string) => { return observe(({next}) => { const h = target.watch( name, (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue) ); next(target.get(name)); return () => h.remove(); }); }; export const attach = (target: W, name: K) => (v: W[K]) => target.set(name, v); export const bind = (attr: K, subj: Subscribable) => { let h = { unsubscribe() { } }; return (el: Element | { set(name: K, value: T, priority?: boolean): void; } | undefined) => { if (el) { if (isElementNode(el)) { h = subj.subscribe({ next: value => djAttr.set(el, attr, value) }); } else { h = subj.subscribe({ next: value => el.set(attr, value, false) }); } } else { h.unsubscribe(); } }; }; /** Creates refHook to toggle the specified css class on the target element * * @param className * @param subj a boolean observable value. When the value is false the className * is removed, when the value is true, the className is added to the target element * @returns refHook */ export const toggleClass = (className: string, subj: Subscribable) => { let h = { unsubscribe() { } }; return (elOrWidget: HTMLElement | _WidgetBase | undefined) => { const el = isWidget(elOrWidget) ? elOrWidget.domNode : elOrWidget; if (el) { h = subj.subscribe({ next: v => djClass.toggle(el, className, v || false) }); } else { h.unsubscribe(); } }; }; /** Combines multiple hooks to the single one */ export const all = []>(...cbs: A): JSX.Ref => (arg: T | undefined) => cbs.forEach(cb => cb(arg)); /** Decorates the method which will be registered as the handle for the specified event. * This decorator can be applied to DjxWidgetBase subclass methods. * * ``` * @on("click") * _onClick(eventObj: MouseEvent) { * // ... * } * ``` */ export const on = (...eventNames: E[]) => , EV extends Event >( target: T, key: K, // eslint-disable-next-line @typescript-eslint/no-unused-vars _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void> ) => { const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key })); target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers; };