|
|
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";
|
|
|
|
|
|
export function createElement<T extends Constructor | string | ((props: any) => Element)>(elementType: T, ...args: any[]): 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: any) => Element);
|
|
|
if (args)
|
|
|
args.forEach(x => ctx.visitNext(x));
|
|
|
|
|
|
return ctx;
|
|
|
} else {
|
|
|
throw new Error(`The element type '${elementType}' is unsupported`);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
export interface EventDetails<T = any> {
|
|
|
detail: T;
|
|
|
}
|
|
|
|
|
|
export interface EventSelector {
|
|
|
selectorTarget: HTMLElement;
|
|
|
target: HTMLElement;
|
|
|
}
|
|
|
|
|
|
export interface QueryResultUpdate<T> {
|
|
|
/** The item is being updated */
|
|
|
item: T;
|
|
|
|
|
|
/** The previous index of the item, -1 in case it is inserted */
|
|
|
prevIndex: number;
|
|
|
|
|
|
/** The new index of the item, -1 in case it is deleted */
|
|
|
newIndex: number;
|
|
|
}
|
|
|
|
|
|
export type DojoMouseEvent<T = any> = MouseEvent & EventSelector & EventDetails<T>;
|
|
|
|
|
|
type StatefulProps<T> = T extends Stateful<infer A> ? 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<W extends _WidgetBase, K extends keyof W>(
|
|
|
target: W,
|
|
|
prop: K,
|
|
|
render: (model: W[K]) => any
|
|
|
): 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<T extends Stateful, K extends keyof StatefulProps<T>>(
|
|
|
target: T,
|
|
|
prop: K,
|
|
|
render: (model: StatefulProps<T>[K]) => any
|
|
|
): Rendition;
|
|
|
export function watch<V>(subj: Subscribable<V>, render: (model: V) => unknown): Rendition;
|
|
|
export function watch(
|
|
|
...args: [Stateful, string, (model: unknown) => unknown] |
|
|
|
[Subscribable<unknown>, (model: unknown) => unknown]
|
|
|
) {
|
|
|
if (args.length === 3) {
|
|
|
const [target, prop, render] = args;
|
|
|
return new WatchRendition(
|
|
|
render,
|
|
|
observe(({next}) => {
|
|
|
const h = target.watch<any>(
|
|
|
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 = <T>(source: T[] | Subscribable<QueryResultUpdate<T>>, render: (item: T, index: number) => unknown, opts: AnimationAttrs = {}) => {
|
|
|
return new WatchForRendition({
|
|
|
...opts,
|
|
|
subject: source,
|
|
|
component: render
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
export const prop: {
|
|
|
<T extends Stateful, K extends string & keyof StatefulProps<T>>(target: T, name: K): Observable<StatefulProps<T>[K]>;
|
|
|
<T extends _WidgetBase, K extends keyof T>(target: T, name: K): Observable<T[K]>;
|
|
|
} = (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 = <W extends DjxWidgetBase, K extends keyof W>(target: W, name: K) => (v: W[K]) => target.set(name, v);
|
|
|
|
|
|
export const bind = <K extends string, T>(attr: K, subj: Subscribable<T>) => {
|
|
|
let h = { unsubscribe() { } };
|
|
|
|
|
|
return <E extends (HTMLElement & { [p in K]: T }) | { set(name: K, value: T): void; }>(el: E | 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)
|
|
|
});
|
|
|
}
|
|
|
} else {
|
|
|
h.unsubscribe();
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
export const toggleClass = (className: string, subj: Subscribable<boolean>) => {
|
|
|
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)
|
|
|
});
|
|
|
} else {
|
|
|
h.unsubscribe();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
export const all = <T, A extends JSX.Ref<T>[]>(...cbs: A): JSX.Ref<T> => (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 = <E extends string>(...eventNames: E[]) =>
|
|
|
<K extends string,
|
|
|
T extends DjxWidgetBase<any, { [p in E]: EV }>,
|
|
|
EV extends Event
|
|
|
>(
|
|
|
target: T,
|
|
|
key: K,
|
|
|
_descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
|
|
|
): any => {
|
|
|
const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key }));
|
|
|
target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers;
|
|
|
};
|
|
|
|