UNPKG

10.8 kBJavaScriptView Raw
1import { DOCUMENT, CommonModule } from '@angular/common';
2import { ɵɵdefineInjectable, ɵɵinject, NgZone, Injectable, Inject, Optional, EventEmitter, Directive, ElementRef, Renderer2, Input, Output, ViewContainerRef, TemplateRef, NgModule } from '@angular/core';
3import { WINDOW } from 'ngx-window-token';
4import { Subject } from 'rxjs';
5
6/**
7 * The following code is heavily copied from https://github.com/zenorocha/clipboard.js
8 */
9class ClipboardService {
10 constructor(ngZone, document, window) {
11 this.ngZone = ngZone;
12 this.document = document;
13 this.window = window;
14 this.copySubject = new Subject();
15 this.copyResponse$ = this.copySubject.asObservable();
16 this.config = {};
17 }
18 configure(config) {
19 this.config = config;
20 }
21 copy(content) {
22 if (!this.isSupported || !content) {
23 return this.pushCopyResponse({ isSuccess: false, content });
24 }
25 const copyResult = this.copyFromContent(content);
26 if (copyResult) {
27 return this.pushCopyResponse({ content, isSuccess: copyResult });
28 }
29 return this.pushCopyResponse({ isSuccess: false, content });
30 }
31 get isSupported() {
32 return !!this.document.queryCommandSupported && !!this.document.queryCommandSupported('copy') && !!this.window;
33 }
34 isTargetValid(element) {
35 if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
36 if (element.hasAttribute('disabled')) {
37 throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
38 }
39 return true;
40 }
41 throw new Error('Target should be input or textarea');
42 }
43 /**
44 * Attempts to copy from an input `targetElm`
45 */
46 copyFromInputElement(targetElm, isFocus = true) {
47 try {
48 this.selectTarget(targetElm);
49 const re = this.copyText();
50 this.clearSelection(isFocus ? targetElm : undefined, this.window);
51 return re && this.isCopySuccessInIE11();
52 }
53 catch (error) {
54 return false;
55 }
56 }
57 /**
58 * This is a hack for IE11 to return `true` even if copy fails.
59 */
60 isCopySuccessInIE11() {
61 const clipboardData = this.window['clipboardData'];
62 if (clipboardData && clipboardData.getData) {
63 if (!clipboardData.getData('Text')) {
64 return false;
65 }
66 }
67 return true;
68 }
69 /**
70 * Creates a fake textarea element, sets its value from `text` property,
71 * and makes a selection on it.
72 */
73 copyFromContent(content, container = this.document.body) {
74 // check if the temp textarea still belongs to the current container.
75 // In case we have multiple places using ngx-clipboard, one is in a modal using container but the other one is not.
76 if (this.tempTextArea && !container.contains(this.tempTextArea)) {
77 this.destroy(this.tempTextArea.parentElement || undefined);
78 }
79 if (!this.tempTextArea) {
80 this.tempTextArea = this.createTempTextArea(this.document, this.window);
81 try {
82 container.appendChild(this.tempTextArea);
83 }
84 catch (error) {
85 throw new Error('Container should be a Dom element');
86 }
87 }
88 this.tempTextArea.value = content;
89 const toReturn = this.copyFromInputElement(this.tempTextArea, false);
90 if (this.config.cleanUpAfterCopy) {
91 this.destroy(this.tempTextArea.parentElement || undefined);
92 }
93 return toReturn;
94 }
95 /**
96 * Remove temporary textarea if any exists.
97 */
98 destroy(container = this.document.body) {
99 if (this.tempTextArea) {
100 container.removeChild(this.tempTextArea);
101 // removeChild doesn't remove the reference from memory
102 this.tempTextArea = undefined;
103 }
104 }
105 /**
106 * Select the target html input element.
107 */
108 selectTarget(inputElement) {
109 inputElement.select();
110 inputElement.setSelectionRange(0, inputElement.value.length);
111 return inputElement.value.length;
112 }
113 copyText() {
114 return this.document.execCommand('copy');
115 }
116 /**
117 * Moves focus away from `target` and back to the trigger, removes current selection.
118 */
119 clearSelection(inputElement, window) {
120 var _a;
121 inputElement && inputElement.focus();
122 (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
123 }
124 /**
125 * Creates a fake textarea for copy command.
126 */
127 createTempTextArea(doc, window) {
128 const isRTL = doc.documentElement.getAttribute('dir') === 'rtl';
129 let ta;
130 ta = doc.createElement('textarea');
131 // Prevent zooming on iOS
132 ta.style.fontSize = '12pt';
133 // Reset box model
134 ta.style.border = '0';
135 ta.style.padding = '0';
136 ta.style.margin = '0';
137 // Move element out of screen horizontally
138 ta.style.position = 'absolute';
139 ta.style[isRTL ? 'right' : 'left'] = '-9999px';
140 // Move element to the same position vertically
141 const yPosition = window.pageYOffset || doc.documentElement.scrollTop;
142 ta.style.top = yPosition + 'px';
143 ta.setAttribute('readonly', '');
144 return ta;
145 }
146 /**
147 * Pushes copy operation response to copySubject, to provide global access
148 * to the response.
149 */
150 pushCopyResponse(response) {
151 if (this.copySubject.observers.length > 0) {
152 this.ngZone.run(() => {
153 this.copySubject.next(response);
154 });
155 }
156 }
157 /**
158 * @deprecated use pushCopyResponse instead.
159 */
160 pushCopyReponse(response) {
161 this.pushCopyResponse(response);
162 }
163}
164ClipboardService.ɵprov = ɵɵdefineInjectable({ factory: function ClipboardService_Factory() { return new ClipboardService(ɵɵinject(NgZone), ɵɵinject(DOCUMENT), ɵɵinject(WINDOW, 8)); }, token: ClipboardService, providedIn: "root" });
165ClipboardService.decorators = [
166 { type: Injectable, args: [{ providedIn: 'root' },] }
167];
168ClipboardService.ctorParameters = () => [
169 { type: NgZone },
170 { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] },
171 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [WINDOW,] }] }
172];
173
174class ClipboardDirective {
175 constructor(ngZone, host, renderer, clipboardSrv) {
176 this.ngZone = ngZone;
177 this.host = host;
178 this.renderer = renderer;
179 this.clipboardSrv = clipboardSrv;
180 this.cbOnSuccess = new EventEmitter();
181 this.cbOnError = new EventEmitter();
182 this.onClick = (event) => {
183 if (!this.clipboardSrv.isSupported) {
184 this.handleResult(false, undefined, event);
185 }
186 else if (this.targetElm && this.clipboardSrv.isTargetValid(this.targetElm)) {
187 this.handleResult(this.clipboardSrv.copyFromInputElement(this.targetElm), this.targetElm.value, event);
188 }
189 else if (this.cbContent) {
190 this.handleResult(this.clipboardSrv.copyFromContent(this.cbContent, this.container), this.cbContent, event);
191 }
192 };
193 }
194 // tslint:disable-next-line:no-empty
195 ngOnInit() {
196 this.ngZone.runOutsideAngular(() => {
197 // By default each host listener schedules change detection and also wrapped
198 // into additional function that calls `markForCheck()`. We're listening the `click`
199 // event in the context of the root zone to avoid running unnecessary change detections,
200 // since this directive doesn't do anything template-related (e.g. updates template variables).
201 this.clickListener = this.renderer.listen(this.host.nativeElement, 'click', this.onClick);
202 });
203 }
204 ngOnDestroy() {
205 this.clickListener();
206 this.clipboardSrv.destroy(this.container);
207 }
208 /**
209 * Fires an event based on the copy operation result.
210 * @param succeeded
211 */
212 handleResult(succeeded, copiedContent, event) {
213 let response = {
214 isSuccess: succeeded,
215 event
216 };
217 if (succeeded) {
218 if (this.cbOnSuccess.observers.length > 0) {
219 response = Object.assign(response, {
220 content: copiedContent,
221 successMessage: this.cbSuccessMsg
222 });
223 this.ngZone.run(() => {
224 this.cbOnSuccess.emit(response);
225 });
226 }
227 }
228 else {
229 if (this.cbOnError.observers.length > 0) {
230 this.ngZone.run(() => {
231 this.cbOnError.emit(response);
232 });
233 }
234 }
235 this.clipboardSrv.pushCopyResponse(response);
236 }
237}
238ClipboardDirective.decorators = [
239 { type: Directive, args: [{ selector: '[ngxClipboard]' },] }
240];
241ClipboardDirective.ctorParameters = () => [
242 { type: NgZone },
243 { type: ElementRef },
244 { type: Renderer2 },
245 { type: ClipboardService }
246];
247ClipboardDirective.propDecorators = {
248 targetElm: [{ type: Input, args: ['ngxClipboard',] }],
249 container: [{ type: Input }],
250 cbContent: [{ type: Input }],
251 cbSuccessMsg: [{ type: Input }],
252 cbOnSuccess: [{ type: Output }],
253 cbOnError: [{ type: Output }]
254};
255
256class ClipboardIfSupportedDirective {
257 constructor(_clipboardService, _viewContainerRef, _templateRef) {
258 this._clipboardService = _clipboardService;
259 this._viewContainerRef = _viewContainerRef;
260 this._templateRef = _templateRef;
261 }
262 ngOnInit() {
263 if (this._clipboardService.isSupported) {
264 this._viewContainerRef.createEmbeddedView(this._templateRef);
265 }
266 }
267}
268ClipboardIfSupportedDirective.decorators = [
269 { type: Directive, args: [{
270 selector: '[ngxClipboardIfSupported]'
271 },] }
272];
273ClipboardIfSupportedDirective.ctorParameters = () => [
274 { type: ClipboardService },
275 { type: ViewContainerRef },
276 { type: TemplateRef }
277];
278
279class ClipboardModule {
280}
281ClipboardModule.decorators = [
282 { type: NgModule, args: [{
283 imports: [CommonModule],
284 declarations: [ClipboardDirective, ClipboardIfSupportedDirective],
285 exports: [ClipboardDirective, ClipboardIfSupportedDirective]
286 },] }
287];
288
289/*
290 * Public API Surface of ngx-clipboard
291 */
292
293/**
294 * Generated bundle index. Do not edit.
295 */
296
297export { ClipboardDirective, ClipboardIfSupportedDirective, ClipboardModule, ClipboardService };
298//# sourceMappingURL=ngx-clipboard.js.map