@pionjs/pion
Version:
Hooks for web components
114 lines (113 loc) • 3.16 kB
JavaScript
import { State } from "./state";
import { commitSymbol, phaseSymbol, updateSymbol, effectsSymbol, layoutEffectsSymbol, } from "./symbols";
import { InfiniteLoopError } from "./errors";
const MAX_UPDATES = 100;
const defer = Promise.resolve().then.bind(Promise.resolve());
function runner() {
let tasks = [];
let id;
function runTasks() {
id = null;
let t = tasks;
tasks = [];
for (var i = 0, len = t.length; i < len; i++) {
t[i]();
}
}
return function (task) {
tasks.push(task);
if (id == null) {
id = defer(runTasks);
}
};
}
const read = runner();
const write = runner();
class BaseScheduler {
renderer;
host;
state;
[phaseSymbol];
_updateQueued;
_active;
_updateCount;
_processing;
static maxUpdates = MAX_UPDATES;
constructor(renderer, host) {
this.renderer = renderer;
this.host = host;
this.state = new State(this.update.bind(this), host);
this[phaseSymbol] = null;
this._updateQueued = false;
this._active = false;
this._updateCount = 0;
this._processing = false;
}
_checkForInfiniteLoop() {
if (!this._processing) {
this._updateCount = 0;
}
this._updateCount++;
if (this._updateCount > BaseScheduler.maxUpdates) {
const tagName = this.host instanceof HTMLElement
? this.host.tagName.toLowerCase()
: undefined;
this._active = false;
throw new InfiniteLoopError(tagName);
}
}
update() {
if (!this._active)
return;
if (this._updateQueued)
return;
this._checkForInfiniteLoop();
this._processing = true;
read(() => {
let result = this.handlePhase(updateSymbol);
write(() => {
this.handlePhase(commitSymbol, result);
write(() => {
this.handlePhase(effectsSymbol);
if (!this._updateQueued) {
this._processing = false;
}
});
});
this._updateQueued = false;
});
this._updateQueued = true;
}
handlePhase(phase, arg) {
this[phaseSymbol] = phase;
switch (phase) {
case commitSymbol:
this.commit(arg);
this.runEffects(layoutEffectsSymbol);
return;
case updateSymbol:
return this.render();
case effectsSymbol:
return this.runEffects(effectsSymbol);
}
}
render() {
return this.state.run(() => this.renderer.call(this.host, this.host));
}
runEffects(phase) {
this.state._runEffects(phase);
}
teardown() {
this.state.teardown();
this._updateCount = 0;
this._processing = false;
}
pause() {
this._active = false;
}
resume() {
this._active = true;
this._updateCount = 0;
}
}
export { BaseScheduler };