@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.
- package/README.md +6 -0
- package/dist/index.js +581 -479
- package/dist/index.js.map +1 -1
- package/dist/translations/ar.js +1 -1
- package/dist/translations/ar.umd.js +1 -1
- package/dist/translations/az.js +1 -1
- package/dist/translations/az.umd.js +1 -1
- package/dist/translations/bg.js +1 -1
- package/dist/translations/bg.umd.js +1 -1
- package/dist/translations/bn.js +1 -1
- package/dist/translations/bn.umd.js +1 -1
- package/dist/translations/ca.js +1 -1
- package/dist/translations/ca.umd.js +1 -1
- package/dist/translations/cs.js +1 -1
- package/dist/translations/cs.umd.js +1 -1
- package/dist/translations/da.js +1 -1
- package/dist/translations/da.umd.js +1 -1
- package/dist/translations/de-ch.js +1 -1
- package/dist/translations/de-ch.umd.js +1 -1
- package/dist/translations/de.js +1 -1
- package/dist/translations/de.umd.js +1 -1
- package/dist/translations/el.js +1 -1
- package/dist/translations/el.umd.js +1 -1
- package/dist/translations/en-au.js +1 -1
- package/dist/translations/en-au.umd.js +1 -1
- package/dist/translations/en.js +1 -1
- package/dist/translations/en.umd.js +1 -1
- package/dist/translations/es.js +1 -1
- package/dist/translations/es.umd.js +1 -1
- package/dist/translations/et.js +1 -1
- package/dist/translations/et.umd.js +1 -1
- package/dist/translations/fa.js +1 -1
- package/dist/translations/fa.umd.js +1 -1
- package/dist/translations/fi.js +1 -1
- package/dist/translations/fi.umd.js +1 -1
- package/dist/translations/fr.js +1 -1
- package/dist/translations/fr.umd.js +1 -1
- package/dist/translations/gl.js +1 -1
- package/dist/translations/gl.umd.js +1 -1
- package/dist/translations/he.js +1 -1
- package/dist/translations/he.umd.js +1 -1
- package/dist/translations/hi.js +1 -1
- package/dist/translations/hi.umd.js +1 -1
- package/dist/translations/hr.js +1 -1
- package/dist/translations/hr.umd.js +1 -1
- package/dist/translations/hu.js +1 -1
- package/dist/translations/hu.umd.js +1 -1
- package/dist/translations/id.js +1 -1
- package/dist/translations/id.umd.js +1 -1
- package/dist/translations/it.js +1 -1
- package/dist/translations/it.umd.js +1 -1
- package/dist/translations/ja.js +1 -1
- package/dist/translations/ja.umd.js +1 -1
- package/dist/translations/ko.js +1 -1
- package/dist/translations/ko.umd.js +1 -1
- package/dist/translations/ku.js +1 -1
- package/dist/translations/ku.umd.js +1 -1
- package/dist/translations/lt.js +1 -1
- package/dist/translations/lt.umd.js +1 -1
- package/dist/translations/lv.js +1 -1
- package/dist/translations/lv.umd.js +1 -1
- package/dist/translations/ms.js +1 -1
- package/dist/translations/ms.umd.js +1 -1
- package/dist/translations/nl.js +1 -1
- package/dist/translations/nl.umd.js +1 -1
- package/dist/translations/no.js +1 -1
- package/dist/translations/no.umd.js +1 -1
- package/dist/translations/pl.js +1 -1
- package/dist/translations/pl.umd.js +1 -1
- package/dist/translations/pt-br.js +1 -1
- package/dist/translations/pt-br.umd.js +1 -1
- package/dist/translations/pt.js +1 -1
- package/dist/translations/pt.umd.js +1 -1
- package/dist/translations/ro.js +1 -1
- package/dist/translations/ro.umd.js +1 -1
- package/dist/translations/ru.js +1 -1
- package/dist/translations/ru.umd.js +1 -1
- package/dist/translations/sk.js +1 -1
- package/dist/translations/sk.umd.js +1 -1
- package/dist/translations/sq.js +1 -1
- package/dist/translations/sq.umd.js +1 -1
- package/dist/translations/sr-latn.js +1 -1
- package/dist/translations/sr-latn.umd.js +1 -1
- package/dist/translations/sr.js +1 -1
- package/dist/translations/sr.umd.js +1 -1
- package/dist/translations/sv.js +1 -1
- package/dist/translations/sv.umd.js +1 -1
- package/dist/translations/th.js +1 -1
- package/dist/translations/th.umd.js +1 -1
- package/dist/translations/tk.js +1 -1
- package/dist/translations/tk.umd.js +1 -1
- package/dist/translations/tr.js +1 -1
- package/dist/translations/tr.umd.js +1 -1
- package/dist/translations/uk.js +1 -1
- package/dist/translations/uk.umd.js +1 -1
- package/dist/translations/ur.js +1 -1
- package/dist/translations/ur.umd.js +1 -1
- package/dist/translations/uz.js +1 -1
- package/dist/translations/uz.umd.js +1 -1
- package/dist/translations/vi.js +1 -1
- package/dist/translations/vi.umd.js +1 -1
- package/dist/translations/zh-cn.js +1 -1
- package/dist/translations/zh-cn.umd.js +1 -1
- package/dist/translations/zh.js +1 -1
- package/dist/translations/zh.umd.js +1 -1
- package/dist/types/highlightstack.d.ts +1 -1
- package/dist/types/widgetresize/resizer.d.ts +1 -1
- package/dist/types/widgetresize/resizerstate.d.ts +1 -1
- package/lang/contexts.json +2 -1
- package/lang/translations/ar.po +4 -0
- package/lang/translations/az.po +4 -0
- package/lang/translations/bg.po +4 -0
- package/lang/translations/bn.po +4 -0
- package/lang/translations/ca.po +4 -0
- package/lang/translations/cs.po +4 -0
- package/lang/translations/da.po +4 -0
- package/lang/translations/de-ch.po +4 -0
- package/lang/translations/de.po +4 -0
- package/lang/translations/el.po +4 -0
- package/lang/translations/en-au.po +4 -0
- package/lang/translations/en.po +4 -0
- package/lang/translations/es.po +4 -0
- package/lang/translations/et.po +4 -0
- package/lang/translations/fa.po +4 -0
- package/lang/translations/fi.po +4 -0
- package/lang/translations/fr.po +4 -0
- package/lang/translations/gl.po +4 -0
- package/lang/translations/he.po +4 -0
- package/lang/translations/hi.po +4 -0
- package/lang/translations/hr.po +4 -0
- package/lang/translations/hu.po +4 -0
- package/lang/translations/id.po +4 -0
- package/lang/translations/it.po +4 -0
- package/lang/translations/ja.po +4 -0
- package/lang/translations/ko.po +4 -0
- package/lang/translations/ku.po +4 -0
- package/lang/translations/lt.po +4 -0
- package/lang/translations/lv.po +4 -0
- package/lang/translations/ms.po +4 -0
- package/lang/translations/nl.po +4 -0
- package/lang/translations/no.po +4 -0
- package/lang/translations/pl.po +4 -0
- package/lang/translations/pt-br.po +4 -0
- package/lang/translations/pt.po +4 -0
- package/lang/translations/ro.po +4 -0
- package/lang/translations/ru.po +4 -0
- package/lang/translations/sk.po +4 -0
- package/lang/translations/sq.po +4 -0
- package/lang/translations/sr-latn.po +4 -0
- package/lang/translations/sr.po +4 -0
- package/lang/translations/sv.po +4 -0
- package/lang/translations/th.po +4 -0
- package/lang/translations/tk.po +4 -0
- package/lang/translations/tr.po +4 -0
- package/lang/translations/uk.po +4 -0
- package/lang/translations/ur.po +4 -0
- package/lang/translations/uz.po +4 -0
- package/lang/translations/vi.po +4 -0
- package/lang/translations/zh-cn.po +4 -0
- package/lang/translations/zh.po +4 -0
- package/package.json +7 -7
- package/src/highlightstack.d.ts +1 -1
- package/src/highlightstack.js +1 -1
- package/src/widget.js +4 -0
- package/src/widgetresize/resizer.d.ts +1 -1
- package/src/widgetresize/resizer.js +1 -1
- package/src/widgetresize/resizerstate.d.ts +1 -1
- 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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
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
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
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
|
-
|
549
|
-
|
569
|
+
* @inheritDoc
|
570
|
+
*/ static get requires() {
|
550
571
|
return [
|
551
572
|
Enter,
|
552
573
|
Delete
|
553
574
|
];
|
554
575
|
}
|
555
576
|
/**
|
556
|
-
|
557
|
-
|
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
|
-
|
590
|
-
|
610
|
+
* @inheritDoc
|
611
|
+
*/ destroy() {
|
591
612
|
super.destroy();
|
592
613
|
this._currentFakeCaretModelElement = null;
|
593
614
|
}
|
594
615
|
/**
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
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
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
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
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
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
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
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
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
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
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
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
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
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
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
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
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
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
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
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
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
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
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
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
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
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
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
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
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
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
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
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
|
-
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
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
|
-
|
1461
|
-
|
1488
|
+
* @inheritDoc
|
1489
|
+
*/ static get requires() {
|
1462
1490
|
return [
|
1463
1491
|
WidgetTypeAround,
|
1464
1492
|
Delete
|
1465
1493
|
];
|
1466
1494
|
}
|
1467
1495
|
/**
|
1468
|
-
|
1469
|
-
|
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
|
-
|
1661
|
-
|
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
|
-
|
1699
|
-
|
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
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
|
1723
|
-
|
1724
|
-
|
1725
|
-
|
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
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1785
|
-
|
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
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
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
|
-
|
1828
|
-
|
1829
|
-
|
1830
|
-
|
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
|
-
|
1837
|
-
|
1838
|
-
|
1839
|
-
|
1840
|
-
|
1841
|
-
|
1842
|
-
|
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
|
-
|
1864
|
-
|
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
|
-
|
1872
|
-
|
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
|
-
|
1894
|
-
|
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
|
-
|
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
|
-
|
1984
|
-
|
2038
|
+
* @inheritDoc
|
2039
|
+
*/ static get requires() {
|
1985
2040
|
return [
|
1986
2041
|
ContextualBalloon
|
1987
2042
|
];
|
1988
2043
|
}
|
1989
2044
|
/**
|
1990
|
-
|
1991
|
-
|
2045
|
+
* @inheritDoc
|
2046
|
+
*/ static get pluginName() {
|
1992
2047
|
return 'WidgetToolbarRepository';
|
1993
2048
|
}
|
1994
2049
|
/**
|
1995
|
-
|
1996
|
-
|
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
|
-
|
2031
|
-
|
2032
|
-
|
2033
|
-
|
2034
|
-
|
2035
|
-
|
2036
|
-
|
2037
|
-
|
2038
|
-
|
2039
|
-
|
2040
|
-
|
2041
|
-
|
2042
|
-
|
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
|
-
|
2047
|
-
|
2048
|
-
|
2049
|
-
|
2050
|
-
|
2051
|
-
|
2052
|
-
|
2053
|
-
|
2054
|
-
|
2055
|
-
|
2056
|
-
|
2057
|
-
|
2058
|
-
|
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
|
-
|
2070
|
-
|
2071
|
-
|
2072
|
-
|
2073
|
-
|
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
|
-
|
2101
|
-
|
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
|
-
|
2134
|
-
|
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
|
-
|
2140
|
-
|
2141
|
-
|
2142
|
-
|
2143
|
-
|
2144
|
-
|
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
|
-
|
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
|
-
|
2213
|
-
|
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
|
-
|
2218
|
-
|
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
|
-
|
2223
|
-
|
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
|
-
|
2228
|
-
|
2321
|
+
* A width to height ratio of the resized image.
|
2322
|
+
*/ get aspectRatio() {
|
2229
2323
|
return this._aspectRatio;
|
2230
2324
|
}
|
2231
2325
|
/**
|
2232
|
-
|
2233
|
-
|
2234
|
-
|
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
|
-
|
2323
|
-
|
2324
|
-
|
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
|
-
|
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
|
-
|
2376
|
-
|
2377
|
-
|
2378
|
-
|
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
|
-
|
2383
|
-
|
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
|
-
|
2391
|
-
|
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
|
-
|
2399
|
-
|
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
|
-
|
2432
|
-
|
2433
|
-
|
2434
|
-
|
2435
|
-
|
2436
|
-
|
2437
|
-
|
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
|
-
|
2445
|
-
|
2446
|
-
|
2447
|
-
|
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
|
-
|
2475
|
-
|
2476
|
-
|
2477
|
-
|
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
|
-
|
2488
|
-
|
2489
|
-
|
2490
|
-
|
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
|
-
|
2495
|
-
|
2619
|
+
* Destroys the resizer.
|
2620
|
+
*/ destroy() {
|
2496
2621
|
this.cancel();
|
2497
2622
|
}
|
2498
2623
|
/**
|
2499
|
-
|
2500
|
-
|
2501
|
-
|
2502
|
-
|
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
|
-
|
2558
|
-
|
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
|
-
|
2567
|
-
|
2568
|
-
|
2569
|
-
|
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
|
-
|
2616
|
-
|
2617
|
-
|
2618
|
-
|
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
|
-
|
2624
|
-
|
2625
|
-
|
2626
|
-
|
2627
|
-
|
2628
|
-
|
2629
|
-
|
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
|
-
|
2635
|
-
|
2636
|
-
|
2637
|
-
|
2638
|
-
|
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
|
-
|
2643
|
-
|
2644
|
-
|
2645
|
-
|
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
|
-
|
2663
|
-
|
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
|
-
|
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
|
-
|
2715
|
-
|
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
|
-
|
2720
|
-
|
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
|
-
|
2763
|
-
|
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
|
-
|
2770
|
-
|
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
|
-
|
2780
|
-
|
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
|
-
|
2787
|
-
|
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
|
-
|
2795
|
-
|
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
|
-
|
2830
|
-
|
2831
|
-
|
2832
|
-
|
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
|
-
|
2837
|
-
|
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
|
-
|
2846
|
-
|
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
|
-
|
2861
|
-
|
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 };
|