diff --git a/src/morphdom.js b/src/morphdom.js
index f978372..64cd697 100644
--- a/src/morphdom.js
+++ b/src/morphdom.js
@@ -236,6 +236,27 @@ export default function morphdomFactory(morphAttrs) {
}
}
+ function morphText(fromNode, toNode) {
+ var fromText = fromNode.nodeValue;
+ var toText = toNode.nodeValue;
+
+ if (fromText === toText) {
+ return;
+ }
+
+ // Handle incremental update case
+ if (fromNode.nodeType === TEXT_NODE && toText.startsWith(fromText)) {
+ var appendedText = toText.substring(fromText.length);
+ fromNode.after(appendedText);
+ fromNode.parentNode.normalize();
+ return;
+ }
+
+ // Simply update nodeValue on the original node to
+ // change the text value
+ fromNode.nodeValue = toText;
+ }
+
function morphChildren(fromEl, toEl) {
var skipFrom = skipFromChildren(fromEl, toEl);
var curToNodeChild = toEl.firstChild;
@@ -336,12 +357,7 @@ export default function morphdomFactory(morphAttrs) {
} else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {
// Both nodes being compared are Text or Comment nodes
isCompatible = true;
- // Simply update nodeValue on the original node to
- // change the text value
- if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {
- curFromNodeChild.nodeValue = curToNodeChild.nodeValue;
- }
-
+ morphText(curFromNodeChild, curToNodeChild);
}
}
@@ -426,9 +442,7 @@ export default function morphdomFactory(morphAttrs) {
}
} else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node
if (toNodeType === morphedNodeType) {
- if (morphedNode.nodeValue !== toNode.nodeValue) {
- morphedNode.nodeValue = toNode.nodeValue;
- }
+ morphText(morphedNode, toNode);
return morphedNode;
} else {
diff --git a/test/browser/test.js b/test/browser/test.js
index 146d4be..a4fcf4e 100644
--- a/test/browser/test.js
+++ b/test/browser/test.js
@@ -1657,6 +1657,84 @@ describe('morphdom' , function() {
expect(noUpdateParentBefore.isSameNode(noUpdateBefore.parentNode)).to.equal(false);
});
+ it('should preserve text selection during incremental updates', () => {
+ // Initial setup
+ var fromEl = document.createElement('div');
+ var initialText = 'Hello world';
+ fromEl.textContent = initialText;
+ document.body.appendChild(fromEl);
+
+ // Create and set selection
+ var selection = window.getSelection();
+ var range = document.createRange();
+ range.setStart(fromEl.firstChild, 0);
+ range.setEnd(fromEl.firstChild, 5); // Select "Hello"
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ // Create target node with incremental update
+ var toEl = document.createElement('div');
+ toEl.textContent = 'Hello world, how are you?';
+
+ // Apply morphdom with incremental update option
+ morphdom(fromEl, toEl);
+
+ // Verify selection is preserved
+ var updatedSelection = window.getSelection();
+ expect(updatedSelection.toString()).to.equal('Hello');
+ expect(fromEl.textContent).to.equal('Hello world, how are you?');
+ });
+
+ it('should handle multiple incremental updates correctly', () => {
+ var fromEl = document.createElement('div');
+ fromEl.textContent = 'Start';
+ document.body.appendChild(fromEl);
+
+ // Create and set selection
+ var selection = window.getSelection();
+ var range = document.createRange();
+ range.setStart(fromEl.firstChild, 0);
+ range.setEnd(fromEl.firstChild, 5);
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ // Perform multiple updates
+ var updates = [
+ 'Start with',
+ 'Start with more',
+ 'Start with more text',
+ 'Start with more text!!!',
+ ];
+
+ updates.forEach(text => {
+ var toEl = document.createElement('div');
+ toEl.textContent = text;
+
+ morphdom(fromEl, toEl);
+
+ // Verify text content after each update
+ expect(fromEl.textContent).to.equal(text);
+ });
+
+ // Verify final selection
+ expect(window.getSelection().toString()).to.equal('Start');
+ });
+
+ it('should properly normalize DOM after incremental updates', () => {
+ var fromEl = document.createElement('div');
+ fromEl.textContent = 'Initial';
+
+ var toEl = document.createElement('div');
+ toEl.textContent = 'Initial text with more content';
+
+ morphdom(fromEl, toEl);
+
+ // Verify that we have a single text node after normalization
+ expect(fromEl.childNodes.length).to.equal(1);
+ expect(fromEl.childNodes[0].nodeType).to.equal(3); // TEXT_NODE
+ expect(fromEl.textContent).to.equal('Initial text with more content');
+ });
+
xit('should reuse DOM element with matching ID and class name (2)', function() {
// NOTE: This test is currently failing. We need to improve the special case code
// for handling incompatible root nodes.
diff --git a/test/fixtures/autotest/incremental-text/from.html b/test/fixtures/autotest/incremental-text/from.html
new file mode 100644
index 0000000..bc090af
--- /dev/null
+++ b/test/fixtures/autotest/incremental-text/from.html
@@ -0,0 +1,5 @@
+test1
+test2
+test3
+
+test4
\ No newline at end of file
diff --git a/test/fixtures/autotest/incremental-text/to.html b/test/fixtures/autotest/incremental-text/to.html
new file mode 100644
index 0000000..5415a79
--- /dev/null
+++ b/test/fixtures/autotest/incremental-text/to.html
@@ -0,0 +1,5 @@
+test1 FOO
+test2 BAR
+test3 FOO
+BAR
+test4 FOO
\ No newline at end of file