widget.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import Plugin from '../../core/plugin.js';
  6. import WidgetEngine from './widgetengine.js';
  7. import MouseObserver from '../../engine/view/observer/mouseobserver.js';
  8. import ModelRange from '../../engine/model/range.js';
  9. import ModelSelection from '../../engine/model/selection.js';
  10. import { isWidget } from './utils.js';
  11. import { keyCodes } from '../../utils/keyboard.js';
  12. /**
  13. * The widget plugin.
  14. * Adds default {@link engine.view.Document#mousedown mousedown} handling on widget elements.
  15. *
  16. * @memberOf image.widget
  17. * @extends core.Plugin.
  18. */
  19. export default class Widget extends Plugin {
  20. /**
  21. * @inheritDoc
  22. */
  23. static get requires() {
  24. return [ WidgetEngine ];
  25. }
  26. /**
  27. * @inheritDoc
  28. */
  29. init() {
  30. const viewDocument = this.editor.editing.view;
  31. // If mouse down is pressed on widget - create selection over whole widget.
  32. viewDocument.addObserver( MouseObserver );
  33. this.listenTo( viewDocument, 'mousedown', ( ...args ) => this._onMousedown( ...args ) );
  34. // Handle custom keydown behaviour.
  35. this.listenTo( viewDocument, 'keydown', ( ...args ) => this._onKeydown( ...args ), { priority: 'high' } );
  36. }
  37. /**
  38. * Handles {@link engine.view.Document#mousedown mousedown} events on widget elements.
  39. *
  40. * @private
  41. * @param {utils.EventInfo} eventInfo
  42. * @param {envine.view.observer.DomEventData} domEventData
  43. */
  44. _onMousedown( eventInfo, domEventData ) {
  45. let widgetElement = domEventData.target;
  46. const editor = this.editor;
  47. const viewDocument = editor.editing.view;
  48. // If target is not a widget element - check if one of the ancestors is.
  49. if ( !isWidget( widgetElement ) ) {
  50. widgetElement = widgetElement.findAncestor( element => isWidget( element ) );
  51. if ( !widgetElement ) {
  52. return;
  53. }
  54. }
  55. domEventData.preventDefault();
  56. // Focus editor if is not focused already.
  57. if ( !viewDocument.isFocused ) {
  58. viewDocument.focus();
  59. }
  60. // Create model selection over widget.
  61. const modelElement = editor.editing.mapper.toModelElement( widgetElement );
  62. editor.document.enqueueChanges( ( ) => {
  63. this._setSelectionOverElement( modelElement );
  64. } );
  65. }
  66. /**
  67. * Handles {@link engine.view.Document#keydown keydown} events.
  68. *
  69. * @private
  70. * @param {utils.EventInfo} eventInfo
  71. * @param {envine.view.observer.DomEventData} domEventData
  72. */
  73. _onKeydown( eventInfo, domEventData ) {
  74. const keyCode = domEventData.keyCode;
  75. // Handle only delete and backspace.
  76. if ( keyCode !== keyCodes.delete && keyCode !== keyCodes.backspace ) {
  77. return;
  78. }
  79. const dataController = this.editor.data;
  80. const modelDocument = this.editor.document;
  81. const modelSelection = modelDocument.selection;
  82. // Do nothing on non-collapsed selection.
  83. if ( !modelSelection.isCollapsed ) {
  84. return;
  85. }
  86. // Clone current selection to use it as a probe. We must leave default selection as it is so it can return
  87. // to its current state after undo.
  88. const probe = ModelSelection.createFromSelection( modelSelection );
  89. const isForward = ( keyCode == keyCodes.delete );
  90. dataController.modifySelection( probe, { direction: isForward ? 'forward' : 'backward' } );
  91. const objectElement = isForward ? probe.focus.nodeBefore : probe.focus.nodeAfter;
  92. if ( objectElement && modelDocument.schema.objects.has( objectElement.name ) ) {
  93. domEventData.preventDefault();
  94. eventInfo.stop();
  95. modelDocument.enqueueChanges( () => {
  96. // Remove previous element if empty.
  97. const previousNode = probe.anchor.parent;
  98. if ( previousNode.isEmpty ) {
  99. const batch = modelDocument.batch();
  100. batch.remove( previousNode );
  101. }
  102. this._setSelectionOverElement( objectElement );
  103. } );
  104. }
  105. }
  106. /**
  107. * Sets {@link engine.model.Selection document's selection} over given element.
  108. *
  109. * @private
  110. * @param {engine.model.Element} element
  111. */
  112. _setSelectionOverElement( element ) {
  113. this.editor.document.selection.setRanges( [ ModelRange.createOn( element ) ] );
  114. }
  115. }