@ckeditor/ckeditor5-utils 49.0.1 → 49.1.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.
@@ -0,0 +1,111 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
+ */
5
+ /**
6
+ * @module utils/dom/domtargetslistener
7
+ */
8
+ import { type DomEmitter, type DomEventMap } from "./emittermixin.js";
9
+ import { type CallbackOptions } from "../emittermixin.js";
10
+ import { type EventInfo } from "../eventinfo.js";
11
+ /**
12
+ * Keeps `emitter`'s callback attached to exactly the DOM event targets returned by `getTargets()`, for code whose
13
+ * targets are known only later or change over time.
14
+ *
15
+ * To listen to the events of the whole editor UI, use {@link module:utils/dom/shadowrootregistry~listenToEventRoots}
16
+ * instead, which follows the editor's {@link module:utils/dom/shadowrootregistry~ShadowRootRegistry registry} on its own.
17
+ * Use this listener for particular elements, such as the positioning target of a balloon or the limiter of a sticky
18
+ * panel: return `document` and their {@link module:utils/dom/geteventroots~getEventRoots event roots} from `getTargets()`.
19
+ * Unlike the registry listeners, it does not listen to `document` on its own.
20
+ *
21
+ * The targets are read once, when the listener is created. Call {@link #refresh} whenever they may have changed:
22
+ * it calls `getTargets()` again, attaches the callback to the targets that appeared and detaches it from the targets
23
+ * that disappeared, leaving the others alone. Call {@link #destroy} to detach the callback from every target:
24
+ *
25
+ * ```ts
26
+ * const scrollListener = new DomTargetsListener( () => [ document, ...getEventRoots( limiterElement ) ], {
27
+ * emitter: view,
28
+ * event: 'scroll',
29
+ * listenerOptions: { useCapture: true },
30
+ * callback: onScroll
31
+ * } );
32
+ *
33
+ * // For example once the limiter was moved to another shadow root.
34
+ * scrollListener.refresh();
35
+ *
36
+ * // For example when the view is destroyed.
37
+ * scrollListener.destroy();
38
+ * ```
39
+ *
40
+ * Only this callback is ever detached, so other listeners of the same emitter on the same targets are not affected.
41
+ */
42
+ export declare class DomTargetsListener<TEvent extends keyof DomEventMap = keyof DomEventMap> {
43
+ /**
44
+ * Returns the targets that should be listened to.
45
+ */
46
+ private readonly _getTargets;
47
+ /**
48
+ * The emitter the callback is attached with.
49
+ */
50
+ private readonly _emitter;
51
+ /**
52
+ * The DOM event the callback is attached to.
53
+ */
54
+ private readonly _event;
55
+ /**
56
+ * The callback attached to each target.
57
+ */
58
+ private readonly _callback;
59
+ /**
60
+ * The options the callback is attached with.
61
+ */
62
+ private readonly _listenerOptions;
63
+ /**
64
+ * The targets the callback is currently attached to.
65
+ */
66
+ private readonly _attachedTargets;
67
+ /**
68
+ * Creates the listener and attaches the callback to the targets returned by `getTargets()`.
69
+ *
70
+ * @param getTargets Returns the targets that should be listened to. `null` and `undefined` entries are skipped.
71
+ * @param options Describes the listener to attach to each target.
72
+ */
73
+ constructor(getTargets: () => Iterable<EventTarget | null | undefined>, { emitter, event, listenerOptions, callback }: DomTargetsListenerOptions<TEvent>);
74
+ /**
75
+ * Reads the targets again: attaches the callback to the targets that appeared and detaches it from the targets that
76
+ * disappeared.
77
+ */
78
+ refresh(): void;
79
+ /**
80
+ * Detaches the callback from every target.
81
+ */
82
+ destroy(): void;
83
+ /**
84
+ * Attaches the callback to exactly the given targets.
85
+ */
86
+ private _attachTo;
87
+ }
88
+ /**
89
+ * The listener that {@link ~DomTargetsListener} and the registry helpers attach to each DOM event target.
90
+ */
91
+ export interface DomTargetsListenerOptions<TEvent extends keyof DomEventMap> {
92
+ /**
93
+ * The emitter to which this behavior should be added.
94
+ */
95
+ emitter: DomEmitter;
96
+ /**
97
+ * The DOM event to listen to.
98
+ */
99
+ event: TEvent;
100
+ /**
101
+ * Listener options, passed through to `DomEmitter#listenTo()`.
102
+ */
103
+ listenerOptions?: CallbackOptions & {
104
+ useCapture?: boolean;
105
+ usePassive?: boolean;
106
+ };
107
+ /**
108
+ * The event handler.
109
+ */
110
+ callback: (evt: EventInfo, domEvent: DomEventMap[TEvent]) => void;
111
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
+ */
5
+ /**
6
+ * Returns every event root of the given node: the shadow roots it renders in, innermost first, followed by
7
+ * the document it is connected to.
8
+ *
9
+ * For code that listens to the DOM events of a node, which a listener on `document` alone may miss: events that
10
+ * are not `composed`, such as `scroll`, never leave the shadow root they started in, and the events that do are
11
+ * retargeted to the shadow host. Works for open and closed roots alike.
12
+ *
13
+ * Like {@link module:utils/dom/getshadowroots~getShadowRoots}, it reports the shadow roots of a detached node too.
14
+ * The document is only included while the node is connected to it. Empty for `null`.
15
+ *
16
+ * @param node The node to collect the event roots of.
17
+ */
18
+ export declare function getEventRoots(node: Node | null): Array<Document | ShadowRoot>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
+ */
5
+ /**
6
+ * @module utils/dom/isprocessinginstruction
7
+ */
8
+ /**
9
+ * Checks whether the object is a native DOM Processing Instruction node.
10
+ */
11
+ export declare function isProcessingInstruction(obj: any): obj is ProcessingInstruction;
@@ -2,21 +2,24 @@
2
2
  * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
4
  */
5
- import { type CallbackOptions, type EmitterMixinConstructor } from "../emittermixin.js";
6
- import { type DomEmitter, type DomEventMap } from "./emittermixin.js";
7
- import { type EventInfo } from "../eventinfo.js";
5
+ import { type DomTargetsListenerOptions } from "./domtargetslistener.js";
6
+ import { type EmitterMixinConstructor } from "../emittermixin.js";
7
+ import { type DomEventMap } from "./emittermixin.js";
8
8
  declare const ShadowRootRegistryBase: EmitterMixinConstructor;
9
9
  /**
10
10
  * Keeps track of the shadow roots hosting a set of registered DOM nodes (for example an editor UI's editables,
11
11
  * toolbars and menu bars, or a feature's own UI), so that consumers can, for instance, inject styles or listeners
12
12
  * into every shadow root those nodes currently live in.
13
+ *
14
+ * It also keeps track of the wider set of the {@link #getEventRoots event roots} of those nodes – the shadow roots
15
+ * plus the documents the nodes are connected to – for consumers that listen to DOM events of those nodes.
13
16
  */
14
17
  export declare class ShadowRootRegistry extends ShadowRootRegistryBase {
15
18
  /**
16
- * The shadow roots hosting each registered node, as of the last time that node was looked at. Empty for a node
17
- * that was detached or lived in the light DOM then. Re-derived for every node by each {@link #refresh}, so a
18
- * node that has since been attached, or moved to another tree, is picked up. The union of the values is
19
- * {@link #getShadowRoots}.
19
+ * The event roots of each registered node, as of the last time that node was looked at: the shadow roots hosting
20
+ * it and the document it is connected to. Empty for a node that was detached then. Re-derived for every node by
21
+ * each {@link #refresh}, so a node that has since been attached, or moved to another tree, is picked up.
22
+ * The union of the values is {@link #getEventRoots}, and its shadow roots are {@link #getShadowRoots}.
20
23
  */
21
24
  private _rootsByNode;
22
25
  /**
@@ -24,6 +27,13 @@ export declare class ShadowRootRegistry extends ShadowRootRegistryBase {
24
27
  */
25
28
  getShadowRoots(): Set<ShadowRoot>;
26
29
  /**
30
+ * Returns the event roots of the currently registered nodes: every shadow root hosting at least one of them,
31
+ * as in {@link #getShadowRoots}, and every document at least one of them is connected to.
32
+ *
33
+ * See {@link module:utils/dom/geteventroots~getEventRoots} for what an event root is.
34
+ */
35
+ getEventRoots(): Set<Document | ShadowRoot>;
36
+ /**
27
37
  * Registers a node, so the shadow roots hosting it become a part of {@link #getShadowRoots}.
28
38
  *
29
39
  * The node does not have to be attached to the DOM yet; while it is not, it simply contributes no shadow root.
@@ -37,7 +47,8 @@ export declare class ShadowRootRegistry extends ShadowRootRegistryBase {
37
47
  unregisterNode(node: Node): void;
38
48
  /**
39
49
  * Re-derives the shadow roots of every registered node, firing {@link #event:add} for the roots that started
40
- * hosting one and {@link #event:remove} for those that stopped.
50
+ * hosting one and {@link #event:remove} for those that stopped. Fires {@link #event:changeEventRoots} if
51
+ * the {@link #getEventRoots event roots} changed.
41
52
  *
42
53
  * Call it whenever a registered node may have been attached, detached, or moved – for example on every editor
43
54
  * UI update. Nodes are re-derived rather than resolved once, because the tree a node lives in is not fixed:
@@ -47,14 +58,15 @@ export declare class ShadowRootRegistry extends ShadowRootRegistryBase {
47
58
  refresh(): void;
48
59
  /**
49
60
  * Unregisters every currently registered node at once, firing {@link #event:remove} for every shadow root
50
- * that consequently stops being tracked.
61
+ * that consequently stops being tracked, and {@link #event:changeEventRoots} if any event root was tracked.
51
62
  *
52
63
  * @internal
53
64
  */
54
65
  destroy(): void;
55
66
  /**
56
- * Fires {@link #event:add} and {@link #event:remove} for the difference between the given set of tracked
57
- * shadow roots and the current one.
67
+ * Fires {@link #event:add} and {@link #event:remove} for the difference between the shadow roots in the given
68
+ * set of tracked event roots and the current ones, followed by a single {@link #event:changeEventRoots} if
69
+ * the event roots differ at all.
58
70
  */
59
71
  private _fireRootChanges;
60
72
  }
@@ -75,33 +87,51 @@ export declare class ShadowRootRegistry extends ShadowRootRegistryBase {
75
87
  * const stopListening = listenToShadowRoots( editor.ui.shadowRootRegistry, {
76
88
  * emitter: view,
77
89
  * event: 'scroll',
78
- * callback: repositionOnScroll,
79
- * listenerOptions: { useCapture: true }
90
+ * listenerOptions: { useCapture: true },
91
+ * callback: repositionOnScroll
80
92
  * } );
81
93
  * ```
82
94
  *
95
+ * To cover both with a single call, use {@link ~listenToEventRoots} instead.
96
+ *
83
97
  * The `useCapture` above is not incidental. The events this function exists for do not bubble when fired on an
84
98
  * element, so a listener on a shadow root only sees one that originates inside that root during the capture
85
99
  * phase. Without it the listener is attached but never fires.
86
100
  *
87
- * Call the returned function to detach.
101
+ * Call the returned function to detach. There is nothing to refresh, as the listener follows the registry on its own.
88
102
  *
89
103
  * @param registry The registry whose shadow roots should be listened to.
90
104
  * @param options Describes the listener to attach to each shadow root.
91
- * @param options.emitter The emitter to which this behavior should be added.
92
- * @param options.event The DOM event to listen to.
93
- * @param options.callback The event handler.
94
- * @param options.listenerOptions Listener options, passed through to `DomEmitter#listenTo()`.
95
105
  */
96
- export declare function listenToShadowRoots(registry: ShadowRootRegistry, { emitter, event, callback, listenerOptions }: {
97
- emitter: DomEmitter;
98
- event: keyof DomEventMap;
99
- callback: (evt: EventInfo, domEvent: any) => void;
100
- listenerOptions?: CallbackOptions & {
101
- useCapture?: boolean;
102
- usePassive?: boolean;
103
- };
104
- }): () => void;
106
+ export declare function listenToShadowRoots<TEvent extends keyof DomEventMap>(registry: ShadowRootRegistry, options: DomTargetsListenerOptions<TEvent>): () => void;
107
+ /**
108
+ * Attaches `emitter`'s callback to the global `document` and to every {@link ~ShadowRootRegistry#getEventRoots event root}
109
+ * tracked by `registry`, and keeps that up to date – attaching to roots that appear later, detaching from roots that
110
+ * disappear – for as long as the returned function is not called.
111
+ *
112
+ * It replaces a pair of a `document` listener and {@link ~listenToShadowRoots}:
113
+ *
114
+ * ```ts
115
+ * const stopListening = listenToEventRoots( editor.ui.shadowRootRegistry, {
116
+ * emitter: view,
117
+ * event: 'scroll',
118
+ * listenerOptions: { useCapture: true },
119
+ * callback: repositionOnScroll
120
+ * } );
121
+ * ```
122
+ *
123
+ * The global `document` is always listened to, even if no registered node lives in it, so the callback receives
124
+ * the same events as a listener on `document` would, plus the events of the other event roots. Each root is listened
125
+ * to once, no matter how many registered nodes it hosts.
126
+ *
127
+ * As with {@link ~listenToShadowRoots}, use `useCapture: true` for events that do not bubble, such as `scroll`.
128
+ *
129
+ * Call the returned function to detach. There is nothing to refresh, as the listener follows the registry on its own.
130
+ *
131
+ * @param registry The registry whose event roots should be listened to.
132
+ * @param options Describes the listener to attach to each event root.
133
+ */
134
+ export declare function listenToEventRoots<TEvent extends keyof DomEventMap>(registry: ShadowRootRegistry, options: DomTargetsListenerOptions<TEvent>): () => void;
105
135
  /**
106
136
  * Fired when a shadow root starts hosting a registered node.
107
137
  *
@@ -122,4 +152,15 @@ export type ShadowRootRegistryRemoveEvent = {
122
152
  name: "remove";
123
153
  args: [root: ShadowRoot];
124
154
  };
155
+ /**
156
+ * Fired when the {@link ~ShadowRootRegistry#getEventRoots event roots} of the registered nodes change: a shadow root
157
+ * or a document starts or stops hosting a registered node. Fired once per change, after any {@link ~ShadowRootRegistry#event:add}
158
+ * and {@link ~ShadowRootRegistry#event:remove} events, so read the current roots with {@link ~ShadowRootRegistry#getEventRoots}.
159
+ *
160
+ * @eventName ~ShadowRootRegistry#changeEventRoots
161
+ */
162
+ export type ShadowRootRegistryChangeEventRootsEvent = {
163
+ name: "changeEventRoots";
164
+ args: [];
165
+ };
125
166
  export {};
package/dist/index.d.ts CHANGED
@@ -36,6 +36,8 @@ export { getElementFromPoint } from "./dom/getelementfrompoint.js";
36
36
  export { getPositionedAncestor } from "./dom/getpositionedancestor.js";
37
37
  export { getActiveElement } from "./dom/getactiveelement.js";
38
38
  export { getShadowRoots } from "./dom/getshadowroots.js";
39
+ export { getEventRoots } from "./dom/geteventroots.js";
40
+ export { DomTargetsListener, type DomTargetsListenerOptions } from "./dom/domtargetslistener.js";
39
41
  export { getOverlayMountRoot } from "./dom/getoverlaymountroot.js";
40
42
  export { adoptGlobalStyleSheet } from "./dom/adoptglobalstylesheet.js";
41
43
  export { getParentElement } from "./dom/getparentelement.js";
@@ -51,6 +53,7 @@ export { toUnit, type ToUnitHelper } from "./dom/tounit.js";
51
53
  export { indexOf } from "./dom/indexof.js";
52
54
  export { insertAt } from "./dom/insertat.js";
53
55
  export { isComment } from "./dom/iscomment.js";
56
+ export { isProcessingInstruction } from "./dom/isprocessinginstruction.js";
54
57
  export { isDomSelectionBackward } from "./dom/isdomselectionbackward.js";
55
58
  export { isOffline } from "./dom/isoffline.js";
56
59
  export { isNode } from "./dom/isnode.js";
@@ -58,7 +61,7 @@ export { isRange } from "./dom/isrange.js";
58
61
  export { isShadowRoot } from "./dom/isshadowroot.js";
59
62
  export { isShadowHostOf } from "./dom/isshadowhostof.js";
60
63
  export { whenElementConnected } from "./dom/whenelementconnected.js";
61
- export { ShadowRootRegistry, listenToShadowRoots, type ShadowRootRegistryAddEvent, type ShadowRootRegistryRemoveEvent } from "./dom/shadowrootregistry.js";
64
+ export { ShadowRootRegistry, listenToShadowRoots, listenToEventRoots, type ShadowRootRegistryAddEvent, type ShadowRootRegistryRemoveEvent, type ShadowRootRegistryChangeEventRootsEvent } from "./dom/shadowrootregistry.js";
62
65
  export { isValidAttributeName } from "./dom/isvalidattributename.js";
63
66
  export { isVisible } from "./dom/isvisible.js";
64
67
  export { getOptimalPosition, getConstrainedViewportRect, type DomOptimalPositionOptions, type PositioningFunction, type DomPoint, type DomPositioningFunctionResult } from "./dom/position.js";