UNPKG

reactronic-dom

Version:

Reactronic DOM - Transactional Reactive Front-End Development Framework

557 lines (556 loc) 21.4 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { reaction, nonreactive, Transaction, Rx, options, Reentrance } from 'reactronic'; import { RxNode } from './RxDomV1.Types'; export class BasicNodeType { constructor(name, sequential) { this.name = name; this.sequential = sequential; } initialize(node) { if (!node.inline) Rx.setLoggingHint(node, node.id); } render(node, args) { const inst = node.instance; if (!inst) throw new Error('element must be initialized before rendering'); if (inst.buffer) throw new Error('rendering re-entrance is not supported yet'); inst.buffer = []; let result; if (node.superRender) result = node.superRender(options => { const res = node.render(inst.native, options); if (res instanceof Promise) return res.then(); else return options; }, inst.native); else result = node.render(inst.native, args); if (result instanceof Promise) result = result.then(value => { RxDom.renderChildrenThenDo(NOP); return value; }, error => { console.log(error); RxDom.renderChildrenThenDo(NOP); }); else RxDom.renderChildrenThenDo(NOP); } finalize(node, initiator) { const inst = node.instance; if (inst) { inst.native = undefined; if (!node.inline && node.instance) Transaction.standalone(() => Rx.dispose(node.instance)); for (const x of inst.children) tryToFinalize(x, initiator); for (const x of inst.guests) tryToFinalize(x, initiator); } } } export class RxNodeInstanceImpl { constructor(level) { this.revision = ~0; this.native = undefined; this.model = undefined; this.children = EMPTY; this.buffer = undefined; this.guests = EMPTY; this.resizeObserver = undefined; this.uuid = ++RxNodeInstanceImpl.gUuid; this.level = level; } rerender(node) { invokeRender(node, node.args); Rx.configureCurrentOperation({ order: this.level }); } } RxNodeInstanceImpl.gUuid = 0; __decorate([ reaction, options({ reentrance: Reentrance.CancelPrevious, triggeringArgs: true, noSideEffects: true }), __metadata("design:type", Function), __metadata("design:paramtypes", [RxNode]), __metadata("design:returntype", void 0) ], RxNodeInstanceImpl.prototype, "rerender", null); export class RxDom { static Root(render) { const inst = SYSTEM.instance; if (inst.buffer) throw new Error('rendering re-entrance is not supported yet'); inst.buffer = []; let result = render(); if (result instanceof Promise) result = result.then(value => { Transaction.run(null, RxDom.renderChildrenThenDo, NOP); return value; }, error => { console.log(error); Transaction.run(null, RxDom.renderChildrenThenDo, NOP); }); else Transaction.run(null, RxDom.renderChildrenThenDo, NOP); return result; } static Node(id, args, render, superRender, type, inline, creator, host) { var _a, _b; const o = creator !== null && creator !== void 0 ? creator : gCreator; const inst = o.instance; if (!inst) throw new Error('element must be initialized before children'); if (type === undefined) type = RxDom.basic; if (!host) host = gHost; const node = new RxNode(id, args, render, superRender, 0, false, type, inline !== null && inline !== void 0 ? inline : false, o, host); if (inst.buffer === undefined) throw new Error('children are rendered already'); const rev = (_b = (_a = host.instance) === null || _a === void 0 ? void 0 : _a.revision) !== null && _b !== void 0 ? _b : ~1; if (rev >= ~0) inst.buffer.push(node); return node; } static renderChildrenThenDo(action) { const node = gCreator; if (node.type.sequential) RxDom.mergeAndRenderSequentialChildren(node, action); else RxDom.mergeAndRenderChildren(node, action); } static usingAnotherHost(host, run) { var _a; const native = (_a = host.instance) === null || _a === void 0 ? void 0 : _a.native; if (native !== undefined) { const outer = gHost; try { gHost = host; run(native); } finally { gHost = outer; } } } static createRootNode(id, sequential, native) { const inst = new RxNodeInstanceImpl(0); const node = new RxNode(id, null, () => { }, undefined, 0, false, { name: id, sequential }, false, {}, {}, inst); const a = node; a['creator'] = node; a['host'] = node; inst.native = native; return node; } static get self() { return gCreator; } static currentNodeInstance() { const inst = gCreator.instance; if (!inst) throw new Error('currentNodeInstance function can be called only inside rendering function'); return inst; } static currentNodeInstanceInternal() { const inst = gCreator.instance; if (!inst) throw new Error('currentNodeInstanceInternal function can be called only inside rendering function'); return inst; } static currentNodeRevision() { var _a, _b; return (_b = (_a = gCreator.instance) === null || _a === void 0 ? void 0 : _a.revision) !== null && _b !== void 0 ? _b : ~0; } static forAll(action) { RxDom.forEachChildRecursively(SYSTEM, action); } static mergeAndRenderSequentialChildren(node, finish) { var _a, _b, _c; const inst = node.instance; if (inst !== undefined && inst.buffer !== undefined) { try { const existing = inst.children; const sequenced = inst.buffer; const sorted = sequenced.slice().sort(compareNodes); inst.buffer = undefined; let host = inst; let guests = EMPTY; let sibling = undefined; let i = 0, j = 0; while (i < existing.length) { const old = existing[i]; const x = sorted[j]; const diff = x !== undefined ? compareNodes(x, old) : 1; if (diff <= 0) { const h = x.host.instance; if (h !== inst) { if (h !== host) { RxDom.mergeGuests(host, inst, guests); guests = []; host = h; } guests.push(x); } if (sibling !== undefined && x.id === sibling.id) throw new Error(`duplicate id '${sibling.id}' inside '${node.id}'`); if (diff === 0) { x.instance = old.instance; x.old = old; i++, j++; } else j++; sibling = x; } else { if (!Transaction.isCanceled) tryToFinalize(old, old); i++; } } if (host !== inst) RxDom.mergeGuests(host, inst, guests); sibling = undefined; i = 0; while (i < sequenced.length && !Transaction.isCanceled) { const x = sequenced[i]; const old = x.old; x.old = undefined; x.prevSibling = sibling; const instance = x.instance; if (old && instance) { if ((sibling === null || sibling === void 0 ? void 0 : sibling.instance) !== ((_a = old.prevSibling) === null || _a === void 0 ? void 0 : _a.instance)) (_c = (_b = x.type).mount) === null || _c === void 0 ? void 0 : _c.call(_b, x); if (x.inline || !argsAreEqual(x.args, old.args)) tryToRender(x); } else { tryToInitialize(x); tryToRender(x); } if (x.native) sibling = x; i++; } if (!Transaction.isCanceled) inst.children = sorted; } finally { finish(); } } } static mergeAndRenderChildren(node, finish) { const inst = node.instance; if (inst !== undefined && inst.buffer !== undefined) { let promised = undefined; try { const existing = inst.children; const buffer = inst.buffer.sort(compareNodes); inst.buffer = undefined; let p1 = undefined; let p2 = undefined; let host = inst; let guests = EMPTY; let sibling = undefined; let i = 0, j = 0; while (i < existing.length || j < buffer.length) { const old = existing[i]; const x = buffer[j]; const diff = compareNullable(x, old, compareNodes); if (diff <= 0) { const h = x.host.instance; if (h !== inst) { if (h !== host) { RxDom.mergeGuests(host, inst, guests); guests = []; host = h; } guests.push(x); } if (sibling !== undefined && x.id === sibling.id) throw new Error(`duplicate id '${sibling.id}' inside '${node.id}'`); if (diff === 0) { if (old.instance) { x.instance = old.instance; if (x.inline || !argsAreEqual(x.args, old.args)) { if (!Transaction.isCanceled) { if (x.priority === 0) tryToRender(x); else if (x.priority === 1) p1 = push(p1, x); else p2 = push(p2, x); } } } else { if (!Transaction.isCanceled) { if (x.priority === 0) { tryToInitialize(x); tryToRender(x); } else if (x.priority === 1) p1 = push(p1, x); else p2 = push(p2, x); } } i++, j++; } else { if (!Transaction.isCanceled) { if (x.priority === 0) { tryToInitialize(x); tryToRender(x); } else if (x.priority === 1) p1 = push(p1, x); else p2 = push(p2, x); } j++; } sibling = x; } else { if (!Transaction.isCanceled) tryToFinalize(old, old); i++; } } if (host !== inst) RxDom.mergeGuests(host, inst, guests); if (!Transaction.isCanceled) { inst.children = buffer; if (p1 !== undefined || p2 !== undefined) promised = RxDom.renderIncrementally(node, p1, p2).then(finish, finish); } } finally { if (!promised) finish(); } } } static renderIncrementally(parent, p1, p2, checkEveryN = 30, timeLimit = 12) { return __awaiter(this, void 0, void 0, function* () { if (Transaction.isFrameOver(checkEveryN, timeLimit)) yield Transaction.requestNextFrame(); if (!Transaction.isCanceled) { if (p1 !== undefined) { if (parent.childrenShuffling) shuffle(p1); for (const x of p1) { if (!x.instance) tryToInitialize(x); tryToRender(x); if (Transaction.isCanceled) break; if (Transaction.isFrameOver(checkEveryN, timeLimit)) yield Transaction.requestNextFrame(); if (Transaction.isCanceled) break; } } if (p2 !== undefined) { if (parent.childrenShuffling) shuffle(p2); for (const x of p2) { if (!x.instance) tryToInitialize(x); tryToRender(x); if (Transaction.isCanceled) break; if (Transaction.isFrameOver(checkEveryN, timeLimit)) yield Transaction.requestNextFrame(); if (Transaction.isCanceled) break; } } } }); } static mergeGuests(host, creator, guests) { if (host !== creator) { const existing = host.guests; const merged = []; let i = 0, j = 0; while (i < existing.length || j < guests.length) { const old = existing[i]; const x = guests[j]; const diff = compareNullable(x, old, compareNodes); if (diff <= 0) { merged.push(x); if (diff === 0) i++, j++; else j++; } else { if (old.creator.instance !== creator) merged.push(old); i++; } } host.guests = merged; } } static forEachChildRecursively(node, action) { const inst = node.instance; if (inst) { const native = inst.native; native && action(native); inst.children.forEach(x => RxDom.forEachChildRecursively(x, action)); } } } RxDom.basic = new BasicNodeType('basic', false); function tryToRender(node) { const inst = node.instance; if (node.inline) invokeRender(node, node.args); else nonreactive(inst.rerender, node); } function tryToInitialize(node) { var _a, _b; const type = node.type; const inst = node.instance = new RxNodeInstanceImpl(node.creator.instance.level + 1); (_a = type.initialize) === null || _a === void 0 ? void 0 : _a.call(type, node); (_b = type.mount) === null || _b === void 0 ? void 0 : _b.call(type, node); if (!node.inline) Rx.setLoggingHint(inst, node.id); return inst; } function tryToFinalize(node, initiator) { const inst = node.instance; if (inst && inst.revision >= ~0) { inst.revision = ~inst.revision; invokeFinalize(node, initiator); } } function invokeRender(node, args) { const host = node.native !== undefined ? node : node.host; runUnder(node, host, () => { node.instance.revision++; const type = node.type; if (type.render) type.render(node, args); else RxDom.basic.render(node, args); }); } function invokeFinalize(node, initiator) { const type = node.type; if (type.finalize) type.finalize(node, initiator); else RxDom.basic.finalize(node, initiator); } function wrap(func) { const creator = gCreator; const host = gHost; const wrappedRendering = (...args) => { return runUnder(creator, host, func, ...args); }; return wrappedRendering; } function runUnder(creator, host, func, ...args) { const outerCreator = gCreator; const outerHost = gHost; try { gCreator = creator; gHost = host; return func(...args); } finally { gHost = outerHost; gCreator = outerCreator; } } function compareNodes(node1, node2) { let result = 0; const hp1 = node1.host.instance; const hp2 = node2.host.instance; if (hp1 !== hp2) { result = hp1.uuid - hp2.uuid; if (result === 0) result = node1.id.localeCompare(node2.id); } else result = node1.id.localeCompare(node2.id); return result; } function compareNullable(a, b, comparer) { let diff; if (b !== undefined) diff = a !== undefined ? comparer(a, b) : 1; else diff = a !== undefined ? -1 : 0; return diff; } function argsAreEqual(a1, a2) { let result = a1 === a2; if (!result) { if (Array.isArray(a1)) { result = Array.isArray(a2) && a1.length === a2.length && a1.every((t, i) => t === a2[i]); } else if (a1 === Object(a1) && a2 === Object(a2)) { for (const p in a1) { result = a1[p] === a2[p]; if (!result) break; } } } return result; } function push(array, item) { if (array == undefined) array = new Array(); array.push(item); return array; } function shuffle(array) { let i = array.length - 1; while (i >= 0) { const j = Math.floor(Math.random() * i); const t = array[i]; array[i] = array[j]; array[j] = t; i--; } return array; } const ORIGINAL_PROMISE_THEN = Promise.prototype.then; function reactronicDomHookedThen(resolve, reject) { resolve = resolve ? wrap(resolve) : resolveReturn; reject = reject ? wrap(reject) : rejectRethrow; return ORIGINAL_PROMISE_THEN.call(this, resolve, reject); } export function resolveReturn(value) { return value; } export function rejectRethrow(error) { throw error; } Promise.prototype.then = reactronicDomHookedThen; const NOP = () => { }; const EMPTY = Object.freeze([]); const SYSTEM = RxDom.createRootNode('SYSTEM', false, 'SYSTEM'); let gCreator = SYSTEM; let gHost = SYSTEM;