UNPKG

@dark-engine/platform-server

Version:
311 lines (310 loc) 10 kB
import { REF_ATTR, ATTR_BLACK_LIST, CREATE_EFFECT_TAG, UPDATE_EFFECT_TAG, DELETE_EFFECT_TAG, MOVE_MASK, FLUSH_MASK, detectIsUndefined, detectIsBoolean, detectIsObject, NodeType, detectIsTagVirtualNode, detectIsTextVirtualNode, detectIsPlainVirtualNode, getFiberWithElement, collectElements, walk, applyRef, detectIsHydration, } from '@dark-engine/core'; import { detectIsSvgElement, detectIsVoidElement, illegal, removeContent, setInnerHTML, detectIsBrowser, } from '../utils'; import { delegateEvent, detectIsEvent, getEventName } from '../events'; import { INPUT_TAG, TEXTAREA_TAG, STYLE_ATTR, CLASS_ATTR, CLASS_NAME_ATTR, VALUE_ATTR, AS_ATTR, EXCLUDE_ATTR_MARK, DANGER_HTML_ATTR, DASH_MARK, PREVENT, } from '../constants'; let moves = []; let patches = []; let trackUpdate = null; function createNativeElement(vNode) { switch (vNode.type) { case NodeType.TAG: const name = vNode.name; const tag = detectIsSvgElement(name) ? document.createElementNS('http://www.w3.org/2000/svg', name) : document.createElement(name); return tag; case NodeType.TEXT: return document.createTextNode(vNode.value); case NodeType.COMMENT: return document.createComment(vNode.value); } } 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 (detectIsEvent(attrName)) { delegateEvent(tagElement, getEventName(attrName), attrValue); } else if (!isHydration && !detectIsUndefined(attrValue) && !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 (!detectIsUndefined(nextAttrValue)) { if (detectIsEvent(attrName)) { prevAttrValue !== nextAttrValue && delegateEvent(tagElement, getEventName(attrName), nextAttrValue); } else if (!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] === EXCLUDE_ATTR_MARK) return null; if (attrName === DANGER_HTML_ATTR) { setInnerHTML(tagElement, String(nextAttrValue)); return null; } if (attrName === REF_ATTR) { applyRef(nextAttrValue, tagElement); return null; } if ((attrName === CLASS_ATTR || attrName === CLASS_NAME_ATTR) && nextAttrValue !== prevAttrValue) { toggleAttribute(tagElement, CLASS_ATTR, nextAttrValue); return null; } if (attrName === STYLE_ATTR && nextAttrValue && nextAttrValue !== prevAttrValue && detectIsObject(nextAttrValue)) { setObjectStyle(tagElement, nextAttrValue); return null; } if (attrName === PREVENT) { tagElement[PREVENT] = true; return null; } if (attrName === AS_ATTR) { attrName = attrName.slice(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 && detectIsBoolean(attrValue)) { stop = !$attrName.includes(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 = { [INPUT_TAG]: (element, attrName, attrValue) => { if (attrName === VALUE_ATTR) { patches.push(() => { detectIsBoolean(attrValue) ? (element.checked = attrValue) : (element.value = String(attrValue)); }); } return false; }, [TEXTAREA_TAG]: (element, attrName, attrValue) => { if (attrName === VALUE_ATTR) { element.innerText = String(attrValue); return true; } return false; }, }; function commitCreation(fiber) { const parent = getFiberWithElement(fiber.parent); const parentElement = parent.el; const childNodes = parentElement.childNodes; const isHydration = detectIsHydration(); if (isHydration) { let nativeElement = childNodes[fiber.eidx]; if (nativeElement instanceof DocumentType) { nativeElement = nativeElement.nextSibling; } if ( detectIsTextVirtualNode(fiber.inst) && nativeElement instanceof Text && fiber.inst.value.length !== nativeElement.length ) { nativeElement.splitText(fiber.inst.value.length); } if (fiber.el.nodeName !== nativeElement.nodeName) { illegal('Inconsistent element for hydration!'); } fiber.el = nativeElement; } else { if (detectIsTagVirtualNode(parent.inst) && parent.inst.attrs[DANGER_HTML_ATTR]) { illegal(`The element with danger content can't have a children!`); } if (childNodes.length === 0 || fiber.eidx > childNodes.length - 1) { !detectIsVoidElement(parent.inst.name) && m.appendElement.call(parentElement, fiber.el); } else { m.insertElement.call(parentElement, fiber.el, parentElement.childNodes[fiber.eidx]); } } detectIsTagVirtualNode(fiber.inst) && addAttributes(fiber.el, fiber.inst, isHydration); fiber.el.parentElement?.[PREVENT] && (fiber.el[PREVENT] = true); } function commitUpdate(fiber) { const element = fiber.el; const prevInst = fiber.alt.inst; const nextInst = fiber.inst; detectIsPlainVirtualNode(nextInst) ? prevInst.value !== nextInst.value && (element.nodeValue = nextInst.value) : updateAttributes(element, prevInst, nextInst); } function commitDeletion(fiber) { const parent = getFiberWithElement(fiber.parent); if (fiber.mask & FLUSH_MASK) { parent.el.children.length > 0 && removeContent(parent.el); } else { 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 = 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 CREATE_EFFECT_TAG: if (!fiber.el || fiber.hook?.getIsPortal()) return; trackUpdate && trackUpdate(fiber.el); commitCreation(fiber); break; case UPDATE_EFFECT_TAG: fiber.mask & MOVE_MASK && (move(fiber), (fiber.mask &= ~MOVE_MASK)); if (!fiber.el || fiber.hook?.getIsPortal()) return; trackUpdate && trackUpdate(fiber.el); commitUpdate(fiber); break; case DELETE_EFFECT_TAG: commitDeletion(fiber); break; default: break; } } function finishCommit() { moves.forEach(x => x()); patches.forEach(x => x()); moves = []; patches = []; } function setup() { if (!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); const toggle = (element, isVisible) => { isVisible ? m.hasAttribute.call(element, STYLE_ATTR) && m.removeAttribute.call(element, STYLE_ATTR) : m.setStyleProp.call(element.style, 'display', 'none', 'important'); }; export { createNativeElement, commit, finishCommit, setTrackUpdate, toggle }; //# sourceMappingURL=dom.js.map