easy-richtextarea 4.0.3 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +0 -8
  2. package/example.js +1399 -982
  3. package/lib/constants.js +13 -4
  4. package/lib/contentCompare.js +34 -0
  5. package/lib/example/richTextarea.js +2 -2
  6. package/lib/example/view.js +18 -9
  7. package/lib/keyCodes.js +13 -0
  8. package/lib/operation/delete.js +101 -52
  9. package/lib/operation/empty.js +21 -6
  10. package/lib/operation/insert.js +78 -31
  11. package/lib/operations/fromJSON.js +4 -4
  12. package/lib/operations/generate.js +3 -3
  13. package/lib/operations/transform.js +3 -7
  14. package/lib/richTextarea.js +244 -156
  15. package/lib/selection.js +55 -13
  16. package/lib/types.js +13 -13
  17. package/lib/undoBuffer.js +105 -0
  18. package/package.json +3 -2
  19. package/src/constants.js +1 -0
  20. package/src/contentCompare.js +34 -0
  21. package/src/example/richTextarea.js +11 -9
  22. package/src/example/view.js +28 -10
  23. package/src/keyCodes.js +3 -0
  24. package/src/operation/delete.js +135 -58
  25. package/src/operation/empty.js +19 -14
  26. package/src/operation/insert.js +97 -39
  27. package/src/operations/fromJSON.js +4 -4
  28. package/src/operations/generate.js +8 -4
  29. package/src/operations/transform.js +3 -3
  30. package/src/richTextarea.js +316 -185
  31. package/src/selection.js +53 -9
  32. package/src/types.js +6 -6
  33. package/src/undoBuffer.js +73 -0
  34. package/test/content/transform.js +9 -11
  35. package/test/helpers.js +27 -21
  36. package/test/operation/delete.js +187 -147
  37. package/test/operation/empty.js +3 -5
  38. package/test/operation/insert.js +134 -118
  39. package/test/operations/generate.js +19 -22
  40. package/test/operations/transform.js +37 -98
  41. package/lib/stringCompare.js +0 -33
  42. package/src/stringCompare.js +0 -33
@@ -23,17 +23,21 @@ export default function generateOperations(contentA, contentB) {
23
23
  }
24
24
 
25
25
  if (left + right !== contentALength) {
26
- const length = contentALength - left - right, ///
26
+ const start = left, ///
27
+ end = contentALength - right,
28
+ content = contentA.substring(start, end),
27
29
  position = left, ///
28
- deleteOperation = DeleteOperation.fromLengthAndPosition(length, position);
30
+ deleteOperation = DeleteOperation.fromContentAndPosition(content, position);
29
31
 
30
32
  operations.push(deleteOperation);
31
33
  }
32
34
 
33
35
  if (left + right !== contentBLength) {
34
- const string = contentB.substring(left, contentBLength - right), ///
36
+ const start = left, ///
37
+ end = contentBLength - right,
38
+ content = contentB.substring(start, end),
35
39
  position = left, ///
36
- insertOperation = InsertOperation.fromStringAndPosition(string, position);
40
+ insertOperation = InsertOperation.fromContentAndPosition(content, position);
37
41
 
38
42
  operations.push(insertOperation);
39
43
  }
@@ -2,16 +2,16 @@
2
2
 
3
3
  import { arrayUtilities } from "necessary";
4
4
 
5
- const { first, tail } = arrayUtilities;
5
+ const { first, head, tail } = arrayUtilities;
6
6
 
