utils.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module widget/utils
  7. */
  8. import HighlightStack from './highlightstack';
  9. import Position from '@ckeditor/ckeditor5-engine/src/view/position';
  10. import IconView from '@ckeditor/ckeditor5-ui/src/icon/iconview';
  11. import dragHandlerIcon from '../theme/icons/drag-handler.svg';
  12. const widgetSymbol = Symbol( 'isWidget' );
  13. const labelSymbol = Symbol( 'label' );
  14. /**
  15. * CSS class added to each widget element.
  16. *
  17. * @const {String}
  18. */
  19. export const WIDGET_CLASS_NAME = 'ck-widget';
  20. /**
  21. * CSS class added to currently selected widget element.
  22. *
  23. * @const {String}
  24. */
  25. export const WIDGET_SELECTED_CLASS_NAME = 'ck-widget_selected';
  26. /**
  27. * Returns `true` if given {@link module:engine/view/element~Element} is a widget.
  28. *
  29. * @param {module:engine/view/element~Element} element
  30. * @returns {Boolean}
  31. */
  32. export function isWidget( element ) {
  33. return !!element.getCustomProperty( widgetSymbol );
  34. }
  35. /**
  36. * Converts given {@link module:engine/view/element~Element} to widget in following way:
  37. * * sets `contenteditable` attribute to `"true"`,
  38. * * adds custom `getFillerOffset` method returning `null`,
  39. * * adds `ck-widget` CSS class,
  40. * * adds custom property allowing to recognize widget elements by using {@link ~isWidget},
  41. * * implements `addHighlight` and `removeHighlight` custom properties to handle view highlight on widgets.
  42. *
  43. * @param {module:engine/view/element~Element} element
  44. * @param {module:engine/view/writer~Writer} writer
  45. * @param {Object} [options={}]
  46. * @param {String|Function} [options.label] Element's label provided to {@link ~setLabel} function. It can be passed as
  47. * a plain string or a function returning a string.
  48. * @param {Boolean} [options.hasSelectionHandler=false] If `true`, the widget will have a selection handler added.
  49. * @returns {module:engine/view/element~Element} Returns same element.
  50. */
  51. export function toWidget( element, writer, options = {} ) {
  52. writer.setAttribute( 'contenteditable', 'false', element );
  53. writer.addClass( WIDGET_CLASS_NAME, element );
  54. writer.setCustomProperty( widgetSymbol, true, element );
  55. element.getFillerOffset = getFillerOffset;
  56. if ( options.label ) {
  57. setLabel( element, options.label, writer );
  58. }
  59. if ( options.hasSelectionHandler ) {
  60. hasSelectionHandler( element, writer );
  61. }
  62. setHighlightHandling(
  63. element,
  64. writer,
  65. ( element, descriptor, writer ) => writer.addClass( normalizeToArray( descriptor.classes ), element ),
  66. ( element, descriptor, writer ) => writer.removeClass( normalizeToArray( descriptor.classes ), element )
  67. );
  68. return element;
  69. // Normalizes CSS class in descriptor that can be provided in form of an array or a string.
  70. function normalizeToArray( classes ) {
  71. return Array.isArray( classes ) ? classes : [ classes ];
  72. }
  73. }
  74. /**
  75. * Sets highlight handling methods. Uses {@link module:widget/highlightstack~HighlightStack} to
  76. * properly determine which highlight descriptor should be used at given time.
  77. *
  78. * @param {module:engine/view/element~Element} element
  79. * @param {module:engine/view/writer~Writer} writer
  80. * @param {Function} add
  81. * @param {Function} remove
  82. */
  83. export function setHighlightHandling( element, writer, add, remove ) {
  84. const stack = new HighlightStack();
  85. stack.on( 'change:top', ( evt, data ) => {
  86. if ( data.oldDescriptor ) {
  87. remove( element, data.oldDescriptor, data.writer );
  88. }
  89. if ( data.newDescriptor ) {
  90. add( element, data.newDescriptor, data.writer );
  91. }
  92. } );
  93. writer.setCustomProperty( 'addHighlight', ( element, descriptor, writer ) => stack.add( descriptor, writer ), element );
  94. writer.setCustomProperty( 'removeHighlight', ( element, id, writer ) => stack.remove( id, writer ), element );
  95. }
  96. /**
  97. * Sets label for given element.
  98. * It can be passed as a plain string or a function returning a string. Function will be called each time label is retrieved by
  99. * {@link ~getLabel}.
  100. *
  101. * @param {module:engine/view/element~Element} element
  102. * @param {String|Function} labelOrCreator
  103. * @param {module:engine/view/writer~Writer} writer
  104. */
  105. export function setLabel( element, labelOrCreator, writer ) {
  106. writer.setCustomProperty( labelSymbol, labelOrCreator, element );
  107. }
  108. /**
  109. * Returns label for provided element.
  110. *
  111. * @param {module:engine/view/element~Element} element
  112. * @returns {String}
  113. */
  114. export function getLabel( element ) {
  115. const labelCreator = element.getCustomProperty( labelSymbol );
  116. if ( !labelCreator ) {
  117. return '';
  118. }
  119. return typeof labelCreator == 'function' ? labelCreator() : labelCreator;
  120. }
  121. /**
  122. * Adds functionality to provided {module:engine/view/editableelement~EditableElement} to act as a widget's editable:
  123. * * adds `ck-editor__editable` and `ck-editor__nested-editable` CSS classes,
  124. * * sets `contenteditable` as `true` when {module:engine/view/editableelement~EditableElement#isReadOnly} is `false`
  125. * otherwise set `false`,
  126. * * adds `ck-editor__nested-editable_focused` CSS class when editable is focused and removes it when it's blurred.
  127. *
  128. * @param {module:engine/view/editableelement~EditableElement} editable
  129. * @param {module:engine/view/writer~Writer} writer
  130. * @returns {module:engine/view/editableelement~EditableElement} Returns same element that was provided in `editable` param.
  131. */
  132. export function toWidgetEditable( editable, writer ) {
  133. writer.addClass( [ 'ck-editor__editable', 'ck-editor__nested-editable' ], editable );
  134. // Set initial contenteditable value.
  135. writer.setAttribute( 'contenteditable', editable.isReadOnly ? 'false' : 'true', editable );
  136. // Bind contenteditable property to element#isReadOnly.
  137. editable.on( 'change:isReadOnly', ( evt, property, is ) => {
  138. writer.setAttribute( 'contenteditable', is ? 'false' : 'true', editable );
  139. } );
  140. editable.on( 'change:isFocused', ( evt, property, is ) => {
  141. if ( is ) {
  142. writer.addClass( 'ck-editor__nested-editable_focused', editable );
  143. } else {
  144. writer.removeClass( 'ck-editor__nested-editable_focused', editable );
  145. }
  146. } );
  147. return editable;
  148. }
  149. // Default filler offset function applied to all widget elements.
  150. //
  151. // @returns {null}
  152. function getFillerOffset() {
  153. return null;
  154. }
  155. // Adds a drag handler to the editable element.
  156. //
  157. // @param {module:engine/view/editableelement~EditableElement}
  158. // @param {module:engine/view/writer~Writer} writer
  159. function hasSelectionHandler( editable, writer ) {
  160. const selectionHandler = writer.createUIElement( 'div', { class: 'ck ck-widget__selection-handler' }, function( domDocument ) {
  161. const domElement = this.toDomElement( domDocument );
  162. // Use the IconView from the ui library.
  163. const icon = new IconView();
  164. icon.set( 'content', dragHandlerIcon );
  165. // Render the icon view right away to append its #element to the selectionHandler DOM element.
  166. icon.render();
  167. domElement.appendChild( icon.element );
  168. return domElement;
  169. } );
  170. // Append the selection handler into the widget wrapper.
  171. writer.insert( Position.createAt( editable ), selectionHandler );
  172. writer.addClass( [ 'ck-widget_selectable' ], editable );
  173. }