diff --git a/djx/src/main/ts/observable.ts b/djx/src/main/ts/observable.ts new file mode 100644 --- /dev/null +++ b/djx/src/main/ts/observable.ts @@ -0,0 +1,34 @@ +import { IDestroyable } from "@implab/core-amd/interfaces"; + +export interface Sink { + next: (value: T) => void; + error: (e: unknown) => void; + complete: () => void; +} + +export type Consumer = Partial>; + +export type Producer = (sink: Sink) => (void | (() => void)); + +export interface Observable { + on(sink: Partial>): IDestroyable; +} + +const noop = () => {}; + +const sink = (consumer: Consumer) => { + const { next = noop, error = noop, complete = noop } = consumer; + let done = false; + + return { + next: (value: T) => done && next(value), + error: (e: unknown) => done && (done = true, error(e)), + complete: () => done && (done = true, complete()) + }; +} + +export const observe = (producer: Producer) => ({ + on: (consumer: Consumer) => ({ + destroy: producer(sink(consumer)) ?? noop + }) +}); diff --git a/djx/src/main/ts/tsx.ts b/djx/src/main/ts/tsx.ts --- a/djx/src/main/ts/tsx.ts +++ b/djx/src/main/ts/tsx.ts @@ -1,11 +1,13 @@ -import { Constructor, IDestroyable, IRemovable } from "@implab/core-amd/interfaces"; +import { Constructor } from "@implab/core-amd/interfaces"; import { HtmlRendition } from "./tsx/HtmlRendition"; import { WidgetRendition } from "./tsx/WidgetRendition"; -import { destroy, isWidgetConstructor, Rendition } from "./tsx/traits"; +import { 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 { observe } from "./observable"; export function createElement Element)>(elementType: T, ...args: any[]): Rendition { if (typeof elementType === "string") { @@ -44,7 +46,6 @@ export type DojoMouseEvent = Mo type StatefulProps = T extends Stateful ? A : never; -type CleanFn = (instance: IRemovable | IDestroyable) => void; /** * Observers the property and calls render callback each change. @@ -52,14 +53,12 @@ type CleanFn = (instance: IRemovable | I * @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 - * @param cleanupOrOwner The object with method `own` or an callback to register lifecycle for the observer. * @returns Rendition which is created instantly */ export function watch( target: W, prop: K, - render: (model: W[K]) => any, - cleanupOrOwner?: { own: CleanFn } | CleanFn + render: (model: W[K]) => any ): Rendition; /** * Observers the property and calls render callback each change. @@ -67,32 +66,28 @@ export function watch>( target: T, prop: K, - render: (model: StatefulProps[K]) => any, - cleanupOrOwner?: { own: CleanFn } | CleanFn + render: (model: StatefulProps[K]) => any ): Rendition; export function watch & string>( target: T, prop: K, - render: (model: StatefulProps[K]) => any, - cleanupOrOwner: { own: CleanFn } | CleanFn = () => { } + render: (model: StatefulProps[K]) => any ) { - let rendition = new FunctionRendition(() => render(target.get(prop))); - const _own = cleanupOrOwner instanceof Function ? cleanupOrOwner : (x: IRemovable) => cleanupOrOwner.own(x); - _own(target.watch(prop, (_name, oldValue, newValue) => { - if (oldValue !== newValue) { - const newRendition = new FunctionRendition(() => render(newValue)); - newRendition.placeAt(rendition.getDomNode(), "replace"); - destroy(rendition.getDomNode()); - rendition = newRendition; - } - })); - return rendition; + return new WatchRendition( + render, + observe(({next}) => { + const h = target.watch( + prop, + (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue) + ); + return () => h.remove(); + }) + ) } /** Decorates the method which will be registered as the handle for the specified event. diff --git a/djx/src/main/ts/tsx/FunctionRendition.ts b/djx/src/main/ts/tsx/FunctionRendition.ts --- a/djx/src/main/ts/tsx/FunctionRendition.ts +++ b/djx/src/main/ts/tsx/FunctionRendition.ts @@ -1,5 +1,7 @@ import { argumentNotNull } from "@implab/core-amd/safe"; +import { getItemDom } from "./Renderer"; import { RenditionBase } from "./RenditionBase"; +import { IScope } from "./Scope"; export class FunctionRendition extends RenditionBase { private _component: (...args: any[]) => any; @@ -13,11 +15,12 @@ export class FunctionRendition extends R this._component = component; } - protected _create(attrs: object, children: any[]) { + protected _create(attrs: object, children: any[], scope: IScope) { const _attrs: any = attrs || {}; - const _children = children.map(x => this.getItemDom(x)); - this._node = this.getItemDom( - this._component.call(null, { ..._attrs, children: _children }) + const _children = children.map(x => getItemDom(x, scope)); + this._node = getItemDom( + this._component.call(null, { ..._attrs, children: _children }), + scope ); } diff --git a/djx/src/main/ts/tsx/HtmlRendition.ts b/djx/src/main/ts/tsx/HtmlRendition.ts --- a/djx/src/main/ts/tsx/HtmlRendition.ts +++ b/djx/src/main/ts/tsx/HtmlRendition.ts @@ -1,6 +1,9 @@ import dom = require("dojo/dom-construct"); import { argumentNotEmptyString } from "@implab/core-amd/safe"; import { RenditionBase } from "./RenditionBase"; +import { placeAt } from "./traits"; +import { IScope } from "./Scope"; +import { getItemDom } from "./Renderer"; export class HtmlRendition extends RenditionBase { elementType: string; @@ -14,16 +17,16 @@ export class HtmlRendition extends Rendi this.elementType = elementType; } - _addChild(child: any): void { + _addChild(child: unknown, scope: IScope): void { if (!this._element) throw new Error("The HTML element isn't created"); - dom.place(this.getItemDom(child), this._element); + placeAt(getItemDom(child, scope), this._element); } - _create(attrs: object, children: any[]) { + _create(attrs: object, children: unknown[], scope: IScope) { this._element = dom.create(this.elementType, attrs); - children.forEach(v => this._addChild(v)); + children.forEach(v => this._addChild(v, scope)); } _getDomNode() { diff --git a/djx/src/main/ts/tsx/Renderer.ts b/djx/src/main/ts/tsx/Renderer.ts --- a/djx/src/main/ts/tsx/Renderer.ts +++ b/djx/src/main/ts/tsx/Renderer.ts @@ -1,5 +1,5 @@ -import { Scope } from "./Scope"; -import { destroy, Rendition } from "./traits"; +import { IScope, Scope } from "./Scope"; +import { destroy, isNode, isRendition, isWidget, Rendition } from "./traits"; let _scope = Scope.dummy; @@ -24,3 +24,26 @@ export const render = async (rendition: endRender(); } } + +/** Renders DOM element for different types of the argument. */ +export const getItemDom = (v: unknown, scope: IScope) => { + 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(scope); + } 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, converted to comments + return document.createComment(`[${typeof v} ${String(v)}]`); + } else { + // bug: explicit error otherwise + throw new Error("Invalid parameter: " + v); + } +} diff --git a/djx/src/main/ts/tsx/RenditionBase.ts b/djx/src/main/ts/tsx/RenditionBase.ts --- a/djx/src/main/ts/tsx/RenditionBase.ts +++ b/djx/src/main/ts/tsx/RenditionBase.ts @@ -1,9 +1,8 @@ import { isNull, mixin } from "@implab/core-amd/safe"; -import { isPlainObject, isNode, isRendition, DojoNodePosition, Rendition, isInPage, isWidget, isDocumentFragmentNode, startupWidgets } from "./traits"; +import { isPlainObject, DojoNodePosition, Rendition, isDocumentFragmentNode, placeAt, collectNodes, autostartWidgets } from "./traits"; -import dom = require("dojo/dom-construct"); -import registry = require("dijit/registry"); - +import { IScope } from "./Scope"; +import { getScope } from "./Renderer"; export abstract class RenditionBase implements Rendition { private _attrs = {}; @@ -29,34 +28,9 @@ export abstract class RenditionBase { - const items = []; - for (let i = 0, n = collection.length; i < n; i++) { - items.push(collection[i]); - } - return items; - }; + const startupPending = isDocumentFragmentNode(domNode) ? collectNodes(domNode.children) : [domNode]; - const startup = (node: Node) => { - if (node.parentNode) { - const parentWidget = registry.getEnclosingWidget(node.parentNode); - if (parentWidget && parentWidget._started) - return startupWidgets(node); - } - if (isInPage(node)) - startupWidgets(node); - }; + placeAt(domNode, refNode, position); - const startupPending = isDocumentFragmentNode(domNode) ? collect(domNode.children) : [domNode]; - - dom.place(domNode, refNode, position); - - startupPending.forEach(startup); + startupPending.forEach(autostartWidgets); } - protected abstract _create(attrs: object, children: any[]): void; + protected abstract _create(attrs: object, children: unknown[], scope: IScope): void; protected abstract _getDomNode(): TNode; } diff --git a/djx/src/main/ts/tsx/WatchRendition.ts b/djx/src/main/ts/tsx/WatchRendition.ts --- a/djx/src/main/ts/tsx/WatchRendition.ts +++ b/djx/src/main/ts/tsx/WatchRendition.ts @@ -1,11 +1,10 @@ import { id as mid } from "module"; import { TraceSource } from "@implab/core-amd/log/TraceSource"; import { argumentNotNull } from "@implab/core-amd/safe"; -import { place } from "dojo/dom-construct"; -import { getScope, render } from "./Renderer"; +import { render } from "./Renderer"; import { RenditionBase } from "./RenditionBase"; -import { Scope } from "./Scope"; -import { locateNode } from "./traits"; +import { IScope, Scope } from "./Scope"; +import { Observable } from "../observable"; const trace = TraceSource.get(mid); @@ -16,36 +15,33 @@ export class WatchRendition extends R private readonly _scope = new Scope(); - constructor(component: (arg: T) => any, subject: any) { + private readonly _subject: Observable; + + constructor(component: (arg: T) => any, subject: Observable) { super(); argumentNotNull(component, "component"); this._factory = component; + this._subject = subject; + this._node = document.createComment("WatchRendition placeholder"); } - protected _create(attrs: object, children: any[]) { - const _attrs: any = attrs || {}; - const _children = children.map(x => this.getItemDom(x)); - this._node = this.getItemDom( - this._factory.call(null, { ..._attrs, children: _children }) - ); - - const scope = getScope(); + protected _create(attrs: object, children: any[], scope: IScope) { scope.own(this._scope); - - // если отрендерили текст? или DocumentFragment + scope.own(this._subject.on({ next: this._onValue })) } + private _onValue = (value: T) => void this._render(value).catch( e => trace.error(e)); + private async _render(value: T) { - const [refNode, position] = locateNode(this._node); + const prevNode = this._node; this._scope.clean(); this._node = await render(() => this._factory(value), this._scope); - if (refNode) - place(this._node, refNode, position); + this.placeAt(prevNode, "replace"); } protected _getDomNode() { diff --git a/djx/src/main/ts/tsx/WidgetRendition.ts b/djx/src/main/ts/tsx/WidgetRendition.ts --- a/djx/src/main/ts/tsx/WidgetRendition.ts +++ b/djx/src/main/ts/tsx/WidgetRendition.ts @@ -1,9 +1,10 @@ -import dom = require("dojo/dom-construct"); import { argumentNotNull } from "@implab/core-amd/safe"; -import { RenditionBase } from "./RenditionBase"; -import { DojoNodePosition, isElementNode, isInPage, isWidget } from "./traits"; +import { getItemDom, RenditionBase } from "./RenditionBase"; +import { DojoNodePosition, isElementNode, isInPage, isWidget, placeAt } from "./traits"; import registry = require("dijit/registry"); import ContentPane = require("dijit/layout/ContentPane"); +import { IScope } from "./Scope"; +import { getScope } from "./Renderer"; // tslint:disable-next-line: class-name export interface _Widget { @@ -31,17 +32,17 @@ export class WidgetRendition extends Ren this.widgetClass = widgetClass; } - _addChild(child: any): void { + _addChild(child: any, scope: IScope): void { const instance = this._getInstance(); if (instance.addChild) { if (child instanceof WidgetRendition) { // layout containers add custom logic to addChild methods - instance.addChild(child.getWidgetInstance()); + instance.addChild(child.getWidgetInstance(scope)); } else if (isWidget(child)) { instance.addChild(child); } else { - const childDom = this.getItemDom(child); + const childDom = getItemDom(child, scope); const w = isElementNode(childDom) ? registry.byNode(childDom) : undefined; if (w) { @@ -51,7 +52,7 @@ export class WidgetRendition extends Ren throw new Error("Failed to add DOM content. The widget doesn't have a containerNode"); // the current widget isn't started, it's children shouldn't start too - dom.place(this.getItemDom(child), instance.containerNode); + placeAt(getItemDom(child,scope), instance.containerNode, "last"); } } } else { @@ -59,11 +60,11 @@ export class WidgetRendition extends Ren throw new Error("The widget doesn't have neither addChild nor containerNode"); // the current widget isn't started, it's children shouldn't start too - dom.place(this.getItemDom(child), instance.containerNode); + placeAt(getItemDom(child, scope), instance.containerNode, "last"); } } - protected _create(attrs: any, children: any[]) { + protected _create(attrs: any, children: any[], scope: IScope) { if (this.widgetClass.prototype instanceof ContentPane) { // a special case for the ContentPane this is for // the compatibility with this heavy widget, all @@ -72,14 +73,14 @@ export class WidgetRendition extends Ren // render children to the DocumentFragment const content = document.createDocumentFragment(); - children.forEach(child => content.appendChild(this.getItemDom(child))); + children.forEach(child => content.appendChild(getItemDom(child, scope))); // set the content property to the parameters of the widget const _attrs = { ...attrs, content }; this._instance = new this.widgetClass(_attrs); } else { this._instance = new this.widgetClass(attrs); - children.forEach(x => this._addChild(x)); + children.forEach(x => this._addChild(x, scope)); } } @@ -103,7 +104,7 @@ export class WidgetRendition extends Ren * @param position A position relative to refNode. */ placeAt(refNode: string | Node, position?: DojoNodePosition) { - this.ensureCreated(); + this.ensureCreated(getScope()); const instance = this._getInstance(); if (typeof instance.placeAt === "function") { instance.placeAt(refNode, position); @@ -120,8 +121,8 @@ export class WidgetRendition extends Ren } } - getWidgetInstance() { - this.ensureCreated(); + getWidgetInstance(scope?: IScope) { + this.ensureCreated(scope || getScope()); return this._getInstance(); } diff --git a/djx/src/main/ts/tsx/traits.ts b/djx/src/main/ts/tsx/traits.ts --- a/djx/src/main/ts/tsx/traits.ts +++ b/djx/src/main/ts/tsx/traits.ts @@ -2,16 +2,16 @@ import { IDestroyable } from "@implab/co import { isDestroyable } from "@implab/core-amd/safe"; import _WidgetBase = require("dijit/_WidgetBase"); import registry = require("dijit/registry"); -import dom = require("dojo/dom-construct"); +import { IScope } from "./Scope"; type _WidgetBaseConstructor = typeof _WidgetBase; export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number; -export type DojoNodeLocation = [Node | null, DojoNodePosition]; +export type DojoNodeLocation = [Node, DojoNodePosition]; export interface Rendition { - getDomNode(): TNode; + getDomNode(scope?: IScope): TNode; placeAt(refNode: string | Node, position?: DojoNodePosition): void; } @@ -25,52 +25,32 @@ export interface IRecursivelyDestroyable destroyRecursive(): void; } -export function isNode(el: any): el is Node { - return el && el.nodeName && el.nodeType; -} +export const isNode = (el: unknown): el is Node => !!(el && (el as Node).nodeName && (el as Node).nodeType); -export function isElementNode(el: any): el is Element { - return isNode(el) && el.nodeType === 1; -} +export const isElementNode = (el: unknown): el is Element => isNode(el) && el.nodeType === 1; -export function isTextNode(el: any): el is Text { - return isNode(el) && el.nodeType === 3; -} +export const isTextNode = (el: unknown): el is Text => isNode(el) && el.nodeType === 3; -export function isProcessingInstructionNode(el: any): el is ProcessingInstruction { - return isNode(el) && el.nodeType === 7; -} +export const isProcessingInstructionNode = (el: unknown): el is ProcessingInstruction => isNode(el) && el.nodeType === 7; -export function isCommentNode(el: any): el is Comment { - return isNode(el) && el.nodeType === 8; -} +export const isCommentNode = (el: unknown): el is Comment => isNode(el) && el.nodeType === 8; -export function isDocumentNode(el: any): el is Document { - return isNode(el) && el.nodeType === 9; -} +export const isDocumentNode = (el: unknown): el is Document => isNode(el) && el.nodeType === 9; -export function isDocumentTypeNode(el: any): el is DocumentType { - return isNode(el) && el.nodeType === 10; -} +export const isDocumentTypeNode = (el: unknown): el is DocumentType => isNode(el) && el.nodeType === 10; -export function isDocumentFragmentNode(el: any): el is DocumentFragment { - return isNode(el) && el.nodeType === 11; -} +export const isDocumentFragmentNode = (el: any): el is DocumentFragment => isNode(el) && el.nodeType === 11; -export function isWidget(v: any): v is _WidgetBase { - return v && "domNode" in v; -} +export const isWidget = (v: unknown): v is _WidgetBase => !!(v && "domNode" in (v as _WidgetBase)); -export function isRendition(v: any): v is Rendition { - return v && typeof v.getDomElement === "function"; -} +export const isRendition = (v: unknown): v is Rendition => !!(v && typeof (v as Rendition).getDomNode === "function"); /** * @deprecated use isRendition */ export const isBuildContext = isRendition; -export function isPlainObject(v: object) { +export const isPlainObject = (v: object) => { if (typeof v !== "object") return false; @@ -78,23 +58,21 @@ export function isPlainObject(v: object) return !vp || vp === Object.prototype; } -export function isWidgetConstructor(v: any): v is _WidgetBaseConstructor { - return typeof v === "function" && v.prototype && ( +export const isWidgetConstructor = (v: unknown): v is _WidgetBaseConstructor => + typeof v === "function" && v.prototype && ( "domNode" in v.prototype || "buildRendering" in v.prototype ); -} + /** Tests whether the specified node is placed in visible dom. * @param {Node} node The node to test */ -export function isInPage(node: Node) { - return (node === document.body) ? false : document.body.contains(node); -} +export const isInPage = (node: Node) => node === document.body ? false : document.body.contains(node); -export function isRecursivelyDestroyable(target: any): target is IRecursivelyDestroyable { - return target && typeof target.destroyRecursive === "function"; -} +export const isRecursivelyDestroyable = (target: unknown): target is IRecursivelyDestroyable => + !!(target && typeof (target as IRecursivelyDestroyable).destroyRecursive === "function"); + /** Destroys DOM Node with all contained widgets. @@ -103,7 +81,7 @@ export function isRecursivelyDestroyable * * @param target DOM Node or widget to destroy */ -export function destroy(target: Node | IDestroyable | IRecursivelyDestroyable) { +export const destroy = (target: Node | IDestroyable | IRecursivelyDestroyable) => { if (isRecursivelyDestroyable(target)) { target.destroyRecursive(); } else if (isDestroyable(target)) { @@ -114,8 +92,10 @@ export function destroy(target: Node | I if (w) { w.destroyRecursive(); } else { - registry.findWidgets(target).forEach(destroy); - dom.destroy(target); + emptyNode(target); + const parent = target.parentNode; + if (parent) + parent.removeChild(target); } } } @@ -123,11 +103,14 @@ export function destroy(target: Node | I /** Empties a content of the specified node and destroys all contained widgets. * - * @param target DOM node to . + * @param target DOM node to empty. */ -export function emptyNode(target: Node) { +export const emptyNode = (target: Node) => { registry.findWidgets(target).forEach(destroy); - dom.empty(target); + + for(let c; c = target.lastChild;){ // intentional assignment + target.removeChild(c); + } } /** This function starts all widgets inside the DOM node if the target is a node @@ -136,7 +119,7 @@ export function emptyNode(target: Node) * * @param target DOM node to find and start widgets or the widget itself. */ -export function startupWidgets(target: Node | _WidgetBase, skipNode?: Node) { +export const startupWidgets = (target: Node | _WidgetBase, skipNode?: Node) => { if (isNode(target)) { if (isElementNode(target)) { const w = registry.byNode(target); @@ -153,54 +136,79 @@ export function startupWidgets(target: N } } -export function locateNode(node: Node): DojoNodeLocation { - const next = node.nextSibling; - return next ? - [next, "before"] : - [node.parentNode, "last"]; -} - -export const placeAt = (node: Node, refNodeOrId: string | Node, position: DojoNodePosition) => { - const collect = (collection: HTMLCollection) => { - const items = []; - for (let i = 0, n = collection.length; i < n; i++) { - items.push(collection[i]); - } - return items; - }; - - const startup = (node: Node) => { - if (node.parentNode) { - const parentWidget = registry.getEnclosingWidget(node.parentNode); - if (parentWidget && parentWidget._started) - return startupWidgets(node); - } - if (isInPage(node)) - startupWidgets(node); - }; - +/** Places the specified DOM node at the specified location. + * + * @param node The node which should be placed + * @param refNodeOrId The reference node where the created + * DOM should be placed. + * @param position Optional parameter, specifies the + * position relative to refNode. Default is "last" (i.e. last child). + */ +export const placeAt = (node: Node, refNodeOrId: string | Node, position: DojoNodePosition = "last") => { const ref = typeof refNodeOrId == "string" ? document.getElementById(refNodeOrId) : refNodeOrId; if (!ref) return; const parent = ref.parentNode; - if (typeof position == "number") { + const insertBefore = (node: Node, refNode: Node | null) => parent && parent.insertBefore(node, refNode); + if (typeof position == "number") { + if (ref.childNodes.length <= position) { + ref.appendChild(node); + } else { + ref.insertBefore(node, ref.childNodes[position]); + } } else { - switch(position) { + switch (position) { case "before": - if (parent) - parent.insertBefore(node,ref); + insertBefore(node, ref); + break; case "after": + insertBefore(node, ref.nextSibling); + break; + case "first": + insertBefore(node, parent && parent.firstChild); + break; + case "last": + insertBefore(node, null); + break; + case "only": + emptyNode(ref); + ref.appendChild(node); + break; + case "replace": if (parent) - parent.insertBefore(node, ref.nextSibling); + parent.replaceChild(node, ref); + destroy(ref); + break; } } - - const startupPending = isDocumentFragmentNode(node) ? collect(node.children) : [node]; +} - dom.place(node, refNodeOrId, position); +/** Collects nodes from collection to an array. + * + * @param collection The collection of nodes. + * @returns The array of nodes. + */ +export const collectNodes = (collection: HTMLCollection) => { + const items = []; + for (let i = 0, n = collection.length; i < n; i++) { + items.push(collection[i]); + } + return items; +}; - startupPending.forEach(startup); -} \ No newline at end of file +/** Starts widgets if the node contained in the document or in the started widget. + * + * @param node The node to start. +*/ +export const autostartWidgets = (node: Node) => { + if (node.parentNode) { + const parentWidget = registry.getEnclosingWidget(node.parentNode); + if (parentWidget && parentWidget._started) + return startupWidgets(node); + } + if (isInPage(node)) + startupWidgets(node); +}; \ No newline at end of file diff --git a/djx/src/main/typings/index.d.ts b/djx/src/main/typings/index.d.ts --- a/djx/src/main/typings/index.d.ts +++ b/djx/src/main/typings/index.d.ts @@ -2,7 +2,9 @@ declare namespace JSX { - interface DjxIntrinsicAttributes { + type Ref = (value: T) => void; + + interface DjxIntrinsicAttributes { /** alias for className */ class: string; @@ -14,6 +16,8 @@ declare namespace JSX { /** specifies handlers map for the events */ "data-dojo-attach-event": string; + ref: Ref; + /** @deprecated */ [attr: string]: any; } @@ -56,7 +60,7 @@ declare namespace JSX { type LaxElement = Pick> & - DjxIntrinsicAttributes; + DjxIntrinsicAttributes; type LaxIntrinsicElementsMap = { [tag in keyof HTMLElementTagNameMap]: LaxElement @@ -65,4 +69,12 @@ declare namespace JSX { type IntrinsicElements = { [tag in keyof LaxIntrinsicElementsMap]: RecursivePartial; } + + interface ElementChildrenAttribute { + children: {}; + } + + interface IntrinsicClassAttributes { + ref: (value: T) => void; + } } diff --git a/djx/src/test/ts/view/MyWidget.tsx b/djx/src/test/ts/view/MyWidget.tsx --- a/djx/src/test/ts/view/MyWidget.tsx +++ b/djx/src/test/ts/view/MyWidget.tsx @@ -30,10 +30,10 @@ export class MyWidget extends djbase(Djx counter = 0; render() { - const Frame = (props: any) =>
{props.children}
; + const Frame = ({children, ref}: {ref: JSX.Ref, children: any[]}) =>
{children}
; return
this._onSubmit(e)} tabIndex={3} style={{ alignContent: "center", border: "1px solid" }} >

- + {}}> this._onIncClick(e)}>[+] this._onDecClick()}>[-]