diff --git a/README.md b/README.md index b584580..dd2af07 100644 --- a/README.md +++ b/README.md @@ -583,7 +583,7 @@ svg.selectAll("circle") The selections returned by the *enter* and *update* functions are merged and then returned by *selection*.join. -You also animate enter, update and exit by creating transitions inside the *enter*, *update* and *exit* functions. To avoid breaking the method chain, use *selection*.call to create transitions, or return an undefined enter or update selection to prevent merging: the return value of the *enter* and *update* functions specifies the two selections to merge and return by *selection*.join. +You can animate enter, update and exit by creating transitions inside the *enter*, *update* and *exit* functions. If the *enter* and *update* functions return transitions, their underlying selections are merged and then returned by *selection*.join. The return value of the *exit* function is not used. For more, see the [*selection*.join notebook](https://observablehq.com/@d3/selection-join). diff --git a/src/selection/join.js b/src/selection/join.js index 625cd47..8b47281 100644 --- a/src/selection/join.js +++ b/src/selection/join.js @@ -1,7 +1,15 @@ export default function(onenter, onupdate, onexit) { var enter = this.enter(), update = this, exit = this.exit(); - enter = typeof onenter === "function" ? onenter(enter) : enter.append(onenter + ""); - if (onupdate != null) update = onupdate(update); + if (typeof onenter === "function") { + enter = onenter(enter); + if (enter) enter = enter.selection(); + } else { + enter = enter.append(onenter + ""); + } + if (onupdate != null) { + update = onupdate(update); + if (update) update = update.selection(); + } if (onexit == null) exit.remove(); else onexit(exit); return enter && update ? enter.merge(update).order() : update; } diff --git a/test/selection/join-test.js b/test/selection/join-test.js index 98d890d..44df864 100644 --- a/test/selection/join-test.js +++ b/test/selection/join-test.js @@ -35,3 +35,22 @@ it("selection.join(…) reorders nodes to match the data", () => { assert.strictEqual(document.body.innerHTML, "

0

3

1

2

4

"); p; }); + +it("selection.join(enter, update, exit) allows callbacks to return a transition", "

1

2

", () => { + let p = select(document.body).selectAll("p").datum(function() { return this.textContent; }); + p = p.data([1, 3], d => d).join( + enter => mockTransition(enter.append("p").attr("class", "enter").text(d => d)), + update => mockTransition(update.attr("class", "update")), + exit => mockTransition(exit.attr("class", "exit")) + ); + p; + assert.strictEqual(document.body.innerHTML, "

1

2

3

"); +}); + +function mockTransition(selection) { + return { + selection() { + return selection; + } + }; +}