reactronic-dom
Version:
Reactronic DOM - Transactional Reactive Front-End Development Framework
557 lines (556 loc) • 21.4 kB
JavaScript
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;