Skip to content

Commit

Permalink
handle persistent id's not softMatching
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelWest22 committed Dec 18, 2024
1 parent 65f32e5 commit 18fbd86
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 9 deletions.
44 changes: 36 additions & 8 deletions src/idiomorph.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ var Idiomorph = (function () {
* @property {ConfigInternal['ignoreActive']} ignoreActive
* @property {ConfigInternal['ignoreActiveValue']} ignoreActiveValue
* @property {Map<Node, Set<string>>} idMap
* @property {Set<string>} persistentIds
* @property {Set<string>} deadIds
* @property {ConfigInternal['callbacks']} callbacks
* @property {ConfigInternal['head']} head
Expand Down Expand Up @@ -259,7 +260,7 @@ var Idiomorph = (function () {
oldNode.parentNode?.removeChild(oldNode);
ctx.callbacks.afterNodeRemoved(oldNode);
return null;
} else if (!isSoftMatch(oldNode, newContent)) {
} else if (!isSoftMatch(oldNode, newContent, ctx)) {
if (ctx.callbacks.beforeNodeRemoved(oldNode) === false) return oldNode;
if (ctx.callbacks.beforeNodeAdded(newContent) === false) return oldNode;

Expand Down Expand Up @@ -712,6 +713,7 @@ var Idiomorph = (function () {
ignoreActive: mergedConfig.ignoreActive,
ignoreActiveValue: mergedConfig.ignoreActiveValue,
idMap: createIdMap(oldNode, newContent),
persistentIds: persistentIdSet(oldNode, newContent),
deadIds: new Set(),
callbacks: mergedConfig.callbacks,
head: mergedConfig.head
Expand Down Expand Up @@ -746,12 +748,18 @@ var Idiomorph = (function () {
*
* @param {Node | null} node1
* @param {Node | null} node2
* @param {MorphContext} ctx
* @returns {boolean}
*/
function isSoftMatch(node1, node2) {
function isSoftMatch(node1, node2, ctx) {
if (node1 == null || node2 == null) {
return false;
}
// If the id's do not match and either of the id's are persisted through the morph then they can't be soft matches
if ( /** @type {Element} */ (node1).id !== /** @type {Element} */ (node2).id
&& (ctx.persistentIds.has(/** @type {Element} */ (node1).id) || ctx.persistentIds.has(/** @type {Element} */ (node2).id))) {
return false;
}
return node1.nodeType === node2.nodeType &&
// ok to cast: if one is not element, `tagName` will be undefined and we'll compare that
/** @type {Element} */ (node1).tagName === /** @type {Element} */ (node2).tagName
Expand Down Expand Up @@ -871,11 +879,11 @@ var Idiomorph = (function () {
}

// if we have a soft match with the current node, return it
if (isSoftMatch(newChild, potentialSoftMatch)) {
if (isSoftMatch(newChild, potentialSoftMatch, ctx)) {
return potentialSoftMatch;
}

if (isSoftMatch(nextSibling, potentialSoftMatch)) {
if (isSoftMatch(nextSibling, potentialSoftMatch, ctx)) {
// the next new node has a soft match with this node, so
// increment the count of future soft matches
siblingSoftMatchCount++;
Expand Down Expand Up @@ -1046,7 +1054,7 @@ var Idiomorph = (function () {
// TODO: The function handles node1 and node2 as if they are Elements but the function is
// called in places where node1 and node2 may be just Nodes, not Elements
function scoreElement(node1, node2, ctx) {
if (isSoftMatch(node1, node2)) {
if (isSoftMatch(node1, node2, ctx)) {
// ok to cast: isSoftMatch performs a null check
return .5 + getIdIntersectionCount(ctx, /** @type {Node} */ (node1), node2);
}
Expand Down Expand Up @@ -1128,7 +1136,7 @@ var Idiomorph = (function () {
}

/**
* A bottom up algorithm that finds all elements with ids inside of the node
* A bottom up algorithm that finds all elements with ids in the node
* argument and populates id sets for those nodes and all their parents, generating
* a set of ids contained within all nodes for the entire hierarchy in the DOM
*
Expand All @@ -1137,8 +1145,11 @@ var Idiomorph = (function () {
*/
function populateIdMapForNode(node, idMap) {
let nodeParent = node.parentElement;
// find all elements with an id property
let idElements = node.querySelectorAll('[id]');
// find all inside elements with an id property
let idElements = Array.from(node.querySelectorAll('[id]'));
if (node.id) {
idElements.push(node) // also include the node itself
}
for (const elt of idElements) {
/**
* @type {Element|null}
Expand Down Expand Up @@ -1180,6 +1191,23 @@ var Idiomorph = (function () {
return idMap;
}

/**
* @param {Element} oldContent the old content that will be morphed
* @param {Element} newContent the new content to morph to
* @returns {Set<string>} the set of all persistent nodes that exist in both old and new content
*/
function persistentIdSet(oldContent, newContent) {
let idSet = new Set();
for (const oldNode of oldContent.querySelectorAll('[id]')) {
for (const newNode of newContent.querySelectorAll('[id]')) {
if (oldNode.id === newNode.id) {
idSet.add(oldNode.id);
}
}
}
return idSet;
}

//=============================================================================
// This is what ends up becoming the Idiomorph global object
//=============================================================================
Expand Down
2 changes: 1 addition & 1 deletion test/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ describe("Core morphing tests", function(){
document.body.removeChild(parent);
});

it.only('can prevent element addition w/ the beforeNodeAdded callback', function() {
it('can prevent element addition w/ the beforeNodeAdded callback', function() {
let parent = make("<div><p>1</p><p>2</p></div>");
document.body.append(parent);

Expand Down

0 comments on commit 18fbd86

Please sign in to comment.