@ckeditor/ckeditor5-minimap 48.2.0-alpha.7 → 48.3.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/dist/index.js CHANGED
@@ -2,66 +2,76 @@
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 { Plugin } from '@ckeditor/ckeditor5-core/dist/index.js';
6
- import { toUnit, global, Rect, findClosestScrollableAncestor } from '@ckeditor/ckeditor5-utils/dist/index.js';
7
- import { IframeView, View } from '@ckeditor/ckeditor5-ui/dist/index.js';
8
- import { ViewDomConverter, ViewRenderer } from '@ckeditor/ckeditor5-engine/dist/index.js';
5
+ import { Plugin } from "@ckeditor/ckeditor5-core";
6
+ import { Rect, findClosestScrollableAncestor, global, toUnit } from "@ckeditor/ckeditor5-utils";
7
+ import { IframeView, View } from "@ckeditor/ckeditor5-ui";
8
+ import { ViewDomConverter, ViewRenderer } from "@ckeditor/ckeditor5-engine";
9
9
 
10
- const toPx$1 = /* #__PURE__ */ toUnit('px');
11
10
  /**
12
- * The internal `<iframe>` view that hosts the minimap content.
13
- *
14
- * @internal
15
- */ class MinimapIframeView extends IframeView {
16
- /**
17
- * Cached view constructor options for re-use in other methods.
18
- */ _options;
19
- /**
20
- * Creates an instance of the internal minimap iframe.
21
- */ constructor(locale, options){
22
- super(locale);
23
- const bind = this.bindTemplate;
24
- this.set('top', 0);
25
- this.set('height', 0);
26
- this._options = options;
27
- this.extendTemplate({
28
- attributes: {
29
- tabindex: -1,
30
- 'aria-hidden': 'true',
31
- class: [
32
- 'ck-minimap__iframe'
33
- ],
34
- style: {
35
- top: bind.to('top', (top)=>toPx$1(top)),
36
- height: bind.to('height', (height)=>toPx$1(height))
37
- }
38
- }
39
- });
40
- }
41
- /**
42
- * @inheritDoc
43
- */ render() {
44
- return super.render().then(()=>{
45
- this._prepareDocument();
46
- });
47
- }
48
- /**
49
- * Sets the new height of the iframe.
50
- */ setHeight(newHeight) {
51
- this.height = newHeight;
52
- }
53
- /**
54
- * Sets the top offset of the iframe to move it around vertically.
55
- */ setTopOffset(newOffset) {
56
- this.top = newOffset;
57
- }
58
- /**
59
- * Sets the internal structure of the `<iframe>` readying it to display the
60
- * minimap element.
61
- */ _prepareDocument() {
62
- const iframeDocument = this.element.contentWindow.document;
63
- const domRootClone = iframeDocument.adoptNode(this._options.domRootClone);
64
- const boxStyles = this._options.useSimplePreview ? `
11
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
12
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
13
+ */
14
+ /**
15
+ * @module minimap/minimapiframeview
16
+ */
17
+ const toPx$1 = /* #__PURE__ */ toUnit("px");
18
+ /**
19
+ * The internal `<iframe>` view that hosts the minimap content.
20
+ *
21
+ * @internal
22
+ */
23
+ var MinimapIframeView = class extends IframeView {
24
+ /**
25
+ * Cached view constructor options for re-use in other methods.
26
+ */
27
+ _options;
28
+ /**
29
+ * Creates an instance of the internal minimap iframe.
30
+ */
31
+ constructor(locale, options) {
32
+ super(locale);
33
+ const bind = this.bindTemplate;
34
+ this.set("top", 0);
35
+ this.set("height", 0);
36
+ this._options = options;
37
+ this.extendTemplate({ attributes: {
38
+ tabindex: -1,
39
+ "aria-hidden": "true",
40
+ class: ["ck-minimap__iframe"],
41
+ style: {
42
+ top: bind.to("top", (top) => toPx$1(top)),
43
+ height: bind.to("height", (height) => toPx$1(height))
44
+ }
45
+ } });
46
+ }
47
+ /**
48
+ * @inheritDoc
49
+ */
50
+ render() {
51
+ return super.render().then(() => {
52
+ this._prepareDocument();
53
+ });
54
+ }
55
+ /**
56
+ * Sets the new height of the iframe.
57
+ */
58
+ setHeight(newHeight) {
59
+ this.height = newHeight;
60
+ }
61
+ /**
62
+ * Sets the top offset of the iframe to move it around vertically.
63
+ */
64
+ setTopOffset(newOffset) {
65
+ this.top = newOffset;
66
+ }
67
+ /**
68
+ * Sets the internal structure of the `<iframe>` readying it to display the
69
+ * minimap element.
70
+ */
71
+ _prepareDocument() {
72
+ const iframeDocument = this.element.contentWindow.document;
73
+ const domRootClone = iframeDocument.adoptNode(this._options.domRootClone);
74
+ const boxStyles = this._options.useSimplePreview ? `
65
75
  .ck.ck-editor__editable_inline img {
66
76
  filter: contrast( 0 );
67
77
  }
@@ -75,19 +85,15 @@ const toPx$1 = /* #__PURE__ */ toUnit('px');
75
85
  background: hsl(0, 0%, 60%) !important;
76
86
  color: hsl(0, 0%, 60%) !important;
77
87
  }
78
- ` : '';
79
- const pageStyles = this._options.pageStyles.map((definition)=>{
80
- if (typeof definition === 'string') {
81
- return `<style>${definition}</style>`;
82
- } else {
83
- return `<link rel="stylesheet" type="text/css" href="${definition.href}">`;
84
- }
85
- }).join('\n');
86
- const html = `<!DOCTYPE html><html lang="en">
88
+ ` : "";
89
+ const html = `<!DOCTYPE html><html lang="en">
87
90
  <head>
88
91
  <meta charset="utf-8">
89
92
  <meta name="viewport" content="width=device-width, initial-scale=1">
90
- ${pageStyles}
93
+ ${this._options.pageStyles.map((definition) => {
94
+ if (typeof definition === "string") return `<style>${definition}</style>`;
95
+ else return `<link rel="stylesheet" type="text/css" href="${definition.href}">`;
96
+ }).join("\n")}
91
97
  <style>
92
98
  html, body {
93
99
  margin: 0 !important;
@@ -118,445 +124,454 @@ const toPx$1 = /* #__PURE__ */ toUnit('px');
118
124
  ${boxStyles}
119
125
  </style>
120
126
  </head>
121
- <body class="${this._options.extraClasses || ''}"></body>
127
+ <body class="${this._options.extraClasses || ""}"></body>
122
128
  </html>`;
123
- iframeDocument.open();
124
- iframeDocument.write(html);
125
- iframeDocument.close();
126
- iframeDocument.body.appendChild(domRootClone);
127
- }
128
- }
129
+ iframeDocument.open();
130
+ iframeDocument.write(html);
131
+ iframeDocument.close();
132
+ iframeDocument.body.appendChild(domRootClone);
133
+ }
134
+ };
129
135
 
130
- const toPx = /* #__PURE__ */ toUnit('px');
131
136
  /**
132
- * The position tracker visualizing the visible subset of the content. Displayed over the minimap.
133
- *
134
- * @internal
135
- */ class MinimapPositionTrackerView extends View {
136
- constructor(locale){
137
- super(locale);
138
- const bind = this.bindTemplate;
139
- this.set('height', 0);
140
- this.set('top', 0);
141
- this.set('scrollProgress', 0);
142
- this.set('_isDragging', false);
143
- this.setTemplate({
144
- tag: 'div',
145
- attributes: {
146
- class: [
147
- 'ck',
148
- 'ck-minimap__position-tracker',
149
- bind.if('_isDragging', 'ck-minimap__position-tracker_dragging')
150
- ],
151
- style: {
152
- top: bind.to('top', (top)=>toPx(top)),
153
- height: bind.to('height', (height)=>toPx(height))
154
- },
155
- 'data-progress': bind.to('scrollProgress')
156
- },
157
- on: {
158
- mousedown: bind.to(()=>{
159
- this._isDragging = true;
160
- })
161
- }
162
- });
163
- }
164
- /**
165
- * @inheritDoc
166
- */ render() {
167
- super.render();
168
- this.listenTo(global.document, 'mousemove', (evt, data)=>{
169
- if (!this._isDragging) {
170
- return;
171
- }
172
- this.fire('drag', data.movementY);
173
- }, {
174
- useCapture: true
175
- });
176
- this.listenTo(global.document, 'mouseup', ()=>{
177
- this._isDragging = false;
178
- }, {
179
- useCapture: true
180
- });
181
- }
182
- /**
183
- * Sets the new height of the tracker to visualize the subset of the content visible to the user.
184
- */ setHeight(newHeight) {
185
- this.height = newHeight;
186
- }
187
- /**
188
- * Sets the top offset of the tracker to move it around vertically.
189
- */ setTopOffset(newOffset) {
190
- this.top = newOffset;
191
- }
192
- /**
193
- * Sets the scroll progress (in %) to inform the user using a label when the tracker is being dragged.
194
- */ setScrollProgress(newProgress) {
195
- this.scrollProgress = newProgress;
196
- }
197
- }
137
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
138
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
139
+ */
140
+ /**
141
+ * @module minimap/minimappositiontrackerview
142
+ */
143
+ const toPx = /* #__PURE__ */ toUnit("px");
144
+ /**
145
+ * The position tracker visualizing the visible subset of the content. Displayed over the minimap.
146
+ *
147
+ * @internal
148
+ */
149
+ var MinimapPositionTrackerView = class extends View {
150
+ constructor(locale) {
151
+ super(locale);
152
+ const bind = this.bindTemplate;
153
+ this.set("height", 0);
154
+ this.set("top", 0);
155
+ this.set("scrollProgress", 0);
156
+ this.set("_isDragging", false);
157
+ this.setTemplate({
158
+ tag: "div",
159
+ attributes: {
160
+ class: [
161
+ "ck",
162
+ "ck-minimap__position-tracker",
163
+ bind.if("_isDragging", "ck-minimap__position-tracker_dragging")
164
+ ],
165
+ style: {
166
+ top: bind.to("top", (top) => toPx(top)),
167
+ height: bind.to("height", (height) => toPx(height))
168
+ },
169
+ "data-progress": bind.to("scrollProgress")
170
+ },
171
+ on: { mousedown: bind.to(() => {
172
+ this._isDragging = true;
173
+ }) }
174
+ });
175
+ }
176
+ /**
177
+ * @inheritDoc
178
+ */
179
+ render() {
180
+ super.render();
181
+ this.listenTo(global.document, "mousemove", (evt, data) => {
182
+ if (!this._isDragging) return;
183
+ this.fire("drag", data.movementY);
184
+ }, { useCapture: true });
185
+ this.listenTo(global.document, "mouseup", () => {
186
+ this._isDragging = false;
187
+ }, { useCapture: true });
188
+ }
189
+ /**
190
+ * Sets the new height of the tracker to visualize the subset of the content visible to the user.
191
+ */
192
+ setHeight(newHeight) {
193
+ this.height = newHeight;
194
+ }
195
+ /**
196
+ * Sets the top offset of the tracker to move it around vertically.
197
+ */
198
+ setTopOffset(newOffset) {
199
+ this.top = newOffset;
200
+ }
201
+ /**
202
+ * Sets the scroll progress (in %) to inform the user using a label when the tracker is being dragged.
203
+ */
204
+ setScrollProgress(newProgress) {
205
+ this.scrollProgress = newProgress;
206
+ }
207
+ };
198
208
 
199
209
  /**
200
- * The main view of the minimap. It renders the original content but scaled down with a tracker element
201
- * visualizing the subset of the content visible to the user and allowing interactions (scrolling, dragging).
202
- *
203
- * @internal
204
- */ class MinimapView extends View {
205
- /**
206
- * An instance of the tracker view displayed over the minimap.
207
- */ _positionTrackerView;
208
- /**
209
- * The scale ratio of the minimap relative to the original editing DOM root with the content.
210
- */ _scaleRatio;
211
- /**
212
- * An instance of the iframe view that hosts the minimap.
213
- */ _minimapIframeView;
214
- /**
215
- * Creates an instance of the minimap view.
216
- */ constructor({ locale, scaleRatio, pageStyles, extraClasses, useSimplePreview, domRootClone }){
217
- super(locale);
218
- const bind = this.bindTemplate;
219
- this._positionTrackerView = new MinimapPositionTrackerView(locale);
220
- this._positionTrackerView.delegate('drag').to(this);
221
- this._scaleRatio = scaleRatio;
222
- this._minimapIframeView = new MinimapIframeView(locale, {
223
- useSimplePreview,
224
- pageStyles,
225
- extraClasses,
226
- scaleRatio,
227
- domRootClone
228
- });
229
- this.setTemplate({
230
- tag: 'div',
231
- attributes: {
232
- class: [
233
- 'ck',
234
- 'ck-minimap'
235
- ]
236
- },
237
- children: [
238
- this._positionTrackerView
239
- ],
240
- on: {
241
- click: bind.to(this._handleMinimapClick.bind(this)),
242
- wheel: bind.to(this._handleMinimapMouseWheel.bind(this))
243
- }
244
- });
245
- }
246
- /**
247
- * @inheritDoc
248
- */ destroy() {
249
- this._minimapIframeView.destroy();
250
- super.destroy();
251
- }
252
- /**
253
- * Returns the DOM {@link module:utils/dom/rect~Rect} height of the minimap.
254
- */ get height() {
255
- return new Rect(this.element).height;
256
- }
257
- /**
258
- * Returns the number of available space (pixels) the position tracker (visible subset of the content) can use to scroll vertically.
259
- */ get scrollHeight() {
260
- return Math.max(0, Math.min(this.height, this._minimapIframeView.height) - this._positionTrackerView.height);
261
- }
262
- /**
263
- * @inheritDoc
264
- */ render() {
265
- super.render();
266
- this._minimapIframeView.render();
267
- this.element.appendChild(this._minimapIframeView.element);
268
- }
269
- /**
270
- * Sets the new height of the minimap (in px) to respond to the changes in the original editing DOM root.
271
- *
272
- * **Note**:The provided value should be the `offsetHeight` of the original editing DOM root.
273
- */ setContentHeight(newHeight) {
274
- this._minimapIframeView.setHeight(newHeight * this._scaleRatio);
275
- }
276
- /**
277
- * Sets the minimap scroll progress.
278
- *
279
- * The minimap scroll progress is linked to the original editing DOM root and its scrollable container (ancestor).
280
- * Changing the progress will alter the vertical position of the minimap (and its position tracker) and give the user an accurate
281
- * overview of the visible document.
282
- *
283
- * **Note**: The value should be between 0 and 1. 0 when the DOM root has not been scrolled, 1 when the
284
- * scrolling has reached the end.
285
- */ setScrollProgress(newScrollProgress) {
286
- const iframeView = this._minimapIframeView;
287
- const positionTrackerView = this._positionTrackerView;
288
- // The scrolling should end when the bottom edge of the iframe touches the bottom edge of the minimap.
289
- if (iframeView.height < this.height) {
290
- iframeView.setTopOffset(0);
291
- positionTrackerView.setTopOffset((iframeView.height - positionTrackerView.height) * newScrollProgress);
292
- } else {
293
- const totalOffset = iframeView.height - this.height;
294
- iframeView.setTopOffset(-totalOffset * newScrollProgress);
295
- positionTrackerView.setTopOffset((this.height - positionTrackerView.height) * newScrollProgress);
296
- }
297
- positionTrackerView.setScrollProgress(Math.round(newScrollProgress * 100));
298
- }
299
- /**
300
- * Sets the new height of the tracker (in px) to visualize the subset of the content visible to the user.
301
- */ setPositionTrackerHeight(trackerHeight) {
302
- this._positionTrackerView.setHeight(trackerHeight * this._scaleRatio);
303
- }
304
- /**
305
- * @param data DOM event data
306
- */ _handleMinimapClick(data) {
307
- const positionTrackerView = this._positionTrackerView;
308
- if (data.target === positionTrackerView.element) {
309
- return;
310
- }
311
- const trackerViewRect = new Rect(positionTrackerView.element);
312
- const diff = data.clientY - trackerViewRect.top - trackerViewRect.height / 2;
313
- const percentage = diff / this._minimapIframeView.height;
314
- this.fire('click', percentage);
315
- }
316
- /**
317
- * @param data DOM event data
318
- */ _handleMinimapMouseWheel(data) {
319
- this.fire('drag', data.deltaY * this._scaleRatio);
320
- }
321
- }
210
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
211
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
212
+ */
213
+ /**
214
+ * @module minimap/minimapview
215
+ */
216
+ /**
217
+ * The main view of the minimap. It renders the original content but scaled down with a tracker element
218
+ * visualizing the subset of the content visible to the user and allowing interactions (scrolling, dragging).
219
+ *
220
+ * @internal
221
+ */
222
+ var MinimapView = class extends View {
223
+ /**
224
+ * An instance of the tracker view displayed over the minimap.
225
+ */
226
+ _positionTrackerView;
227
+ /**
228
+ * The scale ratio of the minimap relative to the original editing DOM root with the content.
229
+ */
230
+ _scaleRatio;
231
+ /**
232
+ * An instance of the iframe view that hosts the minimap.
233
+ */
234
+ _minimapIframeView;
235
+ /**
236
+ * Creates an instance of the minimap view.
237
+ */
238
+ constructor({ locale, scaleRatio, pageStyles, extraClasses, useSimplePreview, domRootClone }) {
239
+ super(locale);
240
+ const bind = this.bindTemplate;
241
+ this._positionTrackerView = new MinimapPositionTrackerView(locale);
242
+ this._positionTrackerView.delegate("drag").to(this);
243
+ this._scaleRatio = scaleRatio;
244
+ this._minimapIframeView = new MinimapIframeView(locale, {
245
+ useSimplePreview,
246
+ pageStyles,
247
+ extraClasses,
248
+ scaleRatio,
249
+ domRootClone
250
+ });
251
+ this.setTemplate({
252
+ tag: "div",
253
+ attributes: { class: ["ck", "ck-minimap"] },
254
+ children: [this._positionTrackerView],
255
+ on: {
256
+ click: bind.to(this._handleMinimapClick.bind(this)),
257
+ wheel: bind.to(this._handleMinimapMouseWheel.bind(this))
258
+ }
259
+ });
260
+ }
261
+ /**
262
+ * @inheritDoc
263
+ */
264
+ destroy() {
265
+ this._minimapIframeView.destroy();
266
+ super.destroy();
267
+ }
268
+ /**
269
+ * Returns the DOM {@link module:utils/dom/rect~Rect} height of the minimap.
270
+ */
271
+ get height() {
272
+ return new Rect(this.element).height;
273
+ }
274
+ /**
275
+ * Returns the number of available space (pixels) the position tracker (visible subset of the content) can use to scroll vertically.
276
+ */
277
+ get scrollHeight() {
278
+ return Math.max(0, Math.min(this.height, this._minimapIframeView.height) - this._positionTrackerView.height);
279
+ }
280
+ /**
281
+ * @inheritDoc
282
+ */
283
+ render() {
284
+ super.render();
285
+ this._minimapIframeView.render();
286
+ this.element.appendChild(this._minimapIframeView.element);
287
+ }
288
+ /**
289
+ * Sets the new height of the minimap (in px) to respond to the changes in the original editing DOM root.
290
+ *
291
+ * **Note**:The provided value should be the `offsetHeight` of the original editing DOM root.
292
+ */
293
+ setContentHeight(newHeight) {
294
+ this._minimapIframeView.setHeight(newHeight * this._scaleRatio);
295
+ }
296
+ /**
297
+ * Sets the minimap scroll progress.
298
+ *
299
+ * The minimap scroll progress is linked to the original editing DOM root and its scrollable container (ancestor).
300
+ * Changing the progress will alter the vertical position of the minimap (and its position tracker) and give the user an accurate
301
+ * overview of the visible document.
302
+ *
303
+ * **Note**: The value should be between 0 and 1. 0 when the DOM root has not been scrolled, 1 when the
304
+ * scrolling has reached the end.
305
+ */
306
+ setScrollProgress(newScrollProgress) {
307
+ const iframeView = this._minimapIframeView;
308
+ const positionTrackerView = this._positionTrackerView;
309
+ if (iframeView.height < this.height) {
310
+ iframeView.setTopOffset(0);
311
+ positionTrackerView.setTopOffset((iframeView.height - positionTrackerView.height) * newScrollProgress);
312
+ } else {
313
+ const totalOffset = iframeView.height - this.height;
314
+ iframeView.setTopOffset(-totalOffset * newScrollProgress);
315
+ positionTrackerView.setTopOffset((this.height - positionTrackerView.height) * newScrollProgress);
316
+ }
317
+ positionTrackerView.setScrollProgress(Math.round(newScrollProgress * 100));
318
+ }
319
+ /**
320
+ * Sets the new height of the tracker (in px) to visualize the subset of the content visible to the user.
321
+ */
322
+ setPositionTrackerHeight(trackerHeight) {
323
+ this._positionTrackerView.setHeight(trackerHeight * this._scaleRatio);
324
+ }
325
+ /**
326
+ * @param data DOM event data
327
+ */
328
+ _handleMinimapClick(data) {
329
+ const positionTrackerView = this._positionTrackerView;
330
+ if (data.target === positionTrackerView.element) return;
331
+ const trackerViewRect = new Rect(positionTrackerView.element);
332
+ const percentage = (data.clientY - trackerViewRect.top - trackerViewRect.height / 2) / this._minimapIframeView.height;
333
+ this.fire("click", percentage);
334
+ }
335
+ /**
336
+ * @param data DOM event data
337
+ */
338
+ _handleMinimapMouseWheel(data) {
339
+ this.fire("drag", data.deltaY * this._scaleRatio);
340
+ }
341
+ };
322
342
 
323
343
  /**
324
- * Clones the editing view DOM root by using a dedicated pair of {@link module:engine/view/renderer~ViewRenderer} and
325
- * {@link module:engine/view/domconverter~ViewDomConverter}. The DOM root clone updates incrementally to stay in sync with the
326
- * source root.
327
- *
328
- * @internal
329
- * @param editor The editor instance the original editing root belongs to.
330
- * @param rootName The name of the root to clone.
331
- * @returns The editing root DOM clone element.
332
- */ function cloneEditingViewDomRoot(editor, rootName) {
333
- const viewDocument = editor.editing.view.document;
334
- const viewRoot = viewDocument.getRoot(rootName);
335
- const domConverter = new ViewDomConverter(viewDocument);
336
- const renderer = new ViewRenderer(domConverter, viewDocument.selection);
337
- const domRootClone = editor.editing.view.getDomRoot().cloneNode();
338
- domConverter.bindElements(domRootClone, viewRoot);
339
- renderer.markToSync('children', viewRoot);
340
- renderer.markToSync('attributes', viewRoot);
341
- viewRoot.on('change:children', (evt, node)=>renderer.markToSync('children', node));
342
- viewRoot.on('change:attributes', (evt, node)=>renderer.markToSync('attributes', node));
343
- viewRoot.on('change:text', (evt, node)=>renderer.markToSync('text', node));
344
- renderer.render();
345
- editor.editing.view.on('render', ()=>renderer.render());
346
- // TODO: Cleanup after destruction.
347
- editor.on('destroy', ()=>{
348
- domConverter.unbindDomElement(domRootClone);
349
- });
350
- return domRootClone;
344
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
345
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
346
+ */
347
+ /**
348
+ * @module minimap/utils
349
+ */
350
+ /**
351
+ * Clones the editing view DOM root by using a dedicated pair of {@link module:engine/view/renderer~ViewRenderer} and
352
+ * {@link module:engine/view/domconverter~ViewDomConverter}. The DOM root clone updates incrementally to stay in sync with the
353
+ * source root.
354
+ *
355
+ * @internal
356
+ * @param editor The editor instance the original editing root belongs to.
357
+ * @param rootName The name of the root to clone.
358
+ * @returns The editing root DOM clone element.
359
+ */
360
+ function cloneEditingViewDomRoot(editor, rootName) {
361
+ const viewDocument = editor.editing.view.document;
362
+ const viewRoot = viewDocument.getRoot(rootName);
363
+ const domConverter = new ViewDomConverter(viewDocument);
364
+ const renderer = new ViewRenderer(domConverter, viewDocument.selection);
365
+ const domRootClone = editor.editing.view.getDomRoot().cloneNode();
366
+ domConverter.bindElements(domRootClone, viewRoot);
367
+ renderer.markToSync("children", viewRoot);
368
+ renderer.markToSync("attributes", viewRoot);
369
+ viewRoot.on("change:children", (evt, node) => renderer.markToSync("children", node));
370
+ viewRoot.on("change:attributes", (evt, node) => renderer.markToSync("attributes", node));
371
+ viewRoot.on("change:text", (evt, node) => renderer.markToSync("text", node));
372
+ renderer.render();
373
+ editor.editing.view.on("render", () => renderer.render());
374
+ editor.on("destroy", () => {
375
+ domConverter.unbindDomElement(domRootClone);
376
+ });
377
+ return domRootClone;
351
378
  }
352
379
  /**
353
- * Harvests all web page styles, for instance, to allow re-using them in an `<iframe>` preserving the look of the content.
354
- *
355
- * The returned data format is as follows:
356
- *
357
- * ```ts
358
- * [
359
- * 'p { color: red; ... } h2 { font-size: 2em; ... } ...',
360
- * '.spacing { padding: 1em; ... }; ...',
361
- * '...',
362
- * { href: 'http://link.to.external.stylesheet' },
363
- * { href: '...' }
364
- * ]
365
- * ```
366
- *
367
- * **Note**: For stylesheets with `href` different than window origin, an object is returned because
368
- * accessing rules of these styles may cause CORS errors (depending on the configuration of the web page).
369
- *
370
- * @internal
371
- */ function getPageStyles() {
372
- return Array.from(global.document.styleSheets).map((styleSheet)=>{
373
- // CORS
374
- if (styleSheet.href && !styleSheet.href.startsWith(global.window.location.origin)) {
375
- return {
376
- href: styleSheet.href
377
- };
378
- }
379
- return Array.from(styleSheet.cssRules).filter((rule)=>!(rule instanceof CSSMediaRule)).map((rule)=>rule.cssText).join(' \n');
380
- });
380
+ * Harvests all web page styles, for instance, to allow re-using them in an `<iframe>` preserving the look of the content.
381
+ *
382
+ * The returned data format is as follows:
383
+ *
384
+ * ```ts
385
+ * [
386
+ * 'p { color: red; ... } h2 { font-size: 2em; ... } ...',
387
+ * '.spacing { padding: 1em; ... }; ...',
388
+ * '...',
389
+ * { href: 'http://link.to.external.stylesheet' },
390
+ * { href: '...' }
391
+ * ]
392
+ * ```
393
+ *
394
+ * **Note**: For stylesheets with `href` different than window origin, an object is returned because
395
+ * accessing rules of these styles may cause CORS errors (depending on the configuration of the web page).
396
+ *
397
+ * @internal
398
+ */
399
+ function getPageStyles() {
400
+ return Array.from(global.document.styleSheets).map((styleSheet) => {
401
+ if (styleSheet.href && !styleSheet.href.startsWith(global.window.location.origin)) return { href: styleSheet.href };
402
+ return Array.from(styleSheet.cssRules).filter((rule) => !(rule instanceof CSSMediaRule)).map((rule) => rule.cssText).join(" \n");
403
+ });
381
404
  }
382
405
  /**
383
- * Gets dimensions rectangle according to passed DOM element. Returns whole window's size for `body` element.
384
- *
385
- * @internal
386
- */ function getDomElementRect(domElement) {
387
- return new Rect(domElement === global.document.body ? global.window : domElement);
406
+ * Gets dimensions rectangle according to passed DOM element. Returns whole window's size for `body` element.
407
+ *
408
+ * @internal
409
+ */
410
+ function getDomElementRect(domElement) {
411
+ return new Rect(domElement === global.document.body ? global.window : domElement);
388
412
  }
389
413
  /**
390
- * Gets client height according to passed DOM element. Returns window's height for `body` element.
391
- *
392
- * @internal
393
- */ function getClientHeight(domElement) {
394
- return domElement === global.document.body ? global.window.innerHeight : domElement.clientHeight;
414
+ * Gets client height according to passed DOM element. Returns window's height for `body` element.
415
+ *
416
+ * @internal
417
+ */
418
+ function getClientHeight(domElement) {
419
+ return domElement === global.document.body ? global.window.innerHeight : domElement.clientHeight;
395
420
  }
396
421
  /**
397
- * Returns the DOM element itself if it's not a `body` element, whole window otherwise.
398
- *
399
- * @internal
400
- */ function getScrollable(domElement) {
401
- return domElement === global.document.body ? global.window : domElement;
422
+ * Returns the DOM element itself if it's not a `body` element, whole window otherwise.
423
+ *
424
+ * @internal
425
+ */
426
+ function getScrollable(domElement) {
427
+ return domElement === global.document.body ? global.window : domElement;
402
428
  }
403
429
 
404
430
  /**
405
- * The content minimap feature.
406
- */ class Minimap extends Plugin {
407
- /**
408
- * @inheritDoc
409
- */ static get pluginName() {
410
- return 'Minimap';
411
- }
412
- /**
413
- * @inheritDoc
414
- */ static get isOfficialPlugin() {
415
- return true;
416
- }
417
- /**
418
- * The reference to the view of the minimap.
419
- */ _minimapView;
420
- /**
421
- * The DOM element closest to the editable element of the editor as returned
422
- * by {@link module:ui/editorui/editorui~EditorUI#getEditableElement}.
423
- */ _scrollableRootAncestor;
424
- /**
425
- * The DOM element closest to the editable element of the editor as returned
426
- * by {@link module:ui/editorui/editorui~EditorUI#getEditableElement}.
427
- */ _editingRootElement;
428
- /**
429
- * @inheritDoc
430
- */ init() {
431
- const editor = this.editor;
432
- this._minimapView = null;
433
- this._scrollableRootAncestor = null;
434
- this.listenTo(editor.ui, 'ready', this._onUiReady.bind(this));
435
- }
436
- /**
437
- * @inheritDoc
438
- */ destroy() {
439
- super.destroy();
440
- this._minimapView.destroy();
441
- this._minimapView.element.remove();
442
- }
443
- /**
444
- * Initializes the minimap view element and starts the layout synchronization
445
- * on the editing view `render` event.
446
- */ _onUiReady() {
447
- const editor = this.editor;
448
- // TODO: This will not work with the multi-root editor.
449
- const editingRootElement = this._editingRootElement = editor.ui.getEditableElement();
450
- this._scrollableRootAncestor = findClosestScrollableAncestor(editingRootElement);
451
- // DOM root element is not yet attached to the document.
452
- if (!editingRootElement.ownerDocument.body.contains(editingRootElement)) {
453
- editor.ui.once('update', this._onUiReady.bind(this));
454
- return;
455
- }
456
- this._initializeMinimapView();
457
- this.listenTo(editor.editing.view, 'render', ()=>{
458
- if (editor.state !== 'ready') {
459
- return;
460
- }
461
- this._syncMinimapToEditingRootScrollPosition();
462
- });
463
- this._syncMinimapToEditingRootScrollPosition();
464
- }
465
- /**
466
- * Initializes the minimap view and attaches listeners that make it responsive to the environment (document)
467
- * but also allow the minimap to control the document (scroll position).
468
- */ _initializeMinimapView() {
469
- const editor = this.editor;
470
- const locale = editor.locale;
471
- const useSimplePreview = editor.config.get('minimap.useSimplePreview');
472
- // TODO: Throw an error if there is no `minimap` in config.
473
- const minimapContainerElement = editor.config.get('minimap.container');
474
- const scrollableRootAncestor = this._scrollableRootAncestor;
475
- // TODO: This should be dynamic, the root width could change as the viewport scales if not fixed unit.
476
- const editingRootElementWidth = getDomElementRect(this._editingRootElement).width;
477
- const minimapContainerWidth = getDomElementRect(minimapContainerElement).width;
478
- const minimapScaleRatio = minimapContainerWidth / editingRootElementWidth;
479
- const minimapView = this._minimapView = new MinimapView({
480
- locale,
481
- scaleRatio: minimapScaleRatio,
482
- pageStyles: getPageStyles(),
483
- extraClasses: editor.config.get('minimap.extraClasses'),
484
- useSimplePreview,
485
- domRootClone: cloneEditingViewDomRoot(editor)
486
- });
487
- minimapView.render();
488
- // Scrollable ancestor scroll -> minimap position update.
489
- minimapView.listenTo(global.document, 'scroll', (evt, data)=>{
490
- if (scrollableRootAncestor === global.document.body) {
491
- if (data.target !== global.document) {
492
- return;
493
- }
494
- } else if (data.target !== scrollableRootAncestor) {
495
- return;
496
- }
497
- this._syncMinimapToEditingRootScrollPosition();
498
- }, {
499
- useCapture: true,
500
- usePassive: true
501
- });
502
- // Viewport resize -> minimap position update.
503
- minimapView.listenTo(global.window, 'resize', ()=>{
504
- this._syncMinimapToEditingRootScrollPosition();
505
- });
506
- // Dragging the visible content area -> document (scrollable) position update.
507
- minimapView.on('drag', (evt, movementY)=>{
508
- let movementYPercentage;
509
- if (minimapView.scrollHeight === 0) {
510
- movementYPercentage = 0;
511
- } else {
512
- movementYPercentage = movementY / minimapView.scrollHeight;
513
- }
514
- const absoluteScrollProgress = movementYPercentage * (scrollableRootAncestor.scrollHeight - getClientHeight(scrollableRootAncestor));
515
- const scrollable = getScrollable(scrollableRootAncestor);
516
- scrollable.scrollBy(0, Math.round(absoluteScrollProgress));
517
- });
518
- // Clicking the minimap -> center the document (scrollable) to the corresponding position.
519
- minimapView.on('click', (evt, percentage)=>{
520
- const absoluteScrollProgress = percentage * scrollableRootAncestor.scrollHeight;
521
- const scrollable = getScrollable(scrollableRootAncestor);
522
- scrollable.scrollBy(0, Math.round(absoluteScrollProgress));
523
- });
524
- minimapContainerElement.appendChild(minimapView.element);
525
- }
526
- /**
527
- * @private
528
- */ _syncMinimapToEditingRootScrollPosition() {
529
- const editingRootElement = this._editingRootElement;
530
- const minimapView = this._minimapView;
531
- minimapView.setContentHeight(editingRootElement.offsetHeight);
532
- const editingRootRect = getDomElementRect(editingRootElement);
533
- const scrollableRootAncestorRect = getDomElementRect(this._scrollableRootAncestor);
534
- let scrollProgress;
535
- // It's possible that at some point elements do not intersect, e.g. when entering the fullscreen mode.
536
- // Prevent the minimap from being updated in such case.
537
- if (!scrollableRootAncestorRect.getIntersection(editingRootRect)) {
538
- return;
539
- }
540
- // @if CK_DEBUG_MINIMAP // RectDrawer.clear();
541
- // @if CK_DEBUG_MINIMAP // RectDrawer.draw( scrollableRootAncestorRect, { outlineColor: 'red' }, 'scrollableRootAncestor' );
542
- // @if CK_DEBUG_MINIMAP // RectDrawer.draw( editingRootRect, { outlineColor: 'green' }, 'editingRoot' );
543
- // The root is completely visible in the scrollable ancestor.
544
- if (scrollableRootAncestorRect.contains(editingRootRect)) {
545
- scrollProgress = 0;
546
- } else {
547
- if (editingRootRect.top > scrollableRootAncestorRect.top) {
548
- scrollProgress = 0;
549
- } else {
550
- scrollProgress = (editingRootRect.top - scrollableRootAncestorRect.top) / (scrollableRootAncestorRect.height - editingRootRect.height);
551
- scrollProgress = Math.max(0, Math.min(scrollProgress, 1));
552
- }
553
- }
554
- // The intersection helps to change the tracker height when there is a lot of padding around the root.
555
- // Note: It is **essential** that the height is set first because the progress depends on the correct tracker height.
556
- minimapView.setPositionTrackerHeight(scrollableRootAncestorRect.getIntersection(editingRootRect).height);
557
- minimapView.setScrollProgress(scrollProgress);
558
- }
559
- }
431
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
432
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
433
+ */
434
+ /**
435
+ * @module minimap/minimap
436
+ */
437
+ /**
438
+ * The content minimap feature.
439
+ */
440
+ var Minimap = class extends Plugin {
441
+ /**
442
+ * @inheritDoc
443
+ */
444
+ static get pluginName() {
445
+ return "Minimap";
446
+ }
447
+ /**
448
+ * @inheritDoc
449
+ */
450
+ static get isOfficialPlugin() {
451
+ return true;
452
+ }
453
+ /**
454
+ * The reference to the view of the minimap.
455
+ */
456
+ _minimapView;
457
+ /**
458
+ * The DOM element closest to the editable element of the editor as returned
459
+ * by {@link module:ui/editorui/editorui~EditorUI#getEditableElement}.
460
+ */
461
+ _scrollableRootAncestor;
462
+ /**
463
+ * The DOM element closest to the editable element of the editor as returned
464
+ * by {@link module:ui/editorui/editorui~EditorUI#getEditableElement}.
465
+ */
466
+ _editingRootElement;
467
+ /**
468
+ * @inheritDoc
469
+ */
470
+ init() {
471
+ const editor = this.editor;
472
+ this._minimapView = null;
473
+ this._scrollableRootAncestor = null;
474
+ this.listenTo(editor.ui, "ready", this._onUiReady.bind(this));
475
+ }
476
+ /**
477
+ * @inheritDoc
478
+ */
479
+ destroy() {
480
+ super.destroy();
481
+ this._minimapView.destroy();
482
+ this._minimapView.element.remove();
483
+ }
484
+ /**
485
+ * Initializes the minimap view element and starts the layout synchronization
486
+ * on the editing view `render` event.
487
+ */
488
+ _onUiReady() {
489
+ const editor = this.editor;
490
+ const editingRootElement = this._editingRootElement = editor.ui.getEditableElement();
491
+ this._scrollableRootAncestor = findClosestScrollableAncestor(editingRootElement);
492
+ if (!editingRootElement.ownerDocument.body.contains(editingRootElement)) {
493
+ editor.ui.once("update", this._onUiReady.bind(this));
494
+ return;
495
+ }
496
+ this._initializeMinimapView();
497
+ this.listenTo(editor.editing.view, "render", () => {
498
+ if (editor.state !== "ready") return;
499
+ this._syncMinimapToEditingRootScrollPosition();
500
+ });
501
+ this._syncMinimapToEditingRootScrollPosition();
502
+ }
503
+ /**
504
+ * Initializes the minimap view and attaches listeners that make it responsive to the environment (document)
505
+ * but also allow the minimap to control the document (scroll position).
506
+ */
507
+ _initializeMinimapView() {
508
+ const editor = this.editor;
509
+ const locale = editor.locale;
510
+ const useSimplePreview = editor.config.get("minimap.useSimplePreview");
511
+ const minimapContainerElement = editor.config.get("minimap.container");
512
+ const scrollableRootAncestor = this._scrollableRootAncestor;
513
+ const editingRootElementWidth = getDomElementRect(this._editingRootElement).width;
514
+ const minimapScaleRatio = getDomElementRect(minimapContainerElement).width / editingRootElementWidth;
515
+ const minimapView = this._minimapView = new MinimapView({
516
+ locale,
517
+ scaleRatio: minimapScaleRatio,
518
+ pageStyles: getPageStyles(),
519
+ extraClasses: editor.config.get("minimap.extraClasses"),
520
+ useSimplePreview,
521
+ domRootClone: cloneEditingViewDomRoot(editor)
522
+ });
523
+ minimapView.render();
524
+ minimapView.listenTo(global.document, "scroll", (evt, data) => {
525
+ if (scrollableRootAncestor === global.document.body) {
526
+ if (data.target !== global.document) return;
527
+ } else if (data.target !== scrollableRootAncestor) return;
528
+ this._syncMinimapToEditingRootScrollPosition();
529
+ }, {
530
+ useCapture: true,
531
+ usePassive: true
532
+ });
533
+ minimapView.listenTo(global.window, "resize", () => {
534
+ this._syncMinimapToEditingRootScrollPosition();
535
+ });
536
+ minimapView.on("drag", (evt, movementY) => {
537
+ let movementYPercentage;
538
+ if (minimapView.scrollHeight === 0) movementYPercentage = 0;
539
+ else movementYPercentage = movementY / minimapView.scrollHeight;
540
+ const absoluteScrollProgress = movementYPercentage * (scrollableRootAncestor.scrollHeight - getClientHeight(scrollableRootAncestor));
541
+ getScrollable(scrollableRootAncestor).scrollBy(0, Math.round(absoluteScrollProgress));
542
+ });
543
+ minimapView.on("click", (evt, percentage) => {
544
+ const absoluteScrollProgress = percentage * scrollableRootAncestor.scrollHeight;
545
+ getScrollable(scrollableRootAncestor).scrollBy(0, Math.round(absoluteScrollProgress));
546
+ });
547
+ minimapContainerElement.appendChild(minimapView.element);
548
+ }
549
+ /**
550
+ * @private
551
+ */
552
+ _syncMinimapToEditingRootScrollPosition() {
553
+ const editingRootElement = this._editingRootElement;
554
+ const minimapView = this._minimapView;
555
+ minimapView.setContentHeight(editingRootElement.offsetHeight);
556
+ const editingRootRect = getDomElementRect(editingRootElement);
557
+ const scrollableRootAncestorRect = getDomElementRect(this._scrollableRootAncestor);
558
+ let scrollProgress;
559
+ if (!scrollableRootAncestorRect.getIntersection(editingRootRect)) return;
560
+ if (scrollableRootAncestorRect.contains(editingRootRect)) scrollProgress = 0;
561
+ else if (editingRootRect.top > scrollableRootAncestorRect.top) scrollProgress = 0;
562
+ else {
563
+ scrollProgress = (editingRootRect.top - scrollableRootAncestorRect.top) / (scrollableRootAncestorRect.height - editingRootRect.height);
564
+ scrollProgress = Math.max(0, Math.min(scrollProgress, 1));
565
+ }
566
+ minimapView.setPositionTrackerHeight(scrollableRootAncestorRect.getIntersection(editingRootRect).height);
567
+ minimapView.setScrollProgress(scrollProgress);
568
+ }
569
+ };
570
+
571
+ /**
572
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
573
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
574
+ */
560
575
 
561
576
  export { Minimap, MinimapIframeView as _MinimapIframeView, MinimapPositionTrackerView as _MinimapPositionTrackerView, MinimapView as _MinimapView, cloneEditingViewDomRoot as _cloneMinimapEditingViewDomRoot, getClientHeight as _getMinimapClientHeight, getDomElementRect as _getMinimapDomElementRect, getPageStyles as _getMinimapPageStyles, getScrollable as _getMinimapScrollable };
562
- //# sourceMappingURL=index.js.map
577
+ //# sourceMappingURL=index.js.map