@ckeditor/ckeditor5-widget 41.4.2 → 42.0.0-alpha.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. package/README.md +6 -0
  2. package/dist/index.js +581 -479
  3. package/dist/index.js.map +1 -1
  4. package/dist/translations/ar.js +1 -1
  5. package/dist/translations/ar.umd.js +1 -1
  6. package/dist/translations/az.js +1 -1
  7. package/dist/translations/az.umd.js +1 -1
  8. package/dist/translations/bg.js +1 -1
  9. package/dist/translations/bg.umd.js +1 -1
  10. package/dist/translations/bn.js +1 -1
  11. package/dist/translations/bn.umd.js +1 -1
  12. package/dist/translations/ca.js +1 -1
  13. package/dist/translations/ca.umd.js +1 -1
  14. package/dist/translations/cs.js +1 -1
  15. package/dist/translations/cs.umd.js +1 -1
  16. package/dist/translations/da.js +1 -1
  17. package/dist/translations/da.umd.js +1 -1
  18. package/dist/translations/de-ch.js +1 -1
  19. package/dist/translations/de-ch.umd.js +1 -1
  20. package/dist/translations/de.js +1 -1
  21. package/dist/translations/de.umd.js +1 -1
  22. package/dist/translations/el.js +1 -1
  23. package/dist/translations/el.umd.js +1 -1
  24. package/dist/translations/en-au.js +1 -1
  25. package/dist/translations/en-au.umd.js +1 -1
  26. package/dist/translations/en.js +1 -1
  27. package/dist/translations/en.umd.js +1 -1
  28. package/dist/translations/es.js +1 -1
  29. package/dist/translations/es.umd.js +1 -1
  30. package/dist/translations/et.js +1 -1
  31. package/dist/translations/et.umd.js +1 -1
  32. package/dist/translations/fa.js +1 -1
  33. package/dist/translations/fa.umd.js +1 -1
  34. package/dist/translations/fi.js +1 -1
  35. package/dist/translations/fi.umd.js +1 -1
  36. package/dist/translations/fr.js +1 -1
  37. package/dist/translations/fr.umd.js +1 -1
  38. package/dist/translations/gl.js +1 -1
  39. package/dist/translations/gl.umd.js +1 -1
  40. package/dist/translations/he.js +1 -1
  41. package/dist/translations/he.umd.js +1 -1
  42. package/dist/translations/hi.js +1 -1
  43. package/dist/translations/hi.umd.js +1 -1
  44. package/dist/translations/hr.js +1 -1
  45. package/dist/translations/hr.umd.js +1 -1
  46. package/dist/translations/hu.js +1 -1
  47. package/dist/translations/hu.umd.js +1 -1
  48. package/dist/translations/id.js +1 -1
  49. package/dist/translations/id.umd.js +1 -1
  50. package/dist/translations/it.js +1 -1
  51. package/dist/translations/it.umd.js +1 -1
  52. package/dist/translations/ja.js +1 -1
  53. package/dist/translations/ja.umd.js +1 -1
  54. package/dist/translations/ko.js +1 -1
  55. package/dist/translations/ko.umd.js +1 -1
  56. package/dist/translations/ku.js +1 -1
  57. package/dist/translations/ku.umd.js +1 -1
  58. package/dist/translations/lt.js +1 -1
  59. package/dist/translations/lt.umd.js +1 -1
  60. package/dist/translations/lv.js +1 -1
  61. package/dist/translations/lv.umd.js +1 -1
  62. package/dist/translations/ms.js +1 -1
  63. package/dist/translations/ms.umd.js +1 -1
  64. package/dist/translations/nl.js +1 -1
  65. package/dist/translations/nl.umd.js +1 -1
  66. package/dist/translations/no.js +1 -1
  67. package/dist/translations/no.umd.js +1 -1
  68. package/dist/translations/pl.js +1 -1
  69. package/dist/translations/pl.umd.js +1 -1
  70. package/dist/translations/pt-br.js +1 -1
  71. package/dist/translations/pt-br.umd.js +1 -1
  72. package/dist/translations/pt.js +1 -1
  73. package/dist/translations/pt.umd.js +1 -1
  74. package/dist/translations/ro.js +1 -1
  75. package/dist/translations/ro.umd.js +1 -1
  76. package/dist/translations/ru.js +1 -1
  77. package/dist/translations/ru.umd.js +1 -1
  78. package/dist/translations/sk.js +1 -1
  79. package/dist/translations/sk.umd.js +1 -1
  80. package/dist/translations/sq.js +1 -1
  81. package/dist/translations/sq.umd.js +1 -1
  82. package/dist/translations/sr-latn.js +1 -1
  83. package/dist/translations/sr-latn.umd.js +1 -1
  84. package/dist/translations/sr.js +1 -1
  85. package/dist/translations/sr.umd.js +1 -1
  86. package/dist/translations/sv.js +1 -1
  87. package/dist/translations/sv.umd.js +1 -1
  88. package/dist/translations/th.js +1 -1
  89. package/dist/translations/th.umd.js +1 -1
  90. package/dist/translations/tk.js +1 -1
  91. package/dist/translations/tk.umd.js +1 -1
  92. package/dist/translations/tr.js +1 -1
  93. package/dist/translations/tr.umd.js +1 -1
  94. package/dist/translations/uk.js +1 -1
  95. package/dist/translations/uk.umd.js +1 -1
  96. package/dist/translations/ur.js +1 -1
  97. package/dist/translations/ur.umd.js +1 -1
  98. package/dist/translations/uz.js +1 -1
  99. package/dist/translations/uz.umd.js +1 -1
  100. package/dist/translations/vi.js +1 -1
  101. package/dist/translations/vi.umd.js +1 -1
  102. package/dist/translations/zh-cn.js +1 -1
  103. package/dist/translations/zh-cn.umd.js +1 -1
  104. package/dist/translations/zh.js +1 -1
  105. package/dist/translations/zh.umd.js +1 -1
  106. package/dist/types/highlightstack.d.ts +1 -1
  107. package/dist/types/widgetresize/resizer.d.ts +1 -1
  108. package/dist/types/widgetresize/resizerstate.d.ts +1 -1
  109. package/lang/contexts.json +2 -1
  110. package/lang/translations/ar.po +4 -0
  111. package/lang/translations/az.po +4 -0
  112. package/lang/translations/bg.po +4 -0
  113. package/lang/translations/bn.po +4 -0
  114. package/lang/translations/ca.po +4 -0
  115. package/lang/translations/cs.po +4 -0
  116. package/lang/translations/da.po +4 -0
  117. package/lang/translations/de-ch.po +4 -0
  118. package/lang/translations/de.po +4 -0
  119. package/lang/translations/el.po +4 -0
  120. package/lang/translations/en-au.po +4 -0
  121. package/lang/translations/en.po +4 -0
  122. package/lang/translations/es.po +4 -0
  123. package/lang/translations/et.po +4 -0
  124. package/lang/translations/fa.po +4 -0
  125. package/lang/translations/fi.po +4 -0
  126. package/lang/translations/fr.po +4 -0
  127. package/lang/translations/gl.po +4 -0
  128. package/lang/translations/he.po +4 -0
  129. package/lang/translations/hi.po +4 -0
  130. package/lang/translations/hr.po +4 -0
  131. package/lang/translations/hu.po +4 -0
  132. package/lang/translations/id.po +4 -0
  133. package/lang/translations/it.po +4 -0
  134. package/lang/translations/ja.po +4 -0
  135. package/lang/translations/ko.po +4 -0
  136. package/lang/translations/ku.po +4 -0
  137. package/lang/translations/lt.po +4 -0
  138. package/lang/translations/lv.po +4 -0
  139. package/lang/translations/ms.po +4 -0
  140. package/lang/translations/nl.po +4 -0
  141. package/lang/translations/no.po +4 -0
  142. package/lang/translations/pl.po +4 -0
  143. package/lang/translations/pt-br.po +4 -0
  144. package/lang/translations/pt.po +4 -0
  145. package/lang/translations/ro.po +4 -0
  146. package/lang/translations/ru.po +4 -0
  147. package/lang/translations/sk.po +4 -0
  148. package/lang/translations/sq.po +4 -0
  149. package/lang/translations/sr-latn.po +4 -0
  150. package/lang/translations/sr.po +4 -0
  151. package/lang/translations/sv.po +4 -0
  152. package/lang/translations/th.po +4 -0
  153. package/lang/translations/tk.po +4 -0
  154. package/lang/translations/tr.po +4 -0
  155. package/lang/translations/uk.po +4 -0
  156. package/lang/translations/ur.po +4 -0
  157. package/lang/translations/uz.po +4 -0
  158. package/lang/translations/vi.po +4 -0
  159. package/lang/translations/zh-cn.po +4 -0
  160. package/lang/translations/zh.po +4 -0
  161. package/package.json +7 -7
  162. package/src/highlightstack.d.ts +1 -1
  163. package/src/highlightstack.js +1 -1
  164. package/src/widget.js +4 -0
  165. package/src/widgetresize/resizer.d.ts +1 -1
  166. package/src/widgetresize/resizer.js +1 -1
  167. package/src/widgetresize/resizerstate.d.ts +1 -1
  168. package/src/widgetresize/resizerstate.js +1 -1
