diff --git a/examples/example-vdom/index.html b/examples/example-vdom/index.html
new file mode 100644
index 000000000..c81e1e0dd
--- /dev/null
+++ b/examples/example-vdom/index.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/examples/example-vdom/package.json b/examples/example-vdom/package.json
new file mode 100644
index 000000000..6ac853b4e
--- /dev/null
+++ b/examples/example-vdom/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@phosphor/example-vdom",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "build": "tsc && webpack",
+ "clean": "rimraf build"
+ },
+ "dependencies": {
+ "@phosphor/vdom": "^0.0.1",
+ "@phosphor/widgets": "^1.9.3"
+ },
+ "devDependencies": {
+ "rimraf": "^2.5.2",
+ "typescript": "~3.6.0",
+ "webpack": "^2.2.1"
+ }
+}
diff --git a/examples/example-vdom/src/index.tsx b/examples/example-vdom/src/index.tsx
new file mode 100644
index 000000000..8be141038
--- /dev/null
+++ b/examples/example-vdom/src/index.tsx
@@ -0,0 +1,91 @@
+/*------------------------------------------------------------------------------
+| Copyright (c) 2014-2019, PhosphorJS Contributors
+|
+| Distributed under the terms of the BSD 3-Clause License.
+|
+| The full license is in the file LICENSE, distributed with this software.
+|-----------------------------------------------------------------------------*/
+import {
+ VDOM
+} from '@phosphor/vdom';
+
+import {
+ Widget
+} from '@phosphor/widgets';
+
+
+type TickData = {
+ readonly title: string;
+ readonly count: number;
+}
+
+
+const TickRow = (props: TickData) => {
+ return (
+
+ {props.title} |
+ {props.count} |
+
+ );
+};
+
+
+class TimeWidget extends Widget {
+
+ constructor() {
+ super();
+ this.addClass('TimeWidget');
+ }
+
+ protected onBeforeAttach(): void {
+ setInterval(() => this._tick(), 30);
+ }
+
+ protected onUpdateRequest(): void {
+ VDOM.render(this.render(), this.node);
+ }
+
+ protected render() {
+ let time = this._time;
+ let now = this._now;
+ return (
+
+
This page is updated every 30ms
+
+ UTC Time:
+ {time.toUTCString()}
+
+
+ Local Time:
+ {time.toString()}
+
+
+ Milliseconds Since Epoch:
+ {now.toString()}
+
+
+
+ );
+ }
+
+ private _tick(): void {
+ this._time = new Date();
+ this._now = Date.now();
+ this.update();
+ }
+
+ private _time = new Date();
+ private _now = Date.now();
+}
+
+
+function main(): void {
+ Widget.attach(new TimeWidget(), document.body);
+}
+
+
+window.onload = main;
diff --git a/examples/example-vdom/tsconfig.json b/examples/example-vdom/tsconfig.json
new file mode 100644
index 000000000..a5b0b0374
--- /dev/null
+++ b/examples/example-vdom/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "declaration": false,
+ "noImplicitAny": true,
+ "noEmitOnError": true,
+ "noUnusedLocals": true,
+ "strictNullChecks": true,
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "target": "es5",
+ "outDir": "./build",
+ "jsx": "react",
+ "jsxFactory": "VDOM.createElement",
+ "lib": ["es2015", "dom"],
+ "types": []
+ },
+ "include": ["src/*"]
+ }
diff --git a/examples/example-vdom/webpack.config.js b/examples/example-vdom/webpack.config.js
new file mode 100644
index 000000000..2a76db0f3
--- /dev/null
+++ b/examples/example-vdom/webpack.config.js
@@ -0,0 +1,16 @@
+var path = require('path');
+
+module.exports = {
+ entry: './build/index.js',
+ output: {
+ path: __dirname + '/build/',
+ filename: 'bundle.example.js',
+ publicPath: './build/'
+ },
+ module: {
+ rules: [
+ { test: /\.css$/, use: ['style-loader', 'css-loader'] },
+ { test: /\.png$/, use: 'file-loader' }
+ ]
+ }
+};
diff --git a/packages/vdom/package.json b/packages/vdom/package.json
new file mode 100644
index 000000000..6777e211c
--- /dev/null
+++ b/packages/vdom/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "@phosphor/vdom",
+ "version": "0.0.1",
+ "description": "PhosphorJS - VDOM",
+ "homepage": "https://github.com/phosphorjs/phosphor",
+ "bugs": {
+ "url": "https://github.com/phosphorjs/phosphor/issues"
+ },
+ "license": "BSD-3-Clause",
+ "author": "S. Chris Colbert ",
+ "contributors": [
+ "S. Chris Colbert "
+ ],
+ "files": [
+ "lib/*.d.ts",
+ "lib/*.js"
+ ],
+ "main": "lib/index.js",
+ "types": "lib/index.d.ts",
+ "directories": {
+ "lib": "lib/"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/phosphorjs/phosphor.git"
+ },
+ "scripts": {
+ "build": "tsc --build",
+ "clean": "rimraf lib",
+ "watch": "tsc --build --watch"
+ },
+ "dependencies": {},
+ "devDependencies": {
+ "rimraf": "^2.5.2",
+ "typescript": "~3.6.0"
+ }
+}
diff --git a/packages/vdom/src/index.ts b/packages/vdom/src/index.ts
new file mode 100644
index 000000000..f75fb4b55
--- /dev/null
+++ b/packages/vdom/src/index.ts
@@ -0,0 +1,10 @@
+/*------------------------------------------------------------------------------
+| Copyright (c) 2014-2019, PhosphorJS Contributors
+|
+| Distributed under the terms of the BSD 3-Clause License.
+|
+| The full license is in the file LICENSE, distributed with this software.
+|-----------------------------------------------------------------------------*/
+export * from './pjsx';
+export * from './vdom';
+export * from './vnode';
diff --git a/packages/vdom/src/pjsx.ts b/packages/vdom/src/pjsx.ts
new file mode 100644
index 000000000..9b936e2aa
--- /dev/null
+++ b/packages/vdom/src/pjsx.ts
@@ -0,0 +1,708 @@
+/*------------------------------------------------------------------------------
+| Copyright (c) 2014-2019, PhosphorJS Contributors
+|
+| Distributed under the terms of the BSD 3-Clause License.
+|
+| The full license is in the file LICENSE, distributed with this software.
+|-----------------------------------------------------------------------------*/
+import {
+ VNode
+} from './vnode';
+
+
+/**
+ * The namespace for the Phosphor JSX type defintions.
+ */
+export
+namespace PJSX {
+
+ export type Element = VNode;
+ export type Child = VNode.Child | number | boolean | null;
+ export type Children = Child[] | Child;
+ export type Key = VNode.Key;
+ export type Ref = VNode.Ref;
+
+ export
+ interface ElementChildrenAttribute {
+ children: {}
+ }
+
+ export
+ interface SpecialAttributes {
+ children?: Children;
+ key?: Key;
+ ref?: Ref;
+ }
+
+ export type EventHandlerFunction = (event: E) => void;
+ export type EventHandlerObject = { handleEvent(event: E): void; };
+ export type EventHandler = EventHandlerFunction | EventHandlerObject;
+
+ export
+ interface DOMAttributes extends SpecialAttributes {
+ // Clipboard Events
+ oncopy?: EventHandler;
+ oncut?: EventHandler;
+ onpaste?: EventHandler;
+
+ // Composition Events
+ oncompositionend?: EventHandler;
+ oncompositionstart?: EventHandler;
+ oncompositionupdate?: EventHandler;
+
+ // Focus Events
+ onfocus?: EventHandler;
+ onblur?: EventHandler;
+
+ // Form Events
+ onchange?: EventHandler;
+ oninput?: EventHandler;
+ onsearch?: EventHandler;
+ onsubmit?: EventHandler;
+ oninvalid?: EventHandler;
+
+ // Image Events
+ onload?: EventHandler;
+ onerror?: EventHandler;
+
+ // Keyboard Events
+ onkeydown?: EventHandler;
+ onkeypress?: EventHandler;
+ onkeyup?: EventHandler;
+
+ // Media Events
+ onabort?: EventHandler;
+ oncanplay?: EventHandler;
+ oncanplaythrough?: EventHandler;
+ ondurationchange?: EventHandler;
+ onemptied?: EventHandler;
+ onencrypted?: EventHandler;
+ onended?: EventHandler;
+ onloadeddata?: EventHandler;
+ onloadedmetadata?: EventHandler;
+ onloadstart?: EventHandler;
+ onpause?: EventHandler;
+ onplay?: EventHandler;
+ onplaying?: EventHandler;
+ onprogress?: EventHandler;
+ onratechange?: EventHandler;
+ onseeked?: EventHandler;
+ onseeking?: EventHandler;
+ onstalled?: EventHandler;
+ onsuspend?: EventHandler;
+ ontimeupdate?: EventHandler;
+ onvolumechange?: EventHandler;
+ onwaiting?: EventHandler;
+
+ // MouseEvents
+ onclick?: EventHandler;
+ oncontextmenu?: EventHandler;
+ ondblclick?: EventHandler;
+ ondrag?: EventHandler;
+ ondragend?: EventHandler;
+ ondragenter?: EventHandler;
+ ondragexit?: EventHandler;
+ ondragleave?: EventHandler;
+ ondragover?: EventHandler;
+ ondragstart?: EventHandler;
+ ondrop?: EventHandler;
+ onmousedown?: EventHandler;
+ onmouseenter?: EventHandler;
+ onmouseleave?: EventHandler;
+ onmousemove?: EventHandler;
+ onmouseout?: EventHandler;
+ onmouseover?: EventHandler;
+ onmouseup?: EventHandler;
+
+ // Selection Events
+ onselect?: EventHandler;
+
+ // Touch Events
+ ontouchcancel?: EventHandler;
+ ontouchend?: EventHandler;
+ ontouchmove?: EventHandler;
+ ontouchstart?: EventHandler;
+
+ // Pointer Events
+ onpointerover?: EventHandler;
+ onpointerenter?: EventHandler;
+ onpointerdown?: EventHandler;
+ onpointermove?: EventHandler;
+ onpointerup?: EventHandler;
+ onpointercancel?: EventHandler;
+ onpointerout?: EventHandler;
+ onpointerleave?: EventHandler;
+ ongotpointercapture?: EventHandler;
+ onlostpointercapture?: EventHandler;
+
+ // UI Events
+ onscroll?: EventHandler;
+
+ // Wheel Events
+ onwheel?: EventHandler;
+
+ // Animation Events
+ onanimationstart?: EventHandler;
+ onanimationend?: EventHandler;
+ onanimationiteration?: EventHandler;
+
+ // Transition Events
+ ontransitionend?: EventHandler;
+ }
+
+ export
+ type StyleAttributes = { [key: string]: string | number };
+
+ export
+ interface HTMLAttributes extends DOMAttributes {
+ // Standard HTML Attributes
+ accept?: string;
+ acceptcharset?: string;
+ accesskey?: string;
+ action?: string;
+ allowfullscreen?: boolean;
+ allowtransparency?: boolean;
+ alt?: string;
+ async?: boolean;
+ autocomplete?: string;
+ autocorrect?: string;
+ autofocus?: boolean;
+ autoplay?: boolean;
+ capture?: boolean;
+ cellpadding?: number | string;
+ cellspacing?: number | string;
+ charset?: string;
+ challenge?: string;
+ checked?: boolean;
+ class?: string;
+ cols?: number;
+ colspan?: number;
+ content?: string;
+ contenteditable?: boolean;
+ contextmenu?: string;
+ controls?: boolean;
+ controlslist?: string;
+ coords?: string;
+ crossorigin?: string;
+ data?: string;
+ datetime?: string;
+ default?: boolean;
+ defer?: boolean;
+ dir?: string;
+ disabled?: boolean;
+ disableremoteplayback?: boolean;
+ download?: any;
+ draggable?: boolean;
+ enctype?: string;
+ form?: string;
+ formaction?: string;
+ formenctype?: string;
+ formmethod?: string;
+ formnovalidate?: boolean;
+ formtarget?: string;
+ frameborder?: number | string;
+ headers?: string;
+ height?: number | string;
+ hidden?: boolean;
+ high?: number;
+ href?: string;
+ hreflang?: string;
+ for?: string;
+ httpequiv?: string;
+ icon?: string;
+ id?: string;
+ inputmode?: string;
+ integrity?: string;
+ is?: string;
+ keyparams?: string;
+ keytype?: string;
+ kind?: string;
+ label?: string;
+ lang?: string;
+ list?: string;
+ loop?: boolean;
+ low?: number;
+ manifest?: string;
+ marginheight?: number;
+ marginwidth?: number;
+ max?: number | string;
+ maxlength?: number;
+ media?: string;
+ mediagroup?: string;
+ method?: string;
+ min?: number | string;
+ minlength?: number;
+ multiple?: boolean;
+ muted?: boolean;
+ name?: string;
+ novalidate?: boolean;
+ open?: boolean;
+ optimum?: number;
+ pattern?: string;
+ placeholder?: string;
+ playsinline?: boolean;
+ poster?: string;
+ preload?: string;
+ radiogroup?: string;
+ readonly?: boolean;
+ rel?: string;
+ required?: boolean;
+ role?: string;
+ rows?: number;
+ rowspan?: number;
+ sandbox?: string;
+ scope?: string;
+ scoped?: boolean;
+ scrolling?: string;
+ seamless?: boolean;
+ selected?: boolean;
+ shape?: string;
+ size?: number;
+ sizes?: string;
+ slot?: string;
+ span?: number;
+ spellcheck?: boolean;
+ src?: string;
+ srcdoc?: string;
+ srclang?: string;
+ srcset?: string;
+ start?: number;
+ step?: number | string;
+ style?: StyleAttributes;
+ summary?: string;
+ tabindex?: number;
+ target?: string;
+ title?: string;
+ type?: string;
+ usemap?: string;
+ value?: string | string[] | number;
+ volume?: string | number;
+ width?: number | string;
+ wmode?: string;
+ wrap?: string;
+
+ // RDFa Attributes
+ about?: string;
+ datatype?: string;
+ inlist?: any;
+ prefix?: string;
+ property?: string;
+ resource?: string;
+ typeof?: string;
+ vocab?: string;
+
+ // Microdata Attributes
+ itemprop?: string;
+ itemscope?: boolean;
+ itemtype?: string;
+ itemid?: string;
+ itemref?: string;
+ }
+
+ export
+ interface SVGAttributes extends HTMLAttributes {
+ accentheight?: number | string;
+ accumulate?: "none" | "sum";
+ additive?: "replace" | "sum";
+ alignmentbaseline?: "auto" | "baseline" | "before-edge" | "text-before-edge" | "middle" | "central" | "after-edge" | "text-after-edge" | "ideographic" | "alphabetic" | "hanging" | "mathematical" | "inherit";
+ allowreorder?: "no" | "yes";
+ alphabetic?: number | string;
+ amplitude?: number | string;
+ arabicform?: "initial" | "medial" | "terminal" | "isolated";
+ ascent?: number | string;
+ attributename?: string;
+ attributetype?: string;
+ autoreverse?: number | string;
+ azimuth?: number | string;
+ basefrequency?: number | string;
+ baselineshift?: number | string;
+ baseprofile?: number | string;
+ bbox?: number | string;
+ begin?: number | string;
+ bias?: number | string;
+ by?: number | string;
+ calcmode?: number | string;
+ capheight?: number | string;
+ clip?: number | string;
+ clippath?: string;
+ clippathunits?: number | string;
+ cliprule?: number | string;
+ colorinterpolation?: number | string;
+ colorinterpolationfilters?: "auto" | "sRGB" | "linearRGB" | "inherit";
+ colorprofile?: number | string;
+ colorrendering?: number | string;
+ contentscripttype?: number | string;
+ contentstyletype?: number | string;
+ cursor?: number | string;
+ cx?: number | string;
+ cy?: number | string;
+ d?: string;
+ decelerate?: number | string;
+ descent?: number | string;
+ diffuseconstant?: number | string;
+ direction?: number | string;
+ display?: number | string;
+ divisor?: number | string;
+ dominantbaseline?: number | string;
+ dur?: number | string;
+ dx?: number | string;
+ dy?: number | string;
+ edgemode?: number | string;
+ elevation?: number | string;
+ enablebackground?: number | string;
+ end?: number | string;
+ exponent?: number | string;
+ externalresourcesrequired?: number | string;
+ fill?: string;
+ fillopacity?: number | string;
+ fillrule?: "nonzero" | "evenodd" | "inherit";
+ filter?: string;
+ filterres?: number | string;
+ filterunits?: number | string;
+ floodcolor?: number | string;
+ floodopacity?: number | string;
+ focusable?: number | string;
+ fontfamily?: string;
+ fontsize?: number | string;
+ fontsizeadjust?: number | string;
+ fontstretch?: number | string;
+ fontstyle?: number | string;
+ fontvariant?: number | string;
+ fontweight?: number | string;
+ format?: number | string;
+ from?: number | string;
+ fx?: number | string;
+ fy?: number | string;
+ g1?: number | string;
+ g2?: number | string;
+ glyphname?: number | string;
+ glyphorientationhorizontal?: number | string;
+ glyphorientationvertical?: number | string;
+ glyphref?: number | string;
+ gradienttransform?: string;
+ gradientunits?: string;
+ hanging?: number | string;
+ horizadvx?: number | string;
+ horizoriginx?: number | string;
+ ideographic?: number | string;
+ imagerendering?: number | string;
+ in2?: number | string;
+ in?: string;
+ intercept?: number | string;
+ k1?: number | string;
+ k2?: number | string;
+ k3?: number | string;
+ k4?: number | string;
+ k?: number | string;
+ kernelmatrix?: number | string;
+ kernelunitlength?: number | string;
+ kerning?: number | string;
+ keypoints?: number | string;
+ keysplines?: number | string;
+ keytimes?: number | string;
+ lengthadjust?: number | string;
+ letterspacing?: number | string;
+ lightingcolor?: number | string;
+ limitingconeangle?: number | string;
+ local?: number | string;
+ markerend?: string;
+ markerheight?: number | string;
+ markermid?: string;
+ markerstart?: string;
+ markerunits?: number | string;
+ markerwidth?: number | string;
+ mask?: string;
+ maskcontentunits?: number | string;
+ maskunits?: number | string;
+ mathematical?: number | string;
+ mode?: number | string;
+ numoctaves?: number | string;
+ offset?: number | string;
+ opacity?: number | string;
+ operator?: number | string;
+ order?: number | string;
+ orient?: number | string;
+ orientation?: number | string;
+ origin?: number | string;
+ overflow?: number | string;
+ overlineposition?: number | string;
+ overlinethickness?: number | string;
+ paintorder?: number | string;
+ panose1?: number | string;
+ pathlength?: number | string;
+ patterncontentunits?: string;
+ patterntransform?: number | string;
+ patternunits?: string;
+ pointerevents?: number | string;
+ points?: string;
+ pointsatx?: number | string;
+ pointsaty?: number | string;
+ pointsatz?: number | string;
+ preservealpha?: number | string;
+ preserveaspectratio?: string;
+ primitiveunits?: number | string;
+ r?: number | string;
+ radius?: number | string;
+ refx?: number | string;
+ refy?: number | string;
+ renderingintent?: number | string;
+ repeatcount?: number | string;
+ repeatdur?: number | string;
+ requiredextensions?: number | string;
+ requiredfeatures?: number | string;
+ restart?: number | string;
+ result?: string;
+ rotate?: number | string;
+ rx?: number | string;
+ ry?: number | string;
+ scale?: number | string;
+ seed?: number | string;
+ shaperendering?: number | string;
+ slope?: number | string;
+ spacing?: number | string;
+ specularconstant?: number | string;
+ specularexponent?: number | string;
+ speed?: number | string;
+ spreadmethod?: string;
+ startoffset?: number | string;
+ stddeviation?: number | string;
+ stemh?: number | string;
+ stemv?: number | string;
+ stitchtiles?: number | string;
+ stopcolor?: string;
+ stopopacity?: number | string;
+ strikethroughposition?: number | string;
+ strikethroughthickness?: number | string;
+ string?: number | string;
+ stroke?: string;
+ strokedasharray?: string | number;
+ strokedashoffset?: string | number;
+ strokelinecap?: "butt" | "round" | "square" | "inherit";
+ strokelinejoin?: "miter" | "round" | "bevel" | "inherit";
+ strokemiterlimit?: string;
+ strokeopacity?: number | string;
+ strokewidth?: number | string;
+ surfacescale?: number | string;
+ systemlanguage?: number | string;
+ tablevalues?: number | string;
+ targetx?: number | string;
+ targety?: number | string;
+ textanchor?: string;
+ textdecoration?: number | string;
+ textlength?: number | string;
+ textrendering?: number | string;
+ to?: number | string;
+ transform?: string;
+ u1?: number | string;
+ u2?: number | string;
+ underlineposition?: number | string;
+ underlinethickness?: number | string;
+ unicode?: number | string;
+ unicodebidi?: number | string;
+ unicoderange?: number | string;
+ unitsperem?: number | string;
+ valphabetic?: number | string;
+ values?: string;
+ vectoreffect?: number | string;
+ version?: string;
+ vertadvy?: number | string;
+ vertoriginx?: number | string;
+ vertoriginy?: number | string;
+ vhanging?: number | string;
+ videographic?: number | string;
+ viewbox?: string;
+ viewtarget?: number | string;
+ visibility?: number | string;
+ vmathematical?: number | string;
+ widths?: number | string;
+ wordspacing?: number | string;
+ writingmode?: number | string;
+ x1?: number | string;
+ x2?: number | string;
+ x?: number | string;
+ xchannelselector?: string;
+ xheight?: number | string;
+ xlinkactuate?: string;
+ xlinkarcrole?: string;
+ xlinkhref?: string;
+ xlinkrole?: string;
+ xlinkshow?: string;
+ xlinktitle?: string;
+ xlinktype?: string;
+ xmlbase?: string;
+ xmllang?: string;
+ xmlns?: string;
+ xmlnsxlink?: string;
+ xmlspace?: string;
+ y1?: number | string;
+ y2?: number | string;
+ y?: number | string;
+ ychannelselector?: string;
+ z?: number | string;
+ zoomandpan?: string;
+ }
+
+ export
+ interface IntrinsicElements {
+ // HTML
+ a: HTMLAttributes;
+ abbr: HTMLAttributes;
+ address: HTMLAttributes;
+ area: HTMLAttributes;
+ article: HTMLAttributes;
+ aside: HTMLAttributes;
+ audio: HTMLAttributes;
+ b: HTMLAttributes;
+ base: HTMLAttributes;
+ bdi: HTMLAttributes;
+ bdo: HTMLAttributes;
+ big: HTMLAttributes;
+ blockquote: HTMLAttributes;
+ body: HTMLAttributes;
+ br: HTMLAttributes;
+ button: HTMLAttributes;
+ canvas: HTMLAttributes;
+ caption: HTMLAttributes;
+ cite: HTMLAttributes;
+ code: HTMLAttributes;
+ col: HTMLAttributes;
+ colgroup: HTMLAttributes;
+ data: HTMLAttributes;
+ datalist: HTMLAttributes;
+ dd: HTMLAttributes;
+ del: HTMLAttributes;
+ details: HTMLAttributes;
+ dfn: HTMLAttributes;
+ dialog: HTMLAttributes;
+ div: HTMLAttributes;
+ dl: HTMLAttributes;
+ dt: HTMLAttributes;
+ em: HTMLAttributes;
+ embed: HTMLAttributes;
+ fieldset: HTMLAttributes;
+ figcaption: HTMLAttributes;
+ figure: HTMLAttributes;
+ footer: HTMLAttributes;
+ form: HTMLAttributes;
+ h1: HTMLAttributes;
+ h2: HTMLAttributes;
+ h3: HTMLAttributes;
+ h4: HTMLAttributes;
+ h5: HTMLAttributes;
+ h6: HTMLAttributes;
+ head: HTMLAttributes;
+ header: HTMLAttributes;
+ hgroup: HTMLAttributes;
+ hr: HTMLAttributes;
+ html: HTMLAttributes;
+ i: HTMLAttributes;
+ iframe: HTMLAttributes;
+ img: HTMLAttributes;
+ input: HTMLAttributes;
+ ins: HTMLAttributes;
+ kbd: HTMLAttributes;
+ keygen: HTMLAttributes;
+ label: HTMLAttributes;
+ legend: HTMLAttributes;
+ li: HTMLAttributes;
+ link: HTMLAttributes;
+ main: HTMLAttributes;
+ map: HTMLAttributes;
+ mark: HTMLAttributes;
+ menu: HTMLAttributes;
+ menuitem: HTMLAttributes;
+ meta: HTMLAttributes;
+ meter: HTMLAttributes;
+ nav: HTMLAttributes;
+ noscript: HTMLAttributes;
+ object: HTMLAttributes;
+ ol: HTMLAttributes;
+ optgroup: HTMLAttributes;
+ option: HTMLAttributes;
+ output: HTMLAttributes;
+ p: HTMLAttributes;
+ param: HTMLAttributes;
+ picture: HTMLAttributes;
+ pre: HTMLAttributes;
+ progress: HTMLAttributes;
+ q: HTMLAttributes;
+ rp: HTMLAttributes;
+ rt: HTMLAttributes;
+ ruby: HTMLAttributes;
+ s: HTMLAttributes;
+ samp: HTMLAttributes;
+ script: HTMLAttributes;
+ section: HTMLAttributes;
+ select: HTMLAttributes;
+ slot: HTMLAttributes;
+ small: HTMLAttributes;
+ source: HTMLAttributes;
+ span: HTMLAttributes;
+ strong: HTMLAttributes;
+ style: HTMLAttributes;
+ sub: HTMLAttributes;
+ summary: HTMLAttributes;
+ sup: HTMLAttributes;
+ table: HTMLAttributes;
+ tbody: HTMLAttributes;
+ td: HTMLAttributes;
+ textarea: HTMLAttributes;
+ tfoot: HTMLAttributes;
+ th: HTMLAttributes;
+ thead: HTMLAttributes;
+ time: HTMLAttributes;
+ title: HTMLAttributes;
+ tr: HTMLAttributes;
+ track: HTMLAttributes;
+ u: HTMLAttributes;
+ ul: HTMLAttributes;
+ "var": HTMLAttributes;
+ video: HTMLAttributes;
+ wbr: HTMLAttributes;
+
+ //SVG
+ svg: SVGAttributes;
+ animate: SVGAttributes;
+ circle: SVGAttributes;
+ clipPath: SVGAttributes;
+ defs: SVGAttributes;
+ desc: SVGAttributes;
+ ellipse: SVGAttributes;
+ feBlend: SVGAttributes;
+ feColorMatrix: SVGAttributes;
+ feComponentTransfer: SVGAttributes;
+ feComposite: SVGAttributes;
+ feConvolveMatrix: SVGAttributes;
+ feDiffuseLighting: SVGAttributes;
+ feDisplacementMap: SVGAttributes;
+ feFlood: SVGAttributes;
+ feGaussianBlur: SVGAttributes;
+ feImage: SVGAttributes;
+ feMerge: SVGAttributes;
+ feMergeNode: SVGAttributes;
+ feMorphology: SVGAttributes;
+ feOffset: SVGAttributes;
+ feSpecularLighting: SVGAttributes;
+ feTile: SVGAttributes;
+ feTurbulence: SVGAttributes;
+ filter: SVGAttributes;
+ foreignObject: SVGAttributes;
+ g: SVGAttributes;
+ image: SVGAttributes;
+ line: SVGAttributes;
+ linearGradient: SVGAttributes;
+ marker: SVGAttributes;
+ mask: SVGAttributes;
+ path: SVGAttributes;
+ pattern: SVGAttributes;
+ polygon: SVGAttributes;
+ polyline: SVGAttributes;
+ radialGradient: SVGAttributes;
+ rect: SVGAttributes;
+ stop: SVGAttributes;
+ symbol: SVGAttributes;
+ text: SVGAttributes;
+ tspan: SVGAttributes;
+ use: SVGAttributes;
+ }
+}
diff --git a/packages/vdom/src/vdom.ts b/packages/vdom/src/vdom.ts
new file mode 100644
index 000000000..57afa5aef
--- /dev/null
+++ b/packages/vdom/src/vdom.ts
@@ -0,0 +1,483 @@
+/*------------------------------------------------------------------------------
+| Copyright (c) 2014-2019, PhosphorJS Contributors
+|
+| Distributed under the terms of the BSD 3-Clause License.
+|
+| The full license is in the file LICENSE, distributed with this software.
+|-----------------------------------------------------------------------------*/
+import {
+ ArrayExt
+} from '@phosphor/algorithm';
+
+import {
+ PJSX
+} from './pjsx';
+
+import {
+ VNode
+} from './vnode';
+
+
+/**
+ * The namespace for the virtual DOM functionality.
+ */
+export
+namespace VDOM {
+ /**
+ * Export `PJSX` as the namespace `JSX`.
+ */
+ export
+ import JSX = PJSX;
+
+ /**
+ * A type alias for VDOM props.
+ */
+ export
+ type Props = PJSX.SpecialAttributes & Record;
+
+ /**
+ * A type alias for a pure function component.
+ */
+ export
+ type FC = (props: Props) => PJSX.Element;
+
+ /**
+ * Create a virtual DOM node for the given content.
+ *
+ * @param type - The element tag or function component to create.
+ *
+ * @param props - The props for the component.
+ *
+ * @param children - The children for the component.
+ *
+ * @returns A new virtual node for the given parameters.
+ */
+ export
+ function createElement(type: string | FC, props: Props | null, ...children: PJSX.Children[]): PJSX.Element {
+ return Private.createElement(type, props, children);
+ }
+
+ /**
+ * The namespace for the `createElement` function statics.
+ */
+ export
+ namespace createElement {
+ /**
+ * Export `PJSX` as the namespace `JSX`.
+ */
+ export
+ import JSX = PJSX;
+ }
+
+ /**
+ * Render virtual DOM content into a host element.
+ *
+ * @param content - The virtual content to render.
+ *
+ * @param host - The host element into which the content will be rendered.
+ */
+ export
+ function render(content: PJSX.Children, host: Element): void {
+ Private.render(content, host);
+ }
+}
+
+
+/**
+ * The namespace for the module implementation details.
+ */
+namespace Private {
+ /**
+ * Create a virtual DOM node for the given parameters.
+ */
+ export
+ function createElement(type: string | VDOM.FC, props: VDOM.Props | null, children: PJSX.Children[]): PJSX.Element {
+ let element: PJSX.Element;
+ if (typeof type === 'string') {
+ element = { tag: type, props: createProps(props, children) };
+ } else {
+ element = type(createProps(props, children));
+ }
+ return element;
+ }
+
+ /**
+ * Render virtual DOM content into a host node.
+ */
+ export
+ function render(content: PJSX.Children, host: Element): void {
+ // Fetch the old content.
+ let oldContent: VNode.Children = hostMap.get(host) || emptyArray;
+
+ // Flatten the new content.
+ let newContent: VNode.Children = flattenChildren([content]);
+
+ // Save the new content.
+ hostMap.set(host, newContent);
+
+ // Update the host with the difference between old and new.
+ updateContent(host, oldContent, newContent);
+ }
+
+ /**
+ * A weakmap of host element to rendered content.
+ */
+ const hostMap = new WeakMap>();
+
+ /**
+ * A frozen empty VNode array.
+ */
+ const emptyArray: ReadonlyArray = Object.freeze([]);
+
+ /**
+ * A frozen empty VNode props object.
+ */
+ const emptyProps: VNode.Props = Object.freeze({ children: emptyArray });
+
+ /**
+ * A frozen empty style attributes object.
+ */
+ const emptyStyle: PJSX.StyleAttributes = Object.freeze({});
+
+ /**
+ * A type guard for DOM elements.
+ */
+ function isElement(node: Node): node is Element {
+ return node.nodeType === Node.ELEMENT_NODE;
+ }
+
+ /**
+ * Create the props for a virtual node.
+ */
+ function createProps(props: VDOM.Props | null, children: PJSX.Children[]): VNode.Props {
+ // Flatten the children.
+ let kids = flattenChildren(children);
+
+ // Clone and return the props.
+ return props ? { ...props, children: kids } : { children: kids };
+ }
+
+ /**
+ * Process virtual DOM children in a flat VNode array.
+ */
+ function flattenChildren(children: PJSX.Children[]): VNode.Children {
+ // Return the frozen array singleton if there are no children.
+ if (children.length === 0) {
+ return emptyArray;
+ }
+
+ // Set up the flat result array.
+ let result: VNode.Child[] = [];
+
+ // Process each child.
+ children.forEach(process);
+
+ // Return the result.
+ return result;
+
+ // Process an element from the children array.
+ function process(child: PJSX.Children): void {
+ // Skip null children.
+ if (child === null) {
+ return;
+ }
+
+ // Handle string children.
+ if (typeof child === 'string') {
+ result.push(child);
+ return;
+ }
+
+ // Handle other primitive children.
+ if (typeof child === 'number' || typeof child === 'boolean') {
+ result.push(String(child));
+ return;
+ }
+
+ // Handle VNode children.
+ if (!Array.isArray(child)) {
+ result.push(child);
+ return;
+ }
+
+ // Handle array children.
+ child.forEach(process);
+ }
+ }
+
+ /**
+ * Create a new DOM node for the given virtual node.
+ */
+ function createDOM(node: string): Text;
+ function createDOM(node: VNode): Element;
+ function createDOM(node: VNode | string): Element | Text;
+ function createDOM(node: VNode | string): Element | Text {
+ // Handle string content.
+ if (typeof node === 'string') {
+ return document.createTextNode(node);
+ }
+
+ // Create the HTML element with the specified tag.
+ let element = document.createElement(node.tag);
+
+ // Update the props of the element.
+ updateProps(element, emptyProps, node.props);
+
+ // Update the content of the element.
+ updateContent(element, emptyArray, node.props.children);
+
+ // Return the populated element.
+ return element;
+ }
+
+ /**
+ * Find the index of a keyed vnode in a content array.
+ */
+ function findKeyIndex(key: string | number, content: VNode.Children, start: number): number {
+ for (let i = start; i < content.length; ++i) {
+ let child = content[i];
+ if (typeof child !== 'string' && key === child.props.key) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Update a host element with the delta of the virtual content.
+ *
+ * This is the core "diff" algorithm. There is no explicit "patch"
+ * phase. The host is patched at each step as the diff progresses.
+ */
+ function updateContent(host: Element, oldContent: VNode.Children, newContent: VNode.Children): void {
+ // Bail early if the content is identical.
+ if (oldContent === newContent) {
+ return;
+ }
+
+ // Get a shallow copy of the content that can be mutated in-place.
+ let tmpContent = [...oldContent];
+
+
+ // Set up the current node variable.
+ let currNode = host.firstChild;
+
+ // Update the host with the new content. The diff always proceeds
+ // forward and never modifies a previously visited index. The tmp
+ // copy array is modified in-place to reflect the changes made to
+ // the host children. This causes the stale nodes to be pushed to
+ // the end of the host node and removed at the end of the loop.
+ for (let i = 0; i < newContent.length; ++i) {
+ // Look up the new child.
+ let newChild = newContent[i];
+
+ // If the old content is exhausted, create a new node.
+ if (i >= tmpContent.length) {
+ host.appendChild(createDOM(newChild));
+ continue;
+ }
+
+ // Sanity check the DOM state.
+ if (currNode === null) {
+ throw new Error('invalid VDOM state');
+ }
+
+ // Lookup the old child.
+ let oldChild = tmpContent[i];
+
+ // If both elements are identical, there is nothing to do.
+ if (oldChild === newChild) {
+ currNode = currNode.nextSibling;
+ continue;
+ }
+
+ // Handle the simplest case of in-place text update first.
+ if (typeof oldChild === 'string' && typeof newChild === 'string') {
+ currNode.textContent = newChild;
+ currNode = currNode.nextSibling;
+ continue;
+ }
+
+ // If the old or new node is a text node, the other node is now
+ // known to be an element node, so create and insert a new node.
+ if (typeof oldChild === 'string' || typeof newChild === 'string') {
+ host.insertBefore(createDOM(newChild), currNode);
+ ArrayExt.insert(tmpContent, i, newChild);
+ continue;
+ }
+
+ // If the new elem is keyed, move an old keyed elem to the proper
+ // location before proceeding with the diff. The search can start
+ // at the current index, since the unmatched old keyed elems are
+ // pushed forward in the content array.
+ if (newChild.props.key !== undefined) {
+ let j = findKeyIndex(newChild.props.key, tmpContent, i);
+ if (j !== -1 && i !== j) {
+ let node = host.childNodes[j];
+ host.insertBefore(node, currNode);
+ ArrayExt.move(tmpContent, j, i);
+ oldChild = tmpContent[i] as VNode;
+ currNode = node;
+ }
+ }
+
+ // If both nodes are identical, there is nothing to do.
+ if (oldChild === newChild) {
+ currNode = currNode.nextSibling;
+ continue;
+ }
+
+ // If the keys are different, create a new node.
+ if (oldChild.props.key !== newChild.props.key) {
+ host.insertBefore(createDOM(newChild), currNode);
+ ArrayExt.insert(tmpContent, i, newChild);
+ continue;
+ }
+
+ // If the tags are different, create a new node.
+ if (oldChild.tag !== newChild.tag) {
+ host.insertBefore(createDOM(newChild), currNode);
+ ArrayExt.insert(tmpContent, i, newChild);
+ continue;
+ }
+
+ // Sanity check the DOM state.
+ if (!isElement(currNode)) {
+ throw new Error('invalid virtual DOM state');
+ }
+
+ // Update the props of the current element.
+ updateProps(currNode, oldChild.props, newChild.props);
+
+ // Update the content of the current element.
+ updateContent(currNode, oldChild.props.children, newChild.props.children);
+
+ // Step to the next sibling element.
+ currNode = currNode.nextSibling;
+ }
+
+ // Dispose of the old nodes pushed to the end of the host.
+ for (let n = tmpContent.length - newContent.length; n > 0; --n) {
+ host.removeChild(host.lastChild!);
+ }
+ }
+
+ /**
+ * Update an element with the difference of props.
+ */
+ function updateProps(element: Element, oldProps: VDOM.Props, newProps: VDOM.Props): void {
+ // Do nothing if the props are the same object.
+ if (oldProps === newProps) {
+ return;
+ }
+
+ // Process the old props.
+ for (let name in oldProps) {
+ if (!(name in newProps)) {
+ setProp(element, name, oldProps[name], null);
+ }
+ }
+
+ // Process the new props.
+ for (let name in newProps) {
+ if (name in oldProps) {
+ setProp(element, name, oldProps[name], newProps[name]);
+ } else {
+ setProp(element, name, null, newProps[name]);
+ }
+ }
+ }
+
+ /**
+ * Apply a property difference to an element.
+ */
+ function setProp(element: Element, name: string, oldValue: any | null, newValue: any | null): void {
+ // Skip the special `key` and `children` props.
+ if (name === 'key' || name === 'children') {
+ return;
+ }
+
+ // Handle the special `ref` prop.
+ if (name === 'ref') {
+ if (oldValue) {
+ oldValue.current = null;
+ }
+ if (newValue) {
+ newValue.current = element;
+ }
+ return;
+ }
+
+ // Bail early if the value does not change.
+ if (oldValue === newValue) {
+ return;
+ }
+
+ // Handle the style props.
+ if (name === 'style') {
+ if (oldValue === null) {
+ oldValue = emptyStyle;
+ }
+ if (newValue === null) {
+ newValue = emptyStyle;
+ }
+ updateStyle((element as HTMLElement).style, oldValue, newValue);
+ return;
+ }
+
+ // Handle inline event listeners.
+ if (name[0] === 'o' && name[1] === 'n') {
+ (element as any)[name] = newValue;
+ return;
+ }
+
+ // Set or remove the attribute as appropriate.
+ if (newValue === false || newValue === null) {
+ element.removeAttribute(name);
+ } else if (newValue === true) {
+ element.setAttribute(name, '');
+ } else {
+ element.setAttribute(name, newValue);
+ }
+
+ // Special-case `input.value`.
+ if (name === 'value' && element.tagName === 'INPUT') {
+ (element as HTMLInputElement).value = newValue;
+ }
+ }
+
+ /**
+ * Update a style declaration with the difference of style attributes.
+ */
+ function updateStyle(style: CSSStyleDeclaration, oldAttrs: PJSX.StyleAttributes, newAttrs: PJSX.StyleAttributes): void {
+ // Bail early if the attr objects don't change.
+ if (oldAttrs === newAttrs) {
+ return;
+ }
+
+ // Process the old attrs.
+ for (let name in oldAttrs) {
+ if (!(name in newAttrs)) {
+ setStyleAttr(style, name, oldAttrs[name], '');
+ }
+ }
+
+ // Process the new attrs.
+ for (let name in newAttrs) {
+ if (name in oldAttrs) {
+ setStyleAttr(style, name, oldAttrs[name], newAttrs[name]);
+ } else {
+ setStyleAttr(style, name, '', newAttrs[name]);
+ }
+ }
+ }
+
+ /**
+ * Apply an attribute difference to a style declaration.
+ */
+ function setStyleAttr(style: CSSStyleDeclaration, name: string, oldValue: string | number, newValue: string | number): void {
+ if (oldValue !== newValue) {
+ (style as any)[name] = String(newValue);
+ }
+ }
+}
diff --git a/packages/vdom/src/vnode.ts b/packages/vdom/src/vnode.ts
new file mode 100644
index 000000000..6ee0eda80
--- /dev/null
+++ b/packages/vdom/src/vnode.ts
@@ -0,0 +1,88 @@
+/*------------------------------------------------------------------------------
+| Copyright (c) 2014-2019, PhosphorJS Contributors
+|
+| Distributed under the terms of the BSD 3-Clause License.
+|
+| The full license is in the file LICENSE, distributed with this software.
+|-----------------------------------------------------------------------------*/
+
+
+/**
+ * A type alias for a virtual node.
+ */
+export
+type VNode = {
+ /**
+ * The element tag name.
+ */
+ readonly tag: string;
+
+ /**
+ * The element props.
+ */
+ readonly props: VNode.Props;
+};
+
+
+/**
+ * The namespace for the `VNode` type statics.
+ */
+export
+namespace VNode {
+ /**
+ * A type alias for a node child.
+ */
+ export
+ type Child = VNode | string;
+
+ /**
+ * A type alias for VNode children.
+ */
+ export
+ type Children = ReadonlyArray;
+
+ /**
+ * A type alias for a node key.
+ */
+ export
+ type Key = string | number;
+
+ /**
+ * A type alias for a node ref.
+ */
+ export
+ type Ref = { current?: HTMLElement | null };
+
+ /**
+ * A type alias for intrinsic node props.
+ */
+ export
+ type IntrinsicProps = {
+ /**
+ * The children of the node.
+ */
+ readonly children: Children;
+
+ /**
+ * The key for the node.
+ */
+ readonly key?: Key;
+
+ /**
+ * The ref for the node.
+ */
+ readonly ref?: Ref;
+ };
+
+ /**
+ * A type alias for the attribute node props.
+ */
+ export
+ type AttributeProps = Readonly>;
+
+ /**
+ * A type alias for the node props.
+ */
+ export
+ type Props = IntrinsicProps & AttributeProps;
+}
diff --git a/packages/vdom/tsconfig.json b/packages/vdom/tsconfig.json
new file mode 100644
index 000000000..5f8158f37
--- /dev/null
+++ b/packages/vdom/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "declaration": true,
+ "noImplicitAny": true,
+ "noEmitOnError": true,
+ "noUnusedLocals": true,
+ "strictNullChecks": true,
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "target": "ES5",
+ "outDir": "lib",
+ "lib": ["es2015", "dom"],
+ "types": [],
+ "rootDir": "src"
+ },
+ "include": ["src/*"]
+}