153 lines
3.7 KiB
JavaScript
153 lines
3.7 KiB
JavaScript
import { isFunction } from 'trimkit';
|
|
|
|
import { sanitizeObject, supportsPassive } from './utils.js';
|
|
|
|
const OVERRIDING_EVENTS = ['contextmenu', 'dragover', 'drop'];
|
|
function getEventList(element) {
|
|
const evtString = element.getAttribute('evl');
|
|
return evtString ? evtString.split(';') : [];
|
|
}
|
|
|
|
export function Projector(domRoot) {
|
|
const elementMap = new Map();
|
|
const pendingFrames = [];
|
|
const eventCallbacks = [];
|
|
const eventMap = new Map();
|
|
let runningNextFrame;
|
|
|
|
function eventHandler(evt) {
|
|
if (OVERRIDING_EVENTS.includes(eventName)) {
|
|
evt.preventDefault();
|
|
}
|
|
|
|
const fakeEvt = sanitizeObject(evt);
|
|
if (evt.target) {
|
|
fakeEvt.target = evt.target._id;
|
|
}
|
|
|
|
eventCallbacks.forEach(cb => cb(fakeEvt));
|
|
}
|
|
function removeEvent(eventSet, id, eventName) {
|
|
eventSet.remove(element._id);
|
|
if (!eventSet.size) {
|
|
domRoot.removeEventListener(eventName, eventHandler);
|
|
}
|
|
}
|
|
|
|
function setAttributes(element, props) {
|
|
Object.entries(props).forEach(([name, value]) => {
|
|
if (name in element) {
|
|
if (name.startsWith('on')) {
|
|
const eventName = name.substr(2);
|
|
const eventSet = eventMap.get(eventName) || new Set();
|
|
const eventList = getEventList(element);
|
|
if (value === null) {
|
|
// remove event
|
|
eventList.splice(eventList.indexOf(eventName), 1);
|
|
removeEvent(eventSet, element._id, eventName);
|
|
} else {
|
|
// add event
|
|
if (!eventSet.size) {
|
|
domRoot.addEventListener(
|
|
eventName,
|
|
eventHandler,
|
|
supportsPassive && !OVERRIDING_EVENTS.includes(eventName)
|
|
? { passive: true, capture: false }
|
|
: false
|
|
);
|
|
}
|
|
eventList.push(eventName);
|
|
eventSet.add(element._id);
|
|
if (!eventMap.has(eventName)) {
|
|
eventMap.set(eventName, eventSet);
|
|
}
|
|
}
|
|
element.setAttribute('evl', eventList.join(';'));
|
|
} else {
|
|
element[name] = value;
|
|
}
|
|
} else if (value === null) {
|
|
element.removeAttribute(name);
|
|
} else {
|
|
element.setAttribute(name, value);
|
|
}
|
|
});
|
|
}
|
|
|
|
function createElement({ t: type, n: name, p: props, i: id, c: children }) {
|
|
let element;
|
|
if (type === 3) {
|
|
element = document.createTextNode(props.textContent);
|
|
} else if (type === 1) {
|
|
element = document.createElement(name);
|
|
}
|
|
setAttributes(element, props);
|
|
elementMap.set((element._id = id), element);
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
element.appendChild(createElement(children[i]));
|
|
}
|
|
return element;
|
|
}
|
|
|
|
function removeElement(element) {
|
|
Array.from(element.childNodes).forEach(removeElement);
|
|
|
|
getEventList(element).forEach(eventName => {
|
|
removeEvent(eventMap.get(eventName), element._id, eventName);
|
|
});
|
|
|
|
element.parentNode.removeChild(element);
|
|
elementMap.delete(element._id);
|
|
}
|
|
|
|
const ACTION_METHODS = [
|
|
function addElement(parent, data, nextSiblingId) {
|
|
parent.insertBefore(createElement(data), getElement(nextSiblingId));
|
|
},
|
|
setAttributes,
|
|
removeElement
|
|
];
|
|
|
|
const queueFrame = patchFrame => {
|
|
if (!patchFrame || !patchFrame.length) {
|
|
return;
|
|
}
|
|
pendingFrames.unshift(patchFrame);
|
|
if (!runningNextFrame) {
|
|
updateFrame();
|
|
}
|
|
};
|
|
|
|
const updateFrame = () => {
|
|
const patches = pendingFrames.pop();
|
|
if (!patches) {
|
|
runningNextFrame = null;
|
|
return;
|
|
}
|
|
// console.group('PatchSet');
|
|
let patch;
|
|
while ((patch = patches.shift())) {
|
|
// console.log(ACTION_METHODS[patch[0]].name, JSON.stringify(patch));
|
|
ACTION_METHODS[patch[0]](getElement(patch[1]), patch[2], patch[3]);
|
|
}
|
|
// console.groupEnd('PatchSet');
|
|
|
|
runningNextFrame = requestAnimationFrame(updateFrame, domRoot);
|
|
};
|
|
|
|
function getElement(id) {
|
|
return id === null ? domRoot : elementMap.get(id);
|
|
}
|
|
|
|
function subscribe(fn) {
|
|
eventCallbacks.push(fn);
|
|
}
|
|
|
|
return {
|
|
queueFrame,
|
|
getElement,
|
|
subscribe
|
|
};
|
|
}
|