@ckeditor/ckeditor5-widget 46.1.1 → 47.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-widget",
3
- "version": "46.1.1",
3
+ "version": "47.0.0-alpha.1",
4
4
  "description": "Widget API for CKEditor 5.",
5
5
  "keywords": [
6
6
  "ckeditor",
@@ -12,13 +12,13 @@
12
12
  "type": "module",
13
13
  "main": "src/index.js",
14
14
  "dependencies": {
15
- "@ckeditor/ckeditor5-core": "46.1.1",
16
- "@ckeditor/ckeditor5-engine": "46.1.1",
17
- "@ckeditor/ckeditor5-enter": "46.1.1",
18
- "@ckeditor/ckeditor5-icons": "46.1.1",
19
- "@ckeditor/ckeditor5-ui": "46.1.1",
20
- "@ckeditor/ckeditor5-utils": "46.1.1",
21
- "@ckeditor/ckeditor5-typing": "46.1.1",
15
+ "@ckeditor/ckeditor5-core": "47.0.0-alpha.1",
16
+ "@ckeditor/ckeditor5-engine": "47.0.0-alpha.1",
17
+ "@ckeditor/ckeditor5-enter": "47.0.0-alpha.1",
18
+ "@ckeditor/ckeditor5-icons": "47.0.0-alpha.1",
19
+ "@ckeditor/ckeditor5-ui": "47.0.0-alpha.1",
20
+ "@ckeditor/ckeditor5-utils": "47.0.0-alpha.1",
21
+ "@ckeditor/ckeditor5-typing": "47.0.0-alpha.1",
22
22
  "es-toolkit": "1.39.5"
23
23
  },
24
24
  "author": "CKSource (http://cksource.com/)",
@@ -83,28 +83,26 @@ function findTextRangeFromSelection(editing, selection, isForward) {
83
83
  const endPosition = getNearestNonInlineLimit(model, startPosition, 'forward');
84
84
  // There is no limit element, browser should handle this.
85
85
  if (!endPosition) {
86
- return null;
86
+ return;
87
87
  }
88
88
  const range = model.createRange(startPosition, endPosition);
89
89
  const lastRangePosition = getNearestTextPosition(model.schema, range, 'backward');
90
90
  if (lastRangePosition) {
91
91
  return model.createRange(startPosition, lastRangePosition);
92
92
  }
93
- return null;
94
93
  }
95
94
  else {
96
95
  const endPosition = selection.isCollapsed ? selection.focus : selection.getFirstPosition();
97
96
  const startPosition = getNearestNonInlineLimit(model, endPosition, 'backward');
98
97
  // There is no limit element, browser should handle this.
99
98
  if (!startPosition) {
100
- return null;
99
+ return;
101
100
  }
102
101
  const range = model.createRange(startPosition, endPosition);
103
102
  const firstRangePosition = getNearestTextPosition(model.schema, range, 'forward');
104
103
  if (firstRangePosition) {
105
104
  return model.createRange(firstRangePosition, endPosition);
106
105
  }
107
- return null;
108
106
  }
109
107
  }
110
108
  /**
@@ -147,7 +145,6 @@ function getNearestTextPosition(schema, range, direction) {
147
145
  return nextPosition;
148
146
  }
149
147
  }
150
- return null;
151
148
  }
152
149
  /**
153
150
  * Checks if the DOM range corresponding to the provided model range renders as a single line by analyzing DOMRects
package/src/widget.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * @module widget/widget
7
7
  */
8
8
  import { Plugin } from '@ckeditor/ckeditor5-core';
9
- import { type ModelElement, type ModelNode } from '@ckeditor/ckeditor5-engine';
9
+ import { type ModelElement, type ModelNode, type ViewPosition, type ModelRange } from '@ckeditor/ckeditor5-engine';
10
10
  import { Delete } from '@ckeditor/ckeditor5-typing';
11
11
  import { WidgetTypeAround } from './widgettypearound/widgettypearound.js';
12
12
  import '../theme/widget.css';
