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

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 (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 };