easy-richtextarea 4.0.3 → 4.0.5

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