package/dist/index.js CHANGED
@@ -10,12 +10,23 @@ import { IconView, Template, ContextualBalloon, ToolbarView, BalloonPanelView, V
10
10
  import { Enter } from '@ckeditor/ckeditor5-enter/dist/index.js';
11
11
  import { throttle } from 'lodash-es';
12
12
 
13
- class HighlightStack extends EmitterMixin() {
14
- /**
15
- * Adds highlight descriptor to the stack.
16
- *
17
- * @fires change:top
18
- */ add(descriptor, writer) {
13
+ /**
14
+ * Class used to handle the correct order of highlights on elements.
15
+ *
16
+ * When different highlights are applied to same element the correct order should be preserved:
17
+ *
18
+ * * highlight with highest priority should be applied,
19
+ * * if two highlights have same priority - sort by CSS class provided in
20
+ * {@link module:engine/conversion/downcasthelpers~HighlightDescriptor}.
21
+ *
22
+ * This way, highlight will be applied with the same rules it is applied on texts.
23
+ */ class HighlightStack extends /* #__PURE__ */ EmitterMixin() {
24
+ _stack = [];
25
+ /**
26
+ * Adds highlight descriptor to the stack.
27
+ *
28
+ * @fires change:top
29
+ */ add(descriptor, writer) {
19
30
  const stack = this._stack;
20
31
  // Save top descriptor and insert new one. If top is changed - fire event.
21
32
  const oldTop = stack[0];
@@ -31,11 +42,11 @@ class HighlightStack extends EmitterMixin() {
31
42
  }
32
43
  }
33
44
  /**
34
- * Removes highlight descriptor from the stack.
35
- *
36
- * @fires change:top
37
- * @param id Id of the descriptor to remove.
38
- */ remove(id, writer) {
45
+ * Removes highlight descriptor from the stack.
46
+ *
47
+ * @fires change:top
48
+ * @param id Id of the descriptor to remove.
49
+ */ remove(id, writer) {
39
50
  const stack = this._stack;
40
51
  const oldTop = stack[0];
41
52
  this._removeDescriptor(id);
@@ -50,9 +61,9 @@ class HighlightStack extends EmitterMixin() {
50
61
  }
51
62
  }
52
63
  /**
53
- * Inserts a given descriptor in correct place in the stack. It also takes care about updating information
54
- * when descriptor with same id is already present.
55
- */ _insertDescriptor(descriptor) {
64
+ * Inserts a given descriptor in correct place in the stack. It also takes care about updating information
65
+ * when descriptor with same id is already present.
66
+ */ _insertDescriptor(descriptor) {
56
67
  const stack = this._stack;
57
68
  const index = stack.findIndex((item)=>item.id === descriptor.id);
58
69
  // Inserting exact same descriptor - do nothing.
@@ -72,10 +83,10 @@ class HighlightStack extends EmitterMixin() {
72
83
  stack.splice(i, 0, descriptor);
73
84
  }
74
85
  /**
75
- * Removes descriptor with given id from the stack.
76
- *
77
- * @param id Descriptor's id.
78
- */ _removeDescriptor(id) {
86
+ * Removes descriptor with given id from the stack.
87
+ *
88
+ * @param id Descriptor's id.
89
+ */ _removeDescriptor(id) {
79
90
  const stack = this._stack;
80
91
  const index = stack.findIndex((item)=>item.id === id);
81
92
  // If descriptor with same id is on the list - remove it.
@@ -83,10 +94,6 @@ class HighlightStack extends EmitterMixin() {
83
94
  stack.splice(index, 1);
84
95
  }
85
96
  }
86
- constructor(){
87
- super(...arguments);
88
- this._stack = [];
89
- }
90
97
  }
91
98
  /**
92
99
  * Compares two descriptors by checking their priority and class list.
@@ -177,12 +184,12 @@ var dragHandleIcon = "<svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/
177
184
  */ function toWidget(element, writer, options = {}) {
178
185
  if (!element.is('containerElement')) {
179
186
  /**
180
- * The element passed to `toWidget()` must be a {@link module:engine/view/containerelement~ContainerElement}
181
- * instance.
182
- *
183
- * @error widget-to-widget-wrong-element-type
184
- * @param element The view element passed to `toWidget()`.
185
- */ throw new CKEditorError('widget-to-widget-wrong-element-type', null, {
187
+ * The element passed to `toWidget()` must be a {@link module:engine/view/containerelement~ContainerElement}
188
+ * instance.
189
+ *
190
+ * @error widget-to-widget-wrong-element-type
191
+ * @param element The view element passed to `toWidget()`.
192
+ */ throw new CKEditorError('widget-to-widget-wrong-element-type', null, {
186
193
  element
187
194
  });
188
195
  }
@@ -538,23 +545,37 @@ const POSSIBLE_INSERTION_POSITIONS = [
538
545
  // Do the SVG parsing once and then clone the result <svg> DOM element for each new button.
539
546
  const RETURN_ARROW_ICON_ELEMENT = new DOMParser().parseFromString(returnIcon, 'image/svg+xml').firstChild;
540
547
  const PLUGIN_DISABLED_EDITING_ROOT_CLASS = 'ck-widget__type-around_disabled';
541
- class WidgetTypeAround extends Plugin {
542
- /**
543
- * @inheritDoc
544
- */ static get pluginName() {
548
+ /**
549
+ * A plugin that allows users to type around widgets where normally it is impossible to place the caret due
550
+ * to limitations of web browsers. These "tight spots" occur, for instance, before (or after) a widget being
551
+ * the first (or last) child of its parent or between two block widgets.
552
+ *
553
+ * This plugin extends the {@link module:widget/widget~Widget `Widget`} plugin and injects the user interface
554
+ * with two buttons into each widget instance in the editor. Each of the buttons can be clicked by the
555
+ * user if the widget is next to the "tight spot". Once clicked, a paragraph is created with the selection anchored
556
+ * in it so that users can type (or insert content, paste, etc.) straight away.
557
+ */ class WidgetTypeAround extends Plugin {
558
+ /**
559
+ * A reference to the model widget element that has the fake caret active
560
+ * on either side of it. It is later used to remove CSS classes associated with the fake caret
561
+ * when the widget no longer needs it.
562
+ */ _currentFakeCaretModelElement = null;
563
+ /**
564
+ * @inheritDoc
565
+ */ static get pluginName() {
545
566
  return 'WidgetTypeAround';
546
567
  }
547
568
  /**
548
- * @inheritDoc
549
- */ static get requires() {
569
+ * @inheritDoc
570
+ */ static get requires() {
550
571
  return [
551
572
  Enter,
552
573
  Delete
553
574
  ];
554
575
  }
555
576
  /**
556
- * @inheritDoc
557
- */ init() {
577
+ * @inheritDoc
578
+ */ init() {
558
579
  const editor = this.editor;
559
580
  const editingView = editor.editing.view;
560
581
  // Set a CSS class on the view editing root when the plugin is disabled so all the buttons
@@ -586,20 +607,20 @@ class WidgetTypeAround extends Plugin {
586
607
  this._enableDeleteContentIntegration();
587
608
  }
588
609
  /**
589
- * @inheritDoc
590
- */ destroy() {
610
+ * @inheritDoc
611
+ */ destroy() {
591
612
  super.destroy();
592
613
  this._currentFakeCaretModelElement = null;
593
614
  }
594
615
  /**
595
- * Inserts a new paragraph next to a widget element with the selection anchored in it.
596
- *
597
- * **Note**: This method is heavily user-oriented and will both focus the editing view and scroll
598
- * the viewport to the selection in the inserted paragraph.
599
- *
600
- * @param widgetModelElement The model widget element next to which a paragraph is inserted.
601
- * @param position The position where the paragraph is inserted. Either `'before'` or `'after'` the widget.
602
- */ _insertParagraph(widgetModelElement, position) {
616
+ * Inserts a new paragraph next to a widget element with the selection anchored in it.
617
+ *
618
+ * **Note**: This method is heavily user-oriented and will both focus the editing view and scroll
619
+ * the viewport to the selection in the inserted paragraph.
620
+ *
621
+ * @param widgetModelElement The model widget element next to which a paragraph is inserted.
622
+ * @param position The position where the paragraph is inserted. Either `'before'` or `'after'` the widget.
623
+ */ _insertParagraph(widgetModelElement, position) {
603
624
  const editor = this.editor;
604
625
  const editingView = editor.editing.view;
605
626
  const attributesToCopy = editor.model.schema.getAttributesWithProperty(widgetModelElement, 'copyOnReplace', true);
@@ -611,16 +632,16 @@ class WidgetTypeAround extends Plugin {
611
632
  editingView.scrollToTheSelection();
612
633
  }
613
634
  /**
614
- * A wrapper for the {@link module:utils/emittermixin~Emitter#listenTo} method that executes the callbacks only
615
- * when the plugin {@link #isEnabled is enabled}.
616
- *
617
- * @param emitter The object that fires the event.
618
- * @param event The name of the event.
619
- * @param callback The function to be called on event.
620
- * @param options Additional options.
621
- * @param options.priority The priority of this event callback. The higher the priority value the sooner
622
- * the callback will be fired. Events having the same priority are called in the order they were added.
623
- */ _listenToIfEnabled(emitter, event, callback, options) {
635
+ * A wrapper for the {@link module:utils/emittermixin~Emitter#listenTo} method that executes the callbacks only
636
+ * when the plugin {@link #isEnabled is enabled}.
637
+ *
638
+ * @param emitter The object that fires the event.
639
+ * @param event The name of the event.
640
+ * @param callback The function to be called on event.
641
+ * @param options Additional options.
642
+ * @param options.priority The priority of this event callback. The higher the priority value the sooner
643
+ * the callback will be fired. Events having the same priority are called in the order they were added.
644
+ */ _listenToIfEnabled(emitter, event, callback, options) {
624
645
  this.listenTo(emitter, event, (...args)=>{
625
646
  // Do not respond if the plugin is disabled.
626
647
  if (this.isEnabled) {
@@ -629,16 +650,16 @@ class WidgetTypeAround extends Plugin {
629
650
  }, options);
630
651
  }
631
652
  /**
632
- * Similar to {@link #_insertParagraph}, this method inserts a paragraph except that it
633
- * does not expect a position. Instead, it performs the insertion next to a selected widget
634
- * according to the `widget-type-around` model selection attribute value (fake caret position).
635
- *
636
- * Because this method requires the `widget-type-around` attribute to be set,
637
- * the insertion can only happen when the widget's fake caret is active (e.g. activated
638
- * using the keyboard).
639
- *
640
- * @returns Returns `true` when the paragraph was inserted (the attribute was present) and `false` otherwise.
641
- */ _insertParagraphAccordingToFakeCaretPosition() {
653
+ * Similar to {@link #_insertParagraph}, this method inserts a paragraph except that it
654
+ * does not expect a position. Instead, it performs the insertion next to a selected widget
655
+ * according to the `widget-type-around` model selection attribute value (fake caret position).
656
+ *
657
+ * Because this method requires the `widget-type-around` attribute to be set,
658
+ * the insertion can only happen when the widget's fake caret is active (e.g. activated
659
+ * using the keyboard).
660
+ *
661
+ * @returns Returns `true` when the paragraph was inserted (the attribute was present) and `false` otherwise.
662
+ */ _insertParagraphAccordingToFakeCaretPosition() {
642
663
  const editor = this.editor;
643
664
  const model = editor.model;
644
665
  const modelSelection = model.document.selection;
@@ -656,12 +677,12 @@ class WidgetTypeAround extends Plugin {
656
677
  return true;
657
678
  }
658
679
  /**
659
- * Creates a listener in the editing conversion pipeline that injects the widget type around
660
- * UI into every single widget instance created in the editor.
661
- *
662
- * The UI is delivered as a {@link module:engine/view/uielement~UIElement}
663
- * wrapper which renders DOM buttons that users can use to insert paragraphs.
664
- */ _enableTypeAroundUIInjection() {
680
+ * Creates a listener in the editing conversion pipeline that injects the widget type around
681
+ * UI into every single widget instance created in the editor.
682
+ *
683
+ * The UI is delivered as a {@link module:engine/view/uielement~UIElement}
684
+ * wrapper which renders DOM buttons that users can use to insert paragraphs.
685
+ */ _enableTypeAroundUIInjection() {
665
686
  const editor = this.editor;
666
687
  const schema = editor.model.schema;
667
688
  const t = editor.locale.t;
@@ -687,30 +708,30 @@ class WidgetTypeAround extends Plugin {
687
708
  });
688
709
  }
689
710
  /**
690
- * Brings support for the fake caret that appears when either:
691
- *
692
- * * the selection moves to a widget from a position next to it using arrow keys,
693
- * * the arrow key is pressed when the widget is already selected.
694
- *
695
- * The fake caret lets the user know that they can start typing or just press
696
- * <kbd>Enter</kbd> to insert a paragraph at the position next to a widget as suggested by the fake caret.
697
- *
698
- * The fake caret disappears when the user changes the selection or the editor
699
- * gets blurred.
700
- *
701
- * The whole idea is as follows:
702
- *
703
- * 1. A user does one of the 2 scenarios described at the beginning.
704
- * 2. The "keydown" listener is executed and the decision is made whether to show or hide the fake caret.
705
- * 3. If it should show up, the `widget-type-around` model selection attribute is set indicating
706
- * on which side of the widget it should appear.
707
- * 4. The selection dispatcher reacts to the selection attribute and sets CSS classes responsible for the
708
- * fake caret on the view widget.
709
- * 5. If the fake caret should disappear, the selection attribute is removed and the dispatcher
710
- * does the CSS class clean-up in the view.
711
- * 6. Additionally, `change:range` and `FocusTracker#isFocused` listeners also remove the selection
712
- * attribute (the former also removes widget CSS classes).
713
- */ _enableTypeAroundFakeCaretActivationUsingKeyboardArrows() {
711
+ * Brings support for the fake caret that appears when either:
712
+ *
713
+ * * the selection moves to a widget from a position next to it using arrow keys,
714
+ * * the arrow key is pressed when the widget is already selected.
715
+ *
716
+ * The fake caret lets the user know that they can start typing or just press
717
+ * <kbd>Enter</kbd> to insert a paragraph at the position next to a widget as suggested by the fake caret.
718
+ *
719
+ * The fake caret disappears when the user changes the selection or the editor
720
+ * gets blurred.
721
+ *
722
+ * The whole idea is as follows:
723
+ *
724
+ * 1. A user does one of the 2 scenarios described at the beginning.
725
+ * 2. The "keydown" listener is executed and the decision is made whether to show or hide the fake caret.
726
+ * 3. If it should show up, the `widget-type-around` model selection attribute is set indicating
727
+ * on which side of the widget it should appear.
728
+ * 4. The selection dispatcher reacts to the selection attribute and sets CSS classes responsible for the
729
+ * fake caret on the view widget.
730
+ * 5. If the fake caret should disappear, the selection attribute is removed and the dispatcher
731
+ * does the CSS class clean-up in the view.
732
+ * 6. Additionally, `change:range` and `FocusTracker#isFocused` listeners also remove the selection
733
+ * attribute (the former also removes widget CSS classes).
734
+ */ _enableTypeAroundFakeCaretActivationUsingKeyboardArrows() {
714
735
  const editor = this.editor;
715
736
  const model = editor.model;
716
737
  const modelSelection = model.document.selection;
@@ -798,17 +819,17 @@ class WidgetTypeAround extends Plugin {
798
819
  }
799
820
  }
800
821
  /**
801
- * A listener executed on each "keydown" in the view document, a part of
802
- * {@link #_enableTypeAroundFakeCaretActivationUsingKeyboardArrows}.
803
- *
804
- * It decides whether the arrow keypress should activate the fake caret or not (also whether it should
805
- * be deactivated).
806
- *
807
- * The fake caret activation is done by setting the `widget-type-around` model selection attribute
808
- * in this listener, and stopping and preventing the event that would normally be handled by the widget
809
- * plugin that is responsible for the regular keyboard navigation near/across all widgets (that
810
- * includes inline widgets, which are ignored by the widget type around plugin).
811
- */ _handleArrowKeyPress(evt, domEventData) {
822
+ * A listener executed on each "keydown" in the view document, a part of
823
+ * {@link #_enableTypeAroundFakeCaretActivationUsingKeyboardArrows}.
824
+ *
825
+ * It decides whether the arrow keypress should activate the fake caret or not (also whether it should
826
+ * be deactivated).
827
+ *
828
+ * The fake caret activation is done by setting the `widget-type-around` model selection attribute
829
+ * in this listener, and stopping and preventing the event that would normally be handled by the widget
830
+ * plugin that is responsible for the regular keyboard navigation near/across all widgets (that
831
+ * includes inline widgets, which are ignored by the widget type around plugin).
832
+ */ _handleArrowKeyPress(evt, domEventData) {
812
833
  const editor = this.editor;
813
834
  const model = editor.model;
814
835
  const modelSelection = model.document.selection;
@@ -833,15 +854,15 @@ class WidgetTypeAround extends Plugin {
833
854
  }
834
855
  }
835
856
  /**
836
- * Handles the keyboard navigation on "keydown" when a widget is currently selected and activates or deactivates
837
- * the fake caret for that widget, depending on the current value of the `widget-type-around` model
838
- * selection attribute and the direction of the pressed arrow key.
839
- *
840
- * @param isForward `true` when the pressed arrow key was responsible for the forward model selection movement
841
- * as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
842
- * @returns Returns `true` when the keypress was handled and no other keydown listener of the editor should
843
- * process the event any further. Returns `false` otherwise.
844
- */ _handleArrowKeyPressOnSelectedWidget(isForward) {
857
+ * Handles the keyboard navigation on "keydown" when a widget is currently selected and activates or deactivates
858
+ * the fake caret for that widget, depending on the current value of the `widget-type-around` model
859
+ * selection attribute and the direction of the pressed arrow key.
860
+ *
861
+ * @param isForward `true` when the pressed arrow key was responsible for the forward model selection movement
862
+ * as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
863
+ * @returns Returns `true` when the keypress was handled and no other keydown listener of the editor should
864
+ * process the event any further. Returns `false` otherwise.
865
+ */ _handleArrowKeyPressOnSelectedWidget(isForward) {
845
866
  const editor = this.editor;
846
867
  const model = editor.model;
847
868
  const modelSelection = model.document.selection;
@@ -870,19 +891,19 @@ class WidgetTypeAround extends Plugin {
870
891
  });
871
892
  }
872
893
  /**
873
- * Handles the keyboard navigation on "keydown" when **no** widget is selected but the selection is **directly** next
874
- * to one and upon the fake caret should become active for this widget upon arrow keypress
875
- * (AKA entering/selecting the widget).
876
- *
877
- * **Note**: This code mirrors the implementation from the widget plugin but also adds the selection attribute.
878
- * Unfortunately, there is no safe way to let the widget plugin do the selection part first and then just set the
879
- * selection attribute here in the widget type around plugin. This is why this code must duplicate some from the widget plugin.
880
- *
881
- * @param isForward `true` when the pressed arrow key was responsible for the forward model selection movement
882
- * as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
883
- * @returns Returns `true` when the keypress was handled and no other keydown listener of the editor should
884
- * process the event any further. Returns `false` otherwise.
885
- */ _handleArrowKeyPressWhenSelectionNextToAWidget(isForward) {
894
+ * Handles the keyboard navigation on "keydown" when **no** widget is selected but the selection is **directly** next
895
+ * to one and upon the fake caret should become active for this widget upon arrow keypress
896
+ * (AKA entering/selecting the widget).
897
+ *
898
+ * **Note**: This code mirrors the implementation from the widget plugin but also adds the selection attribute.
899
+ * Unfortunately, there is no safe way to let the widget plugin do the selection part first and then just set the
900
+ * selection attribute here in the widget type around plugin. This is why this code must duplicate some from the widget plugin.
901
+ *
902
+ * @param isForward `true` when the pressed arrow key was responsible for the forward model selection movement
903
+ * as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
904
+ * @returns Returns `true` when the keypress was handled and no other keydown listener of the editor should
905
+ * process the event any further. Returns `false` otherwise.
906
+ */ _handleArrowKeyPressWhenSelectionNextToAWidget(isForward) {
886
907
  const editor = this.editor;
887
908
  const model = editor.model;
888
909
  const schema = model.schema;
@@ -902,14 +923,14 @@ class WidgetTypeAround extends Plugin {
902
923
  return false;
903
924
  }
904
925
  /**
905
- * Handles the keyboard navigation on "keydown" when a widget is currently selected (together with some other content)
906
- * and the widget is the first or last element in the selection. It activates or deactivates the fake caret for that widget.
907
- *
908
- * @param isForward `true` when the pressed arrow key was responsible for the forward model selection movement
909
- * as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
910
- * @returns Returns `true` when the keypress was handled and no other keydown listener of the editor should
911
- * process the event any further. Returns `false` otherwise.
912
- */ _handleArrowKeyPressWhenNonCollapsedSelection(isForward) {
926
+ * Handles the keyboard navigation on "keydown" when a widget is currently selected (together with some other content)
927
+ * and the widget is the first or last element in the selection. It activates or deactivates the fake caret for that widget.
928
+ *
929
+ * @param isForward `true` when the pressed arrow key was responsible for the forward model selection movement
930
+ * as in {@link module:utils/keyboard~isForwardArrowKeyCode}.
931
+ * @returns Returns `true` when the keypress was handled and no other keydown listener of the editor should
932
+ * process the event any further. Returns `false` otherwise.
933
+ */ _handleArrowKeyPressWhenNonCollapsedSelection(isForward) {
913
934
  const editor = this.editor;
914
935
  const model = editor.model;
915
936
  const schema = model.schema;
@@ -928,10 +949,10 @@ class WidgetTypeAround extends Plugin {
928
949
  return false;
929
950
  }
930
951
  /**
931
- * Registers a `mousedown` listener for the view document which intercepts events
932
- * coming from the widget type around UI, which happens when a user clicks one of the buttons
933
- * that insert a paragraph next to a widget.
934
- */ _enableInsertingParagraphsOnButtonClick() {
952
+ * Registers a `mousedown` listener for the view document which intercepts events
953
+ * coming from the widget type around UI, which happens when a user clicks one of the buttons
954
+ * that insert a paragraph next to a widget.
955
+ */ _enableInsertingParagraphsOnButtonClick() {
935
956
  const editor = this.editor;
936
957
  const editingView = editor.editing.view;
937
958
  this._listenToIfEnabled(editingView.document, 'mousedown', (evt, domEventData)=>{
@@ -948,18 +969,18 @@ class WidgetTypeAround extends Plugin {
948
969
  });
949
970
  }
950
971
  /**
951
- * Creates the <kbd>Enter</kbd> key listener on the view document that allows the user to insert a paragraph
952
- * near the widget when either:
953
- *
954
- * * The fake caret was first activated using the arrow keys,
955
- * * The entire widget is selected in the model.
956
- *
957
- * In the first case, the new paragraph is inserted according to the `widget-type-around` selection
958
- * attribute (see {@link #_handleArrowKeyPress}).
959
- *
960
- * In the second case, the new paragraph is inserted based on whether a soft (<kbd>Shift</kbd>+<kbd>Enter</kbd>) keystroke
961
- * was pressed or not.
962
- */ _enableInsertingParagraphsOnEnterKeypress() {
972
+ * Creates the <kbd>Enter</kbd> key listener on the view document that allows the user to insert a paragraph
973
+ * near the widget when either:
974
+ *
975
+ * * The fake caret was first activated using the arrow keys,
976
+ * * The entire widget is selected in the model.
977
+ *
978
+ * In the first case, the new paragraph is inserted according to the `widget-type-around` selection
979
+ * attribute (see {@link #_handleArrowKeyPress}).
980
+ *
981
+ * In the second case, the new paragraph is inserted based on whether a soft (<kbd>Shift</kbd>+<kbd>Enter</kbd>) keystroke
982
+ * was pressed or not.
983
+ */ _enableInsertingParagraphsOnEnterKeypress() {
963
984
  const editor = this.editor;
964
985
  const selection = editor.model.document.selection;
965
986
  const editingView = editor.editing.view;
@@ -990,18 +1011,18 @@ class WidgetTypeAround extends Plugin {
990
1011
  });
991
1012
  }
992
1013
  /**
993
- * Similar to the {@link #_enableInsertingParagraphsOnEnterKeypress}, it allows the user
994
- * to insert a paragraph next to a widget when the fake caret was activated using arrow
995
- * keys but it responds to typing instead of <kbd>Enter</kbd>.
996
- *
997
- * Listener enabled by this method will insert a new paragraph according to the `widget-type-around`
998
- * model selection attribute as the user simply starts typing, which creates the impression that the fake caret
999
- * behaves like a real one rendered by the browser (AKA your text appears where the caret was).
1000
- *
1001
- * **Note**: At the moment this listener creates 2 undo steps: one for the `insertParagraph` command
1002
- * and another one for actual typing. It is not a disaster but this may need to be fixed
1003
- * sooner or later.
1004
- */ _enableInsertingParagraphsOnTypingKeystroke() {
1014
+ * Similar to the {@link #_enableInsertingParagraphsOnEnterKeypress}, it allows the user
1015
+ * to insert a paragraph next to a widget when the fake caret was activated using arrow
1016
+ * keys but it responds to typing instead of <kbd>Enter</kbd>.
1017
+ *
1018
+ * Listener enabled by this method will insert a new paragraph according to the `widget-type-around`
1019
+ * model selection attribute as the user simply starts typing, which creates the impression that the fake caret
1020
+ * behaves like a real one rendered by the browser (AKA your text appears where the caret was).
1021
+ *
1022
+ * **Note**: At the moment this listener creates 2 undo steps: one for the `insertParagraph` command
1023
+ * and another one for actual typing. It is not a disaster but this may need to be fixed
1024
+ * sooner or later.
1025
+ */ _enableInsertingParagraphsOnTypingKeystroke() {
1005
1026
  const editor = this.editor;
1006
1027
  const viewDocument = editor.editing.view.document;
1007
1028
  // Note: The priority must precede the default Input plugin insertText handler.
@@ -1035,13 +1056,13 @@ class WidgetTypeAround extends Plugin {
1035
1056
  }
1036
1057
  }
1037
1058
  /**
1038
- * It creates a "delete" event listener on the view document to handle cases when the <kbd>Delete</kbd> or <kbd>Backspace</kbd>
1039
- * is pressed and the fake caret is currently active.
1040
- *
1041
- * The fake caret should create an illusion of a real browser caret so that when it appears before or after
1042
- * a widget, pressing <kbd>Delete</kbd> or <kbd>Backspace</kbd> should remove a widget or delete the content
1043
- * before or after a widget (depending on the content surrounding the widget).
1044
- */ _enableDeleteIntegration() {
1059
+ * It creates a "delete" event listener on the view document to handle cases when the <kbd>Delete</kbd> or <kbd>Backspace</kbd>
1060
+ * is pressed and the fake caret is currently active.
1061
+ *
1062
+ * The fake caret should create an illusion of a real browser caret so that when it appears before or after
1063
+ * a widget, pressing <kbd>Delete</kbd> or <kbd>Backspace</kbd> should remove a widget or delete the content
1064
+ * before or after a widget (depending on the content surrounding the widget).
1065
+ */ _enableDeleteIntegration() {
1045
1066
  const editor = this.editor;
1046
1067
  const editingView = editor.editing.view;
1047
1068
  const model = editor.model;
@@ -1106,11 +1127,11 @@ class WidgetTypeAround extends Plugin {
1106
1127
  });
1107
1128
  }
1108
1129
  /**
1109
- * Attaches the {@link module:engine/model/model~Model#event:insertContent} event listener that, for instance, allows the user to paste
1110
- * content near a widget when the fake caret is first activated using the arrow keys.
1111
- *
1112
- * The content is inserted according to the `widget-type-around` selection attribute (see {@link #_handleArrowKeyPress}).
1113
- */ _enableInsertContentIntegration() {
1130
+ * Attaches the {@link module:engine/model/model~Model#event:insertContent} event listener that, for instance, allows the user to paste
1131
+ * content near a widget when the fake caret is first activated using the arrow keys.
1132
+ *
1133
+ * The content is inserted according to the `widget-type-around` selection attribute (see {@link #_handleArrowKeyPress}).
1134
+ */ _enableInsertContentIntegration() {
1114
1135
  const editor = this.editor;
1115
1136
  const model = this.editor.model;
1116
1137
  const documentSelection = model.document.selection;
@@ -1136,12 +1157,12 @@ class WidgetTypeAround extends Plugin {
1136
1157
  });
1137
1158
  }
1138
1159
  /**
1139
- * Attaches the {@link module:engine/model/model~Model#event:insertObject} event listener that modifies the
1140
- * `options.findOptimalPosition`parameter to position of fake caret in relation to selected element
1141
- * to reflect user's intent of desired insertion position.
1142
- *
1143
- * The object is inserted according to the `widget-type-around` selection attribute (see {@link #_handleArrowKeyPress}).
1144
- */ _enableInsertObjectIntegration() {
1160
+ * Attaches the {@link module:engine/model/model~Model#event:insertObject} event listener that modifies the
1161
+ * `options.findOptimalPosition`parameter to position of fake caret in relation to selected element
1162
+ * to reflect user's intent of desired insertion position.
1163
+ *
1164
+ * The object is inserted according to the `widget-type-around` selection attribute (see {@link #_handleArrowKeyPress}).
1165
+ */ _enableInsertObjectIntegration() {
1145
1166
  const editor = this.editor;
1146
1167
  const model = this.editor.model;
1147
1168
  const documentSelection = model.document.selection;
@@ -1161,13 +1182,13 @@ class WidgetTypeAround extends Plugin {
1161
1182
  });
1162
1183
  }
1163
1184
  /**
1164
- * Attaches the {@link module:engine/model/model~Model#event:deleteContent} event listener to block the event when the fake
1165
- * caret is active.
1166
- *
1167
- * This is required for cases that trigger {@link module:engine/model/model~Model#deleteContent `model.deleteContent()`}
1168
- * before calling {@link module:engine/model/model~Model#insertContent `model.insertContent()`} like, for instance,
1169
- * plain text pasting.
1170
- */ _enableDeleteContentIntegration() {
1185
+ * Attaches the {@link module:engine/model/model~Model#event:deleteContent} event listener to block the event when the fake
1186
+ * caret is active.
1187
+ *
1188
+ * This is required for cases that trigger {@link module:engine/model/model~Model#deleteContent `model.deleteContent()`}
1189
+ * before calling {@link module:engine/model/model~Model#insertContent `model.insertContent()`} like, for instance,
1190
+ * plain text pasting.
1191
+ */ _enableDeleteContentIntegration() {
1171
1192
  const editor = this.editor;
1172
1193
  const model = this.editor.model;
1173
1194
  const documentSelection = model.document.selection;
@@ -1184,14 +1205,6 @@ class WidgetTypeAround extends Plugin {
1184
1205
  priority: 'high'
1185
1206
  });
1186
1207
  }
1187
- constructor(){
1188
- super(...arguments);
1189
- /**
1190
- * A reference to the model widget element that has the fake caret active
1191
- * on either side of it. It is later used to remove CSS classes associated with the fake caret
1192
- * when the widget no longer needs it.
1193
- */ this._currentFakeCaretModelElement = null;
1194
- }
1195
1208
  }
1196
1209
  /**
1197
1210
  * Injects the type around UI into a view widget instance.
@@ -1450,23 +1463,38 @@ function selectionWillShrink(selection, isForward) {
1450
1463
  return !selection.isCollapsed && selection.isBackward == isForward;
1451
1464
  }
1452
1465
 
1453
- class Widget extends Plugin {
1454
- /**
1455
- * @inheritDoc
1456
- */ static get pluginName() {
1466
+ /**
1467
+ * The widget plugin. It enables base support for widgets.
1468
+ *
1469
+ * See {@glink api/widget package page} for more details and documentation.
1470
+ *
1471
+ * This plugin enables multiple behaviors required by widgets:
1472
+ *
1473
+ * * The model to view selection converter for the editing pipeline (it handles widget custom selection rendering).
1474
+ * If a converted selection wraps around a widget element, that selection is marked as
1475
+ * {@link module:engine/view/selection~Selection#isFake fake}. Additionally, the `ck-widget_selected` CSS class
1476
+ * is added to indicate that widget has been selected.
1477
+ * * The mouse and keyboard events handling on and around widget elements.
1478
+ */ class Widget extends Plugin {
1479
+ /**
1480
+ * Holds previously selected widgets.
1481
+ */ _previouslySelected = new Set();
1482
+ /**
1483
+ * @inheritDoc
1484
+ */ static get pluginName() {
1457
1485
  return 'Widget';
1458
1486
  }
1459
1487
  /**
1460
- * @inheritDoc
1461
- */ static get requires() {
1488
+ * @inheritDoc
1489
+ */ static get requires() {
1462
1490
  return [
1463
1491
  WidgetTypeAround,
1464
1492
  Delete
1465
1493
  ];
1466
1494
  }
1467
1495
  /**
1468
- * @inheritDoc
1469
- */ init() {
1496
+ * @inheritDoc
1497
+ */ init() {
1470
1498
  const editor = this.editor;
1471
1499
  const view = editor.editing.view;
1472
1500
  const viewDocument = view.document;
@@ -1623,6 +1651,10 @@ class Widget extends Plugin {
1623
1651
  id: 'widget',
1624
1652
  label: t('Keystrokes that can be used when a widget is selected (for example: image, table, etc.)'),
1625
1653
  keystrokes: [
1654
+ {
1655
+ label: t('Move focus from an editable area back to the parent widget'),
1656
+ keystroke: 'Esc'
1657
+ },
1626
1658
  {
1627
1659
  label: t('Insert a new paragraph directly after a widget'),
1628
1660
  keystroke: 'Enter'
@@ -1657,8 +1689,8 @@ class Widget extends Plugin {
1657
1689
  });
1658
1690
  }
1659
1691
  /**
1660
- * Handles {@link module:engine/view/document~Document#event:mousedown mousedown} events on widget elements.
1661
- */ _onMousedown(eventInfo, domEventData) {
1692
+ * Handles {@link module:engine/view/document~Document#event:mousedown mousedown} events on widget elements.
1693
+ */ _onMousedown(eventInfo, domEventData) {
1662
1694
  const editor = this.editor;
1663
1695
  const view = editor.editing.view;
1664
1696
  const viewDocument = view.document;
@@ -1695,8 +1727,8 @@ class Widget extends Plugin {
1695
1727
  this._setSelectionOverElement(modelElement);
1696
1728
  }
1697
1729
  /**
1698
- * Selects entire block content, e.g. on triple click it selects entire paragraph.
1699
- */ _selectBlockContent(element) {
1730
+ * Selects entire block content, e.g. on triple click it selects entire paragraph.
1731
+ */ _selectBlockContent(element) {
1700
1732
  const editor = this.editor;
1701
1733
  const model = editor.model;
1702
1734
  const mapper = editor.editing.mapper;
@@ -1715,14 +1747,14 @@ class Widget extends Plugin {
1715
1747
  return true;
1716
1748
  }
1717
1749
  /**
1718
- * Handles {@link module:engine/view/document~Document#event:keydown keydown} events and changes
1719
- * the model selection when:
1720
- *
1721
- * * arrow key is pressed when the widget is selected,
1722
- * * the selection is next to a widget and the widget should become selected upon the arrow key press.
1723
- *
1724
- * See {@link #_preventDefaultOnArrowKeyPress}.
1725
- */ _handleSelectionChangeOnArrowKeyPress(eventInfo, domEventData) {
1750
+ * Handles {@link module:engine/view/document~Document#event:keydown keydown} events and changes
1751
+ * the model selection when:
1752
+ *
1753
+ * * arrow key is pressed when the widget is selected,
1754
+ * * the selection is next to a widget and the widget should become selected upon the arrow key press.
1755
+ *
1756
+ * See {@link #_preventDefaultOnArrowKeyPress}.
1757
+ */ _handleSelectionChangeOnArrowKeyPress(eventInfo, domEventData) {
1726
1758
  const keyCode = domEventData.keyCode;
1727
1759
  const model = this.editor.model;
1728
1760
  const schema = model.schema;
@@ -1777,12 +1809,12 @@ class Widget extends Plugin {
1777
1809
  }
1778
1810
  }
1779
1811
  /**
1780
- * Handles {@link module:engine/view/document~Document#event:keydown keydown} events and prevents
1781
- * the default browser behavior to make sure the fake selection is not being moved from a fake selection
1782
- * container.
1783
- *
1784
- * See {@link #_handleSelectionChangeOnArrowKeyPress}.
1785
- */ _preventDefaultOnArrowKeyPress(eventInfo, domEventData) {
1812
+ * Handles {@link module:engine/view/document~Document#event:keydown keydown} events and prevents
1813
+ * the default browser behavior to make sure the fake selection is not being moved from a fake selection
1814
+ * container.
1815
+ *
1816
+ * See {@link #_handleSelectionChangeOnArrowKeyPress}.
1817
+ */ _preventDefaultOnArrowKeyPress(eventInfo, domEventData) {
1786
1818
  const model = this.editor.model;
1787
1819
  const schema = model.schema;
1788
1820
  const objectElement = model.document.selection.getSelectedElement();
@@ -1793,11 +1825,11 @@ class Widget extends Plugin {
1793
1825
  }
1794
1826
  }
1795
1827
  /**
1796
- * Handles delete keys: backspace and delete.
1797
- *
1798
- * @param isForward Set to true if delete was performed in forward direction.
1799
- * @returns Returns `true` if keys were handled correctly.
1800
- */ _handleDelete(isForward) {
1828
+ * Handles delete keys: backspace and delete.
1829
+ *
1830
+ * @param isForward Set to true if delete was performed in forward direction.
1831
+ * @returns Returns `true` if keys were handled correctly.
1832
+ */ _handleDelete(isForward) {
1801
1833
  const modelDocument = this.editor.model.document;
1802
1834
  const modelSelection = modelDocument.selection;
1803
1835
  // Do nothing when the read only mode is enabled.
@@ -1824,22 +1856,22 @@ class Widget extends Plugin {
1824
1856
  }
1825
1857
  }
1826
1858
  /**
1827
- * Sets {@link module:engine/model/selection~Selection document's selection} over given element.
1828
- *
1829
- * @internal
1830
- */ _setSelectionOverElement(element) {
1859
+ * Sets {@link module:engine/model/selection~Selection document's selection} over given element.
1860
+ *
1861
+ * @internal
1862
+ */ _setSelectionOverElement(element) {
1831
1863
  this.editor.model.change((writer)=>{
1832
1864
  writer.setSelection(writer.createRangeOn(element));
1833
1865
  });
1834
1866
  }
1835
1867
  /**
1836
- * Checks if {@link module:engine/model/element~Element element} placed next to the current
1837
- * {@link module:engine/model/selection~Selection model selection} exists and is marked in
1838
- * {@link module:engine/model/schema~Schema schema} as `object`.
1839
- *
1840
- * @internal
1841
- * @param forward Direction of checking.
1842
- */ _getObjectElementNextToSelection(forward) {
1868
+ * Checks if {@link module:engine/model/element~Element element} placed next to the current
1869
+ * {@link module:engine/model/selection~Selection model selection} exists and is marked in
1870
+ * {@link module:engine/model/schema~Schema schema} as `object`.
1871
+ *
1872
+ * @internal
1873
+ * @param forward Direction of checking.
1874
+ */ _getObjectElementNextToSelection(forward) {
1843
1875
  const model = this.editor.model;
1844
1876
  const schema = model.schema;
1845
1877
  const modelSelection = model.document.selection;
@@ -1860,16 +1892,16 @@ class Widget extends Plugin {
1860
1892
  return null;
1861
1893
  }
1862
1894
  /**
1863
- * Removes CSS class from previously selected widgets.
1864
- */ _clearPreviouslySelectedWidgets(writer) {
1895
+ * Removes CSS class from previously selected widgets.
1896
+ */ _clearPreviouslySelectedWidgets(writer) {
1865
1897
  for (const widget of this._previouslySelected){
1866
1898
  writer.removeClass(WIDGET_SELECTED_CLASS_NAME, widget);
1867
1899
  }
1868
1900
  this._previouslySelected.clear();
1869
1901
  }
1870
1902
  /**
1871
- * Moves the document selection into the first nested editable.
1872
- */ _selectFirstNestedEditable() {
1903
+ * Moves the document selection into the first nested editable.
1904
+ */ _selectFirstNestedEditable() {
1873
1905
  const editor = this.editor;
1874
1906
  const view = this.editor.editing.view;
1875
1907
  const viewDocument = view.document;
@@ -1890,8 +1922,8 @@ class Widget extends Plugin {
1890
1922
  return false;
1891
1923
  }
1892
1924
  /**
1893
- * Updates the document selection so that it selects first ancestor widget.
1894
- */ _selectAncestorWidget() {
1925
+ * Updates the document selection so that it selects first ancestor widget.
1926
+ */ _selectAncestorWidget() {
1895
1927
  const editor = this.editor;
1896
1928
  const mapper = editor.editing.mapper;
1897
1929
  const selection = editor.editing.view.document.selection;
@@ -1910,12 +1942,6 @@ class Widget extends Plugin {
1910
1942
  });
1911
1943
  return true;
1912
1944
  }
1913
- constructor(){
1914
- super(...arguments);
1915
- /**
1916
- * Holds previously selected widgets.
1917
- */ this._previouslySelected = new Set();
1918
- }
1919
1945
  }
1920
1946
  /**
1921
1947
  * Returns `true` when element is a nested editable or is placed inside one.
@@ -1978,22 +2004,51 @@ class Widget extends Plugin {
1978
2004
  return null;
1979
2005
  }
1980
2006
 
1981
- class WidgetToolbarRepository extends Plugin {
2007
+ /**
2008
+ * Widget toolbar repository plugin. A central point for registering widget toolbars. This plugin handles the whole
2009
+ * toolbar rendering process and exposes a concise API.
2010
+ *
2011
+ * To add a toolbar for your widget use the {@link ~WidgetToolbarRepository#register `WidgetToolbarRepository#register()`} method.
2012
+ *
2013
+ * The following example comes from the {@link module:image/imagetoolbar~ImageToolbar} plugin:
2014
+ *
2015
+ * ```ts
2016
+ * class ImageToolbar extends Plugin {
2017
+ * static get requires() {
2018
+ * return [ WidgetToolbarRepository ];
2019
+ * }
2020
+ *
2021
+ * afterInit() {
2022
+ * const editor = this.editor;
2023
+ * const widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
2024
+ *
2025
+ * widgetToolbarRepository.register( 'image', {
2026
+ * items: editor.config.get( 'image.toolbar' ),
2027
+ * getRelatedElement: getClosestSelectedImageWidget
2028
+ * } );
2029
+ * }
2030
+ * }
2031
+ * ```
2032
+ */ class WidgetToolbarRepository extends Plugin {
2033
+ /**
2034
+ * A map of toolbar definitions.
2035
+ */ _toolbarDefinitions = new Map();
2036
+ _balloon;
1982
2037
  /**
1983
- * @inheritDoc
1984
- */ static get requires() {
2038
+ * @inheritDoc
2039
+ */ static get requires() {
1985
2040
  return [
1986
2041
  ContextualBalloon
1987
2042
  ];
1988
2043
  }
1989
2044
  /**
1990
- * @inheritDoc
1991
- */ static get pluginName() {
2045
+ * @inheritDoc
2046
+ */ static get pluginName() {
1992
2047
  return 'WidgetToolbarRepository';
1993
2048
  }
1994
2049
  /**
1995
- * @inheritDoc
1996
- */ init() {
2050
+ * @inheritDoc
2051
+ */ init() {
1997
2052
  const editor = this.editor;
1998
2053
  // Disables the default balloon toolbar for all widgets.
1999
2054
  if (editor.plugins.has('BalloonToolbar')) {
@@ -2027,35 +2082,35 @@ class WidgetToolbarRepository extends Plugin {
2027
2082
  }
2028
2083
  }
2029
2084
  /**
2030
- * Registers toolbar in the WidgetToolbarRepository. It renders it in the `ContextualBalloon` based on the value of the invoked
2031
- * `getRelatedElement` function. Toolbar items are gathered from `items` array.
2032
- * The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option.
2033
- *
2034
- * Note: This method should be called in the {@link module:core/plugin~PluginInterface#afterInit `Plugin#afterInit()`}
2035
- * callback (or later) to make sure that the given toolbar items were already registered by other plugins.
2036
- *
2037
- * @param toolbarId An id for the toolbar. Used to
2038
- * @param options.ariaLabel Label used by assistive technologies to describe this toolbar element.
2039
- * @param options.items Array of toolbar items.
2040
- * @param options.getRelatedElement Callback which returns an element the toolbar should be attached to.
2041
- * @param options.balloonClassName CSS class for the widget balloon.
2042
- */ register(toolbarId, { ariaLabel, items, getRelatedElement, balloonClassName = 'ck-toolbar-container' }) {
2085
+ * Registers toolbar in the WidgetToolbarRepository. It renders it in the `ContextualBalloon` based on the value of the invoked
2086
+ * `getRelatedElement` function. Toolbar items are gathered from `items` array.
2087
+ * The balloon's CSS class is by default `ck-toolbar-container` and may be override with the `balloonClassName` option.
2088
+ *
2089
+ * Note: This method should be called in the {@link module:core/plugin~PluginInterface#afterInit `Plugin#afterInit()`}
2090
+ * callback (or later) to make sure that the given toolbar items were already registered by other plugins.
2091
+ *
2092
+ * @param toolbarId An id for the toolbar. Used to
2093
+ * @param options.ariaLabel Label used by assistive technologies to describe this toolbar element.
2094
+ * @param options.items Array of toolbar items.
2095
+ * @param options.getRelatedElement Callback which returns an element the toolbar should be attached to.
2096
+ * @param options.balloonClassName CSS class for the widget balloon.
2097
+ */ register(toolbarId, { ariaLabel, items, getRelatedElement, balloonClassName = 'ck-toolbar-container' }) {
2043
2098
  // Trying to register a toolbar without any item.
2044
2099
  if (!items.length) {
2045
2100
  /**
2046
- * When {@link module:widget/widgettoolbarrepository~WidgetToolbarRepository#register registering} a new widget toolbar, you
2047
- * need to provide a non-empty array with the items that will be inserted into the toolbar.
2048
- *
2049
- * If you see this error when integrating the editor, you likely forgot to configure one of the widget toolbars.
2050
- *
2051
- * See for instance:
2052
- *
2053
- * * {@link module:table/tableconfig~TableConfig#contentToolbar `config.table.contentToolbar`}
2054
- * * {@link module:image/imageconfig~ImageConfig#toolbar `config.image.toolbar`}
2055
- *
2056
- * @error widget-toolbar-no-items
2057
- * @param toolbarId The id of the toolbar that has not been configured correctly.
2058
- */ logWarning('widget-toolbar-no-items', {
2101
+ * When {@link module:widget/widgettoolbarrepository~WidgetToolbarRepository#register registering} a new widget toolbar, you
2102
+ * need to provide a non-empty array with the items that will be inserted into the toolbar.
2103
+ *
2104
+ * If you see this error when integrating the editor, you likely forgot to configure one of the widget toolbars.
2105
+ *
2106
+ * See for instance:
2107
+ *
2108
+ * * {@link module:table/tableconfig~TableConfig#contentToolbar `config.table.contentToolbar`}
2109
+ * * {@link module:image/imageconfig~ImageConfig#toolbar `config.image.toolbar`}
2110
+ *
2111
+ * @error widget-toolbar-no-items
2112
+ * @param toolbarId The id of the toolbar that has not been configured correctly.
2113
+ */ logWarning('widget-toolbar-no-items', {
2059
2114
  toolbarId
2060
2115
  });
2061
2116
  return;
@@ -2066,11 +2121,11 @@ class WidgetToolbarRepository extends Plugin {
2066
2121
  toolbarView.ariaLabel = ariaLabel || t('Widget toolbar');
2067
2122
  if (this._toolbarDefinitions.has(toolbarId)) {
2068
2123
  /**
2069
- * Toolbar with the given id was already added.
2070
- *
2071
- * @error widget-toolbar-duplicated
2072
- * @param toolbarId Toolbar id.
2073
- */ throw new CKEditorError('widget-toolbar-duplicated', this, {
2124
+ * Toolbar with the given id was already added.
2125
+ *
2126
+ * @error widget-toolbar-duplicated
2127
+ * @param toolbarId Toolbar id.
2128
+ */ throw new CKEditorError('widget-toolbar-duplicated', this, {
2074
2129
  toolbarId
2075
2130
  });
2076
2131
  }
@@ -2097,8 +2152,8 @@ class WidgetToolbarRepository extends Plugin {
2097
2152
  this._toolbarDefinitions.set(toolbarId, toolbarDefinition);
2098
2153
  }
2099
2154
  /**
2100
- * Iterates over stored toolbars and makes them visible or hidden.
2101
- */ _updateToolbarsVisibility() {
2155
+ * Iterates over stored toolbars and makes them visible or hidden.
2156
+ */ _updateToolbarsVisibility() {
2102
2157
  let maxRelatedElementDepth = 0;
2103
2158
  let deepestRelatedElement = null;
2104
2159
  let deepestToolbarDefinition = null;
@@ -2130,18 +2185,18 @@ class WidgetToolbarRepository extends Plugin {
2130
2185
  }
2131
2186
  }
2132
2187
  /**
2133
- * Hides the given toolbar.
2134
- */ _hideToolbar(toolbarDefinition) {
2188
+ * Hides the given toolbar.
2189
+ */ _hideToolbar(toolbarDefinition) {
2135
2190
  this._balloon.remove(toolbarDefinition.view);
2136
2191
  this.stopListening(this._balloon, 'change:visibleView');
2137
2192
  }
2138
2193
  /**
2139
- * Shows up the toolbar if the toolbar is not visible.
2140
- * Otherwise, repositions the toolbar's balloon when toolbar's view is the most top view in balloon stack.
2141
- *
2142
- * It might happen here that the toolbar's view is under another view. Then do nothing as the other toolbar view
2143
- * should be still visible after the {@link module:ui/editorui/editorui~EditorUI#event:update}.
2144
- */ _showToolbar(toolbarDefinition, relatedElement) {
2194
+ * Shows up the toolbar if the toolbar is not visible.
2195
+ * Otherwise, repositions the toolbar's balloon when toolbar's view is the most top view in balloon stack.
2196
+ *
2197
+ * It might happen here that the toolbar's view is under another view. Then do nothing as the other toolbar view
2198
+ * should be still visible after the {@link module:ui/editorui/editorui~EditorUI#event:update}.
2199
+ */ _showToolbar(toolbarDefinition, relatedElement) {
2145
2200
  if (this._isToolbarVisible(toolbarDefinition)) {
2146
2201
  repositionContextualBalloon(this.editor, relatedElement);
2147
2202
  } else if (!this._isToolbarInBalloon(toolbarDefinition)) {
@@ -2174,12 +2229,6 @@ class WidgetToolbarRepository extends Plugin {
2174
2229
  _isToolbarInBalloon(toolbar) {
2175
2230
  return this._balloon.hasView(toolbar.view);
2176
2231
  }
2177
- constructor(){
2178
- super(...arguments);
2179
- /**
2180
- * A map of toolbar definitions.
2181
- */ this._toolbarDefinitions = new Map();
2182
- }
2183
2232
  }
2184
2233
  function repositionContextualBalloon(editor, relatedElement) {
2185
2234
  const balloon = editor.plugins.get('ContextualBalloon');
@@ -2207,31 +2256,76 @@ function isWidgetSelected(selection) {
2207
2256
  return !!(viewElement && isWidget(viewElement));
2208
2257
  }
2209
2258
 
2210
- class ResizeState extends ObservableMixin() {
2259
+ /**
2260
+ * Stores the internal state of a single resizable object.
2261
+ */ class ResizeState extends /* #__PURE__ */ ObservableMixin() {
2262
+ /**
2263
+ * The reference point of the resizer where the dragging started. It is used to measure the distance the user cursor
2264
+ * traveled, so how much the image should be enlarged.
2265
+ * This information is only known after the DOM was rendered, so it will be updated later.
2266
+ *
2267
+ * @internal
2268
+ */ _referenceCoordinates;
2269
+ /**
2270
+ * Resizer options.
2271
+ */ _options;
2272
+ /**
2273
+ * The original width (pixels) of the resized object when the resize process was started.
2274
+ *
2275
+ * @readonly
2276
+ */ _originalWidth;
2277
+ /**
2278
+ * The original height (pixels) of the resized object when the resize process was started.
2279
+ *
2280
+ * @readonly
2281
+ */ _originalHeight;
2282
+ /**
2283
+ * The original width (percents) of the resized object when the resize process was started.
2284
+ *
2285
+ * @readonly
2286
+ */ _originalWidthPercents;
2287
+ /**
2288
+ * A width to height ratio of the resized image.
2289
+ *
2290
+ * @readonly
2291
+ */ _aspectRatio;
2292
+ /**
2293
+ * @param options Resizer options.
2294
+ */ constructor(options){
2295
+ super();
2296
+ this.set('activeHandlePosition', null);
2297
+ this.set('proposedWidthPercents', null);
2298
+ this.set('proposedWidth', null);
2299
+ this.set('proposedHeight', null);
2300
+ this.set('proposedHandleHostWidth', null);
2301
+ this.set('proposedHandleHostHeight', null);
2302
+ this._options = options;
2303
+ this._referenceCoordinates = null;
2304
+ }
2211
2305
  /**
2212
- * The original width (pixels) of the resized object when the resize process was started.
2213
- */ get originalWidth() {
2306
+ * The original width (pixels) of the resized object when the resize process was started.
2307
+ */ get originalWidth() {
2214
2308
  return this._originalWidth;
2215
2309
  }
2216
2310
  /**
2217
- * The original height (pixels) of the resized object when the resize process was started.
2218
- */ get originalHeight() {
2311
+ * The original height (pixels) of the resized object when the resize process was started.
2312
+ */ get originalHeight() {
2219
2313
  return this._originalHeight;
2220
2314
  }
2221
2315
  /**
2222
- * The original width (percents) of the resized object when the resize process was started.
2223
- */ get originalWidthPercents() {
2316
+ * The original width (percents) of the resized object when the resize process was started.
2317
+ */ get originalWidthPercents() {
2224
2318
  return this._originalWidthPercents;
2225
2319
  }
2226
2320
  /**
2227
- * A width to height ratio of the resized image.
2228
- */ get aspectRatio() {
2321
+ * A width to height ratio of the resized image.
2322
+ */ get aspectRatio() {
2229
2323
  return this._aspectRatio;
2230
2324
  }
2231
2325
  /**
2232
- *
2233
- * @param domResizeHandle The handle used to calculate the reference point.
2234
- */ begin(domResizeHandle, domHandleHost, domResizeHost) {
2326
+ *
2327
+ * @param domResizeHandle The handle used to calculate the reference point.
2328
+ */ begin(domResizeHandle, domHandleHost, domResizeHost) {
2235
2329
  const clientRect = new Rect(domHandleHost);
2236
2330
  this.activeHandlePosition = getHandlePosition(domResizeHandle);
2237
2331
  this._referenceCoordinates = getAbsoluteBoundaryPoint(domHandleHost, getOppositePosition(this.activeHandlePosition));
@@ -2252,19 +2346,6 @@ class ResizeState extends ObservableMixin() {
2252
2346
  this.proposedHandleHostWidth = newSize.handleHostWidth;
2253
2347
  this.proposedHandleHostHeight = newSize.handleHostHeight;
2254
2348
  }
2255
- /**
2256
- * @param options Resizer options.
2257
- */ constructor(options){
2258
- super();
2259
- this.set('activeHandlePosition', null);
2260
- this.set('proposedWidthPercents', null);
2261
- this.set('proposedWidth', null);
2262
- this.set('proposedHeight', null);
2263
- this.set('proposedHandleHostWidth', null);
2264
- this.set('proposedHandleHostHeight', null);
2265
- this._options = options;
2266
- this._referenceCoordinates = null;
2267
- }
2268
2349
  }
2269
2350
  /**
2270
2351
  * Returns coordinates of the top-left corner of an element, relative to the document's top-left corner.
@@ -2319,33 +2400,9 @@ class ResizeState extends ObservableMixin() {
2319
2400
  return `${replacements[parts[0]]}-${replacements[parts[1]]}`;
2320
2401
  }
2321
2402
 
2322
- class SizeView extends View {
2323
- /**
2324
- * A method used for binding the `SizeView` instance properties to the `ResizeState` instance observable properties.
2325
- *
2326
- * @internal
2327
- * @param options An object defining the resizer options, used for setting the proper size label.
2328
- * @param resizeState The `ResizeState` class instance, used for keeping the `SizeView` state up to date.
2329
- */ _bindToState(options, resizeState) {
2330
- this.bind('_isVisible').to(resizeState, 'proposedWidth', resizeState, 'proposedHeight', (width, height)=>width !== null && height !== null);
2331
- this.bind('_label').to(resizeState, 'proposedHandleHostWidth', resizeState, 'proposedHandleHostHeight', resizeState, 'proposedWidthPercents', (width, height, widthPercents)=>{
2332
- if (options.unit === 'px') {
2333
- return `${width}×${height}`;
2334
- } else {
2335
- return `${widthPercents}%`;
2336
- }
2337
- });
2338
- this.bind('_viewPosition').to(resizeState, 'activeHandlePosition', resizeState, 'proposedHandleHostWidth', resizeState, 'proposedHandleHostHeight', // If the widget is too small to contain the size label, display the label above.
2339
- (position, width, height)=>width < 50 || height < 50 ? 'above-center' : position);
2340
- }
2341
- /**
2342
- * A method used for cleaning up. It removes the bindings and hides the view.
2343
- *
2344
- * @internal
2345
- */ _dismiss() {
2346
- this.unbind();
2347
- this._isVisible = false;
2348
- }
2403
+ /**
2404
+ * A view displaying the proposed new element size during the resizing.
2405
+ */ class SizeView extends View {
2349
2406
  constructor(){
2350
2407
  super();
2351
2408
  const bind = this.bindTemplate;
@@ -2368,35 +2425,103 @@ class SizeView extends View {
2368
2425
  ]
2369
2426
  });
2370
2427
  }
2428
+ /**
2429
+ * A method used for binding the `SizeView` instance properties to the `ResizeState` instance observable properties.
2430
+ *
2431
+ * @internal
2432
+ * @param options An object defining the resizer options, used for setting the proper size label.
2433
+ * @param resizeState The `ResizeState` class instance, used for keeping the `SizeView` state up to date.
2434
+ */ _bindToState(options, resizeState) {
2435
+ this.bind('_isVisible').to(resizeState, 'proposedWidth', resizeState, 'proposedHeight', (width, height)=>width !== null && height !== null);
2436
+ this.bind('_label').to(resizeState, 'proposedHandleHostWidth', resizeState, 'proposedHandleHostHeight', resizeState, 'proposedWidthPercents', (width, height, widthPercents)=>{
2437
+ if (options.unit === 'px') {
2438
+ return `${width}×${height}`;
2439
+ } else {
2440
+ return `${widthPercents}%`;
2441
+ }
2442
+ });
2443
+ this.bind('_viewPosition').to(resizeState, 'activeHandlePosition', resizeState, 'proposedHandleHostWidth', resizeState, 'proposedHandleHostHeight', // If the widget is too small to contain the size label, display the label above.
2444
+ (position, width, height)=>width < 50 || height < 50 ? 'above-center' : position);
2445
+ }
2446
+ /**
2447
+ * A method used for cleaning up. It removes the bindings and hides the view.
2448
+ *
2449
+ * @internal
2450
+ */ _dismiss() {
2451
+ this.unbind();
2452
+ this._isVisible = false;
2453
+ }
2371
2454
  }
2372
2455
 
2373
- class Resizer extends ObservableMixin() {
2456
+ /**
2457
+ * Represents a resizer for a single resizable object.
2458
+ */ class Resizer extends /* #__PURE__ */ ObservableMixin() {
2459
+ /**
2460
+ * Stores the state of the resizable host geometry, such as the original width, the currently proposed height, etc.
2461
+ *
2462
+ * Note that a new state is created for each resize transaction.
2463
+ */ _state;
2464
+ /**
2465
+ * A view displaying the proposed new element size during the resizing.
2466
+ */ _sizeView;
2467
+ /**
2468
+ * Options passed to the {@link #constructor}.
2469
+ */ _options;
2470
+ /**
2471
+ * A wrapper that is controlled by the resizer. This is usually a widget element.
2472
+ */ _viewResizerWrapper = null;
2473
+ /**
2474
+ * The width of the resized {@link module:widget/widgetresize~ResizerOptions#viewElement viewElement} before the resizing started.
2475
+ */ _initialViewWidth;
2476
+ /**
2477
+ * @param options Resizer options.
2478
+ */ constructor(options){
2479
+ super();
2480
+ this._options = options;
2481
+ this.set('isEnabled', true);
2482
+ this.set('isSelected', false);
2483
+ this.bind('isVisible').to(this, 'isEnabled', this, 'isSelected', (isEnabled, isSelected)=>isEnabled && isSelected);
2484
+ this.decorate('begin');
2485
+ this.decorate('cancel');
2486
+ this.decorate('commit');
2487
+ this.decorate('updateSize');
2488
+ this.on('commit', (event)=>{
2489
+ // State might not be initialized yet. In this case, prevent further handling and make sure that the resizer is
2490
+ // cleaned up (#5195).
2491
+ if (!this.state.proposedWidth && !this.state.proposedWidthPercents) {
2492
+ this._cleanup();
2493
+ event.stop();
2494
+ }
2495
+ }, {
2496
+ priority: 'high'
2497
+ });
2498
+ }
2374
2499
  /**
2375
- * Stores the state of the resizable host geometry, such as the original width, the currently proposed height, etc.
2376
- *
2377
- * Note that a new state is created for each resize transaction.
2378
- */ get state() {
2500
+ * Stores the state of the resizable host geometry, such as the original width, the currently proposed height, etc.
2501
+ *
2502
+ * Note that a new state is created for each resize transaction.
2503
+ */ get state() {
2379
2504
  return this._state;
2380
2505
  }
2381
2506
  /**
2382
- * Makes resizer visible in the UI.
2383
- */ show() {
2507
+ * Makes resizer visible in the UI.
2508
+ */ show() {
2384
2509
  const editingView = this._options.editor.editing.view;
2385
2510
  editingView.change((writer)=>{
2386
2511
  writer.removeClass('ck-hidden', this._viewResizerWrapper);
2387
2512
  });
2388
2513
  }
2389
2514
  /**
2390
- * Hides resizer in the UI.
2391
- */ hide() {
2515
+ * Hides resizer in the UI.
2516
+ */ hide() {
2392
2517
  const editingView = this._options.editor.editing.view;
2393
2518
  editingView.change((writer)=>{
2394
2519
  writer.addClass('ck-hidden', this._viewResizerWrapper);
2395
2520
  });
2396
2521
  }
2397
2522
  /**
2398
- * Attaches the resizer to the DOM.
2399
- */ attach() {
2523
+ * Attaches the resizer to the DOM.
2524
+ */ attach() {
2400
2525
  // eslint-disable-next-line @typescript-eslint/no-this-alias
2401
2526
  const that = this;
2402
2527
  const widgetElement = this._options.viewElement;
@@ -2428,23 +2553,23 @@ class Resizer extends ObservableMixin() {
2428
2553
  });
2429
2554
  }
2430
2555
  /**
2431
- * Starts the resizing process.
2432
- *
2433
- * Creates a new {@link #state} for the current process.
2434
- *
2435
- * @fires begin
2436
- * @param domResizeHandle Clicked handle.
2437
- */ begin(domResizeHandle) {
2556
+ * Starts the resizing process.
2557
+ *
2558
+ * Creates a new {@link #state} for the current process.
2559
+ *
2560
+ * @fires begin
2561
+ * @param domResizeHandle Clicked handle.
2562
+ */ begin(domResizeHandle) {
2438
2563
  this._state = new ResizeState(this._options);
2439
2564
  this._sizeView._bindToState(this._options, this.state);
2440
2565
  this._initialViewWidth = this._options.viewElement.getStyle('width');
2441
2566
  this.state.begin(domResizeHandle, this._getHandleHost(), this._getResizeHost());
2442
2567
  }
2443
2568
  /**
2444
- * Updates the proposed size based on `domEventData`.
2445
- *
2446
- * @fires updateSize
2447
- */ updateSize(domEventData) {
2569
+ * Updates the proposed size based on `domEventData`.
2570
+ *
2571
+ * @fires updateSize
2572
+ */ updateSize(domEventData) {
2448
2573
  const newSize = this._proposeNewSize(domEventData);
2449
2574
  const editingView = this._options.editor.editing.view;
2450
2575
  editingView.change((writer)=>{
@@ -2471,10 +2596,10 @@ class Resizer extends ObservableMixin() {
2471
2596
  });
2472
2597
  }
2473
2598
  /**
2474
- * Applies the geometry proposed with the resizer.
2475
- *
2476
- * @fires commit
2477
- */ commit() {
2599
+ * Applies the geometry proposed with the resizer.
2600
+ *
2601
+ * @fires commit
2602
+ */ commit() {
2478
2603
  const unit = this._options.unit || '%';
2479
2604
  const newValue = (unit === '%' ? this.state.proposedWidthPercents : this.state.proposedWidth) + unit;
2480
2605
  // Both cleanup and onCommit callback are very likely to make view changes. Ensure that it is made in a single step.
@@ -2484,22 +2609,22 @@ class Resizer extends ObservableMixin() {
2484
2609
  });
2485
2610
  }
2486
2611
  /**
2487
- * Cancels and rejects the proposed resize dimensions, hiding the UI.
2488
- *
2489
- * @fires cancel
2490
- */ cancel() {
2612
+ * Cancels and rejects the proposed resize dimensions, hiding the UI.
2613
+ *
2614
+ * @fires cancel
2615
+ */ cancel() {
2491
2616
  this._cleanup();
2492
2617
  }
2493
2618
  /**
2494
- * Destroys the resizer.
2495
- */ destroy() {
2619
+ * Destroys the resizer.
2620
+ */ destroy() {
2496
2621
  this.cancel();
2497
2622
  }
2498
2623
  /**
2499
- * Redraws the resizer.
2500
- *
2501
- * @param handleHostRect Handle host rectangle might be given to improve performance.
2502
- */ redraw(handleHostRect) {
2624
+ * Redraws the resizer.
2625
+ *
2626
+ * @param handleHostRect Handle host rectangle might be given to improve performance.
2627
+ */ redraw(handleHostRect) {
2503
2628
  const domWrapper = this._domResizerWrapper;
2504
2629
  // Refresh only if resizer exists in the DOM.
2505
2630
  if (!existsInDom(domWrapper)) {
@@ -2554,8 +2679,8 @@ class Resizer extends ObservableMixin() {
2554
2679
  return domElement.classList.contains('ck-widget__resizer__handle');
2555
2680
  }
2556
2681
  /**
2557
- * Cleans up the context state.
2558
- */ _cleanup() {
2682
+ * Cleans up the context state.
2683
+ */ _cleanup() {
2559
2684
  this._sizeView._dismiss();
2560
2685
  const editingView = this._options.editor.editing.view;
2561
2686
  editingView.change((writer)=>{
@@ -2563,10 +2688,10 @@ class Resizer extends ObservableMixin() {
2563
2688
  });
2564
2689
  }
2565
2690
  /**
2566
- * Calculates the proposed size as the resize handles are dragged.
2567
- *
2568
- * @param domEventData Event data that caused the size update request. It should be used to calculate the proposed size.
2569
- */ _proposeNewSize(domEventData) {
2691
+ * Calculates the proposed size as the resize handles are dragged.
2692
+ *
2693
+ * @param domEventData Event data that caused the size update request. It should be used to calculate the proposed size.
2694
+ */ _proposeNewSize(domEventData) {
2570
2695
  const state = this.state;
2571
2696
  const currentCoordinates = extractCoordinates(domEventData);
2572
2697
  const isCentered = this._options.isCentered ? this._options.isCentered(this) : true;
@@ -2612,37 +2737,37 @@ class Resizer extends ObservableMixin() {
2612
2737
  };
2613
2738
  }
2614
2739
  /**
2615
- * Obtains the resize host.
2616
- *
2617
- * Resize host is an object that receives dimensions which are the result of resizing.
2618
- */ _getResizeHost() {
2740
+ * Obtains the resize host.
2741
+ *
2742
+ * Resize host is an object that receives dimensions which are the result of resizing.
2743
+ */ _getResizeHost() {
2619
2744
  const widgetWrapper = this._domResizerWrapper.parentElement;
2620
2745
  return this._options.getResizeHost(widgetWrapper);
2621
2746
  }
2622
2747
  /**
2623
- * Obtains the handle host.
2624
- *
2625
- * Handle host is an object that the handles are aligned to.
2626
- *
2627
- * Handle host will not always be an entire widget itself. Take an image as an example. The image widget
2628
- * contains an image and a caption. Only the image should be surrounded with handles.
2629
- */ _getHandleHost() {
2748
+ * Obtains the handle host.
2749
+ *
2750
+ * Handle host is an object that the handles are aligned to.
2751
+ *
2752
+ * Handle host will not always be an entire widget itself. Take an image as an example. The image widget
2753
+ * contains an image and a caption. Only the image should be surrounded with handles.
2754
+ */ _getHandleHost() {
2630
2755
  const widgetWrapper = this._domResizerWrapper.parentElement;
2631
2756
  return this._options.getHandleHost(widgetWrapper);
2632
2757
  }
2633
2758
  /**
2634
- * DOM container of the entire resize UI.
2635
- *
2636
- * Note that this property will have a value only after the element bound with the resizer is rendered
2637
- * (otherwise `null`).
2638
- */ get _domResizerWrapper() {
2759
+ * DOM container of the entire resize UI.
2760
+ *
2761
+ * Note that this property will have a value only after the element bound with the resizer is rendered
2762
+ * (otherwise `null`).
2763
+ */ get _domResizerWrapper() {
2639
2764
  return this._options.editor.editing.view.domConverter.mapViewToDom(this._viewResizerWrapper);
2640
2765
  }
2641
2766
  /**
2642
- * Renders the resize handles in the DOM.
2643
- *
2644
- * @param domElement The resizer wrapper.
2645
- */ _appendHandles(domElement) {
2767
+ * Renders the resize handles in the DOM.
2768
+ *
2769
+ * @param domElement The resizer wrapper.
2770
+ */ _appendHandles(domElement) {
2646
2771
  const resizerPositions = [
2647
2772
  'top-left',
2648
2773
  'top-right',
@@ -2659,39 +2784,13 @@ class Resizer extends ObservableMixin() {
2659
2784
  }
2660
2785
  }
2661
2786
  /**
2662
- * Sets up the {@link #_sizeView} property and adds it to the passed `domElement`.
2663
- */ _appendSizeUI(domElement) {
2787
+ * Sets up the {@link #_sizeView} property and adds it to the passed `domElement`.
2788
+ */ _appendSizeUI(domElement) {
2664
2789
  this._sizeView = new SizeView();
2665
2790
  // Make sure icon#element is rendered before passing to appendChild().
2666
2791
  this._sizeView.render();
2667
2792
  domElement.appendChild(this._sizeView.element);
2668
2793
  }
2669
- /**
2670
- * @param options Resizer options.
2671
- */ constructor(options){
2672
- super();
2673
- /**
2674
- * A wrapper that is controlled by the resizer. This is usually a widget element.
2675
- */ this._viewResizerWrapper = null;
2676
- this._options = options;
2677
- this.set('isEnabled', true);
2678
- this.set('isSelected', false);
2679
- this.bind('isVisible').to(this, 'isEnabled', this, 'isSelected', (isEnabled, isSelected)=>isEnabled && isSelected);
2680
- this.decorate('begin');
2681
- this.decorate('cancel');
2682
- this.decorate('commit');
2683
- this.decorate('updateSize');
2684
- this.on('commit', (event)=>{
2685
- // State might not be initialized yet. In this case, prevent further handling and make sure that the resizer is
2686
- // cleaned up (#5195).
2687
- if (!this.state.proposedWidth && !this.state.proposedWidthPercents) {
2688
- this._cleanup();
2689
- event.stop();
2690
- }
2691
- }, {
2692
- priority: 'high'
2693
- });
2694
- }
2695
2794
  }
2696
2795
  /**
2697
2796
  * @param resizerPosition Expected resizer position like `"top-left"`, `"bottom-right"`.
@@ -2709,15 +2808,24 @@ function existsInDom(element) {
2709
2808
  return element && element.ownerDocument && element.ownerDocument.contains(element);
2710
2809
  }
2711
2810
 
2712
- class WidgetResize extends Plugin {
2811
+ /**
2812
+ * The widget resize feature plugin.
2813
+ *
2814
+ * Use the {@link module:widget/widgetresize~WidgetResize#attachTo} method to create a resizer for the specified widget.
2815
+ */ class WidgetResize extends Plugin {
2713
2816
  /**
2714
- * @inheritDoc
2715
- */ static get pluginName() {
2817
+ * A map of resizers created using this plugin instance.
2818
+ */ _resizers = new Map();
2819
+ _observer;
2820
+ _redrawSelectedResizerThrottled;
2821
+ /**
2822
+ * @inheritDoc
2823
+ */ static get pluginName() {
2716
2824
  return 'WidgetResize';
2717
2825
  }
2718
2826
  /**
2719
- * @inheritDoc
2720
- */ init() {
2827
+ * @inheritDoc
2828
+ */ init() {
2721
2829
  const editing = this.editor.editing;
2722
2830
  const domDocument = global.window.document;
2723
2831
  this.set('selectedResizer', null);
@@ -2759,15 +2867,15 @@ class WidgetResize extends Plugin {
2759
2867
  });
2760
2868
  }
2761
2869
  /**
2762
- * Redraws the selected resizer if there is any selected resizer and if it is visible.
2763
- */ redrawSelectedResizer() {
2870
+ * Redraws the selected resizer if there is any selected resizer and if it is visible.
2871
+ */ redrawSelectedResizer() {
2764
2872
  if (this.selectedResizer && this.selectedResizer.isVisible) {
2765
2873
  this.selectedResizer.redraw();
2766
2874
  }
2767
2875
  }
2768
2876
  /**
2769
- * @inheritDoc
2770
- */ destroy() {
2877
+ * @inheritDoc
2878
+ */ destroy() {
2771
2879
  super.destroy();
2772
2880
  this._observer.stopListening();
2773
2881
  for (const resizer of this._resizers.values()){
@@ -2776,23 +2884,23 @@ class WidgetResize extends Plugin {
2776
2884
  this._redrawSelectedResizerThrottled.cancel();
2777
2885
  }
2778
2886
  /**
2779
- * Marks resizer as selected.
2780
- */ select(resizer) {
2887
+ * Marks resizer as selected.
2888
+ */ select(resizer) {
2781
2889
  this.deselect();
2782
2890
  this.selectedResizer = resizer;
2783
2891
  this.selectedResizer.isSelected = true;
2784
2892
  }
2785
2893
  /**
2786
- * Deselects currently set resizer.
2787
- */ deselect() {
2894
+ * Deselects currently set resizer.
2895
+ */ deselect() {
2788
2896
  if (this.selectedResizer) {
2789
2897
  this.selectedResizer.isSelected = false;
2790
2898
  }
2791
2899
  this.selectedResizer = null;
2792
2900
  }
2793
2901
  /**
2794
- * @param options Resizer options.
2795
- */ attachTo(options) {
2902
+ * @param options Resizer options.
2903
+ */ attachTo(options) {
2796
2904
  const resizer = new Resizer(options);
2797
2905
  const plugins = this.editor.plugins;
2798
2906
  resizer.attach();
@@ -2826,15 +2934,15 @@ class WidgetResize extends Plugin {
2826
2934
  return resizer;
2827
2935
  }
2828
2936
  /**
2829
- * Returns a resizer created for a given view element (widget element).
2830
- *
2831
- * @param viewElement View element associated with the resizer.
2832
- */ getResizerByViewElement(viewElement) {
2937
+ * Returns a resizer created for a given view element (widget element).
2938
+ *
2939
+ * @param viewElement View element associated with the resizer.
2940
+ */ getResizerByViewElement(viewElement) {
2833
2941
  return this._resizers.get(viewElement);
2834
2942
  }
2835
2943
  /**
2836
- * Returns a resizer that contains a given resize handle.
2837
- */ _getResizerByHandle(domResizeHandle) {
2944
+ * Returns a resizer that contains a given resize handle.
2945
+ */ _getResizerByHandle(domResizeHandle) {
2838
2946
  for (const resizer of this._resizers.values()){
2839
2947
  if (resizer.containsHandle(domResizeHandle)) {
2840
2948
  return resizer;
@@ -2842,8 +2950,8 @@ class WidgetResize extends Plugin {
2842
2950
  }
2843
2951
  }
2844
2952
  /**
2845
- * @param domEventData Native DOM event.
2846
- */ _mouseDownListener(event, domEventData) {
2953
+ * @param domEventData Native DOM event.
2954
+ */ _mouseDownListener(event, domEventData) {
2847
2955
  const resizeHandle = domEventData.domTarget;
2848
2956
  if (!Resizer.isResizeHandle(resizeHandle)) {
2849
2957
  return;
@@ -2857,8 +2965,8 @@ class WidgetResize extends Plugin {
2857
2965
  }
2858
2966
  }
2859
2967
  /**
2860
- * @param domEventData Native DOM event.
2861
- */ _mouseMoveListener(event, domEventData) {
2968
+ * @param domEventData Native DOM event.
2969
+ */ _mouseMoveListener(event, domEventData) {
2862
2970
  if (this._activeResizer) {
2863
2971
  this._activeResizer.updateSize(domEventData);
2864
2972
  }
@@ -2869,12 +2977,6 @@ class WidgetResize extends Plugin {
2869
2977
  this._activeResizer = null;
2870
2978
  }
2871
2979
  }
2872
- constructor(){
2873
- super(...arguments);
2874
- /**
2875
- * A map of resizers created using this plugin instance.
2876
- */ this._resizers = new Map();
2877
- }
2878
2980
  }
2879
2981
 
2880
2982
  export { WIDGET_CLASS_NAME, WIDGET_SELECTED_CLASS_NAME, Widget, WidgetResize, WidgetToolbarRepository, WidgetTypeAround, calculateResizeHostAncestorWidth, calculateResizeHostPercentageWidth, findOptimalInsertionRange, getLabel, isWidget, setHighlightHandling, setLabel, toWidget, toWidgetEditable, viewToModelPositionOutsideModelElement };