From db830d2b5abe001a088c0d424f4a501d60dede0e Mon Sep 17 00:00:00 2001 From: Anton Ignatov Date: Sat, 6 Jan 2018 01:16:59 +0300 Subject: [PATCH 1/2] Added handling for TVML special text nodes handling --- .eslintrc | 8 ++- src/react-tvml/index.js | 127 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 3 deletions(-) diff --git a/.eslintrc b/.eslintrc index cb43192..98f1bb8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -9,7 +9,13 @@ "props": false }], "no-underscore-dangle": ["error", { - "allow": ["_sink", "_internalRoot", "_reactRootContainer"] + "allow": [ + "_sink", + "_internalRoot", + "_targetTextNode", + "_handledTextNodes", + "_reactRootContainer" + ] }], "no-prototype-builtins": "off" }, diff --git a/src/react-tvml/index.js b/src/react-tvml/index.js index 5832fda..8ddcd1c 100644 --- a/src/react-tvml/index.js +++ b/src/react-tvml/index.js @@ -5,6 +5,7 @@ import ReactFiberReconciler from 'react-reconciler'; import { broadcast } from '../event-bus'; import { + TEXT_NODE, COMMENT_NODE, DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE, @@ -52,6 +53,10 @@ const supportedEventMapping = { onHoldselect: 'holdselect', }; +function isTextNode(node) { + return node && node.nodeType === TEXT_NODE; +} + function toDashCase(str) { return str.replace(/([A-Z])/g, match => `-${match[0].toLowerCase()}`); } @@ -90,6 +95,13 @@ function createTextNode(text, rootContainerElement) { return textNode; } +function updateNodeValue(node) { + if (node._handledTextNodes) { + const newValue = node._handledTextNodes.map(({ value }) => value).join(''); + node.nodeValue = newValue; + } +} + function setInitialProperties( domElement, type, @@ -332,6 +344,29 @@ const TVMLRenderer = ReactFiberReconciler({ }, appendInitialChild(parentInstance, child) { + const target = parentInstance.lastChild; + + /** + * TVML merge sibling text nodes into one but reconciler don't do this. + * To workaround this issue we create references to each created text nodes + * for later updates. + */ + if (isTextNode(target) && isTextNode(child)) { + if (!target._handledTextNodes) { + target._handledTextNodes = [{ + node: target, + value: target.nodeValue, + }]; + } + + target._handledTextNodes.push({ + node: child, + value: child.nodeValue, + }); + + child._targetTextNode = target; + } + parentInstance.appendChild(child); }, @@ -390,10 +425,52 @@ const TVMLRenderer = ReactFiberReconciler({ }, commitTextUpdate(textInstance, oldText, newText) { - textInstance.nodeValue = newText; + let target; + + /** + * If text node update is commited we need to check if there any + * reference text nodes to figure out which one to update. + */ + if (textInstance._targetTextNode) { + target = textInstance._targetTextNode; + } else if (textInstance._handledTextNodes) { + target = textInstance; + } + + if (target) { + const nodes = target._handledTextNodes; + const reference = nodes.find(({ node }) => node === textInstance); + + reference.value = newText; + updateNodeValue(target); + } else { + textInstance.nodeValue = newText; + } }, appendChild(parentInstance, child) { + const target = parentInstance.lastChild; + + /** + * If we need to attach text node in update we performing same checks + * as in `appendInitialChild`. + */ + if (isTextNode(target) && isTextNode(child)) { + if (!target._handledTextNodes) { + target._handledTextNodes = [{ + node: target, + value: target.nodeValue, + }]; + } + + target._handledTextNodes.push({ + node: child, + value: child.nodeValue, + }); + + child._targetTextNode = target; + } + parentInstance.appendChild(child); }, @@ -406,6 +483,28 @@ const TVMLRenderer = ReactFiberReconciler({ }, insertBefore(parentInstance, child, beforeChild) { + /** + * When in TVML text node is inserted before another text node its value + * will be merged with existed and it will be removed. + * + * Check `appendInitialChild` for more info. + */ + if (isTextNode(beforeChild) && isTextNode(child)) { + if (!beforeChild._handledTextNodes) { + beforeChild._handledTextNodes = [{ + node: beforeChild, + value: beforeChild.nodeValue, + }]; + } + + beforeChild._handledTextNodes.unshift({ + node: child, + value: child.nodeValue, + }); + + child._targetTextNode = beforeChild; + } + parentInstance.insertBefore(child, beforeChild); }, @@ -418,7 +517,31 @@ const TVMLRenderer = ReactFiberReconciler({ }, removeChild(parentInstance, child) { - parentInstance.removeChild(child); + /** + * If we deleting text node with parent node then we just clearing any + * references. No need to use `removeChild` because it was never attached + * to document. + * + * Also we can't remove parent node with active references. But we can + * empty its value. + */ + if (child._targetTextNode) { + const target = child._targetTextNode; + const nodes = target._handledTextNodes; + const referenceIndex = nodes.findIndex(({ node }) => node === child); + + nodes.splice(referenceIndex, 1); + delete child._targetTextNode; + updateNodeValue(target); + } else if (child._handledTextNodes && child._handledTextNodes.length) { + const nodes = child._handledTextNodes; + const reference = nodes.find(({ node }) => node === child); + + reference.value = ''; + updateNodeValue(child); + } else { + parentInstance.removeChild(child); + } }, removeChildFromContainer(container, child) { From cde80bfc8b14948f5f81568f444e66e23d056542 Mon Sep 17 00:00:00 2001 From: Anton Ignatov Date: Sat, 6 Jan 2018 01:44:03 +0300 Subject: [PATCH 2/2] Removed all side effects related to text nodes --- src/react-tvml/index.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/react-tvml/index.js b/src/react-tvml/index.js index 8ddcd1c..1e84bfe 100644 --- a/src/react-tvml/index.js +++ b/src/react-tvml/index.js @@ -116,10 +116,6 @@ function setInitialProperties( if (propName === CHILDREN) { if (type === 'style') { domElement.innerHTML = propValue; - } else if (typeof propValue === 'string') { - if (propValue !== '') domElement.textContent = propValue; - } else if (typeof propValue === 'number') { - domElement.textContent = `${propValue}`; } } else if (propName === DATAITEM) { if (propValue instanceof DataItem) { @@ -248,8 +244,6 @@ function updateProperties( if (propName === CHILDREN) { if (type === 'style') { domElement.innerHTML = propValue; - } else { - domElement.textContent = propValue; } } else if (propName === DATAITEM) { if (propValue == null) {