@@ -101,9 +101,16 @@ export declare class Widget extends Plugin {
101
101
  */
102
102
  private _clearPreviouslySelectedWidgets;
103
103
  /**
104
- * Moves the document selection into the first nested editable.
104
+ * Moves the document selection into the next editable or block widget.
105
105
  */
106
- private _selectFirstNestedEditable;
106
+ private _selectNextEditable;
107
+ /**
108
+ * Looks for next focus point in the document starting from the given view position and direction.
109
+ * The focus point is either a block widget or an editable.
110
+ *
111
+ * @internal
112
+ */
113
+ _findNextFocusRange(startPosition: ViewPosition, direction: 'backward' | 'forward'): ModelRange | null;
107
114
  /**
108
115
  * Updates the document selection so that it selects first ancestor widget.
109
116
  */
package/src/widget.js CHANGED
@@ -8,7 +8,7 @@
8
8
  import { Plugin } from '@ckeditor/ckeditor5-core';
9
9
  import { PointerObserver, MouseObserver, ModelTreeWalker } from '@ckeditor/ckeditor5-engine';
10
10
  import { Delete } from '@ckeditor/ckeditor5-typing';
11
- import { env, keyCodes, getLocalizedArrowKeyCodeDirection, getRangeFromMouseEvent } from '@ckeditor/ckeditor5-utils';
11
+ import { env, keyCodes, getLocalizedArrowKeyCodeDirection, getRangeFromMouseEvent, compareArrays } from '@ckeditor/ckeditor5-utils';
12
12
  import { WidgetTypeAround } from './widgettypearound/widgettypearound.js';
13
13
  import { verticalWidgetNavigationHandler } from './verticalnavigation.js';
14
14
  import { getLabel, isWidget, WIDGET_SELECTED_CLASS_NAME } from './utils.js';
@@ -151,31 +151,17 @@ export class Widget extends Plugin {
151
151
  evt.stop();
152
152
  }
153
153
  }, { context: '$root' });
154
- // Handle Tab key while a widget is selected.
154
+ // Handle Tab/Shift+Tab key.
155
155
  this.listenTo(viewDocument, 'tab', (evt, data) => {
156
- // This event could be triggered from inside the widget, but we are interested
157
- // only when the widget is selected itself.
158
- if (evt.eventPhase != 'atTarget') {
159
- return;
160
- }
161
- if (data.shiftKey) {
162
- return;
163
- }
164
- if (this._selectFirstNestedEditable()) {
165
- data.preventDefault();
166
- evt.stop();
167
- }
168
- }, { context: isWidget, priority: 'low' });
169
- // Handle Shift+Tab key while caret inside a widget editable.
170
- this.listenTo(viewDocument, 'tab', (evt, data) => {
171
- if (!data.shiftKey) {
172
- return;
173
- }
174
- if (this._selectAncestorWidget()) {
156
+ if (this._selectNextEditable(data.shiftKey ? 'backward' : 'forward')) {
157
+ view.scrollToTheSelection();
175
158
  data.preventDefault();
176
159
  evt.stop();
177
160
  }
178
- }, { priority: 'low' });
161
+ }, {
162
+ context: node => isWidget(node) || node.is('editableElement'),
163
+ priority: 'low'
164
+ });
179
165
  // Handle Esc key while inside a nested editable.