7
7
  export default function transformOperations(tau, rho) {
8
8
  if ((tau.length === 0) || (rho.length === 0)) {
9
9
  return tau;
10
10
  }
11
11
 
12
- const tau1 = [first(tau)],
12
+ const tau1 = head(tau),
13
13
  tau2 = tail(tau),
14
- rho1 = [first(rho)],
14
+ rho1 = head(rho),
15
15
  rho2 = tail(rho);
16
16
 
17
17
  if (tau.length > 1 && rho.length > 1) {
@@ -1,86 +1,136 @@
1
1
  "use strict";
2
2
 
3
- import { window, Element, eventTypes } from "easy";
3
+ import { Element, document, eventTypes } from "easy";
4
4
 
5
5
  import Selection from "./selection"
6
+ import UndoBuffer from "./undoBuffer";
7
+ import InsertOperation from "./operation/insert";
8
+ import DeleteOperation from "./operation/delete";
9
+ import generateOperations from "./operations/generate";
10
+
11
+ import { Z_KEY_CODE } from "./keyCodes";
12
+ import { TEXT, EMPTY_STRING } from "./constants";
13
+
14
+ const { CUT_EVENT_TYPE,
15
+ COPY_EVENT_TYPE,
16
+ BLUR_EVENT_TYPE,
17
+ PASTE_EVENT_TYPE,
18
+ INPUT_EVENT_TYPE,
19
+ FOCUS_EVENT_TYPE,
20
+ CHANGE_EVENT_TYPE,
21
+ SCROLL_EVENT_TYPE,
22
+ KEYDOWN_EVENT_TYPE,
23
+ CONTEXTMENU_EVENT_TYPE,
24
+ SELECTIONCHANGE_EVENT_TYPE } = eventTypes;
6
25
 
7
- const { BLUR_EVENT_TYPE,
8
- INPUT_EVENT_TYPE,
9
- FOCUS_EVENT_TYPE,
10
- CHANGE_EVENT_TYPE,
11
- SCROLL_EVENT_TYPE,
12
- KEYDOWN_EVENT_TYPE,
13
- MOUSEUP_EVENT_TYPE,
14
- MOUSEDOWN_EVENT_TYPE,
15
- MOUSEMOVE_EVENT_TYPE,
16
- CONTEXTMENU_EVENT_TYPE } = eventTypes;
26
+ export default class RichTextarea extends Element {
27
+ constructor(selector, focused, readOnly, undoBuffer, previousContent, previousSelection) {
28
+ super(selector);
17
29
 
18
- const defer = (func) => setTimeout(func, 0);
30
+ this.focused = focused;
31
+ this.readOnly = readOnly;
32
+ this.undoBuffer = undoBuffer;
33
+ this.previousContent = previousContent;
34
+ this.previousSelection = previousSelection;
35
+ }
19
36
 
20
- export default class RichTextarea extends Element {
21
- intermediateHandler = (event, element, eventType, forced) => {
22
- const readOnly = this.isReadOnly();
37
+ isFocused() {
38
+ return this.focused;
39
+ }
23
40
 
24
- if (readOnly) {
25
- const previousContent = this.getPreviousContent(),
26
- content = previousContent; ///
41
+ isReadOnly() {
42
+ return this.readOnly;
43
+ }
27
44
 
28
- this.setContent(content);
29
- }
45
+ getUndoBuffer() {
46
+ return this.undoBuffer;
47
+ }
30
48
 
31
- const changed = this.hasChanged();
49
+ getPreviousContent() {
50
+ return this.prevousContent;
51
+ }
32
52
 
33
- if (changed || forced) {
34
- this.callHandlers(eventType, event, element);
53
+ getPreviousSelection() {
54
+ return this.previousSelection;
55
+ }
56
+
57
+ setFocused(focused) {
58
+ this.focused = focused;
59
+ }
60
+
61
+ setReadOnly(readOnly) {
62
+ this.readOnly = readOnly;
63
+ }
64
+
65
+ setPreviousContent(previousContent) {
66
+ this.previousContent = previousContent;
67
+ }
68
+
69
+ setPreviousSelection(previousSelection) {
70
+ this.previousSelection = previousSelection;
71
+ }
72
+
73
+ intermediateHandler = (event, element, undoable = true, eventType = CHANGE_EVENT_TYPE, selectionChanged = this.hasSelectionChanged()) => {
74
+ if (this.readOnly) {
75
+ const content = this.previousContent; ///
76
+
77
+ this.setContent(content);
35
78
  }
36
79
 
37
80
  const content = this.getContent(),
38
81
  selection = this.getSelection(),
39
- previousContent = content, ///
40
- previousSelection = selection; ///
82
+ contentChanged = this.hasContentChanged(),
83
+ changed = (contentChanged || selectionChanged);
41
84
 
42
- this.setPreviousContent(previousContent);
43
- this.setPreviousSelection(previousSelection);
44
- }
85
+ if (changed) {
86
+ this.callHandlers(eventType, event, element);
87
+ }
45
88
 
46
- mouseDownHandler = (event, element) => {
47
- const forced = false,
48
- mouseDown = true,
49
- eventType = CHANGE_EVENT_TYPE;
89
+ if (undoable) {
90
+ if (contentChanged) {
91
+ const operations = generateOperations(this.previousContent, content);
50
92
 
51
- this.setMouseDown(mouseDown);
93
+ this.undoBuffer.addOperations(operations);
94
+ }
95
+ }
52
96
 
53
- defer(() => this.intermediateHandler(event, element, eventType, forced));
54
- }
97
+ this.previousContent = content; ///
55
98
 
56
- mouseMoveHandler = (event, element) => {
57
- const forced = false,
58
- mouseDown = this.isMouseDown(),
59
- eventTYpe = CHANGE_EVENT_TYPE;
99
+ this.previousSelection = selection; ///
100
+ }
60
101
 
61
- if (mouseDown) {
62
- this.intermediateHandler(event, element, eventTYpe, forced);
102
+ selectChangeHandler = (event, element) => {
103
+ if (this.focused) {
104
+ this.intermediateHandler(event, element);
63
105
  }
64
106
  }
65
107
 
66
- mouseUpHandler = (event, element) => {
67
- const mouseDown = false;
68
-
69
- this.setMouseDown(mouseDown);
70
- };
108
+ contextmenuHandler = (event, element) => {
109
+ event.preventDefault();
110
+ }
71
111
 
72
112
  keyDownHandler = (event, element) => {
73
- const forced = false,
74
- eventType = CHANGE_EVENT_TYPE;
113
+ const { keyCode } = event;
75
114
 
76
- defer(() => this.intermediateHandler(event, element, eventType, forced));
77
- }
115
+ if (keyCode === Z_KEY_CODE) {
116
+ const { ctrlKey, metaKey } = event;
78
117
 
79
- inputHandler = (event, element) => {
80
- const forced = false,
81
- eventType = CHANGE_EVENT_TYPE;
118
+ if (ctrlKey || metaKey) {
119
+ const { shiftKey } = event;
120
+
121
+ shiftKey ?
122
+ this.redo(event, element) :
123
+ this.undo(event, element);
124
+
125
+ event.preventDefault();
126
+
127
+ return;
128
+ }
129
+ }
82
130
 
83
- this.intermediateHandler(event, element, eventType, forced);
131
+ defer(() => {
132
+ this.intermediateHandler(event, element);
133
+ });
84
134
  }
85
135
 
86
136
  scrollHandler = (event, element) => {
@@ -90,17 +140,97 @@ export default class RichTextarea extends Element {
90
140
  }
91
141
 
92
142
  focusHandler = (event, element) => {
93
- const forced = true,
94
- eventType = FOCUS_EVENT_TYPE;
143
+ this.focused = true;
144
+
145
+ defer(() => {
146
+ const undoable = true,
147
+ eventType = FOCUS_EVENT_TYPE,
148
+ selectionChanged = true;
95
149
 
96
- defer(() => this.intermediateHandler(event, element, eventType, forced));
150
+ this.intermediateHandler(event, element, undoable, eventType, selectionChanged);
151
+ });
97
152
  }
98
153
 
99
154
  blurHandler = (event, element) => {
100
- const forced = true,
101
- eventType = BLUR_EVENT_TYPE;
155
+ this.focused = false;
156
+
157
+ const undoable = true,
158
+ eventType = BLUR_EVENT_TYPE,
159
+ selectionChanged = true;
160
+
161
+ this.intermediateHandler(event, element, undoable, eventType, selectionChanged);
162
+ }
163
+
164
+ inputHandler = (event, element) => {
165
+ this.intermediateHandler(event, element);
166
+ }
167
+
168
+ pasteHandler = (event, element) => {
169
+ const { clipboardData } = event,
170
+ textData = clipboardData.getData(TEXT);
171
+
172
+ if (textData === EMPTY_STRING) {
173
+ return;
174
+ }
175
+
176
+ const selection = this.getSelection(),
177
+ selectionEmpty = selection.isEmpty();
178
+
179
+ if (!selectionEmpty) {
180
+ const content = this.getContent(),
181
+ deleteOperation = DeleteOperation.fromContentAndSelection(content, selection),
182
+ operation = deleteOperation;
183
+
184
+ this.applyOperation(operation);
185
+ }
186
+
187
+ const content = textData, ///
188
+ insertOperation = InsertOperation.fromContentAndSelection(content, selection),
189
+ operation = insertOperation; ///
102
190
 
103
- this.intermediateHandler(event, element, eventType, forced);
191
+ this.applyOperation(operation);
192
+
193
+ this.intermediateHandler(event, element);
194
+
195
+ event.preventDefault();
196
+ }
197
+
198
+ copyHandler = (event, element) => {
199
+ const selection = this.getSelection(),
200
+ selectionEmpty = selection.isEmpty();
201
+
202
+ if (selectionEmpty) {
203
+ return;
204
+ }
205
+
206
+ const selectedContent = this.getSelectedContent();
207
+
208
+ navigator.clipboard.writeText(selectedContent);
209
+
210
+ event.preventDefault();
211
+ }
212
+
213
+ cutHandler = (event, element) => {
214
+ const selection = this.getSelection(),
215
+ selectionEmpty = selection.isEmpty();
216
+
217
+ if (selectionEmpty) {
218
+ return;
219
+ }
220
+
221
+ const selectedContent = this.getSelectedContent();
222
+
223
+ navigator.clipboard.writeText(selectedContent);
224
+
225
+ const content = this.getContent(),
226
+ deleteOperation = DeleteOperation.fromContentAndSelection(content, selection),
227
+ operation = deleteOperation; ///
228
+
229
+ this.applyOperation(operation);
230
+
231
+ this.intermediateHandler(event, element);
232
+
233
+ event.preventDefault();
104
234
  }
105
235
 
106
236
  isActive() {
@@ -109,6 +239,30 @@ export default class RichTextarea extends Element {
109
239
  return active;
110
240
  }
111
241
 
242
+ undo(event, element) {
243
+ const operation = this.undoBuffer.undo();
244
+
245
+ if (operation !== null) {
246
+ const undoable = false;
247
+
248
+ this.applyOperation(operation);
249
+
250
+ this.intermediateHandler(event, element, undoable);
251
+ }
252
+ }
253
+
254
+ redo(event, element) {
255
+ const operation = this.undoBuffer.redo();
256
+
257
+ if (operation !== null) {
258
+ const undoable = false;
259
+
260
+ this.applyOperation(operation);
261
+
262
+ this.intermediateHandler(event, element, undoable);
263
+ }
264
+ }
265
+
112
266
  getContent() {
113
267
  const domElement = this.getDOMElement(),
114
268
  { value } = domElement,
@@ -124,71 +278,81 @@ export default class RichTextarea extends Element {
124
278
  return selection;
125
279
  }
126
280
 
127
- setContent(content) {
281
+ getSelectedContent() {
282
+ const content = this.getContent(),
283
+ selection = this.getSelection(),
284
+ endPosition = selection.getEndPosition(),
285
+ startPosition = selection.getStartPosition(),
286
+ start = startPosition, ///
287
+ end = endPosition, ///
288
+ selectedContent = content.slice(start, end);
289
+
290
+ return selectedContent;
291
+ }
292
+
293
+ hasContentChanged() {
294
+ const content = this.getContent(),
295
+ contentPreviousContent = (content === this.previousContent),
296
+ contentChanged = !contentPreviousContent;
297
+
298
+ return contentChanged;
299
+ }
300
+
301
+ hasSelectionChanged() {
302
+ const selection = this.getSelection(),
303
+ selectionPreviousSelection = selection.isEqualTo(this.previousSelection),
304
+ selectionChanged = !selectionPreviousSelection; ///
305
+
306
+ return selectionChanged;
307
+ }
308
+
309
+ setContent(content, setPreviousContent = true) {
128
310
  const value = content, ///
129
- previousContent = content, ///
130
311
  domElement = this.getDOMElement();
131
312
 
132
313
  Object.assign(domElement, {
133
314
  value
134
315
  });
135
316
 
136
- this.setPreviousContent(previousContent);
317
+ if (setPreviousContent) {
318
+ this.previousContent = content; ///
319
+ }
137
320
  }
138
321
 
139
- setSelection(selection) {
322
+ setSelection(selection, setPreviousSelection = true) {
140
323
  const selectionStartPosition = selection.getStartPosition(),
141
324
  selectionEndPosition = selection.getEndPosition(),
142
325
  selectionStart = selectionStartPosition, ///
143
326
  selectionEnd = selectionEndPosition, ///
144
- previousSelection = selection, ///
145
327
  domElement = this.getDOMElement();
146
328
 
147
- Object.assign(domElement, {
148
- selectionStart,
149
- selectionEnd
150
- });
329
+ domElement.setSelectionRange(selectionStart, selectionEnd);
151
330
 
152
- this.setPreviousSelection(previousSelection);
331
+ if (setPreviousSelection) {
332
+ this.previousSelection = selection; ///
333
+ }
153
334
  }
154
335
 
155
- hasChanged() {
156
- const contentChanged = this.hasContentChanged(),
157
- selectionChanged = this.hasSelectionChanged(),
158
- changed = (contentChanged || selectionChanged);
336
+ applyOperation(operation) {
337
+ let content = this.getContent(),
338
+ selection = this.getSelection();
159
339
 
160
- return changed;
161
- }
340
+ const transformedContent = operation.transformContent(content),
341
+ transformedSelection = operation.transformSelection(selection);
162
342
 
163
- hasContentChanged() {
164
- const content = this.getContent(),
165
- previousContent = this.getPreviousContent(),
166
- contentDifferentToPreviousContent = (content !== previousContent),
167
- contentChanged = contentDifferentToPreviousContent; ///
343
+ content = transformedContent; ///
168
344
 
169
- return contentChanged;
170
- }
345
+ selection = transformedSelection; ///
171
346
 
172
- hasSelectionChanged() {
173
- const selection = this.getSelection(),
174
- previousSelection = this.getPreviousSelection(),
175
- selectionDifferentToPreviousSelection = selection.isDifferentTo(previousSelection),
176
- selectionChanged = selectionDifferentToPreviousSelection; ///
347
+ const setPreviousContent = false,
348
+ setPreviousSelection = false;
177
349
 
178
- return selectionChanged;
350
+ this.setContent(content, setPreviousContent);
351
+
352
+ this.setSelection(selection, setPreviousSelection);
179
353
  }
180
354
 
181
355
  activate() {
182
- const mouseDown = false;
183
-
184
- this.setMouseDown(mouseDown);
185
-
186
- window.on(`${BLUR_EVENT_TYPE} ${MOUSEUP_EVENT_TYPE} ${CONTEXTMENU_EVENT_TYPE}`, this.mouseUpHandler, this); ///
187
-
188
- this.on(MOUSEDOWN_EVENT_TYPE, this.mouseDownHandler, this);
189
-
190
- this.on(MOUSEMOVE_EVENT_TYPE, this.mouseMoveHandler, this);
191
-
192
356
  this.on(KEYDOWN_EVENT_TYPE, this.keyDownHandler, this);
193
357
 
194
358
  this.on(INPUT_EVENT_TYPE, this.inputHandler, this);
@@ -203,16 +367,6 @@ export default class RichTextarea extends Element {
203
367
  }
204
368
 
205
369
  deactivate() {
206
- const mouseDown = false;
207
-
208
- this.setMouseDown(mouseDown);
209
-
210
- window.off(`${BLUR_EVENT_TYPE} ${MOUSEUP_EVENT_TYPE} ${CONTEXTMENU_EVENT_TYPE}`, this.mouseUpHandler, this); ///
211
-
212
- this.off(MOUSEDOWN_EVENT_TYPE, this.mouseDownHandler, this);
213
-
214
- this.off(MOUSEMOVE_EVENT_TYPE, this.mouseMoveHandler, this);
215
-
216
370
  this.off(KEYDOWN_EVENT_TYPE, this.keyDownHandler, this);
217
371
 
218
372
  this.off(INPUT_EVENT_TYPE, this.inputHandler, this);
@@ -286,8 +440,12 @@ export default class RichTextarea extends Element {
286
440
  const eventListeners = this.findEventListeners(eventType);
287
441
 
288
442
  eventListeners.forEach((eventListener) => {
289
- const { handler, element: handlerElement } = eventListener,
290
- element = this; ///
443
+ let { element } = eventListener;
444
+
445
+ const { handler } = eventListener,
446
+ handlerElement = element; ///
447
+
448
+ element = this; ///
291
449
 
292
450
  if ((handler !== this.blurHandler) && (handler !== this.focusHandler) && (handler !== this.scrollHandler)) {
293
451
  handler.call(handlerElement, ...remainingArguments, element);
@@ -295,88 +453,37 @@ export default class RichTextarea extends Element {
295
453
  });
296
454
  }
297
455
 
298
- isReadOnly() {
299
- const { readOnly } = this.getState();
300
-
301
- return readOnly;
302
- }
303
-
304
- isMouseDown() {
305
- const { mouseDown } = this.getState();
306
-
307
- return mouseDown;
308
- }
309
-
310
- getPreviousContent() {
311
- const { previousContent } = this.getState();
312
-
313
- return previousContent;
314
- }
315
-
316
- getPreviousSelection() {
317
- const { previousSelection } = this.getState();
456
+ didMount() {
457
+ const { onChange, onScroll, onFocus, onBlur } = this.properties,
458
+ changeHandler = onChange, ///
459
+ scrollHandler = onScroll, ///
460
+ focusHandler = onFocus, ///
461
+ blurHandler = onBlur; ///
318
462
 
319
- return previousSelection;
320
- }
463
+ const content = this.getContent(),
464
+ selection = this.getSelection();
321
465
 
322
- setReadOnly(readOnly) {
323
- this.updateState({
324
- readOnly
325
- });
326
- }
466
+ this.previousContent = content; ///
327
467
 
328
- setMouseDown(mouseDown) {
329
- this.updateState({
330
- mouseDown
331
- });
332
- }
468
+ this.previousSelection = selection; ///
333
469
 
334
- setPreviousContent(previousContent) {
335
- this.updateState({
336
- previousContent
337
- });
338
- }
470
+ changeHandler && this.onChange(changeHandler, this);
339
471
 
340
- setPreviousSelection(previousSelection) {
341
- this.updateState({
342
- previousSelection
343
- });
344
- }
472
+ scrollHandler && this.onScroll(scrollHandler, this);
345
473
 
346
- setInitialState() {
347
- const readOnly = false,
348
- mouseDown = false,
349
- previousContent = null,
350
- previousSelection = null;
474
+ focusHandler && this.onFocus(focusHandler, this);
351
475
 
352
- this.setState({
353
- readOnly,
354
- mouseDown,
355
- previousContent,
356
- previousSelection
357
- });
358
- }
476
+ blurHandler && this.onBlur(blurHandler, this);
359
477
 
360
- didMount() {
361
- const { onChange, onScroll, onFocus, onBlur } = this.properties,
362
- changeHandler = onChange, ///
363
- scrollHandler = onScroll, ///
364
- focusHandler = onFocus, ///
365
- blurHandler = onBlur; ///
478
+ this.on(CUT_EVENT_TYPE, this.cutHandler);
366
479
 
367
- changeHandler && this.onChange(changeHandler, this);
368
- scrollHandler && this.onScroll(scrollHandler, this);
369
- focusHandler && this.onFocus(focusHandler, this);
370
- blurHandler && this.onBlur(blurHandler, this);
480
+ this.on(COPY_EVENT_TYPE, this.copyHandler);
371
481
 
372
- const content = this.getContent(),
373
- selection = this.getSelection(),
374
- previousContent = content, ///
375
- previousSelection = selection; ///
482
+ this.on(PASTE_EVENT_TYPE, this.pasteHandler);
376
483
 
377
- this.setPreviousSelection(previousSelection);
484
+ this.on(CONTEXTMENU_EVENT_TYPE, this.contextmenuHandler);
378
485
 
379
- this.setPreviousContent(previousContent);
486
+ document.on(SELECTIONCHANGE_EVENT_TYPE, this.selectChangeHandler);
380
487
  }
381
488
 
382
489
  willUnmount() {
@@ -387,10 +494,23 @@ export default class RichTextarea extends Element {
387
494
  blurHandler = onBlur; ///
388
495
 
389
496
  changeHandler && this.offChange(changeHandler, this);
497
+
390
498
  scrollHandler && this.offScroll(scrollHandler, this);
499
+
391
500
  focusHandler && this.offFocus(focusHandler, this);
501
+
392
502
  blurHandler && this.offBlur(blurHandler, this);
393
503
 
504
+ this.off(CUT_EVENT_TYPE, this.cutHandler);
505
+
506
+ this.off(COPY_EVENT_TYPE, this.copyHandler);
507
+
508
+ this.off(PASTE_EVENT_TYPE, this.pasteHandler);
509
+
510
+ this.off(CONTEXTMENU_EVENT_TYPE, this.contextmenuHandler);
511
+
512
+ document.off(SELECTIONCHANGE_EVENT_TYPE, this.selectChangeHandler);
513
+
394
514
  const active = this.isActive();
395
515
 
396
516
  if (active) {
@@ -398,10 +518,6 @@ export default class RichTextarea extends Element {
398
518
  }
399
519
  }
400
520
 
401
- initialise() {
402
- this.setInitialState();
403
- }
404
-
405
521
  static tagName = "textarea";
406
522
 
407
523
  static ignoredProperties = [
@@ -414,4 +530,19 @@ export default class RichTextarea extends Element {
414
530
  static defaultProperties = {
415
531
  className: "rich"
416
532
  };
533
+
534
+ static fromClass(Class, properties, ...remainingArguments) {
535
+ const focused = false,
536
+ readOnly = false,
537
+ undoBuffer = UndoBuffer.fromNothing(),
538
+ previousContent = null,
539
+ previousSelection = null,
540
+ richTextarea = Element.fromClass(Class, properties, focused, readOnly, undoBuffer, previousContent, previousSelection, ...remainingArguments);
541
+
542
+ return richTextarea;
543
+ }
544
+ }
545
+
546
+ function defer(func) {
547
+ setTimeout(func, 0);
417
548
  }