utils.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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. 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 `addHighlight` and `removeHighlight` custom properties to handle view highlight on widgets.
  39. *
  40. * @param {module:engine/view/element~Element} element
  41. * @param {Object} [options={}]
  42. * @param {String|Function} [options.label] Element's label provided to {@link ~setLabel} function. It can be passed as
  43. * a plain string or a function returning a string.
  44. * @returns {module:engine/view/element~Element} Returns same element.
  45. */
  46. export function toWidget( element, options = {} ) {
  47. element.setAttribute( 'contenteditable', 'false' );
  48. element.getFillerOffset = getFillerOffset;
  49. element.addClass( WIDGET_CLASS_NAME );
  50. element.setCustomProperty( widgetSymbol, true );
  51. if ( options.label ) {
  52. setLabel( element, options.label );
  53. }
  54. setHighlightHandling(
  55. element,
  56. ( element, descriptor ) => element.addClass( ...normalizeToArray( descriptor.class ) ),
  57. ( element, descriptor ) => element.removeClass( ...normalizeToArray( descriptor.class ) )
  58. );
  59. return element;
  60. // Normalizes CSS class in descriptor that can be provided in form of an array or a string.
  61. function normalizeToArray( classes ) {
  62. return Array.isArray( classes ) ? classes : [ classes ];
  63. }
  64. }
  65. /**
  66. * Sets highlight handling methods. Uses {@link module:widget/highlightstack~HighlightStack} to
  67. * properly determine which highlight descriptor should be used at given time.
  68. *
  69. * @param {module:engine/view/element~Element} element
  70. * @param {Function} add
  71. * @param {Function} remove
  72. */
  73. export function setHighlightHandling( element, add, remove ) {
  74. const stack = new HighlightStack();
  75. stack.on( 'change:top', ( evt, data ) => {
  76. if ( data.oldDescriptor ) {
  77. remove( element, data.oldDescriptor );
  78. }
  79. if ( data.newDescriptor ) {
  80. add( element, data.newDescriptor );
  81. }
  82. } );
  83. element.setCustomProperty( 'addHighlight', ( element, descriptor ) => stack.add( descriptor ) );
  84. element.setCustomProperty( 'removeHighlight', ( element, id ) => stack.remove( id ) );
  85. }
  86. /**
  87. * Sets label for given element.
  88. * It can be passed as a plain string or a function returning a string. Function will be called each time label is retrieved by
  89. * {@link ~getLabel}.
  90. *
  91. * @param {module:engine/view/element~Element} element
  92. * @param {String|Function} labelOrCreator
  93. */
  94. export function setLabel( element, labelOrCreator ) {
  95. element.setCustomProperty( labelSymbol, labelOrCreator );
  96. }
  97. /**
  98. * Returns label for provided element.
  99. *
  100. * @param {module:engine/view/element~Element} element
  101. * @return {String}
  102. */
  103. export function getLabel( element ) {
  104. const labelCreator = element.getCustomProperty( labelSymbol );
  105. if ( !labelCreator ) {
  106. return '';
  107. }
  108. return typeof labelCreator == 'function' ? labelCreator() : labelCreator;
  109. }
  110. /**
  111. * Adds functionality to provided {module:engine/view/editableelement~EditableElement} to act as a widget's editable:
  112. * * adds `ck-editable` CSS class,
  113. * * sets `contenteditable` as `true` when {module:engine/view/editableelement~EditableElement#isReadOnly} is `false`
  114. * otherwise set `false`,
  115. * * adds `ck-editable_focused` CSS class when editable is focused and removes it when it's blurred.
  116. *
  117. * @param {module:engine/view/editableelement~EditableElement} editable
  118. * @returns {module:engine/view/editableelement~EditableElement} Returns same element that was provided in `editable` param.
  119. */
  120. export function toWidgetEditable( editable ) {
  121. editable.addClass( 'ck-editable' );
  122. // Set initial contenteditable value.
  123. editable.setAttribute( 'contenteditable', editable.isReadOnly ? 'false' : 'true' );
  124. // Bind contenteditable property to element#isReadOnly.
  125. editable.on( 'change:isReadOnly', ( evt, property, is ) => {
  126. editable.setAttribute( 'contenteditable', is ? 'false' : 'true' );
  127. } );
  128. editable.on( 'change:isFocused', ( evt, property, is ) => {
  129. if ( is ) {
  130. editable.addClass( 'ck-editable_focused' );
  131. } else {
  132. editable.removeClass( 'ck-editable_focused' );
  133. }
  134. } );
  135. return editable;
  136. }
  137. // Default filler offset function applied to all widget elements.
  138. //
  139. // @returns {null}
  140. function getFillerOffset() {
  141. return null;
  142. }