1 | import select from 'select';
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | class ClipboardAction {
|
8 | |
9 |
|
10 |
|
11 | constructor(options) {
|
12 | this.resolveOptions(options);
|
13 | this.initSelection();
|
14 | }
|
15 |
|
16 | |
17 |
|
18 |
|
19 |
|
20 | resolveOptions(options = {}) {
|
21 | this.action = options.action;
|
22 | this.container = options.container;
|
23 | this.emitter = options.emitter;
|
24 | this.target = options.target;
|
25 | this.text = options.text;
|
26 | this.trigger = options.trigger;
|
27 |
|
28 | this.selectedText = '';
|
29 | }
|
30 |
|
31 | |
32 |
|
33 |
|
34 |
|
35 | initSelection() {
|
36 | if (this.text) {
|
37 | this.selectFake();
|
38 | } else if (this.target) {
|
39 | this.selectTarget();
|
40 | }
|
41 | }
|
42 |
|
43 | |
44 |
|
45 |
|
46 | createFakeElement() {
|
47 | const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
|
48 |
|
49 | this.fakeElem = document.createElement('textarea');
|
50 |
|
51 | this.fakeElem.style.fontSize = '12pt';
|
52 |
|
53 | this.fakeElem.style.border = '0';
|
54 | this.fakeElem.style.padding = '0';
|
55 | this.fakeElem.style.margin = '0';
|
56 |
|
57 | this.fakeElem.style.position = 'absolute';
|
58 | this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px';
|
59 |
|
60 | let yPosition = window.pageYOffset || document.documentElement.scrollTop;
|
61 | this.fakeElem.style.top = `${yPosition}px`;
|
62 |
|
63 | this.fakeElem.setAttribute('readonly', '');
|
64 | this.fakeElem.value = this.text;
|
65 |
|
66 | return this.fakeElem;
|
67 | }
|
68 |
|
69 | |
70 |
|
71 |
|
72 |
|
73 | selectFake() {
|
74 | const fakeElem = this.createFakeElement();
|
75 |
|
76 | this.fakeHandlerCallback = () => this.removeFake();
|
77 |
|
78 | this.fakeHandler =
|
79 | this.container.addEventListener('click', this.fakeHandlerCallback) ||
|
80 | true;
|
81 |
|
82 | this.container.appendChild(fakeElem);
|
83 |
|
84 | this.selectedText = select(fakeElem);
|
85 |
|
86 | this.copyText();
|
87 |
|
88 | this.removeFake();
|
89 | }
|
90 |
|
91 | |
92 |
|
93 |
|
94 |
|
95 | removeFake() {
|
96 | if (this.fakeHandler) {
|
97 | this.container.removeEventListener('click', this.fakeHandlerCallback);
|
98 | this.fakeHandler = null;
|
99 | this.fakeHandlerCallback = null;
|
100 | }
|
101 |
|
102 | if (this.fakeElem) {
|
103 | this.container.removeChild(this.fakeElem);
|
104 | this.fakeElem = null;
|
105 | }
|
106 | }
|
107 |
|
108 | |
109 |
|
110 |
|
111 | selectTarget() {
|
112 | this.selectedText = select(this.target);
|
113 | this.copyText();
|
114 | }
|
115 |
|
116 | |
117 |
|
118 |
|
119 | copyText() {
|
120 | let succeeded;
|
121 |
|
122 | try {
|
123 | succeeded = document.execCommand(this.action);
|
124 | } catch (err) {
|
125 | succeeded = false;
|
126 | }
|
127 |
|
128 | this.handleResult(succeeded);
|
129 | }
|
130 |
|
131 | |
132 |
|
133 |
|
134 |
|
135 | handleResult(succeeded) {
|
136 | this.emitter.emit(succeeded ? 'success' : 'error', {
|
137 | action: this.action,
|
138 | text: this.selectedText,
|
139 | trigger: this.trigger,
|
140 | clearSelection: this.clearSelection.bind(this),
|
141 | });
|
142 | }
|
143 |
|
144 | |
145 |
|
146 |
|
147 | clearSelection() {
|
148 | if (this.trigger) {
|
149 | this.trigger.focus();
|
150 | }
|
151 | document.activeElement.blur();
|
152 | window.getSelection().removeAllRanges();
|
153 | }
|
154 |
|
155 | |
156 |
|
157 |
|
158 |
|
159 | set action(action = 'copy') {
|
160 | this._action = action;
|
161 |
|
162 | if (this._action !== 'copy' && this._action !== 'cut') {
|
163 | throw new Error('Invalid "action" value, use either "copy" or "cut"');
|
164 | }
|
165 | }
|
166 |
|
167 | |
168 |
|
169 |
|
170 |
|
171 | get action() {
|
172 | return this._action;
|
173 | }
|
174 |
|
175 | |
176 |
|
177 |
|
178 |
|
179 |
|
180 | set target(target) {
|
181 | if (target !== undefined) {
|
182 | if (target && typeof target === 'object' && target.nodeType === 1) {
|
183 | if (this.action === 'copy' && target.hasAttribute('disabled')) {
|
184 | throw new Error(
|
185 | 'Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'
|
186 | );
|
187 | }
|
188 |
|
189 | if (
|
190 | this.action === 'cut' &&
|
191 | (target.hasAttribute('readonly') || target.hasAttribute('disabled'))
|
192 | ) {
|
193 | throw new Error(
|
194 | 'Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes'
|
195 | );
|
196 | }
|
197 |
|
198 | this._target = target;
|
199 | } else {
|
200 | throw new Error('Invalid "target" value, use a valid Element');
|
201 | }
|
202 | }
|
203 | }
|
204 |
|
205 | |
206 |
|
207 |
|
208 |
|
209 | get target() {
|
210 | return this._target;
|
211 | }
|
212 |
|
213 | |
214 |
|
215 |
|
216 | destroy() {
|
217 | this.removeFake();
|
218 | }
|
219 | }
|
220 |
|
221 | export default ClipboardAction;
|