Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions src/morphdom.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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 {
Expand Down
78 changes: 78 additions & 0 deletions test/browser/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/autotest/incremental-text/from.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
test1
<span>test2</span>
test3
<span></span>
test4
5 changes: 5 additions & 0 deletions test/fixtures/autotest/incremental-text/to.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
test1 FOO
<span>test2 BAR</span>
test3 FOO
<span>BAR</span>
test4 FOO