@ckeditor/ckeditor5-widget 47.1.0 → 47.2.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": "47.1.0",
3
+ "version": "47.2.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": "47.1.0",
16
- "@ckeditor/ckeditor5-engine": "47.1.0",
17
- "@ckeditor/ckeditor5-enter": "47.1.0",
18
- "@ckeditor/ckeditor5-icons": "47.1.0",
19
- "@ckeditor/ckeditor5-ui": "47.1.0",
20
- "@ckeditor/ckeditor5-utils": "47.1.0",
21
- "@ckeditor/ckeditor5-typing": "47.1.0",
15
+ "@ckeditor/ckeditor5-core": "47.2.0-alpha.1",
16
+ "@ckeditor/ckeditor5-engine": "47.2.0-alpha.1",
17
+ "@ckeditor/ckeditor5-enter": "47.2.0-alpha.1",
18
+ "@ckeditor/ckeditor5-icons": "47.2.0-alpha.1",
19
+ "@ckeditor/ckeditor5-ui": "47.2.0-alpha.1",
20
+ "@ckeditor/ckeditor5-utils": "47.2.0-alpha.1",
21
+ "@ckeditor/ckeditor5-typing": "47.2.0-alpha.1",
22
22
  "es-toolkit": "1.39.5"
23
23
  },
24
24
  "author": "CKSource (http://cksource.com/)",
@@ -22,11 +22,6 @@ export function verticalWidgetNavigationHandler(editing) {
22
22
  return;
23
23
  }
24
24
  const isForward = arrowDownPressed;
25
- // Navigation is in the opposite direction than the selection direction so this is shrinking of the selection.
26
- // Selection for sure will not approach any object.
27
- if (expandSelection && selectionWillShrink(selection, isForward)) {
28
- return;
29
- }
30
25
  // Find a range between selection and closest limit element.
31
26
  const range = findTextRangeFromSelection(editing, selection, isForward);
32
27
  // There is no selection position inside the limit element.
@@ -79,7 +74,7 @@ export function verticalWidgetNavigationHandler(editing) {
79
74
  function findTextRangeFromSelection(editing, selection, isForward) {
80
75
  const model = editing.model;
81
76
  if (isForward) {
82
- const startPosition = selection.isCollapsed ? selection.focus : selection.getLastPosition();
77
+ const startPosition = selection.focus;
83
78
  const endPosition = getNearestNonInlineLimit(model, startPosition, 'forward');
84
79
  // There is no limit element, browser should handle this.
85
80
  if (!endPosition) {
@@ -92,7 +87,7 @@ function findTextRangeFromSelection(editing, selection, isForward) {
92
87
  }
93
88
  }
94
89
  else {
95
- const endPosition = selection.isCollapsed ? selection.focus : selection.getFirstPosition();
90
+ const endPosition = selection.focus;
96
91
  const startPosition = getNearestNonInlineLimit(model, endPosition, 'backward');
97
92
  // There is no limit element, browser should handle this.
98
93
  if (!startPosition) {
@@ -188,6 +183,3 @@ function isSingleLineRange(editing, modelRange, isForward) {
188
183
  }
189
184
  return true;
190
185
  }
191
- function selectionWillShrink(selection, isForward) {
192
- return !selection.isCollapsed && selection.isBackward == isForward;
193
- }
package/src/widget.js CHANGED
@@ -10,6 +10,7 @@ import { PointerObserver, MouseObserver, ModelTreeWalker } from '@ckeditor/ckedi
10
10
  import { Delete } from '@ckeditor/ckeditor5-typing';
11
11
  import { env, keyCodes, getLocalizedArrowKeyCodeDirection, getRangeFromMouseEvent, compareArrays } from '@ckeditor/ckeditor5-utils';
12
12
  import { WidgetTypeAround } from './widgettypearound/widgettypearound.js';
13
+ import { getTypeAroundFakeCaretPosition } from './widgettypearound/utils.js';
13
14
  import { verticalWidgetNavigationHandler } from './verticalnavigation.js';
14
15
  import { getLabel, isWidget, WIDGET_SELECTED_CLASS_NAME } from './utils.js';
15
16
  import '../theme/widget.css';
@@ -171,7 +172,10 @@ export class Widget extends Plugin {
171
172
  data.preventDefault();
172
173
  evt.stop();
173
174
  }
174
- }, { priority: 'low' });
175
+ }, {
176
+ context: node => node.is('editableElement'),
177
+ priority: 'low'
178
+ });
175
179
  // Add the information about the keystrokes to the accessibility database.