180
166
  this.listenTo(viewDocument, 'keydown', (evt, data) => {
181
167
  if (data.keystroke != keyCodes.esc) {
@@ -462,28 +448,95 @@ export class Widget extends Plugin {
462
448
  this._previouslySelected.clear();
463
449
  }
464
450
  /**
465
- * Moves the document selection into the first nested editable.
451
+ * Moves the document selection into the next editable or block widget.
466
452
  */
467
- _selectFirstNestedEditable() {
468
- const editor = this.editor;
469
- const view = this.editor.editing.view;
470
- const viewDocument = view.document;
471
- for (const item of viewDocument.selection.getFirstRange().getItems()) {
472
- if (item.is('editableElement')) {
473
- const modelElement = editor.editing.mapper.toModelElement(item);
474
- /* istanbul ignore next -- @preserve */
475
- if (!modelElement) {
453
+ _selectNextEditable(direction) {
454
+ const editing = this.editor.editing;
455
+ const view = editing.view;
456
+ const model = this.editor.model;
457
+ const viewSelection = view.document.selection;
458
+ const modelSelection = model.document.selection;
459
+ // Find start position.
460
+ let startPosition;
461
+ // Multiple table cells are selected - use focus cell.
462
+ if (modelSelection.rangeCount > 1) {
463
+ const selectionRange = modelSelection.isBackward ?
464
+ modelSelection.getFirstRange() :
465
+ modelSelection.getLastRange();
466
+ startPosition = editing.mapper.toViewPosition(direction == 'forward' ?
467
+ selectionRange.end :
468
+ selectionRange.start);
469
+ }
470
+ else {
471
+ startPosition = direction == 'forward' ?
472
+ viewSelection.getFirstPosition() :
473
+ viewSelection.getLastPosition();
474
+ }
475
+ const modelRange = this._findNextFocusRange(startPosition, direction);
476
+ if (modelRange) {
477
+ model.change(writer => {
478
+ writer.setSelection(modelRange);
479
+ });
480
+ return true;
481
+ }
482
+ return false;
483
+ }
484
+ /**
485
+ * Looks for next focus point in the document starting from the given view position and direction.
486
+ * The focus point is either a block widget or an editable.
487
+ *
488
+ * @internal
489
+ */
490
+ _findNextFocusRange(startPosition, direction) {
491
+ const editing = this.editor.editing;
492
+ const view = editing.view;
493
+ const model = this.editor.model;
494
+ const viewSelection = view.document.selection;
495
+ const editableElement = viewSelection.editableElement;
496
+ const editablePath = editableElement.getPath();
497
+ let selectedElement = viewSelection.getSelectedElement();
498
+ if (selectedElement && !isWidget(selectedElement)) {
499
+ selectedElement = null;
500
+ }
501
+ // Look for the next editable.
502
+ const viewRange = direction == 'forward' ?
503
+ view.createRange(startPosition, view.createPositionAt(startPosition.root, 'end')) :
504
+ view.createRange(view.createPositionAt(startPosition.root, 0), startPosition);
505
+ for (const { nextPosition } of viewRange.getWalker({ direction })) {
506
+ const item = nextPosition.parent;
507
+ // Ignore currently selected editable or widget.
508
+ if (item == editableElement || item == selectedElement) {
509
+ continue;
510
+ }
511
+ // Some widget along the way.
512
+ if (isWidget(item)) {
513
+ const modelElement = editing.mapper.toModelElement(item);
514
+ // Do not select inline widgets.
515
+ if (!model.schema.isBlock(modelElement)) {
476
516
  continue;
477
517
  }
478
- const position = editor.model.createPositionAt(modelElement, 0);
479
- const newRange = editor.model.schema.getNearestSelectionRange(position, 'forward');
480
- editor.model.change(writer => {
481
- writer.setSelection(newRange);
482
- });
483
- return true;
518
+ // Do not select widget itself when going out of widget or iterating over sibling elements in a widget.
519
+ if (compareArrays(editablePath, item.getPath()) != 'extension') {
520
+ return model.createRangeOn(modelElement);
521
+ }
522
+ }
523
+ // Encountered an editable element.
524
+ else if (item.is('editableElement')) {
525
+ const modelPosition = editing.mapper.toModelPosition(nextPosition);
526
+ let newRange = model.schema.getNearestSelectionRange(modelPosition, direction);
527
+ // There is nothing to select so just jump to the next one.
528
+ if (!newRange) {
529
+ continue;
530
+ }
531
+ // Select the content of editable element when iterating over sibling editable elements
532
+ // or going deeper into nested widgets.
533
+ if (compareArrays(editablePath, item.getPath()) != 'extension') {
534
+ newRange = model.createRangeIn(modelPosition.parent);
535
+ }
536
+ return newRange;
484
537
  }
485
538
  }
486
- return false;
539
+ return null;
487
540
  }
488
541
  /**
489
542
  * Updates the document selection so that it selects first ancestor widget.