Skip to content

Commit

Permalink
Fix the For component to insert a new item into a cached list witho…
Browse files Browse the repository at this point in the history
…ut replacing reusable item.
  • Loading branch information
Kapelianovych committed Apr 29, 2024
1 parent ebbd1ae commit 27260b7
Showing 1 changed file with 99 additions and 47 deletions.
146 changes: 99 additions & 47 deletions packages/moru/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,74 +2,126 @@ import { context } from "./context.js";
import { createElement } from "./element.js";
import { memo, cached, discard } from "./enhancers.js";

const createChild = (
forContext,
key,
item,
index,
itemKey,
children,
dataStates,
indexStates,
mappedNodes,
) => {
const itemContext = context(forContext);

const [dataGetter] = (dataStates[index] = itemContext.state(
item,
(previous, next) => key(previous) === key(next),
));
const [indexGetter] = (indexStates[index] = itemContext.state(index));

(mappedNodes[index] = cached(
itemContext,
children(dataGetter, indexGetter),
)).itemKey = itemKey;
};

const swap = (array, previousIndex, nextIndex) => {
const previousItem = array[previousIndex];
array[previousIndex] = array[nextIndex];
array[nextIndex] = previousItem;
};

const createKeysFor = (items, generateKey) => {
const keysSet = new Set();
const keysArray = new Array(items.length);

for (let index = 0; index < items.length; index++) {
let key = generateKey(items[index]);

if (keysSet.has(key))
// Unfortunately a duplicate key has been found, fallback to the index
// as a unique part of the key.
key = String(key) + index;

keysSet.add(key);
keysArray[index] = key;
}

return [keysSet, keysArray];
};

export const For = (
{ each, children, fallback, key = (item) => item },
forContext,
) => {
let previousItemKeys = [];

const dataStates = [];
const indexStates = [];
let previousKeysArray = [];

return memo(
forContext,
(cachedNodes = []) => {
let index = 0;
const itemKeys = new Set();
const mappedNodes = [];

for (const item of each()) {
let itemKey = key(item);
const mappedNodes = new Array(each().length);
const [nextKeysSet, nextKeysArray] = createKeysFor(each(), key);

if (itemKeys.has(itemKey))
// Unfortunately a duplicate key has been found, fallback to the index
// as a unique part of the key.
itemKey = String(itemKey) + index;
for (let index = 0; index < each().length; index++) {
const item = each()[index];
const itemKey = nextKeysArray[index];

itemKeys.add(itemKey);
const previousIndex = previousKeysArray.indexOf(itemKey);

const previousItemIndex = previousItemKeys.indexOf(itemKey);

if (previousItemIndex > -1) {
mappedNodes[index] = cachedNodes[previousItemIndex];

dataStates.splice(
index,
0,
...dataStates.splice(previousItemIndex, 1),
);
indexStates.splice(
index,
0,
...indexStates.splice(previousItemIndex, 1),
);
if (previousIndex > -1) {
swap(dataStates, previousIndex, index);
swap(indexStates, previousIndex, index);
swap(previousKeysArray, previousIndex, index);
swap(cachedNodes, previousIndex, index);

mappedNodes[index] = cachedNodes[index];
indexStates[index][1](index);
} else if (index < dataStates.length) {
mappedNodes[index] = cachedNodes[index];
dataStates[index][1](() => item);
} else {
const itemContext = context(forContext);

const [dataGetter] = (dataStates[index] = itemContext.state(
const previousCachedNodeItemKey = previousKeysArray[index];

if (nextKeysSet.has(previousCachedNodeItemKey)) {
swap(dataStates, index, cachedNodes.length);
swap(indexStates, index, cachedNodes.length);
swap(previousKeysArray, index, cachedNodes.length);
swap(cachedNodes, index, cachedNodes.length);

createChild(
forContext,
key,
item,
index,
itemKey,
children,
dataStates,
indexStates,
mappedNodes,
);
} else {
(mappedNodes[index] = cachedNodes[index]).itemKey = itemKey;
dataStates[index][1](() => item);
}
} else
createChild(
forContext,
key,
item,
(previous, next) => key(previous) === key(next),
));
const [indexGetter] = (indexStates[index] = itemContext.state(index));

(mappedNodes[index] = cached(
itemContext,
children(dataGetter, indexGetter),
)).itemKey = itemKey;
}

index++;
index,
itemKey,
children,
dataStates,
indexStates,
mappedNodes,
);
}

previousItemKeys = [...itemKeys];
previousKeysArray = nextKeysArray;
cachedNodes.forEach(
(cachedNode) =>
itemKeys.has(cachedNode?.itemKey) || discard(cachedNode),
nextKeysSet.has(cachedNode?.itemKey) || discard(cachedNode),
);
// Keep states strictly equal to elements discarding excessive ones.
indexStates.length = dataStates.length = mappedNodes.length;
Expand Down

0 comments on commit 27260b7

Please sign in to comment.