176
180
  editor.accessibility.addKeystrokeInfoGroup({
177
181
  id: 'widget',
@@ -304,53 +308,69 @@ export class Widget extends Plugin {
304
308
  const model = this.editor.model;
305
309
  const schema = model.schema;
306
310
  const modelSelection = model.document.selection;
307
- const objectElement = modelSelection.getSelectedElement();
311
+ const selectedElement = modelSelection.getSelectedElement();
308
312
  const direction = getLocalizedArrowKeyCodeDirection(keyCode, this.editor.locale.contentLanguageDirection);
309
313
  const isForward = direction == 'down' || direction == 'right';
310
314
  const isVerticalNavigation = direction == 'up' || direction == 'down';
311
- // If object element is selected.
312
- if (objectElement && schema.isObject(objectElement)) {
313
- const position = isForward ? modelSelection.getLastPosition() : modelSelection.getFirstPosition();
314
- const newRange = schema.getNearestSelectionRange(position, isForward ? 'forward' : 'backward');
315
- if (newRange) {
315
+ // Collapsing a non-collapsed selection.
316
+ if (!domEventData.shiftKey && !modelSelection.isCollapsed) {
317
+ // If object element is selected or object is at the edge of selection.
318
+ if (hasObjectAtEdge(modelSelection, schema)) {
319
+ const position = isForward ? modelSelection.getLastPosition() : modelSelection.getFirstPosition();
320
+ const newRange = schema.getNearestSelectionRange(position, isForward ? 'forward' : 'backward');
321
+ if (newRange) {
322
+ model.change(writer => {
323
+ writer.setSelection(newRange);
324
+ });
325
+ domEventData.preventDefault();
326
+ eventInfo.stop();
327
+ }
328
+ }
329
+ // Else is handled by the browser.
330
+ return;
331
+ }
332
+ // Adjust selection for fake caret and for selection direction when single object is selected.
333
+ const originalSelection = getModelSelectionAdjusted(model, isForward);
334
+ // Clone current selection to use it as a probe. We must leave default selection as it is so it can return
335
+ // to its current state after undo.
336
+ const probe = model.createSelection(originalSelection);
337
+ model.modifySelection(probe, { direction: isForward ? 'forward' : 'backward' });
338
+ // The selection didn't change so there is nothing there.
339
+ if (probe.isEqual(originalSelection)) {
340
+ return;
341
+ }
342
+ // Move probe one step further to make it visually recognizable.
343
+ if (probe.focus.isTouching(originalSelection.focus)) {
344
+ model.modifySelection(probe, { direction: isForward ? 'forward' : 'backward' });
345
+ }
346
+ const lastSelectedNode = isForward ? originalSelection.focus.nodeBefore : originalSelection.focus.nodeAfter;
347
+ const nodeBeforeProbe = probe.focus.nodeBefore;
348
+ const nodeAfterProbe = probe.focus.nodeAfter;
349
+ const lastProbeNode = isForward ? nodeBeforeProbe : nodeAfterProbe;
350
+ if (domEventData.shiftKey) {
351
+ // Expand selection from a selected object or include object in selection.
352
+ if (selectedElement && schema.isObject(selectedElement) ||
353
+ lastProbeNode && schema.isObject(lastProbeNode) ||
354
+ lastSelectedNode && schema.isObject(lastSelectedNode)) {
316
355
  model.change(writer => {
317
- writer.setSelection(newRange);
356
+ writer.setSelection(probe);
318
357
  });
319
358
  domEventData.preventDefault();
320
359
  eventInfo.stop();
321
360
  }
322
- return;
323
361
  }
324
- // Handle collapsing of the selection when there is any widget on the edge of selection.
325
- // This is needed because browsers have problems with collapsing such selection.
326
- if (!modelSelection.isCollapsed && !domEventData.shiftKey) {
327
- const firstPosition = modelSelection.getFirstPosition();
328
- const lastPosition = modelSelection.getLastPosition();
329
- const firstSelectedNode = firstPosition.nodeAfter;
330
- const lastSelectedNode = lastPosition.nodeBefore;
331
- if (firstSelectedNode && schema.isObject(firstSelectedNode) || lastSelectedNode && schema.isObject(lastSelectedNode)) {
362
+ else {
363
+ // Select an object when moving caret over it.
364
+ if (lastProbeNode && schema.isObject(lastProbeNode)) {
365
+ if (schema.isInline(lastProbeNode) && isVerticalNavigation) {
366
+ return;
367
+ }
332
368
  model.change(writer => {
333
- writer.setSelection(isForward ? lastPosition : firstPosition);
369
+ writer.setSelection(lastProbeNode, 'on');
334
370
  });
335
371
  domEventData.preventDefault();
336
372
  eventInfo.stop();
337
373
  }
338
- return;
339
- }
340
- // Return if not collapsed.
341
- if (!modelSelection.isCollapsed) {
342
- return;
343
- }
344
- // If selection is next to object element.
345
- const objectElementNextToSelection = this._getObjectElementNextToSelection(isForward);
346
- if (objectElementNextToSelection && schema.isObject(objectElementNextToSelection)) {
347
- // Do not select an inline widget while handling up/down arrow.
348
- if (schema.isInline(objectElementNextToSelection) && isVerticalNavigation) {
349
- return;
350
- }
351
- this._setSelectionOverElement(objectElementNextToSelection);
352
- domEventData.preventDefault();
353
- eventInfo.stop();
354
374
  }
355
375
  }
356
376
  /**
@@ -433,7 +453,7 @@ export class Widget extends Plugin {
433
453
  return null;
434
454
  }
435
455
  const objectElement = forward ? probe.focus.nodeBefore : probe.focus.nodeAfter;
436
- if (!!objectElement && schema.isObject(objectElement)) {
456
+ if (objectElement && schema.isObject(objectElement)) {
437
457
  return objectElement;
438
458
  }
439
459
  return null;
@@ -504,12 +524,8 @@ export class Widget extends Plugin {
504
524
  view.createRange(view.createPositionAt(startPosition.root, 0), startPosition);
505
525
  for (const { nextPosition } of viewRange.getWalker({ direction })) {
506
526
  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)) {
527
+ // Some widget along the way except the currently selected one.
528
+ if (isWidget(item) && item != selectedElement) {
513
529
  const modelElement = editing.mapper.toModelElement(item);
514
530
  // Do not select inline widgets.
515
531
  if (!model.schema.isBlock(modelElement)) {
@@ -522,16 +538,26 @@ export class Widget extends Plugin {
522
538
  }
523
539
  // Encountered an editable element.
524
540
  else if (item.is('editableElement')) {
541
+ // Ignore the current editable for text selection,
542
+ // but use it when widget was selected to be able to jump after the widget.
543
+ if (item == editableElement && !selectedElement) {
544
+ continue;
545
+ }
525
546
  const modelPosition = editing.mapper.toModelPosition(nextPosition);
526
- let newRange = model.schema.getNearestSelectionRange(modelPosition, direction);
547
+ const newRange = model.schema.getNearestSelectionRange(modelPosition, direction);
527
548
  // There is nothing to select so just jump to the next one.
528
549
  if (!newRange) {
529
550
  continue;
530
551
  }
552
+ // In the same editable while widget was selected - do not select the editable content.
553
+ if (item == editableElement && selectedElement) {
554
+ return newRange;
555
+ }
531
556
  // Select the content of editable element when iterating over sibling editable elements
532
557
  // or going deeper into nested widgets.
533
558
  if (compareArrays(editablePath, item.getPath()) != 'extension') {
534
- newRange = model.createRangeIn(modelPosition.parent);
559
+ // Find a limit element closest to the new selection range.
560
+ return model.createRangeIn(model.schema.getLimitElement(newRange));
535
561
  }
536
562
  return newRange;
537
563
  }
@@ -564,6 +590,36 @@ export class Widget extends Plugin {
564
590
  return true;
565
591
  }
566
592
  }
593
+ /**
594
+ * Returns true if there is an object on an edge of the given selection.
595
+ */
596
+ function hasObjectAtEdge(modelSelection, schema) {
597
+ const firstPosition = modelSelection.getFirstPosition();
598
+ const lastPosition = modelSelection.getLastPosition();
599
+ const firstSelectedNode = firstPosition.nodeAfter;
600
+ const lastSelectedNode = lastPosition.nodeBefore;
601
+ return (!!firstSelectedNode && schema.isObject(firstSelectedNode) ||
602
+ !!lastSelectedNode && schema.isObject(lastSelectedNode));
603
+ }
604
+ /**
605
+ * Returns new instance of the model selection adjusted for fake caret and selection direction on widgets.
606
+ */
607
+ function getModelSelectionAdjusted(model, isForward) {
608
+ const modelSelection = model.document.selection;
609
+ const selectedElement = modelSelection.getSelectedElement();
610
+ // Adjust selection for fake caret.
611
+ const typeAroundFakeCaretPosition = getTypeAroundFakeCaretPosition(modelSelection);
612
+ if (selectedElement && typeAroundFakeCaretPosition == 'before') {
613
+ return model.createSelection(selectedElement, 'before');
614
+ }
615
+ else if (selectedElement && typeAroundFakeCaretPosition == 'after') {
616
+ return model.createSelection(selectedElement, 'after');
617
+ }
618
+ // Make a copy of selection with adjusted direction for object selected.
619
+ return model.createSelection(modelSelection.getRanges(), {
620
+ backward: !!selectedElement && model.schema.isObject(selectedElement) ? !isForward : modelSelection.isBackward
621
+ });
622
+ }
567
623
  /**
568
624
  * Finds the closest ancestor element that is either an editable element or a widget.
569
625
  *
@@ -315,6 +315,10 @@ export class WidgetTypeAround extends Plugin {
315
315
  const modelSelection = model.document.selection;
316
316
  const schema = model.schema;
317
317
  const editingView = editor.editing.view;
318
+ // Selection expanding/shrinking is handled without the fake caret by the widget plugin.
319
+ if (domEventData.shiftKey) {
320
+ return;
321
+ }
318
322
  const keyCode = domEventData.keyCode;
319
323
  const isForward = isForwardArrowKeyCode(keyCode, editor.locale.contentLanguageDirection);
320
324
  const selectedViewElement = editingView.document.selection.getSelectedElement();