@ckeditor/ckeditor5-utils 41.4.1 → 42.0.0-alpha.0
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 +1338 -1013
- package/dist/index.js.map +1 -1
- package/dist/types/collection.d.ts +1 -1
- package/dist/types/env.d.ts +13 -6
- package/dist/types/focustracker.d.ts +1 -1
- package/dist/types/locale.d.ts +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/collection.d.ts +1 -1
- package/src/collection.js +1 -1
- package/src/dom/emittermixin.js +2 -2
- package/src/emittermixin.js +1 -1
- package/src/env.d.ts +13 -6
- package/src/env.js +20 -13
- package/src/focustracker.d.ts +1 -1
- package/src/focustracker.js +1 -1
- package/src/keyboard.js +3 -2
- package/src/locale.d.ts +1 -1
- package/src/locale.js +1 -1
- package/src/observablemixin.js +1 -1
- package/src/unicode.js +1 -1
- package/src/version.d.ts +1 -1
- package/src/version.js +10 -8
package/dist/index.js
CHANGED
|
@@ -7,9 +7,47 @@ import { isObject, isString, isPlainObject, cloneDeepWith, isElement, isFunction
|
|
|
7
7
|
/**
|
|
8
8
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
9
9
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
10
|
-
*/ /* globals
|
|
11
|
-
* @module utils/
|
|
10
|
+
*/ /* globals window, document */ /**
|
|
11
|
+
* @module utils/dom/global
|
|
12
|
+
*/ // This interface exists to make our API pages more readable.
|
|
13
|
+
/**
|
|
14
|
+
* A helper (module) giving an access to the global DOM objects such as `window` and `document`.
|
|
12
15
|
*/ /**
|
|
16
|
+
* A helper (module) giving an access to the global DOM objects such as `window` and
|
|
17
|
+
* `document`. Accessing these objects using this helper allows easy and bulletproof
|
|
18
|
+
* testing, i.e. stubbing native properties:
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { global } from 'ckeditor5/utils';
|
|
22
|
+
*
|
|
23
|
+
* // This stub will work for any code using global module.
|
|
24
|
+
* testUtils.sinon.stub( global, 'window', {
|
|
25
|
+
* innerWidth: 10000
|
|
26
|
+
* } );
|
|
27
|
+
*
|
|
28
|
+
* console.log( global.window.innerWidth );
|
|
29
|
+
* ```
|
|
30
|
+
*/ let globalVar; // named globalVar instead of global: https://github.com/ckeditor/ckeditor5/issues/12971
|
|
31
|
+
// In some environments window and document API might not be available.
|
|
32
|
+
try {
|
|
33
|
+
globalVar = {
|
|
34
|
+
window,
|
|
35
|
+
document
|
|
36
|
+
};
|
|
37
|
+
} catch (e) {
|
|
38
|
+
// It's not possible to mock a window object to simulate lack of a window object without writing extremely convoluted code.
|
|
39
|
+
/* istanbul ignore next -- @preserve */ // Let's cast it to not change module's API.
|
|
40
|
+
// We only handle this so loading editor in environments without window and document doesn't fail.
|
|
41
|
+
// For better DX we shouldn't introduce mixed types and require developers to check the type manually.
|
|
42
|
+
// This module should not be used on purpose in any environment outside browser.
|
|
43
|
+
globalVar = {
|
|
44
|
+
window: {},
|
|
45
|
+
document: {}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
var global = globalVar;
|
|
49
|
+
|
|
50
|
+
/**
|
|
13
51
|
* Safely returns `userAgent` from browser's navigator API in a lower case.
|
|
14
52
|
* If navigator API is not available it will return an empty string.
|
|
15
53
|
*/ function getUserAgent() {
|
|
@@ -20,23 +58,25 @@ import { isObject, isString, isPlainObject, cloneDeepWith, isElement, isFunction
|
|
|
20
58
|
return '';
|
|
21
59
|
}
|
|
22
60
|
}
|
|
23
|
-
const userAgent = getUserAgent();
|
|
61
|
+
const userAgent = /* #__PURE__ */ getUserAgent();
|
|
24
62
|
/**
|
|
25
63
|
* A namespace containing environment and browser information.
|
|
26
64
|
*/ const env = {
|
|
27
|
-
isMac: isMac(userAgent),
|
|
28
|
-
isWindows: isWindows(userAgent),
|
|
29
|
-
isGecko: isGecko(userAgent),
|
|
30
|
-
isSafari: isSafari(userAgent),
|
|
31
|
-
isiOS: isiOS(userAgent),
|
|
32
|
-
isAndroid: isAndroid(userAgent),
|
|
33
|
-
isBlink: isBlink(userAgent),
|
|
34
|
-
isMediaForcedColors
|
|
65
|
+
isMac: /* #__PURE__ */ isMac(userAgent),
|
|
66
|
+
isWindows: /* #__PURE__ */ isWindows(userAgent),
|
|
67
|
+
isGecko: /* #__PURE__ */ isGecko(userAgent),
|
|
68
|
+
isSafari: /* #__PURE__ */ isSafari(userAgent),
|
|
69
|
+
isiOS: /* #__PURE__ */ isiOS(userAgent),
|
|
70
|
+
isAndroid: /* #__PURE__ */ isAndroid(userAgent),
|
|
71
|
+
isBlink: /* #__PURE__ */ isBlink(userAgent),
|
|
72
|
+
get isMediaForcedColors () {
|
|
73
|
+
return isMediaForcedColors();
|
|
74
|
+
},
|
|
35
75
|
get isMotionReduced () {
|
|
36
76
|
return isMotionReduced();
|
|
37
77
|
},
|
|
38
78
|
features: {
|
|
39
|
-
isRegExpUnicodePropertySupported: isRegExpUnicodePropertySupported()
|
|
79
|
+
isRegExpUnicodePropertySupported: /* #__PURE__ */ isRegExpUnicodePropertySupported()
|
|
40
80
|
}
|
|
41
81
|
};
|
|
42
82
|
/**
|
|
@@ -116,13 +156,17 @@ const userAgent = getUserAgent();
|
|
|
116
156
|
}
|
|
117
157
|
/**
|
|
118
158
|
* Checks if the user agent has enabled a forced colors mode (e.g. Windows High Contrast mode).
|
|
159
|
+
*
|
|
160
|
+
* Returns `false` in environments where `window` global object is not available.
|
|
119
161
|
*/ function isMediaForcedColors() {
|
|
120
|
-
return window.matchMedia('(forced-colors: active)').matches;
|
|
162
|
+
return global.window.matchMedia ? global.window.matchMedia('(forced-colors: active)').matches : false;
|
|
121
163
|
}
|
|
122
164
|
/**
|
|
123
|
-
* Checks if user enabled "prefers reduced motion" setting in browser.
|
|
165
|
+
* Checks if the user enabled "prefers reduced motion" setting in browser.
|
|
166
|
+
*
|
|
167
|
+
* Returns `false` in environments where `window` global object is not available.
|
|
124
168
|
*/ function isMotionReduced() {
|
|
125
|
-
return window.matchMedia('(prefers-reduced-motion)').matches;
|
|
169
|
+
return global.window.matchMedia ? global.window.matchMedia('(prefers-reduced-motion)').matches : false;
|
|
126
170
|
}
|
|
127
171
|
|
|
128
172
|
/**
|
|
@@ -620,11 +664,45 @@ diff.fastDiff = fastDiff;
|
|
|
620
664
|
};
|
|
621
665
|
}
|
|
622
666
|
|
|
623
|
-
|
|
667
|
+
/**
|
|
668
|
+
* The event object passed to event callbacks. It is used to provide information about the event as well as a tool to
|
|
669
|
+
* manipulate it.
|
|
670
|
+
*/ class EventInfo {
|
|
671
|
+
/**
|
|
672
|
+
* The object that fired the event.
|
|
673
|
+
*/ source;
|
|
624
674
|
/**
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
675
|
+
* The event name.
|
|
676
|
+
*/ name;
|
|
677
|
+
/**
|
|
678
|
+
* Path this event has followed. See {@link module:utils/emittermixin~Emitter#delegate}.
|
|
679
|
+
*/ path;
|
|
680
|
+
/**
|
|
681
|
+
* Stops the event emitter to call further callbacks for this event interaction.
|
|
682
|
+
*/ stop;
|
|
683
|
+
/**
|
|
684
|
+
* Removes the current callback from future interactions of this event.
|
|
685
|
+
*/ off;
|
|
686
|
+
/**
|
|
687
|
+
* The value which will be returned by {@link module:utils/emittermixin~Emitter#fire}.
|
|
688
|
+
*
|
|
689
|
+
* It's `undefined` by default and can be changed by an event listener:
|
|
690
|
+
*
|
|
691
|
+
* ```ts
|
|
692
|
+
* dataController.fire( 'getSelectedContent', ( evt ) => {
|
|
693
|
+
* // This listener will make `dataController.fire( 'getSelectedContent' )`
|
|
694
|
+
* // always return an empty DocumentFragment.
|
|
695
|
+
* evt.return = new DocumentFragment();
|
|
696
|
+
*
|
|
697
|
+
* // Make sure no other listeners are executed.
|
|
698
|
+
* evt.stop();
|
|
699
|
+
* } );
|
|
700
|
+
* ```
|
|
701
|
+
*/ return;
|
|
702
|
+
/**
|
|
703
|
+
* @param source The emitter.
|
|
704
|
+
* @param name The event name.
|
|
705
|
+
*/ constructor(source, name){
|
|
628
706
|
this.source = source;
|
|
629
707
|
this.name = name;
|
|
630
708
|
this.path = [];
|
|
@@ -675,6 +753,10 @@ class EventInfo {
|
|
|
675
753
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
676
754
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
677
755
|
*/ /**
|
|
756
|
+
* @module utils/priorities
|
|
757
|
+
*/ /**
|
|
758
|
+
* String representing a priority value.
|
|
759
|
+
*/ /**
|
|
678
760
|
* Provides group of constants to use instead of hardcoding numeric priority values.
|
|
679
761
|
*/ const priorities = {
|
|
680
762
|
get (priority = 'normal') {
|
|
@@ -715,57 +797,90 @@ class EventInfo {
|
|
|
715
797
|
*/ /* globals console */ /**
|
|
716
798
|
* URL to the documentation with error codes.
|
|
717
799
|
*/ const DOCUMENTATION_URL = 'https://ckeditor.com/docs/ckeditor5/latest/support/error-codes.html';
|
|
718
|
-
|
|
800
|
+
/**
|
|
801
|
+
* The CKEditor error class.
|
|
802
|
+
*
|
|
803
|
+
* You should throw `CKEditorError` when:
|
|
804
|
+
*
|
|
805
|
+
* * An unexpected situation occurred and the editor (most probably) will not work properly. Such exception will be handled
|
|
806
|
+
* by the {@link module:watchdog/watchdog~Watchdog watchdog} (if it is integrated),
|
|
807
|
+
* * If the editor is incorrectly integrated or the editor API is used in the wrong way. This way you will give
|
|
808
|
+
* feedback to the developer as soon as possible. Keep in mind that for common integration issues which should not
|
|
809
|
+
* stop editor initialization (like missing upload adapter, wrong name of a toolbar component) we use
|
|
810
|
+
* {@link module:utils/ckeditorerror~logWarning `logWarning()`} and
|
|
811
|
+
* {@link module:utils/ckeditorerror~logError `logError()`}
|
|
812
|
+
* to improve developers experience and let them see the a working editor as soon as possible.
|
|
813
|
+
*
|
|
814
|
+
* ```ts
|
|
815
|
+
* /**
|
|
816
|
+
* * Error thrown when a plugin cannot be loaded due to JavaScript errors, lack of plugins with a given name, etc.
|
|
817
|
+
* *
|
|
818
|
+
* * @error plugin-load
|
|
819
|
+
* * @param pluginName The name of the plugin that could not be loaded.
|
|
820
|
+
* * @param moduleName The name of the module which tried to load this plugin.
|
|
821
|
+
* *\/
|
|
822
|
+
* throw new CKEditorError( 'plugin-load', {
|
|
823
|
+
* pluginName: 'foo',
|
|
824
|
+
* moduleName: 'bar'
|
|
825
|
+
* } );
|
|
826
|
+
* ```
|
|
827
|
+
*/ class CKEditorError extends Error {
|
|
828
|
+
/**
|
|
829
|
+
* A context of the error by which the Watchdog is able to determine which editor crashed.
|
|
830
|
+
*/ context;
|
|
831
|
+
/**
|
|
832
|
+
* The additional error data passed to the constructor. Undefined if none was passed.
|
|
833
|
+
*/ data;
|
|
834
|
+
/**
|
|
835
|
+
* Creates an instance of the CKEditorError class.
|
|
836
|
+
*
|
|
837
|
+
* @param errorName The error id in an `error-name` format. A link to this error documentation page will be added
|
|
838
|
+
* to the thrown error's `message`.
|
|
839
|
+
* @param context A context of the error by which the {@link module:watchdog/watchdog~Watchdog watchdog}
|
|
840
|
+
* is able to determine which editor crashed. It should be an editor instance or a property connected to it. It can be also
|
|
841
|
+
* a `null` value if the editor should not be restarted in case of the error (e.g. during the editor initialization).
|
|
842
|
+
* The error context should be checked using the `areConnectedThroughProperties( editor, context )` utility
|
|
843
|
+
* to check if the object works as the context.
|
|
844
|
+
* @param data Additional data describing the error. A stringified version of this object
|
|
845
|
+
* will be appended to the error message, so the data are quickly visible in the console. The original
|
|
846
|
+
* data object will also be later available under the {@link #data} property.
|
|
847
|
+
*/ constructor(errorName, context, data){
|
|
848
|
+
super(getErrorMessage(errorName, data));
|
|
849
|
+
this.name = 'CKEditorError';
|
|
850
|
+
this.context = context;
|
|
851
|
+
this.data = data;
|
|
852
|
+
}
|
|
719
853
|
/**
|
|
720
|
-
|
|
721
|
-
|
|
854
|
+
* Checks if the error is of the `CKEditorError` type.
|
|
855
|
+
*/ is(type) {
|
|
722
856
|
return type === 'CKEditorError';
|
|
723
857
|
}
|
|
724
858
|
/**
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
859
|
+
* A utility that ensures that the thrown error is a {@link module:utils/ckeditorerror~CKEditorError} one.
|
|
860
|
+
* It is useful when combined with the {@link module:watchdog/watchdog~Watchdog} feature, which can restart the editor in case
|
|
861
|
+
* of a {@link module:utils/ckeditorerror~CKEditorError} error.
|
|
862
|
+
*
|
|
863
|
+
* @param err The error to rethrow.
|
|
864
|
+
* @param context An object connected through properties with the editor instance. This context will be used
|
|
865
|
+
* by the watchdog to verify which editor should be restarted.
|
|
866
|
+
*/ static rethrowUnexpectedError(err, context) {
|
|
733
867
|
if (err.is && err.is('CKEditorError')) {
|
|
734
868
|
throw err;
|
|
735
869
|
}
|
|
736
870
|
/**
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
871
|
+
* An unexpected error occurred inside the CKEditor 5 codebase. This error will look like the original one
|
|
872
|
+
* to make the debugging easier.
|
|
873
|
+
*
|
|
874
|
+
* This error is only useful when the editor is initialized using the {@link module:watchdog/watchdog~Watchdog} feature.
|
|
875
|
+
* In case of such error (or any {@link module:utils/ckeditorerror~CKEditorError} error) the watchdog should restart the editor.
|
|
876
|
+
*
|
|
877
|
+
* @error unexpected-error
|
|
878
|
+
*/ const error = new CKEditorError(err.message, context);
|
|
745
879
|
// Restore the original stack trace to make the error look like the original one.
|
|
746
880
|
// See https://github.com/ckeditor/ckeditor5/issues/5595 for more details.
|
|
747
881
|
error.stack = err.stack;
|
|
748
882
|
throw error;
|
|
749
883
|
}
|
|
750
|
-
/**
|
|
751
|
-
* Creates an instance of the CKEditorError class.
|
|
752
|
-
*
|
|
753
|
-
* @param errorName The error id in an `error-name` format. A link to this error documentation page will be added
|
|
754
|
-
* to the thrown error's `message`.
|
|
755
|
-
* @param context A context of the error by which the {@link module:watchdog/watchdog~Watchdog watchdog}
|
|
756
|
-
* is able to determine which editor crashed. It should be an editor instance or a property connected to it. It can be also
|
|
757
|
-
* a `null` value if the editor should not be restarted in case of the error (e.g. during the editor initialization).
|
|
758
|
-
* The error context should be checked using the `areConnectedThroughProperties( editor, context )` utility
|
|
759
|
-
* to check if the object works as the context.
|
|
760
|
-
* @param data Additional data describing the error. A stringified version of this object
|
|
761
|
-
* will be appended to the error message, so the data are quickly visible in the console. The original
|
|
762
|
-
* data object will also be later available under the {@link #data} property.
|
|
763
|
-
*/ constructor(errorName, context, data){
|
|
764
|
-
super(getErrorMessage(errorName, data));
|
|
765
|
-
this.name = 'CKEditorError';
|
|
766
|
-
this.context = context;
|
|
767
|
-
this.data = data;
|
|
768
|
-
}
|
|
769
884
|
}
|
|
770
885
|
/**
|
|
771
886
|
* Logs a warning to the console with a properly formatted message and adds a link to the documentation.
|
|
@@ -850,144 +965,146 @@ class CKEditorError extends Error {
|
|
|
850
965
|
];
|
|
851
966
|
}
|
|
852
967
|
|
|
853
|
-
const version = '
|
|
968
|
+
const version = '42.0.0-alpha.0';
|
|
854
969
|
// The second argument is not a month. It is `monthIndex` and starts from `0`.
|
|
855
|
-
const releaseDate = new Date(2024,
|
|
970
|
+
const releaseDate = new Date(2024, 5, 5);
|
|
856
971
|
/* istanbul ignore next -- @preserve */ if (globalThis.CKEDITOR_VERSION) {
|
|
857
972
|
/**
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
973
|
+
* The best solution to avoid this error is migrating your CKEditor 5 instance to
|
|
974
|
+
* {@glink updating/new-installation-methods new installation methods}.
|
|
975
|
+
*
|
|
976
|
+
* Mentioned below are predefined builds, which are a deprecated installation method. The solutions
|
|
977
|
+
* provided are kept here for legacy support only.
|
|
978
|
+
*
|
|
979
|
+
* This error is thrown when due to a mistake in how CKEditor 5 was installed or initialized, some
|
|
980
|
+
* of its modules were duplicated (evaluated and executed twice). Module duplication leads to inevitable runtime
|
|
981
|
+
* errors.
|
|
982
|
+
*
|
|
983
|
+
* There are many situations in which some modules can be loaded twice. In the worst case scenario,
|
|
984
|
+
* you may need to check your project for each of these issues and fix them all.
|
|
985
|
+
*
|
|
986
|
+
* # Trying to add a plugin to an existing build
|
|
987
|
+
*
|
|
988
|
+
* If you import an existing CKEditor 5 build and a plugin like this:
|
|
989
|
+
*
|
|
990
|
+
* ```ts
|
|
991
|
+
* import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
|
|
992
|
+
* import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight';
|
|
993
|
+
* ```
|
|
994
|
+
*
|
|
995
|
+
* Then your project loads some CKEditor 5 packages twice. How does it happen?
|
|
996
|
+
*
|
|
997
|
+
* The build package contains a file which is already compiled with webpack. This means
|
|
998
|
+
* that it contains all the necessary code from e.g. `@ckeditor/ckeditor5-engine` and `@ckeditor/ckeditor5-utils`.
|
|
999
|
+
*
|
|
1000
|
+
* However, the `Highlight` plugin imports some of the modules from these packages, too. If you ask webpack to
|
|
1001
|
+
* build such a project, you will end up with the modules being included (and run) twice – first, because they are
|
|
1002
|
+
* included inside the build package, and second, because they are required by the `Highlight` plugin.
|
|
1003
|
+
*
|
|
1004
|
+
* Therefore, **you must never add plugins to an existing build** unless your plugin has no dependencies.
|
|
1005
|
+
*
|
|
1006
|
+
* Adding plugins to a build is done by taking the source version of this build (so, before it was built with webpack)
|
|
1007
|
+
* and adding plugins there. In this situation, webpack will know that it only needs to load each plugin once.
|
|
1008
|
+
*
|
|
1009
|
+
* # Confused an editor build with an editor implementation
|
|
1010
|
+
*
|
|
1011
|
+
* This scenario is very similar to the previous one, but has a different origin.
|
|
1012
|
+
*
|
|
1013
|
+
* Let's assume that you wanted to use CKEditor 5 from source.
|
|
1014
|
+
*
|
|
1015
|
+
* The correct way to do so is to import an editor and plugins and run them together like this:
|
|
1016
|
+
*
|
|
1017
|
+
* ```ts
|
|
1018
|
+
* import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
|
|
1019
|
+
* import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
|
|
1020
|
+
* import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
|
|
1021
|
+
* import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
|
|
1022
|
+
* import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
|
|
1023
|
+
*
|
|
1024
|
+
* ClassicEditor
|
|
1025
|
+
* .create( document.querySelector( '#editor' ), {
|
|
1026
|
+
* plugins: [ Essentials, Paragraph, Bold, Italic ],
|
|
1027
|
+
* toolbar: [ 'bold', 'italic' ]
|
|
1028
|
+
* } )
|
|
1029
|
+
* .then( editor => {
|
|
1030
|
+
* console.log( 'Editor was initialized', editor );
|
|
1031
|
+
* } )
|
|
1032
|
+
* .catch( error => {
|
|
1033
|
+
* console.error( error.stack );
|
|
1034
|
+
* } );
|
|
1035
|
+
* ```
|
|
1036
|
+
*
|
|
1037
|
+
* However, you might have mistakenly imported a build instead of the source `ClassicEditor`. In this case
|
|
1038
|
+
* your imports will look like this:
|
|
1039
|
+
*
|
|
1040
|
+
* ```ts
|
|
1041
|
+
* import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
|
|
1042
|
+
* import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
|
|
1043
|
+
* import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
|
|
1044
|
+
* import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
|
|
1045
|
+
* import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
|
|
1046
|
+
* ```
|
|
1047
|
+
*
|
|
1048
|
+
* This creates the same situation as in the previous section because you use a build together with source plugins.
|
|
1049
|
+
*
|
|
1050
|
+
* Remember: `@ckeditor/ckeditor5-build-*` packages contain editor builds and `@ckeditor/ckeditor5-editor-*` contain source editors.
|
|
1051
|
+
*
|
|
1052
|
+
* # Loading two or more builds on one page
|
|
1053
|
+
*
|
|
1054
|
+
* If you use CKEditor 5 builds, you might have loaded two (or more) `ckeditor.js` files on one web page.
|
|
1055
|
+
* Check your web page for duplicated `<script>` elements or make sure your page builder/bundler includes CKEditor only once.
|
|
1056
|
+
*
|
|
1057
|
+
* If you want to use two different types of editors at once, see the
|
|
1058
|
+
* {@glink getting-started/legacy/advanced/using-two-editors "Using two different editors"}
|
|
1059
|
+
* section.
|
|
1060
|
+
*
|
|
1061
|
+
* # Using outdated packages
|
|
1062
|
+
*
|
|
1063
|
+
* Building CKEditor 5 from source requires using multiple npm packages. These packages have their dependencies
|
|
1064
|
+
* to other packages. If you use the latest version of, for example, `@ckeditor/ckeditor5-editor-classic` with
|
|
1065
|
+
* an outdated version of `@ckeditor/ckeditor5-image`, npm or yarn will need to install two different versions of
|
|
1066
|
+
* `@ckeditor/ckeditor5-core` because `@ckeditor/ckeditor5-editor-classic` and `@ckeditor/ckeditor5-image` may require
|
|
1067
|
+
* different versions of the core package.
|
|
1068
|
+
*
|
|
1069
|
+
* The solution to this issue is to update all packages to their latest version. We recommend
|
|
1070
|
+
* using tools like [`npm-check-updates`](https://www.npmjs.com/package/npm-check-updates) which simplify this process.
|
|
1071
|
+
*
|
|
1072
|
+
* # Conflicting version of dependencies
|
|
1073
|
+
*
|
|
1074
|
+
* This is a special case of the previous scenario. If you use CKEditor 5 with some third-party plugins,
|
|
1075
|
+
* it may happen that even if you use the latest versions of the official packages and the latest version of
|
|
1076
|
+
* these third-party packages, there will be a conflict between some of their dependencies.
|
|
1077
|
+
*
|
|
1078
|
+
* Such a problem can be resolved by either downgrading CKEditor 5 packages (which we do not recommend) or
|
|
1079
|
+
* asking the author of the third-party package to upgrade its depdendencies (or forking their project and doing this yourself).
|
|
1080
|
+
*
|
|
1081
|
+
* **Note:** All official CKEditor 5 packages (excluding integrations and `ckeditor5-dev-*` packages) are released in the
|
|
1082
|
+
* same major version. This means that in the `x.y.z` version, the `x` is the same for all packages. This is the simplest way to check
|
|
1083
|
+
* whether you use packages coming from the same CKEditor 5 version. You can read more about versioning in the
|
|
1084
|
+
* {@glink updating/versioning-policy Versioning policy} guide.
|
|
1085
|
+
*
|
|
1086
|
+
* # Packages were duplicated in `node_modules`
|
|
1087
|
+
*
|
|
1088
|
+
* In some situations, especially when calling `npm install` multiple times, it may happen
|
|
1089
|
+
* that npm will not correctly "deduplicate" packages.
|
|
1090
|
+
*
|
|
1091
|
+
* Normally, npm deduplicates all packages so, for example, `@ckeditor/ckeditor5-core` is installed only once in `node_modules/`.
|
|
1092
|
+
* However, it is known to fail to do so from time to time.
|
|
1093
|
+
*
|
|
1094
|
+
* We recommend checking if any of the steps listed below help:
|
|
1095
|
+
*
|
|
1096
|
+
* * `rm -rf node_modules && npm install` to make sure you have a clean `node_modules/` directory. This step
|
|
1097
|
+
* is known to help in most cases.
|
|
1098
|
+
* * If you use `yarn.lock` or `package-lock.json`, remove it before `npm install`.
|
|
1099
|
+
* * Check whether all CKEditor 5 packages are up to date and reinstall them
|
|
1100
|
+
* if you changed anything (`rm -rf node_modules && npm install`).
|
|
1101
|
+
*
|
|
1102
|
+
* If all packages are correct and compatible with each other, the steps above are known to help. If not, you may
|
|
1103
|
+
* try to check with `npm ls` how many times packages like `@ckeditor/ckeditor5-core`, `@ckeditor/ckeditor5-engine` and
|
|
1104
|
+
*`@ckeditor/ckeditor5-utils` are installed. If more than once, verify which package causes that.
|
|
1105
|
+
*
|
|
1106
|
+
* @error ckeditor-duplicated-modules
|
|
1107
|
+
*/ throw new CKEditorError('ckeditor-duplicated-modules', null);
|
|
991
1108
|
} else {
|
|
992
1109
|
globalThis.CKEDITOR_VERSION = version;
|
|
993
1110
|
}
|
|
@@ -995,7 +1112,7 @@ const releaseDate = new Date(2024, 4, 16);
|
|
|
995
1112
|
const _listeningTo = Symbol('listeningTo');
|
|
996
1113
|
const _emitterId = Symbol('emitterId');
|
|
997
1114
|
const _delegations = Symbol('delegations');
|
|
998
|
-
const defaultEmitterClass$1 = EmitterMixin(Object);
|
|
1115
|
+
const defaultEmitterClass$1 = /* #__PURE__ */ EmitterMixin(Object);
|
|
999
1116
|
function EmitterMixin(base) {
|
|
1000
1117
|
if (!base) {
|
|
1001
1118
|
return defaultEmitterClass$1;
|
|
@@ -1426,7 +1543,7 @@ const boundObservablesSymbol = Symbol('boundObservables');
|
|
|
1426
1543
|
const boundPropertiesSymbol = Symbol('boundProperties');
|
|
1427
1544
|
const decoratedMethods = Symbol('decoratedMethods');
|
|
1428
1545
|
const decoratedOriginal = Symbol('decoratedOriginal');
|
|
1429
|
-
const defaultObservableClass = ObservableMixin(EmitterMixin());
|
|
1546
|
+
const defaultObservableClass = /* #__PURE__ */ ObservableMixin(/* #__PURE__ */ EmitterMixin());
|
|
1430
1547
|
function ObservableMixin(base) {
|
|
1431
1548
|
if (!base) {
|
|
1432
1549
|
return defaultObservableClass;
|
|
@@ -1444,22 +1561,22 @@ function ObservableMixin(base) {
|
|
|
1444
1561
|
const properties = this[observablePropertiesSymbol];
|
|
1445
1562
|
if (name in this && !properties.has(name)) {
|
|
1446
1563
|
/**
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1564
|
+
* Cannot override an existing property.
|
|
1565
|
+
*
|
|
1566
|
+
* This error is thrown when trying to {@link module:utils/observablemixin~Observable#set set} a property with
|
|
1567
|
+
* a name of an already existing property. For example:
|
|
1568
|
+
*
|
|
1569
|
+
* ```ts
|
|
1570
|
+
* let observable = new Model();
|
|
1571
|
+
* observable.property = 1;
|
|
1572
|
+
* observable.set( 'property', 2 ); // throws
|
|
1573
|
+
*
|
|
1574
|
+
* observable.set( 'property', 1 );
|
|
1575
|
+
* observable.set( 'property', 2 ); // ok, because this is an existing property.
|
|
1576
|
+
* ```
|
|
1577
|
+
*
|
|
1578
|
+
* @error observable-set-cannot-override
|
|
1579
|
+
*/ throw new CKEditorError('observable-set-cannot-override', this);
|
|
1463
1580
|
}
|
|
1464
1581
|
Object.defineProperty(this, name, {
|
|
1465
1582
|
enumerable: true,
|
|
@@ -1489,27 +1606,27 @@ function ObservableMixin(base) {
|
|
|
1489
1606
|
bind(...bindProperties) {
|
|
1490
1607
|
if (!bindProperties.length || !isStringArray(bindProperties)) {
|
|
1491
1608
|
/**
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1609
|
+
* All properties must be strings.
|
|
1610
|
+
*
|
|
1611
|
+
* @error observable-bind-wrong-properties
|
|
1612
|
+
*/ throw new CKEditorError('observable-bind-wrong-properties', this);
|
|
1496
1613
|
}
|
|
1497
1614
|
if (new Set(bindProperties).size !== bindProperties.length) {
|
|
1498
1615
|
/**
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1616
|
+
* Properties must be unique.
|
|
1617
|
+
*
|
|
1618
|
+
* @error observable-bind-duplicate-properties
|
|
1619
|
+
*/ throw new CKEditorError('observable-bind-duplicate-properties', this);
|
|
1503
1620
|
}
|
|
1504
1621
|
initObservable(this);
|
|
1505
1622
|
const boundProperties = this[boundPropertiesSymbol];
|
|
1506
1623
|
bindProperties.forEach((propertyName)=>{
|
|
1507
1624
|
if (boundProperties.has(propertyName)) {
|
|
1508
1625
|
/**
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1626
|
+
* Cannot bind the same property more than once.
|
|
1627
|
+
*
|
|
1628
|
+
* @error observable-bind-rebind
|
|
1629
|
+
*/ throw new CKEditorError('observable-bind-rebind', this);
|
|
1513
1630
|
}
|
|
1514
1631
|
});
|
|
1515
1632
|
const bindings = new Map();
|
|
@@ -1540,10 +1657,10 @@ function ObservableMixin(base) {
|
|
|
1540
1657
|
if (unbindProperties.length) {
|
|
1541
1658
|
if (!isStringArray(unbindProperties)) {
|
|
1542
1659
|
/**
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1660
|
+
* Properties must be strings.
|
|
1661
|
+
*
|
|
1662
|
+
* @error observable-unbind-wrong-properties
|
|
1663
|
+
*/ throw new CKEditorError('observable-unbind-wrong-properties', this);
|
|
1547
1664
|
}
|
|
1548
1665
|
unbindProperties.forEach((propertyName)=>{
|
|
1549
1666
|
const binding = boundProperties.get(propertyName);
|
|
@@ -1578,12 +1695,12 @@ function ObservableMixin(base) {
|
|
|
1578
1695
|
const originalMethod = this[methodName];
|
|
1579
1696
|
if (!originalMethod) {
|
|
1580
1697
|
/**
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1698
|
+
* Cannot decorate an undefined method.
|
|
1699
|
+
*
|
|
1700
|
+
* @error observablemixin-cannot-decorate-undefined
|
|
1701
|
+
* @param {Object} object The object which method should be decorated.
|
|
1702
|
+
* @param {String} methodName Name of the method which does not exist.
|
|
1703
|
+
*/ throw new CKEditorError('observablemixin-cannot-decorate-undefined', this, {
|
|
1587
1704
|
object: this,
|
|
1588
1705
|
methodName
|
|
1589
1706
|
});
|
|
@@ -1615,6 +1732,10 @@ function ObservableMixin(base) {
|
|
|
1615
1732
|
}
|
|
1616
1733
|
super.stopListening(emitter, event, callback);
|
|
1617
1734
|
}
|
|
1735
|
+
[observablePropertiesSymbol];
|
|
1736
|
+
[decoratedMethods];
|
|
1737
|
+
[boundPropertiesSymbol];
|
|
1738
|
+
[boundObservablesSymbol];
|
|
1618
1739
|
}
|
|
1619
1740
|
return Mixin;
|
|
1620
1741
|
}
|
|
@@ -1734,27 +1855,27 @@ function initObservable(observable) {
|
|
|
1734
1855
|
// Eliminate A.bind( 'x' ).to( B, C )
|
|
1735
1856
|
if (!parsedArgs.callback && parsedArgs.to.length > 1) {
|
|
1736
1857
|
/**
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1858
|
+
* Binding multiple observables only possible with callback.
|
|
1859
|
+
*
|
|
1860
|
+
* @error observable-bind-to-no-callback
|
|
1861
|
+
*/ throw new CKEditorError('observable-bind-to-no-callback', this);
|
|
1741
1862
|
}
|
|
1742
1863
|
// Eliminate A.bind( 'x', 'y' ).to( B, callback )
|
|
1743
1864
|
if (numberOfBindings > 1 && parsedArgs.callback) {
|
|
1744
1865
|
/**
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1866
|
+
* Cannot bind multiple properties and use a callback in one binding.
|
|
1867
|
+
*
|
|
1868
|
+
* @error observable-bind-to-extra-callback
|
|
1869
|
+
*/ throw new CKEditorError('observable-bind-to-extra-callback', this);
|
|
1749
1870
|
}
|
|
1750
1871
|
parsedArgs.to.forEach((to)=>{
|
|
1751
1872
|
// Eliminate A.bind( 'x', 'y' ).to( B, 'a' )
|
|
1752
1873
|
if (to.properties.length && to.properties.length !== numberOfBindings) {
|
|
1753
1874
|
/**
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1875
|
+
* The number of properties must match.
|
|
1876
|
+
*
|
|
1877
|
+
* @error observable-bind-to-properties-length
|
|
1878
|
+
*/ throw new CKEditorError('observable-bind-to-properties-length', this);
|
|
1758
1879
|
}
|
|
1759
1880
|
// When no to.properties specified, observing source properties instead i.e.
|
|
1760
1881
|
// A.bind( 'x', 'y' ).to( B ) -> Observe B.x and B.y
|
|
@@ -1780,10 +1901,10 @@ function initObservable(observable) {
|
|
|
1780
1901
|
*/ function bindToMany(observables, attribute, callback) {
|
|
1781
1902
|
if (this._bindings.size > 1) {
|
|
1782
1903
|
/**
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1904
|
+
* Binding one attribute to many observables only possible with one attribute.
|
|
1905
|
+
*
|
|
1906
|
+
* @error observable-bind-to-many-not-one-binding
|
|
1907
|
+
*/ throw new CKEditorError('observable-bind-to-many-not-one-binding', this);
|
|
1787
1908
|
}
|
|
1788
1909
|
this.to(// Bind to #attribute of each observable...
|
|
1789
1910
|
...getBindingTargets(observables, attribute), // ...using given callback to parse attribute values.
|
|
@@ -1829,10 +1950,10 @@ function initObservable(observable) {
|
|
|
1829
1950
|
// Eliminate A.bind( 'x' ).to()
|
|
1830
1951
|
if (!args.length) {
|
|
1831
1952
|
/**
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1953
|
+
* Invalid argument syntax in `to()`.
|
|
1954
|
+
*
|
|
1955
|
+
* @error observable-bind-to-parse-error
|
|
1956
|
+
*/ throw new CKEditorError('observable-bind-to-parse-error', null);
|
|
1836
1957
|
}
|
|
1837
1958
|
const parsed = {
|
|
1838
1959
|
to: []
|
|
@@ -2001,13 +2122,19 @@ function initObservable(observable) {
|
|
|
2001
2122
|
* the original elements from the DOM.
|
|
2002
2123
|
*/ class ElementReplacer {
|
|
2003
2124
|
/**
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2125
|
+
* The elements replaced by {@link #replace} and their replacements.
|
|
2126
|
+
*/ _replacedElements;
|
|
2127
|
+
constructor(){
|
|
2128
|
+
this._replacedElements = [];
|
|
2129
|
+
}
|
|
2130
|
+
/**
|
|
2131
|
+
* Hides the `element` and, if specified, inserts the the given element next to it.
|
|
2132
|
+
*
|
|
2133
|
+
* The effect of this method can be reverted by {@link #restore}.
|
|
2134
|
+
*
|
|
2135
|
+
* @param element The element to replace.
|
|
2136
|
+
* @param newElement The replacement element. If not passed, then the `element` will just be hidden.
|
|
2137
|
+
*/ replace(element, newElement) {
|
|
2011
2138
|
this._replacedElements.push({
|
|
2012
2139
|
element,
|
|
2013
2140
|
newElement
|
|
@@ -2018,8 +2145,8 @@ function initObservable(observable) {
|
|
|
2018
2145
|
}
|
|
2019
2146
|
}
|
|
2020
2147
|
/**
|
|
2021
|
-
|
|
2022
|
-
|
|
2148
|
+
* Restores what {@link #replace} did.
|
|
2149
|
+
*/ restore() {
|
|
2023
2150
|
this._replacedElements.forEach(({ element, newElement })=>{
|
|
2024
2151
|
element.style.display = '';
|
|
2025
2152
|
if (newElement) {
|
|
@@ -2028,9 +2155,6 @@ function initObservable(observable) {
|
|
|
2028
2155
|
});
|
|
2029
2156
|
this._replacedElements = [];
|
|
2030
2157
|
}
|
|
2031
|
-
constructor(){
|
|
2032
|
-
this._replacedElements = [];
|
|
2033
|
-
}
|
|
2034
2158
|
}
|
|
2035
2159
|
|
|
2036
2160
|
/**
|
|
@@ -2165,7 +2289,32 @@ function initObservable(observable) {
|
|
|
2165
2289
|
return element;
|
|
2166
2290
|
}
|
|
2167
2291
|
|
|
2168
|
-
|
|
2292
|
+
/**
|
|
2293
|
+
* Handles a configuration dictionary.
|
|
2294
|
+
*
|
|
2295
|
+
* @typeParam Cfg A type of the configuration dictionary.
|
|
2296
|
+
*/ class Config {
|
|
2297
|
+
/**
|
|
2298
|
+
* Store for the whole configuration.
|
|
2299
|
+
*/ _config;
|
|
2300
|
+
/**
|
|
2301
|
+
* Creates an instance of the {@link ~Config} class.
|
|
2302
|
+
*
|
|
2303
|
+
* @param configurations The initial configurations to be set. Usually, provided by the user.
|
|
2304
|
+
* @param defaultConfigurations The default configurations. Usually, provided by the system.
|
|
2305
|
+
*/ constructor(configurations, defaultConfigurations){
|
|
2306
|
+
this._config = {};
|
|
2307
|
+
// Set default configuration.
|
|
2308
|
+
if (defaultConfigurations) {
|
|
2309
|
+
// Clone the configuration to make sure that the properties will not be shared
|
|
2310
|
+
// between editors and make the watchdog feature work correctly.
|
|
2311
|
+
this.define(cloneConfig(defaultConfigurations));
|
|
2312
|
+
}
|
|
2313
|
+
// Set initial configuration.
|
|
2314
|
+
if (configurations) {
|
|
2315
|
+
this._setObjectToTarget(this._config, configurations);
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2169
2318
|
set(name, value) {
|
|
2170
2319
|
this._setToTarget(this._config, name, value);
|
|
2171
2320
|
}
|
|
@@ -2174,39 +2323,39 @@ class Config {
|
|
|
2174
2323
|
this._setToTarget(this._config, name, value, isDefine);
|
|
2175
2324
|
}
|
|
2176
2325
|
/**
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2326
|
+
* Gets the value for a configuration entry.
|
|
2327
|
+
*
|
|
2328
|
+
* ```ts
|
|
2329
|
+
* config.get( 'name' );
|
|
2330
|
+
* ```
|
|
2331
|
+
*
|
|
2332
|
+
* Deep configurations can be retrieved by separating each part with a dot.
|
|
2333
|
+
*
|
|
2334
|
+
* ```ts
|
|
2335
|
+
* config.get( 'toolbar.collapsed' );
|
|
2336
|
+
* ```
|
|
2337
|
+
*
|
|
2338
|
+
* @param name The configuration name. Configuration names are case-sensitive.
|
|
2339
|
+
* @returns The configuration value or `undefined` if the configuration entry was not found.
|
|
2340
|
+
*/ get(name) {
|
|
2192
2341
|
return this._getFromSource(this._config, name);
|
|
2193
2342
|
}
|
|
2194
2343
|
/**
|
|
2195
|
-
|
|
2196
|
-
|
|
2344
|
+
* Iterates over all top level configuration names.
|
|
2345
|
+
*/ *names() {
|
|
2197
2346
|
for (const name of Object.keys(this._config)){
|
|
2198
2347
|
yield name;
|
|
2199
2348
|
}
|
|
2200
2349
|
}
|
|
2201
2350
|
/**
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2351
|
+
* Saves passed configuration to the specified target (nested object).
|
|
2352
|
+
*
|
|
2353
|
+
* @param target Nested config object.
|
|
2354
|
+
* @param name The configuration name or an object from which take properties as
|
|
2355
|
+
* configuration entries. Configuration names are case-sensitive.
|
|
2356
|
+
* @param value The configuration value. Used if a name is passed.
|
|
2357
|
+
* @param isDefine Define if passed configuration should overwrite existing one.
|
|
2358
|
+
*/ _setToTarget(target, name, value, isDefine = false) {
|
|
2210
2359
|
// In case of an object, iterate through it and call `_setToTarget` again for each property.
|
|
2211
2360
|
if (isPlainObject(name)) {
|
|
2212
2361
|
this._setObjectToTarget(target, name, isDefine);
|
|
@@ -2243,12 +2392,12 @@ class Config {
|
|
|
2243
2392
|
target[name] = value;
|
|
2244
2393
|
}
|
|
2245
2394
|
/**
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2395
|
+
* Get specified configuration from specified source (nested object).
|
|
2396
|
+
*
|
|
2397
|
+
* @param source level of nested object.
|
|
2398
|
+
* @param name The configuration name. Configuration names are case-sensitive.
|
|
2399
|
+
* @returns The configuration value or `undefined` if the configuration entry was not found.
|
|
2400
|
+
*/ _getFromSource(source, name) {
|
|
2252
2401
|
// The configuration name should be split into parts if it has dots. E.g. `resize.width` -> [`resize`, `width`].
|
|
2253
2402
|
const parts = name.split('.');
|
|
2254
2403
|
// Take the name of the configuration out of the parts. E.g. `resize.width` -> `width`.
|
|
@@ -2266,34 +2415,16 @@ class Config {
|
|
|
2266
2415
|
return source ? cloneConfig(source[name]) : undefined;
|
|
2267
2416
|
}
|
|
2268
2417
|
/**
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2418
|
+
* Iterates through passed object and calls {@link #_setToTarget} method with object key and value for each property.
|
|
2419
|
+
*
|
|
2420
|
+
* @param target Nested config object.
|
|
2421
|
+
* @param configuration Configuration data set
|
|
2422
|
+
* @param isDefine Defines if passed configuration is default configuration or not.
|
|
2423
|
+
*/ _setObjectToTarget(target, configuration, isDefine) {
|
|
2275
2424
|
Object.keys(configuration).forEach((key)=>{
|
|
2276
2425
|
this._setToTarget(target, key, configuration[key], isDefine);
|
|
2277
2426
|
});
|
|
2278
2427
|
}
|
|
2279
|
-
/**
|
|
2280
|
-
* Creates an instance of the {@link ~Config} class.
|
|
2281
|
-
*
|
|
2282
|
-
* @param configurations The initial configurations to be set. Usually, provided by the user.
|
|
2283
|
-
* @param defaultConfigurations The default configurations. Usually, provided by the system.
|
|
2284
|
-
*/ constructor(configurations, defaultConfigurations){
|
|
2285
|
-
this._config = {};
|
|
2286
|
-
// Set default configuration.
|
|
2287
|
-
if (defaultConfigurations) {
|
|
2288
|
-
// Clone the configuration to make sure that the properties will not be shared
|
|
2289
|
-
// between editors and make the watchdog feature work correctly.
|
|
2290
|
-
this.define(cloneConfig(defaultConfigurations));
|
|
2291
|
-
}
|
|
2292
|
-
// Set initial configuration.
|
|
2293
|
-
if (configurations) {
|
|
2294
|
-
this._setObjectToTarget(this._config, configurations);
|
|
2295
|
-
}
|
|
2296
|
-
}
|
|
2297
2428
|
}
|
|
2298
2429
|
/**
|
|
2299
2430
|
* Clones configuration object or value.
|
|
@@ -2346,7 +2477,7 @@ class Config {
|
|
|
2346
2477
|
return false;
|
|
2347
2478
|
}
|
|
2348
2479
|
|
|
2349
|
-
const defaultEmitterClass = DomEmitterMixin(EmitterMixin());
|
|
2480
|
+
const defaultEmitterClass = /* #__PURE__ */ DomEmitterMixin(/* #__PURE__ */ EmitterMixin());
|
|
2350
2481
|
function DomEmitterMixin(base) {
|
|
2351
2482
|
if (!base) {
|
|
2352
2483
|
return defaultEmitterClass;
|
|
@@ -2379,23 +2510,23 @@ function DomEmitterMixin(base) {
|
|
|
2379
2510
|
}
|
|
2380
2511
|
}
|
|
2381
2512
|
/**
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2513
|
+
* Retrieves ProxyEmitter instance for given DOM Node residing in this Host and given options.
|
|
2514
|
+
*
|
|
2515
|
+
* @param node DOM Node of the ProxyEmitter.
|
|
2516
|
+
* @param options Additional options.
|
|
2517
|
+
* @param options.useCapture Indicates that events of this type will be dispatched to the registered
|
|
2518
|
+
* listener before being dispatched to any EventTarget beneath it in the DOM tree.
|
|
2519
|
+
* @param options.usePassive Indicates that the function specified by listener will never call preventDefault()
|
|
2520
|
+
* and prevents blocking browser's main thread by this event handler.
|
|
2521
|
+
* @returns ProxyEmitter instance bound to the DOM Node.
|
|
2522
|
+
*/ _getProxyEmitter(node, options) {
|
|
2392
2523
|
return _getEmitterListenedTo(this, getProxyEmitterId(node, options));
|
|
2393
2524
|
}
|
|
2394
2525
|
/**
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2526
|
+
* Retrieves all the ProxyEmitter instances for given DOM Node residing in this Host.
|
|
2527
|
+
*
|
|
2528
|
+
* @param node DOM Node of the ProxyEmitter.
|
|
2529
|
+
*/ _getAllProxyEmitters(node) {
|
|
2399
2530
|
return [
|
|
2400
2531
|
{
|
|
2401
2532
|
capture: false,
|
|
@@ -2462,18 +2593,39 @@ function DomEmitterMixin(base) {
|
|
|
2462
2593
|
* | | click (DOM Event)
|
|
2463
2594
|
* +-----------------------------------------+
|
|
2464
2595
|
* fire( click, DOM Event )
|
|
2465
|
-
*/ class ProxyEmitter extends EmitterMixin() {
|
|
2596
|
+
*/ class ProxyEmitter extends /* #__PURE__ */ EmitterMixin() {
|
|
2597
|
+
_domNode;
|
|
2598
|
+
_options;
|
|
2466
2599
|
/**
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2600
|
+
* @param node DOM Node that fires events.
|
|
2601
|
+
* @param options Additional options.
|
|
2602
|
+
* @param options.useCapture Indicates that events of this type will be dispatched to the registered
|
|
2603
|
+
* listener before being dispatched to any EventTarget beneath it in the DOM tree.
|
|
2604
|
+
* @param options.usePassive Indicates that the function specified by listener will never call preventDefault()
|
|
2605
|
+
* and prevents blocking browser's main thread by this event handler.
|
|
2606
|
+
*/ constructor(node, options){
|
|
2607
|
+
super();
|
|
2608
|
+
// Set emitter ID to match DOM Node "expando" property.
|
|
2609
|
+
_setEmitterId(this, getProxyEmitterId(node, options));
|
|
2610
|
+
// Remember the DOM Node this ProxyEmitter is bound to.
|
|
2611
|
+
this._domNode = node;
|
|
2612
|
+
// And given options.
|
|
2613
|
+
this._options = options;
|
|
2614
|
+
}
|
|
2615
|
+
/**
|
|
2616
|
+
* Collection of native DOM listeners.
|
|
2617
|
+
*/ _domListeners;
|
|
2618
|
+
/**
|
|
2619
|
+
* Registers a callback function to be executed when an event is fired.
|
|
2620
|
+
*
|
|
2621
|
+
* It attaches a native DOM listener to the DOM Node. When fired,
|
|
2622
|
+
* a corresponding Emitter event will also fire with DOM Event object as an argument.
|
|
2623
|
+
*
|
|
2624
|
+
* **Note**: This is automatically called by the
|
|
2625
|
+
* {@link module:utils/emittermixin~Emitter#listenTo `Emitter#listenTo()`}.
|
|
2626
|
+
*
|
|
2627
|
+
* @param event The name of the event.
|
|
2628
|
+
*/ attach(event) {
|
|
2477
2629
|
// If the DOM Listener for given event already exist it is pointless
|
|
2478
2630
|
// to attach another one.
|
|
2479
2631
|
if (this._domListeners && this._domListeners[event]) {
|
|
@@ -2490,13 +2642,13 @@ function DomEmitterMixin(base) {
|
|
|
2490
2642
|
this._domListeners[event] = domListener;
|
|
2491
2643
|
}
|
|
2492
2644
|
/**
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2645
|
+
* Stops executing the callback on the given event.
|
|
2646
|
+
*
|
|
2647
|
+
* **Note**: This is automatically called by the
|
|
2648
|
+
* {@link module:utils/emittermixin~Emitter#stopListening `Emitter#stopListening()`}.
|
|
2649
|
+
*
|
|
2650
|
+
* @param event The name of the event.
|
|
2651
|
+
*/ detach(event) {
|
|
2500
2652
|
let events;
|
|
2501
2653
|
// Remove native DOM listeners which are orphans. If no callbacks
|
|
2502
2654
|
// are awaiting given event, detach native DOM listener from DOM Node.
|
|
@@ -2506,34 +2658,34 @@ function DomEmitterMixin(base) {
|
|
|
2506
2658
|
}
|
|
2507
2659
|
}
|
|
2508
2660
|
/**
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2661
|
+
* Adds callback to emitter for given event.
|
|
2662
|
+
*
|
|
2663
|
+
* @internal
|
|
2664
|
+
* @param event The name of the event.
|
|
2665
|
+
* @param callback The function to be called on event.
|
|
2666
|
+
* @param options Additional options.
|
|
2667
|
+
*/ _addEventListener(event, callback, options) {
|
|
2516
2668
|
this.attach(event);
|
|
2517
2669
|
EmitterMixin().prototype._addEventListener.call(this, event, callback, options);
|
|
2518
2670
|
}
|
|
2519
2671
|
/**
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2672
|
+
* Removes callback from emitter for given event.
|
|
2673
|
+
*
|
|
2674
|
+
* @internal
|
|
2675
|
+
* @param event The name of the event.
|
|
2676
|
+
* @param callback The function to stop being called.
|
|
2677
|
+
*/ _removeEventListener(event, callback) {
|
|
2526
2678
|
EmitterMixin().prototype._removeEventListener.call(this, event, callback);
|
|
2527
2679
|
this.detach(event);
|
|
2528
2680
|
}
|
|
2529
2681
|
/**
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2682
|
+
* Creates a native DOM listener callback. When the native DOM event
|
|
2683
|
+
* is fired it will fire corresponding event on this ProxyEmitter.
|
|
2684
|
+
* Note: A native DOM Event is passed as an argument.
|
|
2685
|
+
*
|
|
2686
|
+
* @param event The name of the event.
|
|
2687
|
+
* @returns The DOM listener callback.
|
|
2688
|
+
*/ _createDomListener(event) {
|
|
2537
2689
|
const domListener = (domEvt)=>{
|
|
2538
2690
|
this.fire(event, domEvt);
|
|
2539
2691
|
};
|
|
@@ -2546,22 +2698,6 @@ function DomEmitterMixin(base) {
|
|
|
2546
2698
|
};
|
|
2547
2699
|
return domListener;
|
|
2548
2700
|
}
|
|
2549
|
-
/**
|
|
2550
|
-
* @param node DOM Node that fires events.
|
|
2551
|
-
* @param options Additional options.
|
|
2552
|
-
* @param options.useCapture Indicates that events of this type will be dispatched to the registered
|
|
2553
|
-
* listener before being dispatched to any EventTarget beneath it in the DOM tree.
|
|
2554
|
-
* @param options.usePassive Indicates that the function specified by listener will never call preventDefault()
|
|
2555
|
-
* and prevents blocking browser's main thread by this event handler.
|
|
2556
|
-
*/ constructor(node, options){
|
|
2557
|
-
super();
|
|
2558
|
-
// Set emitter ID to match DOM Node "expando" property.
|
|
2559
|
-
_setEmitterId(this, getProxyEmitterId(node, options));
|
|
2560
|
-
// Remember the DOM Node this ProxyEmitter is bound to.
|
|
2561
|
-
this._domNode = node;
|
|
2562
|
-
// And given options.
|
|
2563
|
-
this._options = options;
|
|
2564
|
-
}
|
|
2565
2701
|
}
|
|
2566
2702
|
/**
|
|
2567
2703
|
* Gets an unique DOM Node identifier. The identifier will be set if not defined.
|
|
@@ -2582,44 +2718,6 @@ function DomEmitterMixin(base) {
|
|
|
2582
2718
|
return id;
|
|
2583
2719
|
}
|
|
2584
2720
|
|
|
2585
|
-
/**
|
|
2586
|
-
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
2587
|
-
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
2588
|
-
*/ /**
|
|
2589
|
-
* A helper (module) giving an access to the global DOM objects such as `window` and
|
|
2590
|
-
* `document`. Accessing these objects using this helper allows easy and bulletproof
|
|
2591
|
-
* testing, i.e. stubbing native properties:
|
|
2592
|
-
*
|
|
2593
|
-
* ```ts
|
|
2594
|
-
* import { global } from 'ckeditor5/utils';
|
|
2595
|
-
*
|
|
2596
|
-
* // This stub will work for any code using global module.
|
|
2597
|
-
* testUtils.sinon.stub( global, 'window', {
|
|
2598
|
-
* innerWidth: 10000
|
|
2599
|
-
* } );
|
|
2600
|
-
*
|
|
2601
|
-
* console.log( global.window.innerWidth );
|
|
2602
|
-
* ```
|
|
2603
|
-
*/ let globalVar; // named globalVar instead of global: https://github.com/ckeditor/ckeditor5/issues/12971
|
|
2604
|
-
// In some environments window and document API might not be available.
|
|
2605
|
-
try {
|
|
2606
|
-
globalVar = {
|
|
2607
|
-
window,
|
|
2608
|
-
document
|
|
2609
|
-
};
|
|
2610
|
-
} catch (e) {
|
|
2611
|
-
// It's not possible to mock a window object to simulate lack of a window object without writing extremely convoluted code.
|
|
2612
|
-
/* istanbul ignore next -- @preserve */ // Let's cast it to not change module's API.
|
|
2613
|
-
// We only handle this so loading editor in environments without window and document doesn't fail.
|
|
2614
|
-
// For better DX we shouldn't introduce mixed types and require developers to check the type manually.
|
|
2615
|
-
// This module should not be used on purpose in any environment outside browser.
|
|
2616
|
-
globalVar = {
|
|
2617
|
-
window: {},
|
|
2618
|
-
document: {}
|
|
2619
|
-
};
|
|
2620
|
-
}
|
|
2621
|
-
var global = globalVar;
|
|
2622
|
-
|
|
2623
2721
|
/**
|
|
2624
2722
|
* Returns the closest scrollable ancestor of a DOM element.
|
|
2625
2723
|
*
|
|
@@ -2750,21 +2848,127 @@ const rectProperties = [
|
|
|
2750
2848
|
'width',
|
|
2751
2849
|
'height'
|
|
2752
2850
|
];
|
|
2753
|
-
|
|
2851
|
+
/**
|
|
2852
|
+
* A helper class representing a `ClientRect` object, e.g. value returned by
|
|
2853
|
+
* the native `object.getBoundingClientRect()` method. Provides a set of methods
|
|
2854
|
+
* to manipulate the rect and compare it against other rect instances.
|
|
2855
|
+
*/ class Rect {
|
|
2856
|
+
/**
|
|
2857
|
+
* The "top" value of the rect.
|
|
2858
|
+
*
|
|
2859
|
+
* @readonly
|
|
2860
|
+
*/ top;
|
|
2861
|
+
/**
|
|
2862
|
+
* The "right" value of the rect.
|
|
2863
|
+
*
|
|
2864
|
+
* @readonly
|
|
2865
|
+
*/ right;
|
|
2866
|
+
/**
|
|
2867
|
+
* The "bottom" value of the rect.
|
|
2868
|
+
*
|
|
2869
|
+
* @readonly
|
|
2870
|
+
*/ bottom;
|
|
2871
|
+
/**
|
|
2872
|
+
* The "left" value of the rect.
|
|
2873
|
+
*
|
|
2874
|
+
* @readonly
|
|
2875
|
+
*/ left;
|
|
2876
|
+
/**
|
|
2877
|
+
* The "width" value of the rect.
|
|
2878
|
+
*
|
|
2879
|
+
* @readonly
|
|
2880
|
+
*/ width;
|
|
2881
|
+
/**
|
|
2882
|
+
* The "height" value of the rect.
|
|
2883
|
+
*
|
|
2884
|
+
* @readonly
|
|
2885
|
+
*/ height;
|
|
2886
|
+
/**
|
|
2887
|
+
* The object this rect is for.
|
|
2888
|
+
*
|
|
2889
|
+
* @readonly
|
|
2890
|
+
*/ _source;
|
|
2891
|
+
/**
|
|
2892
|
+
* Creates an instance of rect.
|
|
2893
|
+
*
|
|
2894
|
+
* ```ts
|
|
2895
|
+
* // Rect of an HTMLElement.
|
|
2896
|
+
* const rectA = new Rect( document.body );
|
|
2897
|
+
*
|
|
2898
|
+
* // Rect of a DOM Range.
|
|
2899
|
+
* const rectB = new Rect( document.getSelection().getRangeAt( 0 ) );
|
|
2900
|
+
*
|
|
2901
|
+
* // Rect of a window (web browser viewport).
|
|
2902
|
+
* const rectC = new Rect( window );
|
|
2903
|
+
*
|
|
2904
|
+
* // Rect out of an object.
|
|
2905
|
+
* const rectD = new Rect( { top: 0, right: 10, bottom: 10, left: 0, width: 10, height: 10 } );
|
|
2906
|
+
*
|
|
2907
|
+
* // Rect out of another Rect instance.
|
|
2908
|
+
* const rectE = new Rect( rectD );
|
|
2909
|
+
*
|
|
2910
|
+
* // Rect out of a ClientRect.
|
|
2911
|
+
* const rectF = new Rect( document.body.getClientRects().item( 0 ) );
|
|
2912
|
+
* ```
|
|
2913
|
+
*
|
|
2914
|
+
* **Note**: By default a rect of an HTML element includes its CSS borders and scrollbars (if any)
|
|
2915
|
+
* ant the rect of a `window` includes scrollbars too. Use {@link #excludeScrollbarsAndBorders}
|
|
2916
|
+
* to get the inner part of the rect.
|
|
2917
|
+
*
|
|
2918
|
+
* @param source A source object to create the rect.
|
|
2919
|
+
*/ constructor(source){
|
|
2920
|
+
const isSourceRange = isRange(source);
|
|
2921
|
+
Object.defineProperty(this, '_source', {
|
|
2922
|
+
// If the source is a Rect instance, copy it's #_source.
|
|
2923
|
+
value: source._source || source,
|
|
2924
|
+
writable: true,
|
|
2925
|
+
enumerable: false
|
|
2926
|
+
});
|
|
2927
|
+
if (isDomElement(source) || isSourceRange) {
|
|
2928
|
+
// The `Rect` class depends on `getBoundingClientRect` and `getClientRects` DOM methods. If the source
|
|
2929
|
+
// of a rect in an HTML element or a DOM range but it does not belong to any rendered DOM tree, these methods
|
|
2930
|
+
// will fail to obtain the geometry and the rect instance makes little sense to the features using it.
|
|
2931
|
+
// To get rid of this warning make sure the source passed to the constructor is a descendant of `window.document.body`.
|
|
2932
|
+
// @if CK_DEBUG // const sourceNode = isSourceRange ? source.startContainer : source;
|
|
2933
|
+
// @if CK_DEBUG // if ( !sourceNode.ownerDocument || !sourceNode.ownerDocument.body.contains( sourceNode ) ) {
|
|
2934
|
+
// @if CK_DEBUG // console.warn(
|
|
2935
|
+
// @if CK_DEBUG // 'rect-source-not-in-dom: The source of this rect does not belong to any rendered DOM tree.',
|
|
2936
|
+
// @if CK_DEBUG // { source } );
|
|
2937
|
+
// @if CK_DEBUG // }
|
|
2938
|
+
if (isSourceRange) {
|
|
2939
|
+
const rangeRects = Rect.getDomRangeRects(source);
|
|
2940
|
+
copyRectProperties(this, Rect.getBoundingRect(rangeRects));
|
|
2941
|
+
} else {
|
|
2942
|
+
copyRectProperties(this, source.getBoundingClientRect());
|
|
2943
|
+
}
|
|
2944
|
+
} else if (isWindow(source)) {
|
|
2945
|
+
const { innerWidth, innerHeight } = source;
|
|
2946
|
+
copyRectProperties(this, {
|
|
2947
|
+
top: 0,
|
|
2948
|
+
right: innerWidth,
|
|
2949
|
+
bottom: innerHeight,
|
|
2950
|
+
left: 0,
|
|
2951
|
+
width: innerWidth,
|
|
2952
|
+
height: innerHeight
|
|
2953
|
+
});
|
|
2954
|
+
} else {
|
|
2955
|
+
copyRectProperties(this, source);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2754
2958
|
/**
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2959
|
+
* Returns a clone of the rect.
|
|
2960
|
+
*
|
|
2961
|
+
* @returns A cloned rect.
|
|
2962
|
+
*/ clone() {
|
|
2759
2963
|
return new Rect(this);
|
|
2760
2964
|
}
|
|
2761
2965
|
/**
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2966
|
+
* Moves the rect so that its upper–left corner lands in desired `[ x, y ]` location.
|
|
2967
|
+
*
|
|
2968
|
+
* @param x Desired horizontal location.
|
|
2969
|
+
* @param y Desired vertical location.
|
|
2970
|
+
* @returns A rect which has been moved.
|
|
2971
|
+
*/ moveTo(x, y) {
|
|
2768
2972
|
this.top = y;
|
|
2769
2973
|
this.right = x + this.width;
|
|
2770
2974
|
this.bottom = y + this.height;
|
|
@@ -2772,12 +2976,12 @@ class Rect {
|
|
|
2772
2976
|
return this;
|
|
2773
2977
|
}
|
|
2774
2978
|
/**
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2979
|
+
* Moves the rect in–place by a dedicated offset.
|
|
2980
|
+
*
|
|
2981
|
+
* @param x A horizontal offset.
|
|
2982
|
+
* @param y A vertical offset
|
|
2983
|
+
* @returns A rect which has been moved.
|
|
2984
|
+
*/ moveBy(x, y) {
|
|
2781
2985
|
this.top += y;
|
|
2782
2986
|
this.right += x;
|
|
2783
2987
|
this.left += x;
|
|
@@ -2785,8 +2989,8 @@ class Rect {
|
|
|
2785
2989
|
return this;
|
|
2786
2990
|
}
|
|
2787
2991
|
/**
|
|
2788
|
-
|
|
2789
|
-
|
|
2992
|
+
* Returns a new rect a a result of intersection with another rect.
|
|
2993
|
+
*/ getIntersection(anotherRect) {
|
|
2790
2994
|
const rect = {
|
|
2791
2995
|
top: Math.max(this.top, anotherRect.top),
|
|
2792
2996
|
right: Math.min(this.right, anotherRect.right),
|
|
@@ -2806,10 +3010,10 @@ class Rect {
|
|
|
2806
3010
|
}
|
|
2807
3011
|
}
|
|
2808
3012
|
/**
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
3013
|
+
* Returns the area of intersection with another rect.
|
|
3014
|
+
*
|
|
3015
|
+
* @returns Area of intersection.
|
|
3016
|
+
*/ getIntersectionArea(anotherRect) {
|
|
2813
3017
|
const rect = this.getIntersection(anotherRect);
|
|
2814
3018
|
if (rect) {
|
|
2815
3019
|
return rect.getArea();
|
|
@@ -2818,27 +3022,27 @@ class Rect {
|
|
|
2818
3022
|
}
|
|
2819
3023
|
}
|
|
2820
3024
|
/**
|
|
2821
|
-
|
|
2822
|
-
|
|
3025
|
+
* Returns the area of the rect.
|
|
3026
|
+
*/ getArea() {
|
|
2823
3027
|
return this.width * this.height;
|
|
2824
3028
|
}
|
|
2825
3029
|
/**
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
3030
|
+
* Returns a new rect, a part of the original rect, which is actually visible to the user and is relative to the,`body`,
|
|
3031
|
+
* e.g. an original rect cropped by parent element rects which have `overflow` set in CSS
|
|
3032
|
+
* other than `"visible"`.
|
|
3033
|
+
*
|
|
3034
|
+
* If there's no such visible rect, which is when the rect is limited by one or many of
|
|
3035
|
+
* the ancestors, `null` is returned.
|
|
3036
|
+
*
|
|
3037
|
+
* **Note**: This method does not consider the boundaries of the viewport (window).
|
|
3038
|
+
* To get a rect cropped by all ancestors and the viewport, use an intersection such as:
|
|
3039
|
+
*
|
|
3040
|
+
* ```ts
|
|
3041
|
+
* const visibleInViewportRect = new Rect( window ).getIntersection( new Rect( source ).getVisible() );
|
|
3042
|
+
* ```
|
|
3043
|
+
*
|
|
3044
|
+
* @returns A visible rect instance or `null`, if there's none.
|
|
3045
|
+
*/ getVisible() {
|
|
2842
3046
|
const source = this._source;
|
|
2843
3047
|
let visibleRect = this.clone();
|
|
2844
3048
|
// There's no ancestor to crop <body> with the overflow.
|
|
@@ -2910,13 +3114,13 @@ class Rect {
|
|
|
2910
3114
|
return visibleRect;
|
|
2911
3115
|
}
|
|
2912
3116
|
/**
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
3117
|
+
* Checks if all property values ({@link #top}, {@link #left}, {@link #right},
|
|
3118
|
+
* {@link #bottom}, {@link #width} and {@link #height}) are the equal in both rect
|
|
3119
|
+
* instances.
|
|
3120
|
+
*
|
|
3121
|
+
* @param anotherRect A rect instance to compare with.
|
|
3122
|
+
* @returns `true` when Rects are equal. `false` otherwise.
|
|
3123
|
+
*/ isEqual(anotherRect) {
|
|
2920
3124
|
for (const prop of rectProperties){
|
|
2921
3125
|
if (this[prop] !== anotherRect[prop]) {
|
|
2922
3126
|
return false;
|
|
@@ -2925,17 +3129,17 @@ class Rect {
|
|
|
2925
3129
|
return true;
|
|
2926
3130
|
}
|
|
2927
3131
|
/**
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
3132
|
+
* Checks whether a rect fully contains another rect instance.
|
|
3133
|
+
*
|
|
3134
|
+
* @param anotherRect
|
|
3135
|
+
* @returns `true` if contains, `false` otherwise.
|
|
3136
|
+
*/ contains(anotherRect) {
|
|
2933
3137
|
const intersectRect = this.getIntersection(anotherRect);
|
|
2934
3138
|
return !!(intersectRect && intersectRect.isEqual(anotherRect));
|
|
2935
3139
|
}
|
|
2936
3140
|
/**
|
|
2937
|
-
|
|
2938
|
-
|
|
3141
|
+
* Recalculates screen coordinates to coordinates relative to the positioned ancestor offset.
|
|
3142
|
+
*/ toAbsoluteRect() {
|
|
2939
3143
|
const { scrollX, scrollY } = global.window;
|
|
2940
3144
|
const absoluteRect = this.clone().moveBy(scrollX, scrollY);
|
|
2941
3145
|
if (isDomElement(absoluteRect._source)) {
|
|
@@ -2947,13 +3151,13 @@ class Rect {
|
|
|
2947
3151
|
return absoluteRect;
|
|
2948
3152
|
}
|
|
2949
3153
|
/**
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
3154
|
+
* Excludes scrollbars and CSS borders from the rect.
|
|
3155
|
+
*
|
|
3156
|
+
* * Borders are removed when {@link #_source} is an HTML element.
|
|
3157
|
+
* * Scrollbars are excluded from HTML elements and the `window`.
|
|
3158
|
+
*
|
|
3159
|
+
* @returns A rect which has been updated.
|
|
3160
|
+
*/ excludeScrollbarsAndBorders() {
|
|
2957
3161
|
const source = this._source;
|
|
2958
3162
|
let scrollBarWidth, scrollBarHeight, direction;
|
|
2959
3163
|
if (isWindow(source)) {
|
|
@@ -2983,11 +3187,11 @@ class Rect {
|
|
|
2983
3187
|
return this;
|
|
2984
3188
|
}
|
|
2985
3189
|
/**
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
3190
|
+
* Returns an array of rects of the given native DOM Range.
|
|
3191
|
+
*
|
|
3192
|
+
* @param range A native DOM range.
|
|
3193
|
+
* @returns DOM Range rects.
|
|
3194
|
+
*/ static getDomRangeRects(range) {
|
|
2991
3195
|
const rects = [];
|
|
2992
3196
|
// Safari does not iterate over ClientRectList using for...of loop.
|
|
2993
3197
|
const clientRects = Array.from(range.getClientRects());
|
|
@@ -3008,11 +3212,11 @@ class Rect {
|
|
|
3008
3212
|
return rects;
|
|
3009
3213
|
}
|
|
3010
3214
|
/**
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3215
|
+
* Returns a bounding rectangle that contains all the given `rects`.
|
|
3216
|
+
*
|
|
3217
|
+
* @param rects A list of rectangles that should be contained in the result rectangle.
|
|
3218
|
+
* @returns Bounding rectangle or `null` if no `rects` were given.
|
|
3219
|
+
*/ static getBoundingRect(rects) {
|
|
3016
3220
|
const boundingRectData = {
|
|
3017
3221
|
left: Number.POSITIVE_INFINITY,
|
|
3018
3222
|
top: Number.POSITIVE_INFINITY,
|
|
@@ -3036,73 +3240,6 @@ class Rect {
|
|
|
3036
3240
|
boundingRectData.height = boundingRectData.bottom - boundingRectData.top;
|
|
3037
3241
|
return new Rect(boundingRectData);
|
|
3038
3242
|
}
|
|
3039
|
-
/**
|
|
3040
|
-
* Creates an instance of rect.
|
|
3041
|
-
*
|
|
3042
|
-
* ```ts
|
|
3043
|
-
* // Rect of an HTMLElement.
|
|
3044
|
-
* const rectA = new Rect( document.body );
|
|
3045
|
-
*
|
|
3046
|
-
* // Rect of a DOM Range.
|
|
3047
|
-
* const rectB = new Rect( document.getSelection().getRangeAt( 0 ) );
|
|
3048
|
-
*
|
|
3049
|
-
* // Rect of a window (web browser viewport).
|
|
3050
|
-
* const rectC = new Rect( window );
|
|
3051
|
-
*
|
|
3052
|
-
* // Rect out of an object.
|
|
3053
|
-
* const rectD = new Rect( { top: 0, right: 10, bottom: 10, left: 0, width: 10, height: 10 } );
|
|
3054
|
-
*
|
|
3055
|
-
* // Rect out of another Rect instance.
|
|
3056
|
-
* const rectE = new Rect( rectD );
|
|
3057
|
-
*
|
|
3058
|
-
* // Rect out of a ClientRect.
|
|
3059
|
-
* const rectF = new Rect( document.body.getClientRects().item( 0 ) );
|
|
3060
|
-
* ```
|
|
3061
|
-
*
|
|
3062
|
-
* **Note**: By default a rect of an HTML element includes its CSS borders and scrollbars (if any)
|
|
3063
|
-
* ant the rect of a `window` includes scrollbars too. Use {@link #excludeScrollbarsAndBorders}
|
|
3064
|
-
* to get the inner part of the rect.
|
|
3065
|
-
*
|
|
3066
|
-
* @param source A source object to create the rect.
|
|
3067
|
-
*/ constructor(source){
|
|
3068
|
-
const isSourceRange = isRange(source);
|
|
3069
|
-
Object.defineProperty(this, '_source', {
|
|
3070
|
-
// If the source is a Rect instance, copy it's #_source.
|
|
3071
|
-
value: source._source || source,
|
|
3072
|
-
writable: true,
|
|
3073
|
-
enumerable: false
|
|
3074
|
-
});
|
|
3075
|
-
if (isDomElement(source) || isSourceRange) {
|
|
3076
|
-
// The `Rect` class depends on `getBoundingClientRect` and `getClientRects` DOM methods. If the source
|
|
3077
|
-
// of a rect in an HTML element or a DOM range but it does not belong to any rendered DOM tree, these methods
|
|
3078
|
-
// will fail to obtain the geometry and the rect instance makes little sense to the features using it.
|
|
3079
|
-
// To get rid of this warning make sure the source passed to the constructor is a descendant of `window.document.body`.
|
|
3080
|
-
// @if CK_DEBUG // const sourceNode = isSourceRange ? source.startContainer : source;
|
|
3081
|
-
// @if CK_DEBUG // if ( !sourceNode.ownerDocument || !sourceNode.ownerDocument.body.contains( sourceNode ) ) {
|
|
3082
|
-
// @if CK_DEBUG // console.warn(
|
|
3083
|
-
// @if CK_DEBUG // 'rect-source-not-in-dom: The source of this rect does not belong to any rendered DOM tree.',
|
|
3084
|
-
// @if CK_DEBUG // { source } );
|
|
3085
|
-
// @if CK_DEBUG // }
|
|
3086
|
-
if (isSourceRange) {
|
|
3087
|
-
const rangeRects = Rect.getDomRangeRects(source);
|
|
3088
|
-
copyRectProperties(this, Rect.getBoundingRect(rangeRects));
|
|
3089
|
-
} else {
|
|
3090
|
-
copyRectProperties(this, source.getBoundingClientRect());
|
|
3091
|
-
}
|
|
3092
|
-
} else if (isWindow(source)) {
|
|
3093
|
-
const { innerWidth, innerHeight } = source;
|
|
3094
|
-
copyRectProperties(this, {
|
|
3095
|
-
top: 0,
|
|
3096
|
-
right: innerWidth,
|
|
3097
|
-
bottom: innerHeight,
|
|
3098
|
-
left: 0,
|
|
3099
|
-
width: innerWidth,
|
|
3100
|
-
height: innerHeight
|
|
3101
|
-
});
|
|
3102
|
-
} else {
|
|
3103
|
-
copyRectProperties(this, source);
|
|
3104
|
-
}
|
|
3105
|
-
}
|
|
3106
3243
|
}
|
|
3107
3244
|
/**
|
|
3108
3245
|
* Acquires all the rect properties from the passed source.
|
|
@@ -3188,18 +3325,50 @@ class Rect {
|
|
|
3188
3325
|
* under the hood.
|
|
3189
3326
|
*/ class ResizeObserver {
|
|
3190
3327
|
/**
|
|
3191
|
-
|
|
3192
|
-
|
|
3328
|
+
* The element observed by this observer.
|
|
3329
|
+
*/ _element;
|
|
3330
|
+
/**
|
|
3331
|
+
* The callback executed each time {@link #_element} is resized.
|
|
3332
|
+
*/ _callback;
|
|
3333
|
+
/**
|
|
3334
|
+
* The single native observer instance shared across all {@link module:utils/dom/resizeobserver~ResizeObserver} instances.
|
|
3335
|
+
*/ static _observerInstance = null;
|
|
3336
|
+
/**
|
|
3337
|
+
* A mapping of native DOM elements and their callbacks shared across all
|
|
3338
|
+
* {@link module:utils/dom/resizeobserver~ResizeObserver} instances.
|
|
3339
|
+
*/ static _elementCallbacks = null;
|
|
3340
|
+
/**
|
|
3341
|
+
* Creates an instance of the `ResizeObserver` class.
|
|
3342
|
+
*
|
|
3343
|
+
* @param element A DOM element that is to be observed for resizing. Note that
|
|
3344
|
+
* the element must be visible (i.e. not detached from DOM) for the observer to work.
|
|
3345
|
+
* @param callback A function called when the observed element was resized. It passes
|
|
3346
|
+
* the [`ResizeObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry)
|
|
3347
|
+
* object with information about the resize event.
|
|
3348
|
+
*/ constructor(element, callback){
|
|
3349
|
+
// **Note**: For the maximum performance, this class ensures only a single instance of the native
|
|
3350
|
+
// observer is used no matter how many instances of this class were created.
|
|
3351
|
+
if (!ResizeObserver._observerInstance) {
|
|
3352
|
+
ResizeObserver._createObserver();
|
|
3353
|
+
}
|
|
3354
|
+
this._element = element;
|
|
3355
|
+
this._callback = callback;
|
|
3356
|
+
ResizeObserver._addElementCallback(element, callback);
|
|
3357
|
+
ResizeObserver._observerInstance.observe(element);
|
|
3358
|
+
}
|
|
3359
|
+
/**
|
|
3360
|
+
* The element observed by this observer.
|
|
3361
|
+
*/ get element() {
|
|
3193
3362
|
return this._element;
|
|
3194
3363
|
}
|
|
3195
3364
|
/**
|
|
3196
|
-
|
|
3197
|
-
|
|
3365
|
+
* Destroys the observer which disables the `callback` passed to the {@link #constructor}.
|
|
3366
|
+
*/ destroy() {
|
|
3198
3367
|
ResizeObserver._deleteElementCallback(this._element, this._callback);
|
|
3199
3368
|
}
|
|
3200
3369
|
/**
|
|
3201
|
-
|
|
3202
|
-
|
|
3370
|
+
* Registers a new resize callback for the DOM element.
|
|
3371
|
+
*/ static _addElementCallback(element, callback) {
|
|
3203
3372
|
if (!ResizeObserver._elementCallbacks) {
|
|
3204
3373
|
ResizeObserver._elementCallbacks = new Map();
|
|
3205
3374
|
}
|
|
@@ -3211,9 +3380,9 @@ class Rect {
|
|
|
3211
3380
|
callbacks.add(callback);
|
|
3212
3381
|
}
|
|
3213
3382
|
/**
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3383
|
+
* Removes a resize callback from the DOM element. If no callbacks are left
|
|
3384
|
+
* for the element, it removes the element from the native observer.
|
|
3385
|
+
*/ static _deleteElementCallback(element, callback) {
|
|
3217
3386
|
const callbacks = ResizeObserver._getElementCallbacks(element);
|
|
3218
3387
|
// Remove the element callback. Check if exist first in case someone
|
|
3219
3388
|
// called destroy() twice.
|
|
@@ -3231,16 +3400,16 @@ class Rect {
|
|
|
3231
3400
|
}
|
|
3232
3401
|
}
|
|
3233
3402
|
/**
|
|
3234
|
-
|
|
3235
|
-
|
|
3403
|
+
* Returns are registered resize callbacks for the DOM element.
|
|
3404
|
+
*/ static _getElementCallbacks(element) {
|
|
3236
3405
|
if (!ResizeObserver._elementCallbacks) {
|
|
3237
3406
|
return null;
|
|
3238
3407
|
}
|
|
3239
3408
|
return ResizeObserver._elementCallbacks.get(element);
|
|
3240
3409
|
}
|
|
3241
3410
|
/**
|
|
3242
|
-
|
|
3243
|
-
|
|
3411
|
+
* Creates the single native observer shared across all `ResizeObserver` instances.
|
|
3412
|
+
*/ static _createObserver() {
|
|
3244
3413
|
ResizeObserver._observerInstance = new global.window.ResizeObserver((entries)=>{
|
|
3245
3414
|
for (const entry of entries){
|
|
3246
3415
|
const callbacks = ResizeObserver._getElementCallbacks(entry.target);
|
|
@@ -3252,33 +3421,7 @@ class Rect {
|
|
|
3252
3421
|
}
|
|
3253
3422
|
});
|
|
3254
3423
|
}
|
|
3255
|
-
/**
|
|
3256
|
-
* Creates an instance of the `ResizeObserver` class.
|
|
3257
|
-
*
|
|
3258
|
-
* @param element A DOM element that is to be observed for resizing. Note that
|
|
3259
|
-
* the element must be visible (i.e. not detached from DOM) for the observer to work.
|
|
3260
|
-
* @param callback A function called when the observed element was resized. It passes
|
|
3261
|
-
* the [`ResizeObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry)
|
|
3262
|
-
* object with information about the resize event.
|
|
3263
|
-
*/ constructor(element, callback){
|
|
3264
|
-
// **Note**: For the maximum performance, this class ensures only a single instance of the native
|
|
3265
|
-
// observer is used no matter how many instances of this class were created.
|
|
3266
|
-
if (!ResizeObserver._observerInstance) {
|
|
3267
|
-
ResizeObserver._createObserver();
|
|
3268
|
-
}
|
|
3269
|
-
this._element = element;
|
|
3270
|
-
this._callback = callback;
|
|
3271
|
-
ResizeObserver._addElementCallback(element, callback);
|
|
3272
|
-
ResizeObserver._observerInstance.observe(element);
|
|
3273
|
-
}
|
|
3274
3424
|
}
|
|
3275
|
-
/**
|
|
3276
|
-
* The single native observer instance shared across all {@link module:utils/dom/resizeobserver~ResizeObserver} instances.
|
|
3277
|
-
*/ ResizeObserver._observerInstance = null;
|
|
3278
|
-
/**
|
|
3279
|
-
* A mapping of native DOM elements and their callbacks shared across all
|
|
3280
|
-
* {@link module:utils/dom/resizeobserver~ResizeObserver} instances.
|
|
3281
|
-
*/ ResizeObserver._elementCallbacks = null;
|
|
3282
3425
|
|
|
3283
3426
|
/**
|
|
3284
3427
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
@@ -3609,21 +3752,53 @@ class Rect {
|
|
|
3609
3752
|
* translate directly to the `top` and `left` properties in CSS "`position: absolute` coordinate system". If set on the positioned element
|
|
3610
3753
|
* in DOM, they will make it display it in the right place in the viewport.
|
|
3611
3754
|
*/ class PositionObject {
|
|
3755
|
+
name;
|
|
3756
|
+
config;
|
|
3757
|
+
_positioningFunctionCoordinates;
|
|
3758
|
+
_options;
|
|
3759
|
+
_cachedRect;
|
|
3760
|
+
_cachedAbsoluteRect;
|
|
3612
3761
|
/**
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3762
|
+
* Creates an instance of the {@link module:utils/dom/position~PositionObject} class.
|
|
3763
|
+
*
|
|
3764
|
+
* @param positioningFunction function The function that defines the expected
|
|
3765
|
+
* coordinates the positioned element should move to.
|
|
3766
|
+
* @param options options object.
|
|
3767
|
+
* @param options.elementRect The positioned element rect.
|
|
3768
|
+
* @param options.targetRect The target element rect.
|
|
3769
|
+
* @param options.viewportRect The viewport rect.
|
|
3770
|
+
* @param options.limiterRect The limiter rect.
|
|
3771
|
+
* @param options.positionedElementAncestor Nearest element ancestor element which CSS position is not "static".
|
|
3772
|
+
*/ constructor(positioningFunction, options){
|
|
3773
|
+
const positioningFunctionOutput = positioningFunction(options.targetRect, options.elementRect, options.viewportRect, options.limiterRect);
|
|
3774
|
+
// Nameless position for a function that didn't participate.
|
|
3775
|
+
if (!positioningFunctionOutput) {
|
|
3776
|
+
return;
|
|
3777
|
+
}
|
|
3778
|
+
const { left, top, name, config } = positioningFunctionOutput;
|
|
3779
|
+
this.name = name;
|
|
3780
|
+
this.config = config;
|
|
3781
|
+
this._positioningFunctionCoordinates = {
|
|
3782
|
+
left,
|
|
3783
|
+
top
|
|
3784
|
+
};
|
|
3785
|
+
this._options = options;
|
|
3786
|
+
}
|
|
3787
|
+
/**
|
|
3788
|
+
* The left value in pixels in the CSS `position: absolute` coordinate system.
|
|
3789
|
+
* Set it on the positioned element in DOM to move it to the position.
|
|
3790
|
+
*/ get left() {
|
|
3616
3791
|
return this._absoluteRect.left;
|
|
3617
3792
|
}
|
|
3618
3793
|
/**
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3794
|
+
* The top value in pixels in the CSS `position: absolute` coordinate system.
|
|
3795
|
+
* Set it on the positioned element in DOM to move it to the position.
|
|
3796
|
+
*/ get top() {
|
|
3622
3797
|
return this._absoluteRect.top;
|
|
3623
3798
|
}
|
|
3624
3799
|
/**
|
|
3625
|
-
|
|
3626
|
-
|
|
3800
|
+
* An intersection area between positioned element and limiter within viewport constraints.
|
|
3801
|
+
*/ get limiterIntersectionArea() {
|
|
3627
3802
|
const limiterRect = this._options.limiterRect;
|
|
3628
3803
|
if (limiterRect) {
|
|
3629
3804
|
return limiterRect.getIntersectionArea(this._rect);
|
|
@@ -3631,15 +3806,15 @@ class Rect {
|
|
|
3631
3806
|
return 0;
|
|
3632
3807
|
}
|
|
3633
3808
|
/**
|
|
3634
|
-
|
|
3635
|
-
|
|
3809
|
+
* An intersection area between positioned element and viewport.
|
|
3810
|
+
*/ get viewportIntersectionArea() {
|
|
3636
3811
|
const viewportRect = this._options.viewportRect;
|
|
3637
3812
|
return viewportRect.getIntersectionArea(this._rect);
|
|
3638
3813
|
}
|
|
3639
3814
|
/**
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3815
|
+
* An already positioned element rect. A clone of the element rect passed to the constructor
|
|
3816
|
+
* but placed in the viewport according to the positioning function.
|
|
3817
|
+
*/ get _rect() {
|
|
3643
3818
|
if (this._cachedRect) {
|
|
3644
3819
|
return this._cachedRect;
|
|
3645
3820
|
}
|
|
@@ -3647,40 +3822,14 @@ class Rect {
|
|
|
3647
3822
|
return this._cachedRect;
|
|
3648
3823
|
}
|
|
3649
3824
|
/**
|
|
3650
|
-
|
|
3651
|
-
|
|
3825
|
+
* An already absolutely positioned element rect. See ({@link #_rect}).
|
|
3826
|
+
*/ get _absoluteRect() {
|
|
3652
3827
|
if (this._cachedAbsoluteRect) {
|
|
3653
3828
|
return this._cachedAbsoluteRect;
|
|
3654
3829
|
}
|
|
3655
3830
|
this._cachedAbsoluteRect = this._rect.toAbsoluteRect();
|
|
3656
3831
|
return this._cachedAbsoluteRect;
|
|
3657
3832
|
}
|
|
3658
|
-
/**
|
|
3659
|
-
* Creates an instance of the {@link module:utils/dom/position~PositionObject} class.
|
|
3660
|
-
*
|
|
3661
|
-
* @param positioningFunction function The function that defines the expected
|
|
3662
|
-
* coordinates the positioned element should move to.
|
|
3663
|
-
* @param options options object.
|
|
3664
|
-
* @param options.elementRect The positioned element rect.
|
|
3665
|
-
* @param options.targetRect The target element rect.
|
|
3666
|
-
* @param options.viewportRect The viewport rect.
|
|
3667
|
-
* @param options.limiterRect The limiter rect.
|
|
3668
|
-
* @param options.positionedElementAncestor Nearest element ancestor element which CSS position is not "static".
|
|
3669
|
-
*/ constructor(positioningFunction, options){
|
|
3670
|
-
const positioningFunctionOutput = positioningFunction(options.targetRect, options.elementRect, options.viewportRect, options.limiterRect);
|
|
3671
|
-
// Nameless position for a function that didn't participate.
|
|
3672
|
-
if (!positioningFunctionOutput) {
|
|
3673
|
-
return;
|
|
3674
|
-
}
|
|
3675
|
-
const { left, top, name, config } = positioningFunctionOutput;
|
|
3676
|
-
this.name = name;
|
|
3677
|
-
this.config = config;
|
|
3678
|
-
this._positioningFunctionCoordinates = {
|
|
3679
|
-
left,
|
|
3680
|
-
top
|
|
3681
|
-
};
|
|
3682
|
-
this._options = options;
|
|
3683
|
-
}
|
|
3684
3833
|
}
|
|
3685
3834
|
|
|
3686
3835
|
/**
|
|
@@ -4083,8 +4232,8 @@ const keyCodesToGlyphs = {
|
|
|
4083
4232
|
* * `arrow(left|up|right|bottom)`,
|
|
4084
4233
|
* * `backspace`, `delete`, `enter`, `esc`, `tab`,
|
|
4085
4234
|
* * `ctrl`, `cmd`, `shift`, `alt`.
|
|
4086
|
-
*/ const keyCodes = generateKnownKeyCodes();
|
|
4087
|
-
const keyCodeNames = Object.fromEntries(Object.entries(keyCodes).map(([name, code])=>{
|
|
4235
|
+
*/ const keyCodes = /* #__PURE__ */ generateKnownKeyCodes();
|
|
4236
|
+
const keyCodeNames = /* #__PURE__ */ Object.fromEntries(/* #__PURE__ */ Object.entries(keyCodes).map(([name, code])=>{
|
|
4088
4237
|
let prettyKeyName;
|
|
4089
4238
|
if (code in keyCodesToGlyphs) {
|
|
4090
4239
|
prettyKeyName = keyCodesToGlyphs[code];
|
|
@@ -4109,11 +4258,11 @@ const keyCodeNames = Object.fromEntries(Object.entries(keyCodes).map(([name, cod
|
|
|
4109
4258
|
keyCode = keyCodes[key.toLowerCase()];
|
|
4110
4259
|
if (!keyCode) {
|
|
4111
4260
|
/**
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4261
|
+
* Unknown key name. Only key names included in the {@link module:utils/keyboard#keyCodes} can be used.
|
|
4262
|
+
*
|
|
4263
|
+
* @error keyboard-unknown-key
|
|
4264
|
+
* @param {String} key
|
|
4265
|
+
*/ throw new CKEditorError('keyboard-unknown-key', null, {
|
|
4117
4266
|
key
|
|
4118
4267
|
});
|
|
4119
4268
|
}
|
|
@@ -4285,6 +4434,10 @@ function splitKeystrokeText(keystroke) {
|
|
|
4285
4434
|
/**
|
|
4286
4435
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
4287
4436
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4437
|
+
*/ /**
|
|
4438
|
+
* @module utils/language
|
|
4439
|
+
*/ /**
|
|
4440
|
+
* String representing a language direction.
|
|
4288
4441
|
*/ const RTL_LANGUAGE_CODES = [
|
|
4289
4442
|
'ar',
|
|
4290
4443
|
'ara',
|
|
@@ -4311,6 +4464,14 @@ function splitKeystrokeText(keystroke) {
|
|
|
4311
4464
|
/**
|
|
4312
4465
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
4313
4466
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4467
|
+
*/ /**
|
|
4468
|
+
* @module utils/toarray
|
|
4469
|
+
*/ /**
|
|
4470
|
+
* Transforms any value to an array. If the provided value is already an array, it is returned unchanged.
|
|
4471
|
+
*
|
|
4472
|
+
* @label MUTABLE
|
|
4473
|
+
* @param data The value to transform to an array.
|
|
4474
|
+
* @returns An array created from data.
|
|
4314
4475
|
*/ function toArray(data) {
|
|
4315
4476
|
return Array.isArray(data) ? data : [
|
|
4316
4477
|
data
|
|
@@ -4360,12 +4521,12 @@ function splitKeystrokeText(keystroke) {
|
|
|
4360
4521
|
*/ function _translate(language, message, quantity = 1, translations) {
|
|
4361
4522
|
if (typeof quantity !== 'number') {
|
|
4362
4523
|
/**
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4524
|
+
* An incorrect value was passed to the translation function. This was probably caused
|
|
4525
|
+
* by an incorrect message interpolation of a plural form. Note that for messages supporting plural forms
|
|
4526
|
+
* the second argument of the `t()` function should always be a number or an array with a number as the first element.
|
|
4527
|
+
*
|
|
4528
|
+
* @error translation-service-quantity-not-a-number
|
|
4529
|
+
*/ throw new CKEditorError('translation-service-quantity-not-a-number', null, {
|
|
4369
4530
|
quantity
|
|
4370
4531
|
});
|
|
4371
4532
|
}
|
|
@@ -4411,27 +4572,121 @@ function getNumberOfLanguages(translations) {
|
|
|
4411
4572
|
return Object.keys(translations).length;
|
|
4412
4573
|
}
|
|
4413
4574
|
|
|
4414
|
-
|
|
4575
|
+
/**
|
|
4576
|
+
* Represents the localization services.
|
|
4577
|
+
*/ class Locale {
|
|
4578
|
+
/**
|
|
4579
|
+
* The editor UI language code in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
|
|
4580
|
+
*
|
|
4581
|
+
* If the {@link #contentLanguage content language} was not specified in the `Locale` constructor,
|
|
4582
|
+
* it also defines the language of the content.
|
|
4583
|
+
*/ uiLanguage;
|
|
4584
|
+
/**
|
|
4585
|
+
* Text direction of the {@link #uiLanguage editor UI language}. Either `'ltr'` or `'rtl'`.
|
|
4586
|
+
*/ uiLanguageDirection;
|
|
4587
|
+
/**
|
|
4588
|
+
* The editor content language code in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
|
|
4589
|
+
*
|
|
4590
|
+
* Usually the same as the {@link #uiLanguage editor language}, it can be customized by passing an optional
|
|
4591
|
+
* argument to the `Locale` constructor.
|
|
4592
|
+
*/ contentLanguage;
|
|
4415
4593
|
/**
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4594
|
+
* Text direction of the {@link #contentLanguage editor content language}.
|
|
4595
|
+
*
|
|
4596
|
+
* If the content language was passed directly to the `Locale` constructor, this property represents the
|
|
4597
|
+
* direction of that language.
|
|
4598
|
+
*
|
|
4599
|
+
* If the {@link #contentLanguage editor content language} was derived from the {@link #uiLanguage editor language},
|
|
4600
|
+
* the content language direction is the same as the {@link #uiLanguageDirection UI language direction}.
|
|
4601
|
+
*
|
|
4602
|
+
* The value is either `'ltr'` or `'rtl'`.
|
|
4603
|
+
*/ contentLanguageDirection;
|
|
4604
|
+
/**
|
|
4605
|
+
* Translates the given message to the {@link #uiLanguage}. This method is also available in
|
|
4606
|
+
* {@link module:core/editor/editor~Editor#t `Editor`} and {@link module:ui/view~View#t `View`}.
|
|
4607
|
+
*
|
|
4608
|
+
* This method's context is statically bound to the `Locale` instance and **should always be called as a function**:
|
|
4609
|
+
*
|
|
4610
|
+
* ```ts
|
|
4611
|
+
* const t = locale.t;
|
|
4612
|
+
* t( 'Label' );
|
|
4613
|
+
* ```
|
|
4614
|
+
*
|
|
4615
|
+
* The message can be either a string or an object implementing the {@link module:utils/translation-service~Message} interface.
|
|
4616
|
+
*
|
|
4617
|
+
* The message may contain placeholders (`%<index>`) for value(s) that are passed as a `values` parameter.
|
|
4618
|
+
* For an array of values, the `%<index>` will be changed to an element of that array at the given index.
|
|
4619
|
+
* For a single value passed as the second argument, only the `%0` placeholders will be changed to the provided value.
|
|
4620
|
+
*
|
|
4621
|
+
* ```ts
|
|
4622
|
+
* t( 'Created file "%0" in %1ms.', [ fileName, timeTaken ] );
|
|
4623
|
+
* t( 'Created file "%0", fileName );
|
|
4624
|
+
* ```
|
|
4625
|
+
*
|
|
4626
|
+
* The message supports plural forms. To specify the plural form, use the `plural` property. Singular or plural form
|
|
4627
|
+
* will be chosen depending on the first value from the passed `values`. The value of the `plural` property is used
|
|
4628
|
+
* as a default plural translation when the translation for the target language is missing.
|
|
4629
|
+
*
|
|
4630
|
+
* ```ts
|
|
4631
|
+
* t( { string: 'Add a space', plural: 'Add %0 spaces' }, 1 ); // 'Add a space' for the English language.
|
|
4632
|
+
* t( { string: 'Add a space', plural: 'Add %0 spaces' }, 5 ); // 'Add 5 spaces' for the English language.
|
|
4633
|
+
* t( { string: '%1 a space', plural: '%1 %0 spaces' }, [ 2, 'Add' ] ); // 'Add 2 spaces' for the English language.
|
|
4634
|
+
*
|
|
4635
|
+
* t( { string: 'Add a space', plural: 'Add %0 spaces' }, 1 ); // 'Dodaj spację' for the Polish language.
|
|
4636
|
+
* t( { string: 'Add a space', plural: 'Add %0 spaces' }, 5 ); // 'Dodaj 5 spacji' for the Polish language.
|
|
4637
|
+
* t( { string: '%1 a space', plural: '%1 %0 spaces' }, [ 2, 'Add' ] ); // 'Dodaj 2 spacje' for the Polish language.
|
|
4638
|
+
* ```
|
|
4639
|
+
*
|
|
4640
|
+
* * The message should provide an ID using the `id` property when the message strings are not unique and their
|
|
4641
|
+
* translations should be different.
|
|
4642
|
+
*
|
|
4643
|
+
* ```ts
|
|
4644
|
+
* translate( 'en', { string: 'image', id: 'ADD_IMAGE' } );
|
|
4645
|
+
* translate( 'en', { string: 'image', id: 'AN_IMAGE' } );
|
|
4646
|
+
* ```
|
|
4647
|
+
*/ t;
|
|
4648
|
+
/**
|
|
4649
|
+
* Object that contains translations.
|
|
4650
|
+
*/ translations;
|
|
4651
|
+
/**
|
|
4652
|
+
* Creates a new instance of the locale class. Learn more about
|
|
4653
|
+
* {@glink getting-started/setup/ui-language configuring the language of the editor}.
|
|
4654
|
+
*
|
|
4655
|
+
* @param options Locale configuration.
|
|
4656
|
+
* @param options.uiLanguage The editor UI language code in the
|
|
4657
|
+
* [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format. See {@link #uiLanguage}.
|
|
4658
|
+
* @param options.contentLanguage The editor content language code in the
|
|
4659
|
+
* [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format. If not specified, the same as `options.language`.
|
|
4660
|
+
* See {@link #contentLanguage}.
|
|
4661
|
+
* @param translations Translations passed as a editor config parameter.
|
|
4662
|
+
*/ constructor({ uiLanguage = 'en', contentLanguage, translations } = {}){
|
|
4663
|
+
this.uiLanguage = uiLanguage;
|
|
4664
|
+
this.contentLanguage = contentLanguage || this.uiLanguage;
|
|
4665
|
+
this.uiLanguageDirection = getLanguageDirection(this.uiLanguage);
|
|
4666
|
+
this.contentLanguageDirection = getLanguageDirection(this.contentLanguage);
|
|
4667
|
+
this.translations = _unifyTranslations(translations);
|
|
4668
|
+
this.t = (message, values)=>this._t(message, values);
|
|
4669
|
+
}
|
|
4670
|
+
/**
|
|
4671
|
+
* The editor UI language code in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
|
|
4672
|
+
*
|
|
4673
|
+
* **Note**: This property was deprecated. Please use {@link #uiLanguage} and {@link #contentLanguage}
|
|
4674
|
+
* properties instead.
|
|
4675
|
+
*
|
|
4676
|
+
* @deprecated
|
|
4677
|
+
*/ get language() {
|
|
4423
4678
|
/**
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4679
|
+
* The {@link module:utils/locale~Locale#language `Locale#language`} property was deprecated and will
|
|
4680
|
+
* be removed in the near future. Please use the {@link module:utils/locale~Locale#uiLanguage `Locale#uiLanguage`} and
|
|
4681
|
+
* {@link module:utils/locale~Locale#contentLanguage `Locale#contentLanguage`} properties instead.
|
|
4682
|
+
*
|
|
4683
|
+
* @error locale-deprecated-language-property
|
|
4684
|
+
*/ console.warn('locale-deprecated-language-property: ' + 'The Locale#language property has been deprecated and will be removed in the near future. ' + 'Please use #uiLanguage and #contentLanguage properties instead.');
|
|
4430
4685
|
return this.uiLanguage;
|
|
4431
4686
|
}
|
|
4432
4687
|
/**
|
|
4433
|
-
|
|
4434
|
-
|
|
4688
|
+
* An unbound version of the {@link #t} method.
|
|
4689
|
+
*/ _t(message, values = []) {
|
|
4435
4690
|
values = toArray(values);
|
|
4436
4691
|
if (typeof message === 'string') {
|
|
4437
4692
|
message = {
|
|
@@ -4443,25 +4698,6 @@ class Locale {
|
|
|
4443
4698
|
const translatedString = _translate(this.uiLanguage, message, quantity, this.translations);
|
|
4444
4699
|
return interpolateString(translatedString, values);
|
|
4445
4700
|
}
|
|
4446
|
-
/**
|
|
4447
|
-
* Creates a new instance of the locale class. Learn more about
|
|
4448
|
-
* {@glink features/ui-language configuring the language of the editor}.
|
|
4449
|
-
*
|
|
4450
|
-
* @param options Locale configuration.
|
|
4451
|
-
* @param options.uiLanguage The editor UI language code in the
|
|
4452
|
-
* [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format. See {@link #uiLanguage}.
|
|
4453
|
-
* @param options.contentLanguage The editor content language code in the
|
|
4454
|
-
* [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format. If not specified, the same as `options.language`.
|
|
4455
|
-
* See {@link #contentLanguage}.
|
|
4456
|
-
* @param translations Translations passed as a editor config parameter.
|
|
4457
|
-
*/ constructor({ uiLanguage = 'en', contentLanguage, translations } = {}){
|
|
4458
|
-
this.uiLanguage = uiLanguage;
|
|
4459
|
-
this.contentLanguage = contentLanguage || this.uiLanguage;
|
|
4460
|
-
this.uiLanguageDirection = getLanguageDirection(this.uiLanguage);
|
|
4461
|
-
this.contentLanguageDirection = getLanguageDirection(this.contentLanguage);
|
|
4462
|
-
this.translations = _unifyTranslations(translations);
|
|
4463
|
-
this.t = (message, values)=>this._t(message, values);
|
|
4464
|
-
}
|
|
4465
4701
|
}
|
|
4466
4702
|
/**
|
|
4467
4703
|
* Fills the `%0, %1, ...` string placeholders with values.
|
|
@@ -4471,56 +4707,117 @@ class Locale {
|
|
|
4471
4707
|
});
|
|
4472
4708
|
}
|
|
4473
4709
|
|
|
4474
|
-
|
|
4710
|
+
/**
|
|
4711
|
+
* Collections are ordered sets of objects. Items in the collection can be retrieved by their indexes
|
|
4712
|
+
* in the collection (like in an array) or by their ids.
|
|
4713
|
+
*
|
|
4714
|
+
* If an object without an `id` property is being added to the collection, the `id` property will be generated
|
|
4715
|
+
* automatically. Note that the automatically generated id is unique only within this single collection instance.
|
|
4716
|
+
*
|
|
4717
|
+
* By default an item in the collection is identified by its `id` property. The name of the identifier can be
|
|
4718
|
+
* configured through the constructor of the collection.
|
|
4719
|
+
*
|
|
4720
|
+
* @typeParam T The type of the collection element.
|
|
4721
|
+
*/ class Collection extends /* #__PURE__ */ EmitterMixin() {
|
|
4722
|
+
/**
|
|
4723
|
+
* The internal list of items in the collection.
|
|
4724
|
+
*/ _items;
|
|
4725
|
+
/**
|
|
4726
|
+
* The internal map of items in the collection.
|
|
4727
|
+
*/ _itemMap;
|
|
4728
|
+
/**
|
|
4729
|
+
* The name of the property which is considered to identify an item.
|
|
4730
|
+
*/ _idProperty;
|
|
4731
|
+
/**
|
|
4732
|
+
* A collection instance this collection is bound to as a result
|
|
4733
|
+
* of calling {@link #bindTo} method.
|
|
4734
|
+
*/ _bindToCollection;
|
|
4735
|
+
/**
|
|
4736
|
+
* A helper mapping external items of a bound collection ({@link #bindTo})
|
|
4737
|
+
* and actual items of this collection. It provides information
|
|
4738
|
+
* necessary to properly remove items bound to another collection.
|
|
4739
|
+
*
|
|
4740
|
+
* See {@link #_bindToInternalToExternalMap}.
|
|
4741
|
+
*/ _bindToExternalToInternalMap;
|
|
4742
|
+
/**
|
|
4743
|
+
* A helper mapping items of this collection to external items of a bound collection
|
|
4744
|
+
* ({@link #bindTo}). It provides information necessary to manage the bindings, e.g.
|
|
4745
|
+
* to avoid loops in two–way bindings.
|
|
4746
|
+
*
|
|
4747
|
+
* See {@link #_bindToExternalToInternalMap}.
|
|
4748
|
+
*/ _bindToInternalToExternalMap;
|
|
4749
|
+
/**
|
|
4750
|
+
* Stores indexes of skipped items from bound external collection.
|
|
4751
|
+
*/ _skippedIndexesFromExternal;
|
|
4752
|
+
constructor(initialItemsOrOptions = {}, options = {}){
|
|
4753
|
+
super();
|
|
4754
|
+
const hasInitialItems = isIterable(initialItemsOrOptions);
|
|
4755
|
+
if (!hasInitialItems) {
|
|
4756
|
+
options = initialItemsOrOptions;
|
|
4757
|
+
}
|
|
4758
|
+
this._items = [];
|
|
4759
|
+
this._itemMap = new Map();
|
|
4760
|
+
this._idProperty = options.idProperty || 'id';
|
|
4761
|
+
this._bindToExternalToInternalMap = new WeakMap();
|
|
4762
|
+
this._bindToInternalToExternalMap = new WeakMap();
|
|
4763
|
+
this._skippedIndexesFromExternal = [];
|
|
4764
|
+
// Set the initial content of the collection (if provided in the constructor).
|
|
4765
|
+
if (hasInitialItems) {
|
|
4766
|
+
for (const item of initialItemsOrOptions){
|
|
4767
|
+
this._items.push(item);
|
|
4768
|
+
this._itemMap.set(this._getItemIdBeforeAdding(item), item);
|
|
4769
|
+
}
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4475
4772
|
/**
|
|
4476
|
-
|
|
4477
|
-
|
|
4773
|
+
* The number of items available in the collection.
|
|
4774
|
+
*/ get length() {
|
|
4478
4775
|
return this._items.length;
|
|
4479
4776
|
}
|
|
4480
4777
|
/**
|
|
4481
|
-
|
|
4482
|
-
|
|
4778
|
+
* Returns the first item from the collection or null when collection is empty.
|
|
4779
|
+
*/ get first() {
|
|
4483
4780
|
return this._items[0] || null;
|
|
4484
4781
|
}
|
|
4485
4782
|
/**
|
|
4486
|
-
|
|
4487
|
-
|
|
4783
|
+
* Returns the last item from the collection or null when collection is empty.
|
|
4784
|
+
*/ get last() {
|
|
4488
4785
|
return this._items[this.length - 1] || null;
|
|
4489
4786
|
}
|
|
4490
4787
|
/**
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4788
|
+
* Adds an item into the collection.
|
|
4789
|
+
*
|
|
4790
|
+
* If the item does not have an id, then it will be automatically generated and set on the item.
|
|
4791
|
+
*
|
|
4792
|
+
* @param item
|
|
4793
|
+
* @param index The position of the item in the collection. The item
|
|
4794
|
+
* is pushed to the collection when `index` not specified.
|
|
4795
|
+
* @fires add
|
|
4796
|
+
* @fires change
|
|
4797
|
+
*/ add(item, index) {
|
|
4501
4798
|
return this.addMany([
|
|
4502
4799
|
item
|
|
4503
4800
|
], index);
|
|
4504
4801
|
}
|
|
4505
4802
|
/**
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4803
|
+
* Adds multiple items into the collection.
|
|
4804
|
+
*
|
|
4805
|
+
* Any item not containing an id will get an automatically generated one.
|
|
4806
|
+
*
|
|
4807
|
+
* @param items
|
|
4808
|
+
* @param index The position of the insertion. Items will be appended if no `index` is specified.
|
|
4809
|
+
* @fires add
|
|
4810
|
+
* @fires change
|
|
4811
|
+
*/ addMany(items, index) {
|
|
4515
4812
|
if (index === undefined) {
|
|
4516
4813
|
index = this._items.length;
|
|
4517
4814
|
} else if (index > this._items.length || index < 0) {
|
|
4518
4815
|
/**
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4816
|
+
* The `index` passed to {@link module:utils/collection~Collection#addMany `Collection#addMany()`}
|
|
4817
|
+
* is invalid. It must be a number between 0 and the collection's length.
|
|
4818
|
+
*
|
|
4819
|
+
* @error collection-add-item-invalid-index
|
|
4820
|
+
*/ throw new CKEditorError('collection-add-item-invalid-index', this);
|
|
4524
4821
|
}
|
|
4525
4822
|
let offset = 0;
|
|
4526
4823
|
for (const item of items){
|
|
@@ -4539,11 +4836,11 @@ class Collection extends EmitterMixin() {
|
|
|
4539
4836
|
return this;
|
|
4540
4837
|
}
|
|
4541
4838
|
/**
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4839
|
+
* Gets an item by its ID or index.
|
|
4840
|
+
*
|
|
4841
|
+
* @param idOrIndex The item ID or index in the collection.
|
|
4842
|
+
* @returns The requested item or `null` if such item does not exist.
|
|
4843
|
+
*/ get(idOrIndex) {
|
|
4547
4844
|
let item;
|
|
4548
4845
|
if (typeof idOrIndex == 'string') {
|
|
4549
4846
|
item = this._itemMap.get(idOrIndex);
|
|
@@ -4551,19 +4848,19 @@ class Collection extends EmitterMixin() {
|
|
|
4551
4848
|
item = this._items[idOrIndex];
|
|
4552
4849
|
} else {
|
|
4553
4850
|
/**
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4851
|
+
* An index or ID must be given.
|
|
4852
|
+
*
|
|
4853
|
+
* @error collection-get-invalid-arg
|
|
4854
|
+
*/ throw new CKEditorError('collection-get-invalid-arg', this);
|
|
4558
4855
|
}
|
|
4559
4856
|
return item || null;
|
|
4560
4857
|
}
|
|
4561
4858
|
/**
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4859
|
+
* Returns a Boolean indicating whether the collection contains an item.
|
|
4860
|
+
*
|
|
4861
|
+
* @param itemOrId The item or its ID in the collection.
|
|
4862
|
+
* @returns `true` if the collection contains the item, `false` otherwise.
|
|
4863
|
+
*/ has(itemOrId) {
|
|
4567
4864
|
if (typeof itemOrId == 'string') {
|
|
4568
4865
|
return this._itemMap.has(itemOrId);
|
|
4569
4866
|
} else {
|
|
@@ -4573,12 +4870,12 @@ class Collection extends EmitterMixin() {
|
|
|
4573
4870
|
}
|
|
4574
4871
|
}
|
|
4575
4872
|
/**
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4873
|
+
* Gets an index of an item in the collection.
|
|
4874
|
+
* When an item is not defined in the collection, the index will equal -1.
|
|
4875
|
+
*
|
|
4876
|
+
* @param itemOrId The item or its ID in the collection.
|
|
4877
|
+
* @returns The index of a given item.
|
|
4878
|
+
*/ getIndex(itemOrId) {
|
|
4582
4879
|
let item;
|
|
4583
4880
|
if (typeof itemOrId == 'string') {
|
|
4584
4881
|
item = this._itemMap.get(itemOrId);
|
|
@@ -4588,13 +4885,13 @@ class Collection extends EmitterMixin() {
|
|
|
4588
4885
|
return item ? this._items.indexOf(item) : -1;
|
|
4589
4886
|
}
|
|
4590
4887
|
/**
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4888
|
+
* Removes an item from the collection.
|
|
4889
|
+
*
|
|
4890
|
+
* @param subject The item to remove, its ID or index in the collection.
|
|
4891
|
+
* @returns The removed item.
|
|
4892
|
+
* @fires remove
|
|
4893
|
+
* @fires change
|
|
4894
|
+
*/ remove(subject) {
|
|
4598
4895
|
const [item, index] = this._remove(subject);
|
|
4599
4896
|
this.fire('change', {
|
|
4600
4897
|
added: [],
|
|
@@ -4606,47 +4903,47 @@ class Collection extends EmitterMixin() {
|
|
|
4606
4903
|
return item;
|
|
4607
4904
|
}
|
|
4608
4905
|
/**
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4906
|
+
* Executes the callback for each item in the collection and composes an array or values returned by this callback.
|
|
4907
|
+
*
|
|
4908
|
+
* @typeParam U The result type of the callback.
|
|
4909
|
+
* @param callback
|
|
4910
|
+
* @param ctx Context in which the `callback` will be called.
|
|
4911
|
+
* @returns The result of mapping.
|
|
4912
|
+
*/ map(callback, ctx) {
|
|
4616
4913
|
return this._items.map(callback, ctx);
|
|
4617
4914
|
}
|
|
4618
4915
|
/**
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4916
|
+
* Performs the specified action for each item in the collection.
|
|
4917
|
+
*
|
|
4918
|
+
* @param ctx Context in which the `callback` will be called.
|
|
4919
|
+
*/ forEach(callback, ctx) {
|
|
4623
4920
|
this._items.forEach(callback, ctx);
|
|
4624
4921
|
}
|
|
4625
4922
|
/**
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4923
|
+
* Finds the first item in the collection for which the `callback` returns a true value.
|
|
4924
|
+
*
|
|
4925
|
+
* @param callback
|
|
4926
|
+
* @param ctx Context in which the `callback` will be called.
|
|
4927
|
+
* @returns The item for which `callback` returned a true value.
|
|
4928
|
+
*/ find(callback, ctx) {
|
|
4632
4929
|
return this._items.find(callback, ctx);
|
|
4633
4930
|
}
|
|
4634
4931
|
/**
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4932
|
+
* Returns an array with items for which the `callback` returned a true value.
|
|
4933
|
+
*
|
|
4934
|
+
* @param callback
|
|
4935
|
+
* @param ctx Context in which the `callback` will be called.
|
|
4936
|
+
* @returns The array with matching items.
|
|
4937
|
+
*/ filter(callback, ctx) {
|
|
4641
4938
|
return this._items.filter(callback, ctx);
|
|
4642
4939
|
}
|
|
4643
4940
|
/**
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4941
|
+
* Removes all items from the collection and destroys the binding created using
|
|
4942
|
+
* {@link #bindTo}.
|
|
4943
|
+
*
|
|
4944
|
+
* @fires remove
|
|
4945
|
+
* @fires change
|
|
4946
|
+
*/ clear() {
|
|
4650
4947
|
if (this._bindToCollection) {
|
|
4651
4948
|
this.stopListening(this._bindToCollection);
|
|
4652
4949
|
this._bindToCollection = null;
|
|
@@ -4662,122 +4959,122 @@ class Collection extends EmitterMixin() {
|
|
|
4662
4959
|
});
|
|
4663
4960
|
}
|
|
4664
4961
|
/**
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4962
|
+
* Binds and synchronizes the collection with another one.
|
|
4963
|
+
*
|
|
4964
|
+
* The binding can be a simple factory:
|
|
4965
|
+
*
|
|
4966
|
+
* ```ts
|
|
4967
|
+
* class FactoryClass {
|
|
4968
|
+
* public label: string;
|
|
4969
|
+
*
|
|
4970
|
+
* constructor( data: { label: string } ) {
|
|
4971
|
+
* this.label = data.label;
|
|
4972
|
+
* }
|
|
4973
|
+
* }
|
|
4974
|
+
*
|
|
4975
|
+
* const source = new Collection<{ label: string }>( { idProperty: 'label' } );
|
|
4976
|
+
* const target = new Collection<FactoryClass>();
|
|
4977
|
+
*
|
|
4978
|
+
* target.bindTo( source ).as( FactoryClass );
|
|
4979
|
+
*
|
|
4980
|
+
* source.add( { label: 'foo' } );
|
|
4981
|
+
* source.add( { label: 'bar' } );
|
|
4982
|
+
*
|
|
4983
|
+
* console.log( target.length ); // 2
|
|
4984
|
+
* console.log( target.get( 1 ).label ); // 'bar'
|
|
4985
|
+
*
|
|
4986
|
+
* source.remove( 0 );
|
|
4987
|
+
* console.log( target.length ); // 1
|
|
4988
|
+
* console.log( target.get( 0 ).label ); // 'bar'
|
|
4989
|
+
* ```
|
|
4990
|
+
*
|
|
4991
|
+
* or the factory driven by a custom callback:
|
|
4992
|
+
*
|
|
4993
|
+
* ```ts
|
|
4994
|
+
* class FooClass {
|
|
4995
|
+
* public label: string;
|
|
4996
|
+
*
|
|
4997
|
+
* constructor( data: { label: string } ) {
|
|
4998
|
+
* this.label = data.label;
|
|
4999
|
+
* }
|
|
5000
|
+
* }
|
|
5001
|
+
*
|
|
5002
|
+
* class BarClass {
|
|
5003
|
+
* public label: string;
|
|
5004
|
+
*
|
|
5005
|
+
* constructor( data: { label: string } ) {
|
|
5006
|
+
* this.label = data.label;
|
|
5007
|
+
* }
|
|
5008
|
+
* }
|
|
5009
|
+
*
|
|
5010
|
+
* const source = new Collection<{ label: string }>( { idProperty: 'label' } );
|
|
5011
|
+
* const target = new Collection<FooClass | BarClass>();
|
|
5012
|
+
*
|
|
5013
|
+
* target.bindTo( source ).using( ( item ) => {
|
|
5014
|
+
* if ( item.label == 'foo' ) {
|
|
5015
|
+
* return new FooClass( item );
|
|
5016
|
+
* } else {
|
|
5017
|
+
* return new BarClass( item );
|
|
5018
|
+
* }
|
|
5019
|
+
* } );
|
|
5020
|
+
*
|
|
5021
|
+
* source.add( { label: 'foo' } );
|
|
5022
|
+
* source.add( { label: 'bar' } );
|
|
5023
|
+
*
|
|
5024
|
+
* console.log( target.length ); // 2
|
|
5025
|
+
* console.log( target.get( 0 ) instanceof FooClass ); // true
|
|
5026
|
+
* console.log( target.get( 1 ) instanceof BarClass ); // true
|
|
5027
|
+
* ```
|
|
5028
|
+
*
|
|
5029
|
+
* or the factory out of property name:
|
|
5030
|
+
*
|
|
5031
|
+
* ```ts
|
|
5032
|
+
* const source = new Collection<{ nested: { value: string } }>();
|
|
5033
|
+
* const target = new Collection<{ value: string }>();
|
|
5034
|
+
*
|
|
5035
|
+
* target.bindTo( source ).using( 'nested' );
|
|
5036
|
+
*
|
|
5037
|
+
* source.add( { nested: { value: 'foo' } } );
|
|
5038
|
+
* source.add( { nested: { value: 'bar' } } );
|
|
5039
|
+
*
|
|
5040
|
+
* console.log( target.length ); // 2
|
|
5041
|
+
* console.log( target.get( 0 ).value ); // 'foo'
|
|
5042
|
+
* console.log( target.get( 1 ).value ); // 'bar'
|
|
5043
|
+
* ```
|
|
5044
|
+
*
|
|
5045
|
+
* It's possible to skip specified items by returning null value:
|
|
5046
|
+
*
|
|
5047
|
+
* ```ts
|
|
5048
|
+
* const source = new Collection<{ hidden: boolean }>();
|
|
5049
|
+
* const target = new Collection<{ hidden: boolean }>();
|
|
5050
|
+
*
|
|
5051
|
+
* target.bindTo( source ).using( item => {
|
|
5052
|
+
* if ( item.hidden ) {
|
|
5053
|
+
* return null;
|
|
5054
|
+
* }
|
|
5055
|
+
*
|
|
5056
|
+
* return item;
|
|
5057
|
+
* } );
|
|
5058
|
+
*
|
|
5059
|
+
* source.add( { hidden: true } );
|
|
5060
|
+
* source.add( { hidden: false } );
|
|
5061
|
+
*
|
|
5062
|
+
* console.log( source.length ); // 2
|
|
5063
|
+
* console.log( target.length ); // 1
|
|
5064
|
+
* ```
|
|
5065
|
+
*
|
|
5066
|
+
* **Note**: {@link #clear} can be used to break the binding.
|
|
5067
|
+
*
|
|
5068
|
+
* @typeParam S The type of `externalCollection` element.
|
|
5069
|
+
* @param externalCollection A collection to be bound.
|
|
5070
|
+
* @returns The binding chain object.
|
|
5071
|
+
*/ bindTo(externalCollection) {
|
|
4775
5072
|
if (this._bindToCollection) {
|
|
4776
5073
|
/**
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
5074
|
+
* The collection cannot be bound more than once.
|
|
5075
|
+
*
|
|
5076
|
+
* @error collection-bind-to-rebind
|
|
5077
|
+
*/ throw new CKEditorError('collection-bind-to-rebind', this);
|
|
4781
5078
|
}
|
|
4782
5079
|
this._bindToCollection = externalCollection;
|
|
4783
5080
|
return {
|
|
@@ -4794,10 +5091,10 @@ class Collection extends EmitterMixin() {
|
|
|
4794
5091
|
};
|
|
4795
5092
|
}
|
|
4796
5093
|
/**
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
5094
|
+
* Finalizes and activates a binding initiated by {@link #bindTo}.
|
|
5095
|
+
*
|
|
5096
|
+
* @param factory A function which produces collection items.
|
|
5097
|
+
*/ _setUpBindToBinding(factory) {
|
|
4801
5098
|
const externalCollection = this._bindToCollection;
|
|
4802
5099
|
// Adds the item to the collection once a change has been done to the external collection.
|
|
4803
5100
|
const addItem = (evt, externalItem, index)=>{
|
|
@@ -4898,29 +5195,29 @@ class Collection extends EmitterMixin() {
|
|
|
4898
5195
|
});
|
|
4899
5196
|
}
|
|
4900
5197
|
/**
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
5198
|
+
* Returns an unique id property for a given `item`.
|
|
5199
|
+
*
|
|
5200
|
+
* The method will generate new id and assign it to the `item` if it doesn't have any.
|
|
5201
|
+
*
|
|
5202
|
+
* @param item Item to be added.
|
|
5203
|
+
*/ _getItemIdBeforeAdding(item) {
|
|
4907
5204
|
const idProperty = this._idProperty;
|
|
4908
5205
|
let itemId;
|
|
4909
5206
|
if (idProperty in item) {
|
|
4910
5207
|
itemId = item[idProperty];
|
|
4911
5208
|
if (typeof itemId != 'string') {
|
|
4912
5209
|
/**
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
5210
|
+
* This item's ID should be a string.
|
|
5211
|
+
*
|
|
5212
|
+
* @error collection-add-invalid-id
|
|
5213
|
+
*/ throw new CKEditorError('collection-add-invalid-id', this);
|
|
4917
5214
|
}
|
|
4918
5215
|
if (this.get(itemId)) {
|
|
4919
5216
|
/**
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
5217
|
+
* This item already exists in the collection.
|
|
5218
|
+
*
|
|
5219
|
+
* @error collection-add-item-already-exists
|
|
5220
|
+
*/ throw new CKEditorError('collection-add-item-already-exists', this);
|
|
4924
5221
|
}
|
|
4925
5222
|
} else {
|
|
4926
5223
|
item[idProperty] = itemId = uid();
|
|
@@ -4928,14 +5225,14 @@ class Collection extends EmitterMixin() {
|
|
|
4928
5225
|
return itemId;
|
|
4929
5226
|
}
|
|
4930
5227
|
/**
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
5228
|
+
* Core {@link #remove} method implementation shared in other functions.
|
|
5229
|
+
*
|
|
5230
|
+
* In contrast this method **does not** fire the {@link #event:change} event.
|
|
5231
|
+
*
|
|
5232
|
+
* @param subject The item to remove, its id or index in the collection.
|
|
5233
|
+
* @returns Returns an array with the removed item and its index.
|
|
5234
|
+
* @fires remove
|
|
5235
|
+
*/ _remove(subject) {
|
|
4939
5236
|
let index, id, item;
|
|
4940
5237
|
let itemDoesNotExist = false;
|
|
4941
5238
|
const idProperty = this._idProperty;
|
|
@@ -4961,10 +5258,10 @@ class Collection extends EmitterMixin() {
|
|
|
4961
5258
|
}
|
|
4962
5259
|
if (itemDoesNotExist) {
|
|
4963
5260
|
/**
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
5261
|
+
* Item not found.
|
|
5262
|
+
*
|
|
5263
|
+
* @error collection-remove-404
|
|
5264
|
+
*/ throw new CKEditorError('collection-remove-404', this);
|
|
4968
5265
|
}
|
|
4969
5266
|
this._items.splice(index, 1);
|
|
4970
5267
|
this._itemMap.delete(id);
|
|
@@ -4978,30 +5275,10 @@ class Collection extends EmitterMixin() {
|
|
|
4978
5275
|
];
|
|
4979
5276
|
}
|
|
4980
5277
|
/**
|
|
4981
|
-
|
|
4982
|
-
|
|
5278
|
+
* Iterable interface.
|
|
5279
|
+
*/ [Symbol.iterator]() {
|
|
4983
5280
|
return this._items[Symbol.iterator]();
|
|
4984
5281
|
}
|
|
4985
|
-
constructor(initialItemsOrOptions = {}, options = {}){
|
|
4986
|
-
super();
|
|
4987
|
-
const hasInitialItems = isIterable(initialItemsOrOptions);
|
|
4988
|
-
if (!hasInitialItems) {
|
|
4989
|
-
options = initialItemsOrOptions;
|
|
4990
|
-
}
|
|
4991
|
-
this._items = [];
|
|
4992
|
-
this._itemMap = new Map();
|
|
4993
|
-
this._idProperty = options.idProperty || 'id';
|
|
4994
|
-
this._bindToExternalToInternalMap = new WeakMap();
|
|
4995
|
-
this._bindToInternalToExternalMap = new WeakMap();
|
|
4996
|
-
this._skippedIndexesFromExternal = [];
|
|
4997
|
-
// Set the initial content of the collection (if provided in the constructor).
|
|
4998
|
-
if (hasInitialItems) {
|
|
4999
|
-
for (const item of initialItemsOrOptions){
|
|
5000
|
-
this._items.push(item);
|
|
5001
|
-
this._itemMap.set(this._getItemIdBeforeAdding(item), item);
|
|
5002
|
-
}
|
|
5003
|
-
}
|
|
5004
|
-
}
|
|
5005
5282
|
}
|
|
5006
5283
|
|
|
5007
5284
|
/**
|
|
@@ -5019,16 +5296,40 @@ class Collection extends EmitterMixin() {
|
|
|
5019
5296
|
return iteratorItem.value;
|
|
5020
5297
|
}
|
|
5021
5298
|
|
|
5022
|
-
|
|
5299
|
+
/**
|
|
5300
|
+
* Allows observing a group of `Element`s whether at least one of them is focused.
|
|
5301
|
+
*
|
|
5302
|
+
* Used by the {@link module:core/editor/editor~Editor} in order to track whether the focus is still within the application,
|
|
5303
|
+
* or were used outside of its UI.
|
|
5304
|
+
*
|
|
5305
|
+
* **Note** `focus` and `blur` listeners use event capturing, so it is only needed to register wrapper `Element`
|
|
5306
|
+
* which contain other `focusable` elements. But note that this wrapper element has to be focusable too
|
|
5307
|
+
* (have e.g. `tabindex="-1"`).
|
|
5308
|
+
*
|
|
5309
|
+
* Check out the {@glink framework/deep-dive/ui/focus-tracking "Deep dive into focus tracking"} guide to learn more.
|
|
5310
|
+
*/ class FocusTracker extends /* #__PURE__ */ DomEmitterMixin(/* #__PURE__ */ ObservableMixin()) {
|
|
5023
5311
|
/**
|
|
5024
|
-
|
|
5025
|
-
|
|
5312
|
+
* List of registered elements.
|
|
5313
|
+
*
|
|
5314
|
+
* @internal
|
|
5315
|
+
*/ _elements = new Set();
|
|
5316
|
+
/**
|
|
5317
|
+
* Event loop timeout.
|
|
5318
|
+
*/ _nextEventLoopTimeout = null;
|
|
5319
|
+
constructor(){
|
|
5320
|
+
super();
|
|
5321
|
+
this.set('isFocused', false);
|
|
5322
|
+
this.set('focusedElement', null);
|
|
5323
|
+
}
|
|
5324
|
+
/**
|
|
5325
|
+
* Starts tracking the specified element.
|
|
5326
|
+
*/ add(element) {
|
|
5026
5327
|
if (this._elements.has(element)) {
|
|
5027
5328
|
/**
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5329
|
+
* This element is already tracked by {@link module:utils/focustracker~FocusTracker}.
|
|
5330
|
+
*
|
|
5331
|
+
* @error focustracker-add-element-already-exist
|
|
5332
|
+
*/ throw new CKEditorError('focustracker-add-element-already-exist', this);
|
|
5032
5333
|
}
|
|
5033
5334
|
this.listenTo(element, 'focus', ()=>this._focus(element), {
|
|
5034
5335
|
useCapture: true
|
|
@@ -5039,8 +5340,8 @@ class FocusTracker extends DomEmitterMixin(ObservableMixin()) {
|
|
|
5039
5340
|
this._elements.add(element);
|
|
5040
5341
|
}
|
|
5041
5342
|
/**
|
|
5042
|
-
|
|
5043
|
-
|
|
5343
|
+
* Stops tracking the specified element and stops listening on this element.
|
|
5344
|
+
*/ remove(element) {
|
|
5044
5345
|
if (element === this.focusedElement) {
|
|
5045
5346
|
this._blur();
|
|
5046
5347
|
}
|
|
@@ -5050,48 +5351,77 @@ class FocusTracker extends DomEmitterMixin(ObservableMixin()) {
|
|
|
5050
5351
|
}
|
|
5051
5352
|
}
|
|
5052
5353
|
/**
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5354
|
+
* Destroys the focus tracker by:
|
|
5355
|
+
* - Disabling all event listeners attached to tracked elements.
|
|
5356
|
+
* - Removing all tracked elements that were previously added.
|
|
5357
|
+
*/ destroy() {
|
|
5057
5358
|
this.stopListening();
|
|
5058
5359
|
}
|
|
5059
5360
|
/**
|
|
5060
|
-
|
|
5061
|
-
|
|
5361
|
+
* Stores currently focused element and set {@link #isFocused} as `true`.
|
|
5362
|
+
*/ _focus(element) {
|
|
5062
5363
|
clearTimeout(this._nextEventLoopTimeout);
|
|
5063
5364
|
this.focusedElement = element;
|
|
5064
5365
|
this.isFocused = true;
|
|
5065
5366
|
}
|
|
5066
5367
|
/**
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5368
|
+
* Clears currently focused element and set {@link #isFocused} as `false`.
|
|
5369
|
+
* This method uses `setTimeout` to change order of fires `blur` and `focus` events.
|
|
5370
|
+
*/ _blur() {
|
|
5070
5371
|
clearTimeout(this._nextEventLoopTimeout);
|
|
5071
5372
|
this._nextEventLoopTimeout = setTimeout(()=>{
|
|
5072
5373
|
this.focusedElement = null;
|
|
5073
5374
|
this.isFocused = false;
|
|
5074
5375
|
}, 0);
|
|
5075
5376
|
}
|
|
5076
|
-
constructor(){
|
|
5077
|
-
super();
|
|
5078
|
-
/**
|
|
5079
|
-
* List of registered elements.
|
|
5080
|
-
*
|
|
5081
|
-
* @internal
|
|
5082
|
-
*/ this._elements = new Set();
|
|
5083
|
-
/**
|
|
5084
|
-
* Event loop timeout.
|
|
5085
|
-
*/ this._nextEventLoopTimeout = null;
|
|
5086
|
-
this.set('isFocused', false);
|
|
5087
|
-
this.set('focusedElement', null);
|
|
5088
|
-
}
|
|
5089
5377
|
}
|
|
5090
5378
|
|
|
5091
|
-
|
|
5379
|
+
/**
|
|
5380
|
+
* Keystroke handler allows registering callbacks for given keystrokes.
|
|
5381
|
+
*
|
|
5382
|
+
* The most frequent use of this class is through the {@link module:core/editor/editor~Editor#keystrokes `editor.keystrokes`}
|
|
5383
|
+
* property. It allows listening to keystrokes executed in the editing view:
|
|
5384
|
+
*
|
|
5385
|
+
* ```ts
|
|
5386
|
+
* editor.keystrokes.set( 'Ctrl+A', ( keyEvtData, cancel ) => {
|
|
5387
|
+
* console.log( 'Ctrl+A has been pressed' );
|
|
5388
|
+
* cancel();
|
|
5389
|
+
* } );
|
|
5390
|
+
* ```
|
|
5391
|
+
*
|
|
5392
|
+
* However, this utility class can be used in various part of the UI. For instance, a certain {@link module:ui/view~View}
|
|
5393
|
+
* can use it like this:
|
|
5394
|
+
*
|
|
5395
|
+
* ```ts
|
|
5396
|
+
* class MyView extends View {
|
|
5397
|
+
* constructor() {
|
|
5398
|
+
* this.keystrokes = new KeystrokeHandler();
|
|
5399
|
+
*
|
|
5400
|
+
* this.keystrokes.set( 'tab', handleTabKey );
|
|
5401
|
+
* }
|
|
5402
|
+
*
|
|
5403
|
+
* render() {
|
|
5404
|
+
* super.render();
|
|
5405
|
+
*
|
|
5406
|
+
* this.keystrokes.listenTo( this.element );
|
|
5407
|
+
* }
|
|
5408
|
+
* }
|
|
5409
|
+
* ```
|
|
5410
|
+
*
|
|
5411
|
+
* That keystroke handler will listen to `keydown` events fired in this view's main element.
|
|
5412
|
+
*
|
|
5413
|
+
*/ class KeystrokeHandler {
|
|
5414
|
+
/**
|
|
5415
|
+
* Listener used to listen to events for easier keystroke handler destruction.
|
|
5416
|
+
*/ _listener;
|
|
5092
5417
|
/**
|
|
5093
|
-
|
|
5094
|
-
|
|
5418
|
+
* Creates an instance of the keystroke handler.
|
|
5419
|
+
*/ constructor(){
|
|
5420
|
+
this._listener = new (DomEmitterMixin())();
|
|
5421
|
+
}
|
|
5422
|
+
/**
|
|
5423
|
+
* Starts listening for `keydown` events from a given emitter.
|
|
5424
|
+
*/ listenTo(emitter) {
|
|
5095
5425
|
// The #_listener works here as a kind of dispatcher. It groups the events coming from the same
|
|
5096
5426
|
// keystroke so the listeners can be attached to them with different priorities.
|
|
5097
5427
|
//
|
|
@@ -5106,18 +5436,18 @@ class KeystrokeHandler {
|
|
|
5106
5436
|
});
|
|
5107
5437
|
}
|
|
5108
5438
|
/**
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5439
|
+
* Registers a handler for the specified keystroke.
|
|
5440
|
+
*
|
|
5441
|
+
* @param keystroke Keystroke defined in a format accepted by
|
|
5442
|
+
* the {@link module:utils/keyboard~parseKeystroke} function.
|
|
5443
|
+
* @param callback A function called with the
|
|
5444
|
+
* {@link module:engine/view/observer/keyobserver~KeyEventData key event data} object and
|
|
5445
|
+
* a helper function to call both `preventDefault()` and `stopPropagation()` on the underlying event.
|
|
5446
|
+
* @param options Additional options.
|
|
5447
|
+
* @param options.priority The priority of the keystroke
|
|
5448
|
+
* callback. The higher the priority value the sooner the callback will be executed. Keystrokes having the same priority
|
|
5449
|
+
* are called in the order they were added.
|
|
5450
|
+
*/ set(keystroke, callback, options = {}) {
|
|
5121
5451
|
const keyCode = parseKeystroke(keystroke);
|
|
5122
5452
|
const priority = options.priority;
|
|
5123
5453
|
// Execute the passed callback on KeystrokeHandler#_keydown.
|
|
@@ -5139,28 +5469,23 @@ class KeystrokeHandler {
|
|
|
5139
5469
|
});
|
|
5140
5470
|
}
|
|
5141
5471
|
/**
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5472
|
+
* Triggers a keystroke handler for a specified key combination, if such a keystroke was {@link #set defined}.
|
|
5473
|
+
*
|
|
5474
|
+
* @param keyEvtData Key event data.
|
|
5475
|
+
* @returns Whether the keystroke was handled.
|
|
5476
|
+
*/ press(keyEvtData) {
|
|
5147
5477
|
return !!this._listener.fire('_keydown:' + getCode(keyEvtData), keyEvtData);
|
|
5148
5478
|
}
|
|
5149
5479
|
/**
|
|
5150
|
-
|
|
5151
|
-
|
|
5480
|
+
* Stops listening to `keydown` events from the given emitter.
|
|
5481
|
+
*/ stopListening(emitter) {
|
|
5152
5482
|
this._listener.stopListening(emitter);
|
|
5153
5483
|
}
|
|
5154
5484
|
/**
|
|
5155
|
-
|
|
5156
|
-
|
|
5485
|
+
* Destroys the keystroke handler.
|
|
5486
|
+
*/ destroy() {
|
|
5157
5487
|
this.stopListening();
|
|
5158
5488
|
}
|
|
5159
|
-
/**
|
|
5160
|
-
* Creates an instance of the keystroke handler.
|
|
5161
|
-
*/ constructor(){
|
|
5162
|
-
this._listener = new (DomEmitterMixin())();
|
|
5163
|
-
}
|
|
5164
5489
|
}
|
|
5165
5490
|
|
|
5166
5491
|
/**
|
|
@@ -5460,7 +5785,7 @@ class KeystrokeHandler {
|
|
|
5460
5785
|
*/ function isInsideCombinedSymbol(string, offset) {
|
|
5461
5786
|
return isCombiningMark(string.charAt(offset));
|
|
5462
5787
|
}
|
|
5463
|
-
const EMOJI_PATTERN = buildEmojiRegexp();
|
|
5788
|
+
const EMOJI_PATTERN = /* #__PURE__ */ buildEmojiRegexp();
|
|
5464
5789
|
/**
|
|
5465
5790
|
* Checks whether given offset in a string is inside multi-character emoji sequence.
|
|
5466
5791
|
*
|
|
@@ -5473,15 +5798,15 @@ const EMOJI_PATTERN = buildEmojiRegexp();
|
|
|
5473
5798
|
function buildEmojiRegexp() {
|
|
5474
5799
|
const parts = [
|
|
5475
5800
|
// Emoji Tag Sequence (ETS)
|
|
5476
|
-
|
|
5801
|
+
/\p{Emoji}[\u{E0020}-\u{E007E}]+\u{E007F}/u,
|
|
5477
5802
|
// Emoji Keycap Sequence
|
|
5478
|
-
|
|
5803
|
+
/\p{Emoji}\u{FE0F}?\u{20E3}/u,
|
|
5479
5804
|
// Emoji Presentation Sequence
|
|
5480
|
-
|
|
5805
|
+
/\p{Emoji}\u{FE0F}/u,
|
|
5481
5806
|
// Single-Character Emoji / Emoji Modifier Sequence
|
|
5482
|
-
|
|
5807
|
+
/(?=\p{General_Category=Other_Symbol})\p{Emoji}\p{Emoji_Modifier}*/u
|
|
5483
5808
|
];
|
|
5484
|
-
const flagSequence =
|
|
5809
|
+
const flagSequence = /\p{Regional_Indicator}{2}/u.source;
|
|
5485
5810
|
const emoji = '(?:' + parts.map((part)=>part.source).join('|') + ')';
|
|
5486
5811
|
const sequence = `${flagSequence}|${emoji}(?:\u{200D}${emoji})*`;
|
|
5487
5812
|
return new RegExp(sequence, 'ug');
|