utils.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module widget/utils
  7. */
  8. import VirtualSelectionStack from './virtualselectionstack';
  9. const widgetSymbol = Symbol( 'isWidget' );
  10. const labelSymbol = Symbol( 'label' );
  11. /**
  12. * CSS class added to each widget element.
  13. *
  14. * @const {String}
  15. */
  16. export const WIDGET_CLASS_NAME = 'ck-widget';
  17. /**
  18. * CSS class added to currently selected widget element.
  19. *
  20. * @const {String}
  21. */
  22. export const WIDGET_SELECTED_CLASS_NAME = 'ck-widget_selected';
  23. /**
  24. * Returns `true` if given {@link module:engine/view/element~Element} is a widget.
  25. *
  26. * @param {module:engine/view/element~Element} element
  27. * @returns {Boolean}
  28. */
  29. export function isWidget( element ) {
  30. return !!element.getCustomProperty( widgetSymbol );
  31. }
  32. /**
  33. * Converts given {@link module:engine/view/element~Element} to widget in following way:
  34. * * sets `contenteditable` attribute to `true`,
  35. * * adds custom `getFillerOffset` method returning `null`,
  36. * * adds `ck-widget` CSS class,
  37. * * adds custom property allowing to recognize widget elements by using {@link ~isWidget},
  38. * * implements `setVirtualSelection` and `removeVirtualSelection` custom properties to handle virtual selection
  39. * on widgets.
  40. *
  41. * @param {module:engine/view/element~Element} element
  42. * @param {Object} [options={}]
  43. * @param {String|Function} [options.label] Element's label provided to {@link ~setLabel} function. It can be passed as
  44. * a plain string or a function returning a string.
  45. * @returns {module:engine/view/element~Element} Returns same element.
  46. */
  47. export function toWidget( element, options = {} ) {
  48. element.setAttribute( 'contenteditable', false );
  49. element.getFillerOffset = getFillerOffset;
  50. element.addClass( WIDGET_CLASS_NAME );
  51. element.setCustomProperty( widgetSymbol, true );
  52. if ( options.label ) {
  53. setLabel( element, options.label );
  54. }
  55. setVirtualSelectionHandling(
  56. element,
  57. ( element, descriptor ) => element.addClass( descriptor.class ),
  58. ( element, descriptor ) => element.removeClass( descriptor.class )
  59. );
  60. return element;
  61. }
  62. /**
  63. * Sets virtual selection handling methods. Uses {@link module:widget/virtualselectionstack~VirtualSelectionStack} to
  64. * properly determine which virtual selection should be used at given time.
  65. *
  66. * @param {module:engine/view/element~Element} element
  67. * @param {Function} add
  68. * @param {Function} remove
  69. */
  70. export function setVirtualSelectionHandling( element, add, remove ) {
  71. const stack = new VirtualSelectionStack();
  72. stack.on( 'change:top', ( evt, data ) => {
  73. if ( data.oldDescriptor ) {
  74. remove( element, data.oldDescriptor );
  75. }
  76. if ( data.newDescriptor ) {
  77. add( element, data.newDescriptor );
  78. }
  79. } );
  80. element.setCustomProperty( 'setVirtualSelection', ( element, descriptor ) => stack.add( descriptor ) );
  81. element.setCustomProperty( 'removeVirtualSelection', ( element, descriptor ) => stack.remove( descriptor ) );
  82. }
  83. /**
  84. * Sets label for given element.
  85. * It can be passed as a plain string or a function returning a string. Function will be called each time label is retrieved by
  86. * {@link ~getLabel}.
  87. *
  88. * @param {module:engine/view/element~Element} element
  89. * @param {String|Function} labelOrCreator
  90. */
  91. export function setLabel( element, labelOrCreator ) {
  92. element.setCustomProperty( labelSymbol, labelOrCreator );
  93. }
  94. /**
  95. * Returns label for provided element.
  96. *
  97. * @param {module:engine/view/element~Element} element
  98. * @return {String}
  99. */
  100. export function getLabel( element ) {
  101. const labelCreator = element.getCustomProperty( labelSymbol );
  102. if ( !labelCreator ) {
  103. return '';
  104. }
  105. return typeof labelCreator == 'function' ? labelCreator() : labelCreator;
  106. }
  107. /**
  108. * Adds functionality to provided {module:engine/view/editableelement~EditableElement} to act as a widget's editable:
  109. * * adds `ck-editable` CSS class,
  110. * * sets `contenteditable` as `true` when {module:engine/view/editableelement~EditableElement#isReadOnly} is `false`
  111. * otherwise set `false`,
  112. * * adds `ck-editable_focused` CSS class when editable is focused and removes it when it's blurred.
  113. *
  114. * @param {module:engine/view/editableelement~EditableElement} editable
  115. * @returns {module:engine/view/editableelement~EditableElement} Returns same element that was provided in `editable` param.
  116. */
  117. export function toWidgetEditable( editable ) {
  118. editable.addClass( 'ck-editable' );
  119. // Set initial contenteditable value.
  120. editable.setAttribute( 'contenteditable', !editable.isReadOnly );
  121. // Bind contenteditable property to element#isReadOnly.
  122. editable.on( 'change:isReadOnly', ( evt, property, is ) => {
  123. editable.setAttribute( 'contenteditable', !is );
  124. } );
  125. editable.on( 'change:isFocused', ( evt, property, is ) => {
  126. if ( is ) {
  127. editable.addClass( 'ck-editable_focused' );
  128. } else {
  129. editable.removeClass( 'ck-editable_focused' );
  130. }
  131. } );
  132. return editable;
  133. }
  134. // Default filler offset function applied to all widget elements.
  135. //
  136. // @returns {null}
  137. function getFillerOffset() {
  138. return null;
  139. }