Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow returning a node from decorate modifier to replace a node #410

Closed
wants to merge 3 commits into from
Closed
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
52 changes: 41 additions & 11 deletions src/core/util.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Handle } from './Destroyable';
import { DNode, RenderResult } from './interfaces';
import { DNode, RenderResult, VNode, WNode } from './interfaces';
import { isWNode, isVNode } from './vdom';

const slice = Array.prototype.slice;
const hasOwnProperty = Object.prototype.hasOwnProperty;

export interface Modifier<T extends DNode> {
(dNode: T, breaker: () => void): void;
(dNode: T, breaker: () => void): void | VNode | WNode | null | string | boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this not just be void | DNode?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I didn't do that was because it's checking if it's a new node based on whether the return is strictly equal to undefined. DNode includes undefined so if they returned undefined as a new node it wouldn't be picked up.

If that's a problem I could add another callback to pass the new node to instead or something to avoid the ambiguity?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, it seems like we'd want to support being able to return undefined right?

}

export interface Predicate<T extends DNode> {
Expand Down Expand Up @@ -368,20 +368,50 @@ export function decorate(
shallow = optionsOrModifier.shallow || false;
}

let nodes = Array.isArray(dNodes) ? [...dNodes] : [dNodes];
let shouldDrain = false;
function breaker() {
nodes = [];
shouldDrain = true;
}

function modifyNode(modifier: Modifier<DNode>, node: DNode) {
const modifiedNode = modifier(node, breaker);
return modifiedNode === undefined ? node : modifiedNode;
}
while (nodes.length) {
const node = nodes.shift();

let nodes = Array.isArray(dNodes) ? [...dNodes] : [dNodes];
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node && node !== true) {
if (!shallow && (isWNode(node) || isVNode(node)) && node.children) {
nodes = [...nodes, ...node.children];
}
if (!predicate || predicate(node)) {
modifier(node, breaker);
nodes[i] = modifyNode(modifier, node);
}
}
if (shouldDrain) {
break;
}
}
return dNodes;

const rootNodes = nodes.slice();

if (!shallow) {
while (nodes.length) {
const node = nodes.shift();
if ((isWNode(node) || isVNode(node)) && node.children) {
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
if (child && child !== true) {
if (!predicate || predicate(child)) {
node.children[i] = modifyNode(modifier, child);
}
}
nodes.push(node.children[i]);
if (shouldDrain) {
nodes = [];
}
}
}
}
}

return Array.isArray(dNodes) ? rootNodes : rootNodes[0];
}
45 changes: 45 additions & 0 deletions tests/core/unit/d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,51 @@ registerSuite('d', {
assert.strictEqual(children[4], 'my text');
assert.isNull(children[5]);
}
},
'returning a new node replaces the node, and new children will be checked'() {
const childOne = v('div', { id: 'child-one' });
const nodeOne = w(WidgetBase, {}, [childOne]);
const nodeTwo = 'text node';
const childTwo = v('div');
const nodeThree = v('div', { id: 'node-three' }, [childTwo]);
const nodeFour = v('div', { id: 'node-four' });
const nodes = [nodeOne, nodeTwo, nodeThree, nodeFour];
const newGrandChild = v('div', { id: 'grand-child' });
const newChildOne = w(WidgetBase, {}, [newGrandChild]);
const newChildFour = v('div', { id: 'new-child-four' });
const newNodeFour = v('div', { id: 'new-node-four' }, [newChildFour]);
const newNodes = decorate(nodes, {
modifier: (node: DNode) => {
if (node === 'text node') {
return 'new text node';
}

if (isVNode(node) || isWNode(node)) {
if (node.properties.id === 'child-one') {
return newChildOne;
}

if (node.properties.id === 'grand-child') {
node.properties = { id: 'new-grand-child-id' };
}

if (node.properties.id === 'node-four') {
return newNodeFour;
}

if (node.properties.id === 'node-three' || node.properties.id === 'new-child-four') {
return false;
}
}
}
});
assert.strictEqual(newNodes.length, 4);
assert.strictEqual(nodeOne.children[0], newChildOne);
assert.strictEqual(newGrandChild.properties.id, 'new-grand-child-id');
assert.strictEqual(newNodes[1], 'new text node');
assert.isFalse(newNodes[2]);
assert.strictEqual(newNodes[3], newNodeFour);
assert.isFalse(newNodeFour.children![0]);
}
},
'isVNode and isWNode': {
Expand Down