@thi.ng/hdom
Advanced tools
Comparing version 3.0.35 to 4.0.0
32
api.d.ts
@@ -16,2 +16,34 @@ import { IObjectOf } from "@thi.ng/api/api"; | ||
} | ||
export interface HDOMOpts { | ||
/** | ||
* Root element or ID (default: "app"). | ||
*/ | ||
root: Element | string; | ||
/** | ||
* Arbitrary user context object, passed to all component functions | ||
* embedded in the tree. | ||
*/ | ||
ctx?: any; | ||
/** | ||
* If true (default), text content will be wrapped in `<span>` | ||
*/ | ||
span?: boolean; | ||
/** | ||
* If true (default false), the first frame will only be used to | ||
* inject event listeners. | ||
* | ||
* *Important:* Enabling this option assumes that an equivalent DOM | ||
* (minus listeners) already exists (i.e. generated via SSR) when | ||
* hdom's `start()` function is called. Any other discrepancies | ||
* between the pre-existing DOM and the hdom trees will cause | ||
* undefined behavior. | ||
*/ | ||
hydrate?: boolean; | ||
/** | ||
* If true (default), the hdom component tree will be first | ||
* normalized before diffing (using `normalizeTree()`). Unless you | ||
* know what you're doing, it's best to leave this enabled. | ||
*/ | ||
normalize?: boolean; | ||
} | ||
export declare const DEBUG: boolean; |
@@ -6,2 +6,19 @@ # Change Log | ||
<a name="4.0.0"></a> | ||
# [4.0.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@3.0.35...@thi.ng/hdom@4.0.0) (2018-08-31) | ||
### Features | ||
* **hdom:** add DOM hydration support (SSR), update start() ([#39](https://github.com/thi-ng/umbrella/issues/39)) ([9f8010d](https://github.com/thi-ng/umbrella/commit/9f8010d)) | ||
* **hdom:** update HDOMOpts & start() ([5e74a9c](https://github.com/thi-ng/umbrella/commit/5e74a9c)) | ||
### BREAKING CHANGES | ||
* **hdom:** start() args now as options object | ||
<a name="3.0.35"></a> | ||
@@ -8,0 +25,0 @@ ## [3.0.35](https://github.com/thi-ng/umbrella/compare/@thi.ng/hdom@3.0.34...@thi.ng/hdom@3.0.35) (2018-08-27) |
@@ -20,2 +20,2 @@ /** | ||
*/ | ||
export declare function diffElement(root: Element, prev: any, curr: any): void; | ||
export declare const diffElement: (root: Element, prev: any, curr: any) => void; |
21
diff.js
@@ -31,7 +31,4 @@ "use strict"; | ||
*/ | ||
function diffElement(root, prev, curr) { | ||
_diffElement(root, prev, curr, 0); | ||
} | ||
exports.diffElement = diffElement; | ||
function _diffElement(parent, prev, curr, child) { | ||
exports.diffElement = (root, prev, curr) => _diffElement(root, prev, curr, 0); | ||
const _diffElement = (parent, prev, curr, child) => { | ||
const delta = array_1.diffArray(prev, curr, equiv_1.equiv, true); | ||
@@ -111,4 +108,4 @@ if (delta.distance === 0) { | ||
} | ||
} | ||
function releaseDeep(tag) { | ||
}; | ||
const releaseDeep = (tag) => { | ||
if (isArray(tag)) { | ||
@@ -124,4 +121,4 @@ if (tag.__release) { | ||
} | ||
} | ||
function diffAttributes(el, prev, curr) { | ||
}; | ||
const diffAttributes = (el, prev, curr) => { | ||
let i, e, edits; | ||
@@ -156,4 +153,4 @@ const delta = object_1.diffObject(prev, curr); | ||
} | ||
} | ||
function extractEquivElements(edits) { | ||
}; | ||
const extractEquivElements = (edits) => { | ||
let k, v, e, ek; | ||
@@ -171,2 +168,2 @@ const equiv = {}; | ||
return equiv; | ||
} | ||
}; |
35
dom.d.ts
@@ -14,8 +14,21 @@ /** | ||
*/ | ||
export declare function createDOM(parent: Element, tag: any, insert?: number): any; | ||
export declare function createElement(parent: Element, tag: string, attribs?: any, insert?: number): HTMLElement | SVGElement; | ||
export declare function createTextElement(parent: Element, content: string, insert?: number): Text; | ||
export declare function cloneWithNewAttribs(el: Element, attribs: any): Element; | ||
export declare function setAttribs(el: Element, attribs: any): Element; | ||
export declare const createDOM: (parent: Element, tag: any, insert?: number) => any; | ||
/** | ||
* Takes a DOM root element and normalized hdom tree, then walks tree | ||
* and initializes any event listeners and components with lifecycle | ||
* `init` methods. Assumes that an equivalent DOM (minus listeners) | ||
* already exists (e.g. generated via SSR) when called. Any other | ||
* discrepancies between the pre-existing DOM and the hdom tree will | ||
* cause undefined behavior. | ||
* | ||
* @param parent | ||
* @param tree | ||
* @param i | ||
*/ | ||
export declare const hydrateDOM: (parent: Element, tree: any, i?: number) => any; | ||
export declare const createElement: (parent: Element, tag: string, attribs?: any, insert?: number) => HTMLElement | SVGElement; | ||
export declare const createTextElement: (parent: Element, content: string, insert?: number) => Text; | ||
export declare const cloneWithNewAttribs: (el: Element, attribs: any) => Element; | ||
export declare const setAttribs: (el: Element, attribs: any) => Element; | ||
/** | ||
* Sets a single attribute on given element. If attrib name is NOT | ||
@@ -41,7 +54,7 @@ * an event name and its value is a function, it is called with | ||
*/ | ||
export declare function setAttrib(el: Element, id: string, val: any, attribs?: any): Element; | ||
export declare function updateValueAttrib(el: HTMLInputElement, v: any): void; | ||
export declare function removeAttribs(el: Element, attribs: string[], prev: any): void; | ||
export declare function setStyle(el: Element, styles: any): Element; | ||
export declare function clearDOM(el: Element): void; | ||
export declare function removeChild(parent: Element, childIdx: number): void; | ||
export declare const setAttrib: (el: Element, id: string, val: any, attribs?: any) => Element; | ||
export declare const updateValueAttrib: (el: HTMLInputElement, v: any) => void; | ||
export declare const removeAttribs: (el: Element, attribs: string[], prev: any) => void; | ||
export declare const setStyle: (el: Element, styles: any) => Element; | ||
export declare const clearDOM: (el: Element) => string; | ||
export declare const removeChild: (parent: Element, childIdx: number) => void; |
114
dom.js
@@ -26,9 +26,9 @@ "use strict"; | ||
*/ | ||
function createDOM(parent, tag, insert) { | ||
exports.createDOM = (parent, tag, insert) => { | ||
if (isArray(tag)) { | ||
const t = tag[0]; | ||
if (isFunction(t)) { | ||
return createDOM(parent, t.apply(null, tag.slice(1))); | ||
return exports.createDOM(parent, t.apply(null, tag.slice(1))); | ||
} | ||
const el = createElement(parent, t, tag[1], insert); | ||
const el = exports.createElement(parent, t, tag[1], insert); | ||
if (tag.__init) { | ||
@@ -40,3 +40,3 @@ tag.__init.apply(tag.__this, [el, ...tag.__args]); | ||
for (let i = 2; i < n; i++) { | ||
createDOM(el, tag[i]); | ||
exports.createDOM(el, tag[i]); | ||
} | ||
@@ -49,3 +49,3 @@ } | ||
for (let t of tag) { | ||
res.push(createDOM(parent, t)); | ||
res.push(exports.createDOM(parent, t)); | ||
} | ||
@@ -57,6 +57,43 @@ return res; | ||
} | ||
return createTextElement(parent, tag); | ||
} | ||
exports.createDOM = createDOM; | ||
function createElement(parent, tag, attribs, insert) { | ||
return exports.createTextElement(parent, tag); | ||
}; | ||
/** | ||
* Takes a DOM root element and normalized hdom tree, then walks tree | ||
* and initializes any event listeners and components with lifecycle | ||
* `init` methods. Assumes that an equivalent DOM (minus listeners) | ||
* already exists (e.g. generated via SSR) when called. Any other | ||
* discrepancies between the pre-existing DOM and the hdom tree will | ||
* cause undefined behavior. | ||
* | ||
* @param parent | ||
* @param tree | ||
* @param i | ||
*/ | ||
exports.hydrateDOM = (parent, tree, i = 0) => { | ||
if (isArray(tree)) { | ||
const el = parent.children[i]; | ||
if (isFunction(tree[0])) { | ||
return exports.hydrateDOM(parent, tree[0].apply(null, tree.slice(1)), i); | ||
} | ||
if (tree.__init) { | ||
tree.__init.apply(tree.__this, [el, ...tree.__args]); | ||
} | ||
const attr = tree[1]; | ||
for (let a in attr) { | ||
if (a.indexOf("on") === 0) { | ||
el.addEventListener(a.substr(2), attr[a]); | ||
} | ||
} | ||
for (let n = tree.length, i = 2; i < n; i++) { | ||
exports.hydrateDOM(el, tree[i], i - 2); | ||
} | ||
} | ||
else if (!isString(tree) && isIterable(tree)) { | ||
for (let t of tree) { | ||
exports.hydrateDOM(parent, t, i); | ||
i++; | ||
} | ||
} | ||
}; | ||
exports.createElement = (parent, tag, attribs, insert) => { | ||
const el = api_1.SVG_TAGS[tag] ? | ||
@@ -74,8 +111,7 @@ document.createElementNS(api_1.SVG_NS, tag) : | ||
if (attribs) { | ||
setAttribs(el, attribs); | ||
exports.setAttribs(el, attribs); | ||
} | ||
return el; | ||
} | ||
exports.createElement = createElement; | ||
function createTextElement(parent, content, insert) { | ||
}; | ||
exports.createTextElement = (parent, content, insert) => { | ||
const el = document.createTextNode(content); | ||
@@ -91,18 +127,15 @@ if (parent) { | ||
return el; | ||
} | ||
exports.createTextElement = createTextElement; | ||
function cloneWithNewAttribs(el, attribs) { | ||
}; | ||
exports.cloneWithNewAttribs = (el, attribs) => { | ||
const res = el.cloneNode(true); | ||
setAttribs(res, attribs); | ||
exports.setAttribs(res, attribs); | ||
el.parentNode.replaceChild(res, el); | ||
return res; | ||
} | ||
exports.cloneWithNewAttribs = cloneWithNewAttribs; | ||
function setAttribs(el, attribs) { | ||
}; | ||
exports.setAttribs = (el, attribs) => { | ||
for (let k in attribs) { | ||
setAttrib(el, k, attribs[k], attribs); | ||
exports.setAttrib(el, k, attribs[k], attribs); | ||
} | ||
return el; | ||
} | ||
exports.setAttribs = setAttribs; | ||
}; | ||
/** | ||
@@ -129,3 +162,3 @@ * Sets a single attribute on given element. If attrib name is NOT | ||
*/ | ||
function setAttrib(el, id, val, attribs) { | ||
exports.setAttrib = (el, id, val, attribs) => { | ||
const isListener = id.indexOf("on") === 0; | ||
@@ -138,6 +171,6 @@ if (!isListener && isFunction(val)) { | ||
case "style": | ||
setStyle(el, val); | ||
exports.setStyle(el, val); | ||
break; | ||
case "value": | ||
updateValueAttrib(el, val); | ||
exports.updateValueAttrib(el, val); | ||
break; | ||
@@ -161,5 +194,4 @@ case "checked": | ||
return el; | ||
} | ||
exports.setAttrib = setAttrib; | ||
function updateValueAttrib(el, v) { | ||
}; | ||
exports.updateValueAttrib = (el, v) => { | ||
switch (el.type) { | ||
@@ -182,5 +214,4 @@ case "text": | ||
} | ||
} | ||
exports.updateValueAttrib = updateValueAttrib; | ||
function removeAttribs(el, attribs, prev) { | ||
}; | ||
exports.removeAttribs = (el, attribs, prev) => { | ||
for (let i = attribs.length; --i >= 0;) { | ||
@@ -195,14 +226,6 @@ const a = attribs[i]; | ||
} | ||
} | ||
exports.removeAttribs = removeAttribs; | ||
function setStyle(el, styles) { | ||
el.setAttribute("style", css_1.css(styles)); | ||
return el; | ||
} | ||
exports.setStyle = setStyle; | ||
function clearDOM(el) { | ||
el.innerHTML = ""; | ||
} | ||
exports.clearDOM = clearDOM; | ||
function removeChild(parent, childIdx) { | ||
}; | ||
exports.setStyle = (el, styles) => (el.setAttribute("style", css_1.css(styles)), el); | ||
exports.clearDOM = (el) => el.innerHTML = ""; | ||
exports.removeChild = (parent, childIdx) => { | ||
const n = parent.children[childIdx]; | ||
@@ -212,3 +235,2 @@ if (n !== undefined) { | ||
} | ||
} | ||
exports.removeChild = removeChild; | ||
}; |
@@ -25,3 +25,3 @@ /** | ||
*/ | ||
export declare function normalizeElement(spec: any[], keys: boolean): any[]; | ||
export declare const normalizeElement: (spec: any[], keys: boolean) => any[]; | ||
/** | ||
@@ -68,2 +68,2 @@ * Calling this function is a prerequisite before passing a component | ||
*/ | ||
export declare function normalizeTree(tree: any, ctx?: any, path?: number[], keys?: boolean, span?: boolean): any; | ||
export declare const normalizeTree: (tree: any, ctx?: any, path?: number[], keys?: boolean, span?: boolean) => any; |
@@ -41,3 +41,3 @@ "use strict"; | ||
*/ | ||
function normalizeElement(spec, keys) { | ||
exports.normalizeElement = (spec, keys) => { | ||
let tag = spec[0], hasAttribs = isPlainObject(spec[1]), match, id, clazz, attribs; | ||
@@ -67,8 +67,2 @@ if (!isString(tag) || !(match = api_1.TAG_REGEXP.exec(tag))) { | ||
return [match[1], attribs, ...spec.slice(hasAttribs ? 2 : 1)]; | ||
} | ||
exports.normalizeElement = normalizeElement; | ||
const NO_SPANS = { | ||
option: 1, | ||
text: 1, | ||
textarea: 1, | ||
}; | ||
@@ -116,3 +110,3 @@ /** | ||
*/ | ||
function normalizeTree(tree, ctx, path = [0], keys = true, span = true) { | ||
exports.normalizeTree = (tree, ctx, path = [0], keys = true, span = true) => { | ||
if (tree == null) { | ||
@@ -130,3 +124,3 @@ return; | ||
if (isFunction(tag)) { | ||
return normalizeTree(tag.apply(null, [ctx, ...tree.slice(1)]), ctx, path, keys, span); | ||
return exports.normalizeTree(tag.apply(null, [ctx, ...tree.slice(1)]), ctx, path, keys, span); | ||
} | ||
@@ -137,3 +131,3 @@ // component object w/ life cycle methods | ||
const args = [ctx, ...tree.slice(1)]; | ||
norm = normalizeTree(tag.render.apply(tag, args), ctx, path, keys, span); | ||
norm = exports.normalizeTree(tag.render.apply(tag, args), ctx, path, keys, span); | ||
if (isArray(norm)) { | ||
@@ -147,3 +141,3 @@ norm.__this = tag; | ||
} | ||
norm = normalizeElement(tree, keys); | ||
norm = exports.normalizeElement(tree, keys); | ||
nattribs = norm[1]; | ||
@@ -156,3 +150,3 @@ if (keys && nattribs.key === undefined) { | ||
const res = [tag, nattribs]; | ||
span = span && !NO_SPANS[tag]; | ||
span = span && !api_1.NO_SPANS[tag]; | ||
for (let i = 2, j = 2, k = 0, n = norm.length; i < n; i++) { | ||
@@ -164,3 +158,3 @@ let el = norm[i]; | ||
for (let c of el) { | ||
c = normalizeTree(c, ctx, path.concat(k), keys, span); | ||
c = exports.normalizeTree(c, ctx, path.concat(k), keys, span); | ||
if (c !== undefined) { | ||
@@ -173,3 +167,3 @@ res[j++] = c; | ||
else { | ||
el = normalizeTree(el, ctx, path.concat(k), keys, span); | ||
el = exports.normalizeTree(el, ctx, path.concat(k), keys, span); | ||
if (el !== undefined) { | ||
@@ -187,6 +181,6 @@ res[j++] = el; | ||
if (isFunction(tree)) { | ||
return normalizeTree(tree(ctx), ctx, path, keys, span); | ||
return exports.normalizeTree(tree(ctx), ctx, path, keys, span); | ||
} | ||
if (implementsFunction(tree, "deref")) { | ||
return normalizeTree(tree.deref(), ctx, path, keys, span); | ||
return exports.normalizeTree(tree.deref(), ctx, path, keys, span); | ||
} | ||
@@ -196,3 +190,2 @@ return span ? | ||
tree.toString(); | ||
} | ||
exports.normalizeTree = normalizeTree; | ||
}; |
{ | ||
"name": "@thi.ng/hdom", | ||
"version": "3.0.35", | ||
"version": "4.0.0", | ||
"description": "Lightweight vanilla ES6 UI component & virtual DOM system", | ||
@@ -36,3 +36,3 @@ "main": "./index.js", | ||
"@thi.ng/equiv": "^0.1.7", | ||
"@thi.ng/hiccup": "^2.0.11" | ||
"@thi.ng/hiccup": "^2.1.0" | ||
}, | ||
@@ -39,0 +39,0 @@ "keywords": [ |
146
README.md
@@ -11,3 +11,4 @@ # @thi.ng/hdom | ||
- [About](#about) | ||
- [Minimal example](#minimal-example) | ||
- [Minimal example #1: Local state, RAF update](#minimal-example-1-local-state-raf-update) | ||
- [Minimal example #2 (reactive state & transducer update)](#minimal-example-2-reactive-state--transducer-update) | ||
- [Component tree translation](#component-tree-translation) | ||
@@ -23,2 +24,3 @@ - [Event & state handling options](#event--state-handling-options) | ||
- [Example projects](#example-projects) | ||
- [Realtime crypto chart](#realtime-crypto-chart) | ||
- [Interactive SVG grid generator](#interactive-svg-grid-generator) | ||
@@ -32,7 +34,3 @@ - [Interactive additive waveform visualization](#interactive-additive-waveform-visualization) | ||
- [Todo list](#todo-list) | ||
- [Cellular automata](#cellular-automata) | ||
- [SVG particles](#svg-particles) | ||
- [JSON based components](#json-based-components) | ||
- [@thi.ng/rstream dataflow graph](#thingrstream-dataflow-graph) | ||
- [Basic usage patterns](#basic-usage-patterns) | ||
- [Benchmark](#benchmark) | ||
@@ -46,3 +44,3 @@ - [Authors](#authors) | ||
Lightweight reactive DOM components & VDOM implementation using only | ||
Lightweight reactive DOM components & VDOM-ish implementation using only | ||
vanilla JS data structures (arrays, objects with life cycle functions, | ||
@@ -59,2 +57,5 @@ closures, iterators), based on | ||
arbitrary elements, attributes, events | ||
- Suitable for server side rendering (by passing the same data structure | ||
to @thi.ng/hiccup's `serialize()`) and then "hydrating" listeners and | ||
components with life cycle methods | ||
- Less verbose than HTML / JSX, resulting in smaller file sizes | ||
@@ -65,11 +66,12 @@ - Static components can be distributed as JSON (or [transform JSON | ||
component functions) | ||
- auto-deref of embedded value wrappers which implement the | ||
- CSS conversion from JS objects for `style` attribs | ||
- Auto-deref of embedded value wrappers which implement the | ||
[@thi.ng/api/IDeref](https://github.com/thi-ng/umbrella/tree/master/packages/api/api) | ||
interface (e.g. atoms, cursors, derived views, streams etc.) | ||
- CSS conversion from JS objects for `style` attribs | ||
- Suitable for server side rendering (by passing the same data structure | ||
to @thi.ng/hiccup's `serialize()`) | ||
- Fairly fast (see benchmark example below) | ||
- Only ~4.4KB gzipped | ||
- Only ~5KB gzipped | ||
hdom is *very* flexible and supports different workflows and means to | ||
update a DOM... | ||
In addition to the descriptions in this file, [further information and | ||
@@ -83,3 +85,3 @@ examples are available in the | ||
### Minimal example | ||
### Minimal example #1: Local state, RAF update | ||
@@ -107,3 +109,3 @@ ```ts | ||
// start update loop (browser only, see diagram below) | ||
hdom.start(document.body, app()); | ||
hdom.start(app(), { root: document.body }); | ||
@@ -129,8 +131,49 @@ // alternatively apply DOM tree only once | ||
No template engine & no pre-compilation steps needed, just use the full | ||
expressiveness of ES6/TypeScript to define your DOM tree. Using | ||
TypeScript gives the additional benefit of making UI components strongly | ||
typed, and since they're just normal functions, can use generics, | ||
overrides, varargs etc. | ||
### Minimal example #2 (reactive state & transducer update) | ||
This example uses the | ||
[@thi.ng/transducers-hdom](https://github.com/thi-ng/umbrella/tree/master/packages/transducers-hdom) | ||
support library to perform reactive DOM updates (instead of regular | ||
diffing via RAF). | ||
```ts | ||
import * as rs from "@thi.ng/rstream/stream"; | ||
import * as tx from "@thi.ng/rstream/transducers"; | ||
import { updateDOM } from "@thi.ng/rstream/transducers-hdom"; | ||
// root component function | ||
const app = ({ ticks, clicks }) => | ||
["div", | ||
`${ticks} ticks & `, | ||
["a", | ||
{ href: "#", onclick: () => clickStream.next(0)}, | ||
`${clicks} clicks`] | ||
]; | ||
// click stream (click counter) | ||
const clickStream = rs.stream().transform(tx.scan(tx.count(-1))); | ||
// stream combinator | ||
// waits until all inputs have produced at least one value, | ||
// then updates whenever any input has changed | ||
sync({ | ||
// streams to synchronize | ||
src: { | ||
ticks: rs.fromInterval(1000), | ||
clicks: clickStream, | ||
}, | ||
}).transform( | ||
// transform tuple into hdom component | ||
tx.map(app), | ||
// apply hdom tree to real DOM | ||
updateDOM({ root: document.body }) | ||
); | ||
// kick off | ||
clickStream.next(0); | ||
``` | ||
[Live demo](https://demo.thi.ng/umbrella/transducers-hdom/) | | ||
[standalone example](https://github.com/thi-ng/umbrella/tree/master/examples/transducers-hdom) | ||
### Component tree translation | ||
@@ -176,5 +219,4 @@ | ||
The overall "API" is stable, but there's further work planned on | ||
generalizing the approach beyond standard browser DOM use cases (planned | ||
for v4.0.0). The project has been used for several projects in | ||
production since 2016. | ||
generalizing the approach beyond standard browser DOM use cases. The | ||
project has been used for several projects in production since 2016. | ||
@@ -207,3 +249,2 @@ ## Installation | ||
- [@thi.ng/hiccup](https://github.com/thi-ng/umbrella/tree/master/packages/hiccup) | ||
- [@thi.ng/iterators](https://github.com/thi-ng/umbrella/tree/master/packages/iterators) | ||
@@ -219,3 +260,3 @@ ## Usage | ||
#### `start(parent: Element | string, tree: any, ctx?: any, path?: number[], keys?: boolean, span?: boolean): () => boolean` | ||
#### `start(tree: any, opts?: Partial<HDOMOpts>): () => any` | ||
@@ -244,6 +285,10 @@ Main user function of this package. For most use cases, this function | ||
**Important:** The parent element given is assumed to have NO children at | ||
the time when `start()` is called. Since hdom does NOT track the real | ||
DOM, the resulting changes will result in potentially undefined behavior | ||
if the parent element wasn't empty. | ||
**Important:** Unless the `hydrate` option is enabled, the parent | ||
element given is assumed to have NO children at the time when `start()` | ||
is called. Since hdom does NOT track the real DOM, the resulting changes | ||
will result in potentially undefined behavior if the parent element | ||
wasn't empty. Likewise, if `hydrate` is enabled, it is assumed that an | ||
equivalent DOM (minus listeners) already exists (i.e. generated via SSR) | ||
when `start()` is called. Any other discrepancies between the | ||
pre-existing DOM and the hdom trees will cause undefined behavior. | ||
@@ -306,2 +351,10 @@ Returns a function, which when called, immediately cancels the update | ||
#### `hydrateDOM(parent: Element, tag: any)` | ||
Takes a DOM root element and normalized hdom tree, then walks tree and | ||
initializes any event listeners and components with lifecycle init | ||
methods. Assumes that an equivalent DOM (minus listeners) already exists | ||
(e.g. generated via SSR) when called. Any other discrepancies between | ||
the pre-existing DOM and the hdom tree will cause undefined behavior. | ||
### User context injection | ||
@@ -366,3 +419,3 @@ | ||
// start hdom update loop | ||
start("app", root, ctx); | ||
start(root, { ctx }); | ||
``` | ||
@@ -439,4 +492,4 @@ | ||
start( | ||
document.body, | ||
[canvas(), { width: 100, height: 100 }, "Hello world"] | ||
[canvas(), { width: 100, height: 100 }, "Hello world"], | ||
{ root: document.body } | ||
); | ||
@@ -464,3 +517,3 @@ | ||
start(document.body, app()); | ||
start(app(), { root: document.body }); | ||
``` | ||
@@ -470,3 +523,3 @@ | ||
Most of the | ||
Most of the 25 | ||
[examples](https://github.com/thi-ng/umbrella/tree/master/examples) | ||
@@ -479,2 +532,7 @@ included in this repo are using this package in one way or another. | ||
### Realtime crypto chart | ||
[Source](https://github.com/thi-ng/umbrella/tree/master/examples/crypto-chart) | | ||
[Live version](https://demo.thi.ng/umbrella/crypto-chart/) | ||
### Interactive SVG grid generator | ||
@@ -526,6 +584,2 @@ | ||
### Cellular automata | ||
[Source](https://github.com/thi-ng/umbrella/tree/master/examples/cellular-automata) | [Live version](https://demo.thi.ng/umbrella/cellular-automata/) | ||
### SVG particles | ||
@@ -535,18 +589,2 @@ | ||
### JSON based components | ||
[Source](https://github.com/thi-ng/umbrella/tree/master/examples/json-components) | [Live version](https://demo.thi.ng/umbrella/json-components/) | ||
### @thi.ng/rstream dataflow graph | ||
A small, interactive dataflow graph example: | ||
[Source](https://github.com/thi-ng/umbrella/tree/master/examples/rstream-dataflow) | [Live version](https://demo.thi.ng/umbrella/rstream-dataflow) | ||
### Basic usage patterns | ||
The code below is also available as standalone project in: [/examples/dashboard](https://github.com/thi-ng/umbrella/tree/master/examples/dashboard) | ||
[Source](https://github.com/thi-ng/umbrella/tree/master/examples/dashboard) | [Live version](https://demo.thi.ng/umbrella/dashboard/) | ||
### Benchmark | ||
@@ -564,4 +602,4 @@ | ||
600 / 800 DOM every single frame**, very unlikely for a typical web app. | ||
In Chrome 64 on a MBP2016 this still runs at a stable 60fps (192 cells) | ||
/ 32fps (256 cells). Both FPS readings based the 50 frame | ||
In Chrome 68 on a MBP2016 this still runs at a stable 60fps (192 cells) | ||
/ 37fps (256 cells). Both FPS readings based the 50 frame | ||
[SMA](https://en.wikipedia.org/wiki/Moving_average#Simple_moving_average). | ||
@@ -568,0 +606,0 @@ |
@@ -0,1 +1,2 @@ | ||
import { HDOMOpts } from "./api"; | ||
/** | ||
@@ -22,6 +23,11 @@ * Takes a parent DOM element (or ID), hiccup tree (array, function or | ||
* | ||
* Important: The parent element given is assumed to have NO children at | ||
* the time when `start()` is called. Since hdom does NOT track the real | ||
* DOM, the resulting changes will result in potentially undefined | ||
* behavior if the parent element wasn't empty. | ||
* **Important:** Unless the `hydrate` option is enabled, the parent | ||
* element given is assumed to have NO children at the time when | ||
* `start()` is called. Since hdom does NOT track the real DOM, the | ||
* resulting changes will result in potentially undefined behavior if | ||
* the parent element wasn't empty. Likewise, if `hydrate` is enabled, | ||
* it is assumed that an equivalent DOM (minus listeners) already exists | ||
* (i.e. generated via SSR) when `start()` is called. Any other | ||
* discrepancies between the pre-existing DOM and the hdom trees will | ||
* cause undefined behavior. | ||
* | ||
@@ -31,7 +37,5 @@ * Returns a function, which when called, immediately cancels the update | ||
* | ||
* @param parent root element or ID | ||
* @param tree hiccup DOM tree | ||
* @param ctx arbitrary user context object | ||
* @param spans true (default), if text should be wrapped in `<span>` | ||
* @param opts options | ||
*/ | ||
export declare function start(parent: Element | string, tree: any, ctx?: any, spans?: boolean): () => boolean; | ||
export declare const start: (tree: any, opts?: Partial<HDOMOpts>) => () => boolean; |
46
start.js
@@ -6,2 +6,3 @@ "use strict"; | ||
const normalize_1 = require("./normalize"); | ||
const dom_1 = require("@thi.ng/hdom/src/dom"); | ||
/** | ||
@@ -28,6 +29,11 @@ * Takes a parent DOM element (or ID), hiccup tree (array, function or | ||
* | ||
* Important: The parent element given is assumed to have NO children at | ||
* the time when `start()` is called. Since hdom does NOT track the real | ||
* DOM, the resulting changes will result in potentially undefined | ||
* behavior if the parent element wasn't empty. | ||
* **Important:** Unless the `hydrate` option is enabled, the parent | ||
* element given is assumed to have NO children at the time when | ||
* `start()` is called. Since hdom does NOT track the real DOM, the | ||
* resulting changes will result in potentially undefined behavior if | ||
* the parent element wasn't empty. Likewise, if `hydrate` is enabled, | ||
* it is assumed that an equivalent DOM (minus listeners) already exists | ||
* (i.e. generated via SSR) when `start()` is called. Any other | ||
* discrepancies between the pre-existing DOM and the hdom trees will | ||
* cause undefined behavior. | ||
* | ||
@@ -37,18 +43,25 @@ * Returns a function, which when called, immediately cancels the update | ||
* | ||
* @param parent root element or ID | ||
* @param tree hiccup DOM tree | ||
* @param ctx arbitrary user context object | ||
* @param spans true (default), if text should be wrapped in `<span>` | ||
* @param opts options | ||
*/ | ||
function start(parent, tree, ctx, spans = true) { | ||
exports.start = (tree, opts) => { | ||
opts = Object.assign({ root: "app", span: true, normalize: true }, opts); | ||
let prev = []; | ||
let isActive = true; | ||
parent = is_string_1.isString(parent) ? | ||
document.getElementById(parent) : | ||
parent; | ||
function update() { | ||
const root = is_string_1.isString(opts.root) ? | ||
document.getElementById(opts.root) : | ||
opts.root; | ||
const update = () => { | ||
if (isActive) { | ||
const curr = normalize_1.normalizeTree(tree, ctx, [0], true, spans); | ||
const curr = opts.normalize ? | ||
normalize_1.normalizeTree(tree, opts.ctx, [0], true, opts.span) : | ||
tree; | ||
if (curr != null) { | ||
diff_1.diffElement(parent, prev, curr); | ||
if (opts.hydrate) { | ||
dom_1.hydrateDOM(root, curr); | ||
opts.hydrate = false; | ||
} | ||
else { | ||
diff_1.diffElement(root, prev, curr); | ||
} | ||
prev = curr; | ||
@@ -59,6 +72,5 @@ } | ||
} | ||
} | ||
}; | ||
requestAnimationFrame(update); | ||
return () => (isActive = false); | ||
} | ||
exports.start = start; | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
83436
890
592
Updated@thi.ng/hiccup@^2.1.0