diff --git a/package.json b/package.json index 485e37d0..808d534a 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-preset-shopify": "^16.5.0", + "canvas": "^1.6.13", "codecov": "^3.0.2", "concurrently": "^3.5.1", "core-js": "^2.5.7", diff --git a/src/Draggable/Draggable.js b/src/Draggable/Draggable.js index 097ff8f1..e1e56359 100644 --- a/src/Draggable/Draggable.js +++ b/src/Draggable/Draggable.js @@ -1,4 +1,4 @@ -import {closest} from 'shared/utils'; +import {closest, cloneNode} from 'shared/utils'; import {Announcement, Focusable, Mirror, Scrollable} from './Plugins'; @@ -374,7 +374,7 @@ export default class Draggable { this.lastPlacedContainer.classList.remove(this.getClassNameFor('container:placed')); } - this.source = this.originalSource.cloneNode(true); + this.source = cloneNode(this.originalSource, true); this.originalSource.parentNode.insertBefore(this.source, this.originalSource); this.originalSource.style.display = 'none'; diff --git a/src/Draggable/Plugins/Mirror/Mirror.js b/src/Draggable/Plugins/Mirror/Mirror.js index 4c82e15c..185cff9c 100644 --- a/src/Draggable/Plugins/Mirror/Mirror.js +++ b/src/Draggable/Plugins/Mirror/Mirror.js @@ -1,4 +1,5 @@ import AbstractPlugin from 'shared/AbstractPlugin'; +import {cloneNode} from 'shared/utils'; import { MirrorCreateEvent, @@ -163,7 +164,7 @@ export default class Mirror extends AbstractPlugin { } const appendableContainer = this[getAppendableContainer](source) || sourceContainer; - this.mirror = source.cloneNode(true); + this.mirror = cloneNode(source, true); const mirrorCreatedEvent = new MirrorCreatedEvent({ source, diff --git a/src/shared/utils/cloneNode/cloneNode.js b/src/shared/utils/cloneNode/cloneNode.js new file mode 100644 index 00000000..ff58fad1 --- /dev/null +++ b/src/shared/utils/cloneNode/cloneNode.js @@ -0,0 +1,23 @@ +/** + * Get the new clone with canvas content cloned for a given node. + * + * @param {Node} node The node to clone + * @param {Boolean} deep equal to the deep parameter of Node.cloneNode(), default false + * @return {Node} new clone with canvas content cloned + */ +export default function cloneNode(node, deep = false) { + const newClone = node.cloneNode(deep); + let canvases; + let newCanvases; + if (node.tagName === 'CANVAS') { + canvases = [node]; + newCanvases = [newClone]; + } else { + canvases = node.querySelectorAll('canvas'); + newCanvases = newClone.querySelectorAll('canvas'); + } + newCanvases.forEach((newCanvas, i) => { + newCanvas.getContext('2d').drawImage(canvases[i], 0, 0); + }); + return newClone; +} diff --git a/src/shared/utils/cloneNode/index.js b/src/shared/utils/cloneNode/index.js new file mode 100644 index 00000000..b5bdd5cb --- /dev/null +++ b/src/shared/utils/cloneNode/index.js @@ -0,0 +1,3 @@ +import cloneNode from './cloneNode'; + +export default cloneNode; diff --git a/src/shared/utils/cloneNode/tests/cloneNode.test.js b/src/shared/utils/cloneNode/tests/cloneNode.test.js new file mode 100644 index 00000000..1a7ad3e2 --- /dev/null +++ b/src/shared/utils/cloneNode/tests/cloneNode.test.js @@ -0,0 +1,65 @@ +import {createSandbox} from 'helper'; +import cloneNode from '../cloneNode'; + +const sampleMarkup = ` +
+
First
+ +
Second
+ +
+`; + +describe('utils', () => { + let sandbox; + let canvasContainer; + let firstCanvas; + let secondCanvas; + let firstDiv; + let secondDiv; + + beforeEach(() => { + sandbox = createSandbox(sampleMarkup); + canvasContainer = sandbox.querySelector('.CanvasContainer'); + firstCanvas = canvasContainer.querySelector('.FirstCanvas'); + secondCanvas = canvasContainer.querySelector('.SecondCanvas'); + firstDiv = canvasContainer.querySelector('.FirstDiv'); + secondDiv = canvasContainer.querySelector('.SecondDiv'); + + const firstCtx = firstCanvas.getContext('2d'); + const secondCtx = secondCanvas.getContext('2d'); + + firstCtx.fillRect(0, 0, 10, 10); + secondCtx.fillRect(10, 10, 20, 20); + }); + + afterEach(() => { + sandbox.parentNode.removeChild(sandbox); + }); + + it('clone canvas content when cloned node has canvas elements', () => { + const newClone = cloneNode(canvasContainer, true); + const newFirstCanvas = newClone.querySelector('.FirstCanvas'); + const newSecondCanvas = newClone.querySelector('.SecondCanvas'); + expect(newFirstCanvas.toDataURL()).toEqual(firstCanvas.toDataURL()); + expect(newSecondCanvas.toDataURL()).toEqual(secondCanvas.toDataURL()); + }); + + it('clone nodes when cloned node has canvas elements', () => { + const newClone = cloneNode(canvasContainer, true); + const newFirstDiv = newClone.querySelector('.FirstDiv'); + const newSecondDiv = newClone.querySelector('.SecondDiv'); + expect(newFirstDiv.innerHTML).toEqual(firstDiv.innerHTML); + expect(newSecondDiv.innerHTML).toEqual(secondDiv.innerHTML); + }); + + it('clone canvas content when cloned node is canvas', () => { + const newCanvas = cloneNode(firstCanvas, true); + expect(newCanvas.toDataURL()).toEqual(firstCanvas.toDataURL()); + }); + + it('not clone child nodes when deep is false', () => { + const newClone = cloneNode(canvasContainer); + expect(newClone.childNodes).toHaveLength(0); + }); +}); diff --git a/src/shared/utils/index.js b/src/shared/utils/index.js index b3564560..ce2f0e01 100644 --- a/src/shared/utils/index.js +++ b/src/shared/utils/index.js @@ -1,3 +1,4 @@ export {default as closest} from './closest'; export {default as requestNextAnimationFrame} from './requestNextAnimationFrame'; export {default as distance} from './distance'; +export {default as cloneNode} from './cloneNode';