import { DOCUMENT } from '@angular/common'; import { Inject, Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class ClipboardService { private tempTextArea: HTMLTextAreaElement | undefined; private window: any; constructor(@Inject(DOCUMENT) public document: any) { this.window = window; } public get isSupported(): boolean { return ( !!this.document.queryCommandSupported && !!this.document.queryCommandSupported('copy') && !!this.window ); } public isTargetValid( element: HTMLInputElement | HTMLTextAreaElement ): boolean { if ( element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement ) { if (element.hasAttribute('disabled')) { throw new Error( 'Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute' ); } return true; } throw new Error('Target should be input or textarea'); } /** * copyFromInputElement */ public copyFromInputElement( targetElm: HTMLInputElement | HTMLTextAreaElement ): boolean { try { this.selectTarget(targetElm); const re = this.copyText(); this.clearSelection(targetElm, this.window); return re && this.isCopySuccessInIE11(); } catch (error) { return false; } } // this is for IE11 return true even if copy fail isCopySuccessInIE11() { // tslint:disable-next-line: no-string-literal const clipboardData = this.window['clipboardData']; if (clipboardData && clipboardData.getData) { if (!clipboardData.getData('Text')) { return false; } } return true; } /** * Creates a fake textarea element, sets its value from `text` property, * and makes a selection on it. */ public copyFromContent( content: string, container: HTMLElement = this.document.body ) { // check if the temp textarea still belongs to the current container. // In case we have multiple places using ngx-clipboard, one is in a modal using container but the other one is not. if (this.tempTextArea && !container.contains(this.tempTextArea)) { this.destroy(this.tempTextArea.parentElement); } if (!this.tempTextArea) { this.tempTextArea = this.createTempTextArea(this.document, this.window); try { container.appendChild(this.tempTextArea); } catch (error) { throw new Error('Container should be a Dom element'); } } this.tempTextArea.value = content; const toReturn = this.copyFromInputElement(this.tempTextArea); this.destroy(this.tempTextArea.parentElement); return toReturn; } // remove temporary textarea if any public destroy(container: HTMLElement = this.document.body) { if (this.tempTextArea) { container.removeChild(this.tempTextArea); // removeChild doesn't remove the reference from memory this.tempTextArea = undefined; } } // select the target html input element private selectTarget( inputElement: HTMLInputElement | HTMLTextAreaElement ): number | undefined { inputElement.select(); inputElement.setSelectionRange(0, inputElement.value.length); return inputElement.value.length; } private copyText(): boolean { return this.document.execCommand('copy'); } // Moves focus away from `target` and back to the trigger, removes current selection. private clearSelection( inputElement: HTMLInputElement | HTMLTextAreaElement, window: Window ) { // tslint:disable-next-line:no-unused-expression inputElement && inputElement.focus(); window.getSelection().removeAllRanges(); } // create a fake textarea for copy command private createTempTextArea( doc: Document, window: Window ): HTMLTextAreaElement { const isRTL = doc.documentElement.getAttribute('dir') === 'rtl'; let ta: HTMLTextAreaElement; ta = doc.createElement('textarea'); // Prevent zooming on iOS ta.style.fontSize = '12pt'; // Reset box model ta.style.border = '0'; ta.style.padding = '0'; ta.style.margin = '0'; // Move element out of screen horizontally ta.style.position = 'absolute'; ta.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically const yPosition = window.pageYOffset || doc.documentElement.scrollTop; ta.style.top = yPosition + 'px'; ta.setAttribute('readonly', ''); return ta; } }