diff --git a/demo/demo.component.ts b/demo/demo.component.ts
index 8a8eb3a..ce2ddec 100644
--- a/demo/demo.component.ts
+++ b/demo/demo.component.ts
@@ -1,5 +1,5 @@
/* tslint:disable:max-inline-declarations */
-import { Component } from '@angular/core';
+import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
import { ResizeEvent } from '../src';
@Component({
@@ -21,6 +21,11 @@ import { ResizeEvent } from '../src';
box-sizing: border-box; // required for the enableGhostResize option to work
}
+ canvas {
+ width: 150px;
+ height: 100px;
+ }
+
.resize-handle-top,
.resize-handle-bottom {
position: absolute;
@@ -66,6 +71,8 @@ import { ResizeEvent } from '../src';
[resizeSnapGrid]="{ left: 50, right: 50 }"
(resizeEnd)="onResizeEnd($event)"
>
+
HTML text example
+
`
})
-export class DemoComponent {
+export class DemoComponent implements AfterViewInit {
+ @ViewChild('canvas')
+ public canvas: ElementRef
;
+
public style: object = {};
validate(event: ResizeEvent): boolean {
@@ -115,4 +125,19 @@ export class DemoComponent {
height: `${event.rectangle.height}px`
};
}
+
+ drawCanvas(): void {
+ const ctx = this.canvas.nativeElement.getContext('2d');
+ if (ctx) {
+ ctx.font = '28px serif';
+ ctx.fillText('Canvas text example', 50, 50);
+ ctx.strokeStyle = 'green';
+ ctx.lineWidth = 5;
+ ctx.strokeRect(30, 10, 260, 60);
+ }
+ }
+
+ ngAfterViewInit(): void {
+ this.drawCanvas();
+ }
}
diff --git a/src/clone-node.ts b/src/clone-node.ts
new file mode 100644
index 0000000..156c75d
--- /dev/null
+++ b/src/clone-node.ts
@@ -0,0 +1,82 @@
+/** Creates a deep clone of an element. */
+export function deepCloneNode(node: HTMLElement): HTMLElement {
+ const clone = node.cloneNode(true) as HTMLElement;
+ const descendantsWithId = clone.querySelectorAll('[id]');
+ const nodeName = node.nodeName.toLowerCase();
+
+ // Remove the `id` to avoid having multiple elements with the same id on the page.
+ clone.removeAttribute('id');
+
+ descendantsWithId.forEach(descendant => {
+ descendant.removeAttribute('id');
+ });
+
+ if (nodeName === 'canvas') {
+ transferCanvasData(node as HTMLCanvasElement, clone as HTMLCanvasElement);
+ } else if (
+ nodeName === 'input' ||
+ nodeName === 'select' ||
+ nodeName === 'textarea'
+ ) {
+ transferInputData(node as HTMLInputElement, clone as HTMLInputElement);
+ }
+
+ transferData('canvas', node, clone, transferCanvasData);
+ transferData('input, textarea, select', node, clone, transferInputData);
+ return clone;
+}
+
+/** Matches elements between an element and its clone and allows for their data to be cloned. */
+function transferData(
+ selector: string,
+ node: HTMLElement,
+ clone: HTMLElement,
+ callback: (source: T, clone: T) => void
+) {
+ const descendantElements = node.querySelectorAll(selector);
+
+ if (descendantElements.length) {
+ const cloneElements = clone.querySelectorAll(selector);
+
+ for (let i = 0; i < descendantElements.length; i++) {
+ callback(descendantElements[i], cloneElements[i]);
+ }
+ }
+}
+
+// Counter for unique cloned radio button names.
+let cloneUniqueId = 0;
+
+/** Transfers the data of one input element to another. */
+function transferInputData(
+ source: Element & { value: string },
+ clone: Element & { value: string; name: string; type: string }
+) {
+ // Browsers throw an error when assigning the value of a file input programmatically.
+ if (clone.type !== 'file') {
+ clone.value = source.value;
+ }
+
+ // Radio button `name` attributes must be unique for radio button groups
+ // otherwise original radio buttons can lose their checked state
+ // once the clone is inserted in the DOM.
+ if (clone.type === 'radio' && clone.name) {
+ clone.name = `mat-clone-${clone.name}-${cloneUniqueId++}`;
+ }
+}
+
+/** Transfers the data of one canvas element to another. */
+function transferCanvasData(
+ source: HTMLCanvasElement,
+ clone: HTMLCanvasElement
+) {
+ const context = clone.getContext('2d');
+
+ if (context) {
+ // In some cases `drawImage` can throw (e.g. if the canvas size is 0x0).
+ // We can't do much about it so just ignore the error.
+ try {
+ context.drawImage(source, 0, 0);
+ } catch {}
+ }
+}
diff --git a/src/resizable.directive.ts b/src/resizable.directive.ts
index d62cc71..b91d635 100644
--- a/src/resizable.directive.ts
+++ b/src/resizable.directive.ts
@@ -32,6 +32,7 @@ import { Edges } from './interfaces/edges.interface';
import { BoundingRectangle } from './interfaces/bounding-rectangle.interface';
import { ResizeEvent } from './interfaces/resize-event.interface';
import { IS_TOUCH_DEVICE } from './is-touch-device';
+import { deepCloneNode } from './clone-node';
interface PointerEventCoordinate {
clientX: number;
@@ -723,7 +724,7 @@ export class ResizableDirective implements OnInit, OnChanges, OnDestroy {
this.renderer.setStyle(document.body, 'cursor', cursor);
this.setElementClass(this.elm, RESIZE_ACTIVE_CLASS, true);
if (this.enableGhostResize) {
- currentResize.clonedNode = this.elm.nativeElement.cloneNode(true);
+ currentResize.clonedNode = deepCloneNode(this.elm.nativeElement);
this.elm.nativeElement.parentElement.appendChild(
currentResize.clonedNode
);
diff --git a/test/resizable.spec.ts b/test/resizable.spec.ts
index e8826f4..2956b71 100644
--- a/test/resizable.spec.ts
+++ b/test/resizable.spec.ts
@@ -1,9 +1,11 @@
/* tslint:disable:max-inline-declarations enforce-component-selector */
-import { Component, ElementRef, ViewChild } from '@angular/core';
-import { ResizableDirective } from '../src/resizable.directive';
+import { Component, DebugElement, ElementRef, ViewChild } from '@angular/core';
+import {
+ MOUSE_MOVE_THROTTLE_MS,
+ ResizableDirective
+} from '../src/resizable.directive';
import { Edges } from '../src/interfaces/edges.interface';
-import { ResizeEvent, ResizableModule, ResizeHandleDirective } from '../src';
-import { MOUSE_MOVE_THROTTLE_MS } from '../src/resizable.directive';
+import { ResizableModule, ResizeEvent } from '../src';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { expect } from 'chai';
import * as sinon from 'sinon';
@@ -1630,4 +1632,34 @@ describe('resizable directive', () => {
}
});
});
+
+ it('should add canvas data to the ghost element', () => {
+ const template: string = `
+
+
+
+ `;
+ const fixture: ComponentFixture = createComponent(template);
+ const div = fixture.debugElement.childNodes[0] as DebugElement;
+ const canvas = div.nativeElement.children[0];
+ const ctx = canvas.getContext('2d');
+ ctx.fillText('Canvas text example', 0, 0);
+ const canvasData = canvas.toDataURL();
+
+ const elm: any = fixture.componentInstance.resizable.elm.nativeElement;
+ triggerDomEvent('mousedown', elm, {
+ clientX: 100,
+ clientY: 200
+ });
+ const clonedDiv = elm.nextSibling as HTMLElement;
+ const clonedCanvas = clonedDiv.children[0] as HTMLCanvasElement;
+ const actualCanvasData = clonedCanvas.toDataURL();
+ expect(actualCanvasData).to.equal(canvasData);
+ });
});