@dark-engine/platform-server
Version:
Dark renderer for server
288 lines (287 loc) • 10.4 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
exports.toggle = exports.setTrackUpdate = exports.finishCommit = exports.commit = exports.createNativeElement = void 0;
const core_1 = require('@dark-engine/core');
const utils_1 = require('../utils');
const events_1 = require('../events');
const constants_1 = require('../constants');
let moves = [];
let patches = [];
let trackUpdate = null;
function createNativeElement(vNode) {
switch (vNode.type) {
case core_1.NodeType.TAG:
const name = vNode.name;
const tag = (0, utils_1.detectIsSvgElement)(name)
? document.createElementNS('http://www.w3.org/2000/svg', name)
: document.createElement(name);
return tag;
case core_1.NodeType.TEXT:
return document.createTextNode(vNode.value);
case core_1.NodeType.COMMENT:
return document.createComment(vNode.value);
}
}
exports.createNativeElement = createNativeElement;
function setObjectStyle(element, style) {
for (const key in style) {
m.setStyleProp.call(element.style, key, String(style[key]));
}
}
function addAttributes(element, vNode, isHydration) {
const tagElement = element;
for (let attrName in vNode.attrs) {
const attrValue = vNode.attrs[attrName];
const attribute = performAttribute(tagElement, attrName, attrValue);
if (attribute === null) {
continue;
} else {
attrName = attribute;
}
if ((0, events_1.detectIsEvent)(attrName)) {
(0, events_1.delegateEvent)(tagElement, (0, events_1.getEventName)(attrName), attrValue);
} else if (!isHydration && !(0, core_1.detectIsUndefined)(attrValue) && !core_1.ATTR_BLACK_LIST[attrName]) {
!patchAttributes(tagElement, vNode.name, attrName, attrValue) &&
m.setAttribute.call(tagElement, attrName, attrValue);
}
}
}
function updateAttributes(element, prevVNode, nextVNode) {
const attrNames = getAttributeNames(prevVNode, nextVNode);
const tagElement = element;
for (let attrName of attrNames) {
const prevAttrValue = prevVNode.attrs[attrName];
const nextAttrValue = nextVNode.attrs[attrName];
const attribute = performAttribute(tagElement, attrName, nextAttrValue, prevAttrValue);
if (attribute === null) {
continue;
} else {
attrName = attribute;
}
if (!(0, core_1.detectIsUndefined)(nextAttrValue)) {
if ((0, events_1.detectIsEvent)(attrName)) {
prevAttrValue !== nextAttrValue &&
(0, events_1.delegateEvent)(tagElement, (0, events_1.getEventName)(attrName), nextAttrValue);
} else if (!core_1.ATTR_BLACK_LIST[attrName] && prevAttrValue !== nextAttrValue) {
!patchAttributes(tagElement, nextVNode.name, attrName, nextAttrValue) &&
m.setAttribute.call(tagElement, attrName, nextAttrValue);
}
} else {
m.removeAttribute.call(tagElement, attrName);
}
}
}
function performAttribute(tagElement, attrName, nextAttrValue, prevAttrValue) {
if (attrName[0] === constants_1.EXCLUDE_ATTR_MARK) return null;
if (attrName === constants_1.DANGER_HTML_ATTR) {
(0, utils_1.setInnerHTML)(tagElement, String(nextAttrValue));
return null;
}
if (attrName === core_1.REF_ATTR) {
(0, core_1.applyRef)(nextAttrValue, tagElement);
return null;
}
if (
(attrName === constants_1.CLASS_ATTR || attrName === constants_1.CLASS_NAME_ATTR) &&
nextAttrValue !== prevAttrValue
) {
toggleAttribute(tagElement, constants_1.CLASS_ATTR, nextAttrValue);
return null;
}
if (
attrName === constants_1.STYLE_ATTR &&
nextAttrValue &&
nextAttrValue !== prevAttrValue &&
(0, core_1.detectIsObject)(nextAttrValue)
) {
setObjectStyle(tagElement, nextAttrValue);
return null;
}
if (attrName === constants_1.PREVENT) {
tagElement[constants_1.PREVENT] = true;
return null;
}
if (attrName === constants_1.AS_ATTR) {
attrName = attrName.slice(1, constants_1.AS_ATTR.length);
}
return attrName;
}
function toggleAttribute(element, name, value) {
value ? m.setAttribute.call(element, name, value) : m.removeAttribute.call(element, name);
}
function getAttributeNames(prevVNode, nextVNode) {
const attrNames = new Set();
const prevAttrs = Object.keys(prevVNode.attrs);
const nextAttrs = Object.keys(nextVNode.attrs);
const size = Math.max(prevAttrs.length, nextAttrs.length);
for (let i = 0; i < size; i++) {
attrNames.add(prevAttrs[i] || nextAttrs[i]);
}
return attrNames;
}
const ATTR_TRANSFORM_MAP = {
readonly: 'readOnly',
};
function patchAttributes(element, tagName, attrName, attrValue) {
const fn = specialCasesMap[tagName];
const $attrName = ATTR_TRANSFORM_MAP[attrName] || attrName;
let stop = fn ? fn(element, attrName, attrValue) : false;
if (canSetProperty(element, $attrName)) {
element[$attrName] = attrValue;
}
if (!stop && (0, core_1.detectIsBoolean)(attrValue)) {
stop = !$attrName.includes(constants_1.DASH_MARK);
}
return stop;
}
function canSetProperty(element, key) {
const prototype = Object.getPrototypeOf(element);
const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
return Boolean(descriptor?.set);
}
const specialCasesMap = {
[constants_1.INPUT_TAG]: (element, attrName, attrValue) => {
if (attrName === constants_1.VALUE_ATTR) {
patches.push(() => {
(0, core_1.detectIsBoolean)(attrValue) ? (element.checked = attrValue) : (element.value = String(attrValue));
});
}
return false;
},
[constants_1.TEXTAREA_TAG]: (element, attrName, attrValue) => {
if (attrName === constants_1.VALUE_ATTR) {
element.innerText = String(attrValue);
return true;
}
return false;
},
};
function commitCreation(fiber) {
const parent = (0, core_1.getFiberWithElement)(fiber.parent);
const parentElement = parent.el;
const childNodes = parentElement.childNodes;
const isHydration = (0, core_1.detectIsHydration)();
if (isHydration) {
let nativeElement = childNodes[fiber.eidx];
if (nativeElement instanceof DocumentType) {
nativeElement = nativeElement.nextSibling;
}
if (
(0, core_1.detectIsTextVirtualNode)(fiber.inst) &&
nativeElement instanceof Text &&
fiber.inst.value.length !== nativeElement.length
) {
nativeElement.splitText(fiber.inst.value.length);
}
if (fiber.el.nodeName !== nativeElement.nodeName) {
(0, utils_1.illegal)('Inconsistent element for hydration!');
}
fiber.el = nativeElement;
} else {
if ((0, core_1.detectIsTagVirtualNode)(parent.inst) && parent.inst.attrs[constants_1.DANGER_HTML_ATTR]) {
(0, utils_1.illegal)(`The element with danger content can't have a children!`);
}
if (childNodes.length === 0 || fiber.eidx > childNodes.length - 1) {
!(0, utils_1.detectIsVoidElement)(parent.inst.name) && m.appendElement.call(parentElement, fiber.el);
} else {
m.insertElement.call(parentElement, fiber.el, parentElement.childNodes[fiber.eidx]);
}
}
(0, core_1.detectIsTagVirtualNode)(fiber.inst) && addAttributes(fiber.el, fiber.inst, isHydration);
fiber.el.parentElement?.[constants_1.PREVENT] && (fiber.el[constants_1.PREVENT] = true);
}
function commitUpdate(fiber) {
const element = fiber.el;
const prevInst = fiber.alt.inst;
const nextInst = fiber.inst;
(0, core_1.detectIsPlainVirtualNode)(nextInst)
? prevInst.value !== nextInst.value && (element.nodeValue = nextInst.value)
: updateAttributes(element, prevInst, nextInst);
}
function commitDeletion(fiber) {
const parent = (0, core_1.getFiberWithElement)(fiber.parent);
if (fiber.mask & core_1.FLUSH_MASK) {
parent.el.children.length > 0 && (0, utils_1.removeContent)(parent.el);
} else {
(0, core_1.walk)(fiber, onWalkInCommitDeletion(parent.el));
}
}
const onWalkInCommitDeletion = parentElement => (fiber, skip) => {
if (fiber.el) {
!fiber.hook?.getIsPortal() && m.removeElement.call(parentElement, fiber.el);
return skip();
}
};
function move(fiber) {
const sourceNodes = (0, core_1.collectElements)(fiber, x => x.el);
const sourceNode = sourceNodes[0];
const parentElement = sourceNode.parentElement;
const sourceFragment = new DocumentFragment();
const elementIdx = fiber.eidx;
const move = () => {
for (let i = 1; i < sourceNodes.length; i++) {
m.removeElement.call(parentElement, parentElement.childNodes[elementIdx + 1]);
}
m.replaceElement.call(parentElement, sourceFragment, parentElement.childNodes[elementIdx]);
};
let idx = 0;
for (const node of sourceNodes) {
m.insertElement.call(parentElement, document.createComment(`${elementIdx}:${idx}`), node);
m.appendElement.call(sourceFragment, node);
idx++;
}
moves.push(move);
}
function commit(fiber) {
switch (fiber.tag) {
case core_1.CREATE_EFFECT_TAG:
if (!fiber.el || fiber.hook?.getIsPortal()) return;
trackUpdate && trackUpdate(fiber.el);
commitCreation(fiber);
break;
case core_1.UPDATE_EFFECT_TAG:
fiber.mask & core_1.MOVE_MASK && (move(fiber), (fiber.mask &= ~core_1.MOVE_MASK));
if (!fiber.el || fiber.hook?.getIsPortal()) return;
trackUpdate && trackUpdate(fiber.el);
commitUpdate(fiber);
break;
case core_1.DELETE_EFFECT_TAG:
commitDeletion(fiber);
break;
default:
break;
}
}
exports.commit = commit;
function finishCommit() {
moves.forEach(x => x());
patches.forEach(x => x());
moves = [];
patches = [];
}
exports.finishCommit = finishCommit;
function setup() {
if (!(0, utils_1.detectIsBrowser)()) return {};
const np = Node.prototype;
const ep = Element.prototype;
return {
appendElement: np.appendChild,
insertElement: np.insertBefore,
replaceElement: np.replaceChild,
removeElement: np.removeChild,
hasAttribute: ep.hasAttribute,
setAttribute: ep.setAttribute,
removeAttribute: ep.removeAttribute,
setStyleProp: CSSStyleDeclaration.prototype.setProperty,
};
}
const m = setup();
const setTrackUpdate = fn => (trackUpdate = fn);
exports.setTrackUpdate = setTrackUpdate;
const toggle = (element, isVisible) => {
isVisible
? m.hasAttribute.call(element, constants_1.STYLE_ATTR) && m.removeAttribute.call(element, constants_1.STYLE_ATTR)
: m.setStyleProp.call(element.style, 'display', 'none', 'important');
};
exports.toggle = toggle;
//# sourceMappingURL=dom.js.map