tiny-dream
Version:
A tiny Incremental DOM implementation for modern browsers.
160 lines (131 loc) • 3.36 kB
JavaScript
import createElement from './create-element'
import createTextNode from './create-text-node'
import getNodeData from './get-node-data'
import { hasOwnProperty, detectFocusedAncestors, moveBefore } from './utils'
let doc = null
let currentNode = null
let currentParent = null
const markFocused = (focusedAncestors, focused) => {
for (let i = 0; i < focusedAncestors.length; i += 1) {
getNodeData(focusedAncestors[i]).focused = focused
}
}
const matches = (matchNode, nameOrCtor, key, typeId) => {
const data = getNodeData(matchNode)
return nameOrCtor === data.nameOrCtor
&& typeId === data.typeId
&& key == data.key
}
const alignWithDOM = (nameOrCtor, key, typeId) => {
if (currentNode && matches(currentNode, nameOrCtor, key, typeId)) {
return
}
const parentData = getNodeData(currentParent)
const keyMap = parentData && parentData.keyMap
let node
if (key) {
const keyNode = keyMap[key]
if (keyNode) {
if (matches(keyNode, nameOrCtor, key, typeId)) {
node = keyNode
} else {
getNodeData(keyNode).key = null
}
}
}
if (!node) {
if (nameOrCtor === '#text') {
node = createTextNode(doc)
} else {
node = createElement(doc, currentParent, nameOrCtor, key, typeId)
}
if (key) {
keyMap[key] = node
}
}
if (getNodeData(node).focused) {
moveBefore(currentParent, node, currentNode)
} else {
currentParent.insertBefore(node, currentNode)
}
currentNode = node
}
const clearUnvisitedDOM = (parentNode, startNode, endNode) => {
const data = getNodeData(parentNode)
const keyMap = data.keyMap
let child = startNode
while (child !== endNode) {
const next = child.nextSibling
const key = getNodeData(child).key
parentNode.removeChild(child)
if (key) {
delete keyMap[key]
}
child = next
}
}
const enterNode = () => {
currentParent = currentNode
currentNode = null
}
const getNextNode = () => {
if (currentNode) {
return currentNode.nextSibling
}
return currentParent.firstChild
}
const nextNode = () => {
currentNode = getNextNode()
}
const exitNode = () => {
clearUnvisitedDOM(currentParent, getNextNode(), null)
currentNode = currentParent
currentParent = currentParent.parentNode
}
const open = (nameOrCtor, key, typeId) => {
nextNode()
alignWithDOM(nameOrCtor, key, typeId)
enterNode()
return currentParent
}
const close = () => {
exitNode()
return currentNode
}
const text = function() {
nextNode()
alignWithDOM('#text', null)
return currentNode
}
const component = (c, data) => {
if (hasOwnProperty(c, 'render')) {
c.render.apply(null, [data])
}
}
const patch = (node, fn, data) => {
const prevDoc = doc
const prevCurrentNode = currentNode
const prevCurrentParent = currentParent
let previousInAttributes = false
let previousInSkip = false
doc = (node && node.ownerDocument) || document
currentParent = node && node.parentNode
const focusedAncestors = detectFocusedAncestors(node, currentParent)
markFocused(focusedAncestors, true)
currentNode = node
enterNode()
fn(data)
exitNode()
markFocused(focusedAncestors, false)
doc = prevDoc
currentNode = prevCurrentNode
currentParent = prevCurrentParent
return node
}
export {
open,
close,
text,
component,
patch
}