utils.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  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( element, options.addVritualSelection, options.removeVirtualSelection );
  56. return element;
  57. }
  58. export function setVirtualSelectionHandling( element, add, remove ) {
  59. const stack = new VirtualSelectionStack();
  60. stack.on( 'change:top', ( evt, data ) => {
  61. if ( data.oldDescriptor ) {
  62. remove( data.oldDescriptor );
  63. }
  64. if ( data.newDescriptor ) {
  65. add( data.newDescriptor );
  66. }
  67. } );
  68. element.setCustomProperty( 'setVirtualSelection', descriptor => stack.add( descriptor ) );
  69. element.setCustomProperty( 'removeVirtualSelection', descriptor => stack.remove( descriptor ) );
  70. }
  71. /**
  72. * Sets label for given element.
  73. * It can be passed as a plain string or a function returning a string. Function will be called each time label is retrieved by
  74. * {@link ~getLabel}.
  75. *
  76. * @param {module:engine/view/element~Element} element
  77. * @param {String|Function} labelOrCreator
  78. */
  79. export function setLabel( element, labelOrCreator ) {
  80. element.setCustomProperty( labelSymbol, labelOrCreator );
  81. }
  82. /**
  83. * Returns label for provided element.
  84. *
  85. * @param {module:engine/view/element~Element} element
  86. * @return {String}
  87. */
  88. export function getLabel( element ) {
  89. const labelCreator = element.getCustomProperty( labelSymbol );
  90. if ( !labelCreator ) {
  91. return '';
  92. }
  93. return typeof labelCreator == 'function' ? labelCreator() : labelCreator;
  94. }
  95. /**
  96. * Adds functionality to provided {module:engine/view/editableelement~EditableElement} to act as a widget's editable:
  97. * * adds `ck-editable` CSS class,
  98. * * sets `contenteditable` as `true` when {module:engine/view/editableelement~EditableElement#isReadOnly} is `false`
  99. * otherwise set `false`,
  100. * * adds `ck-editable_focused` CSS class when editable is focused and removes it when it's blurred.
  101. *
  102. * @param {module:engine/view/editableelement~EditableElement} editable
  103. * @returns {module:engine/view/editableelement~EditableElement} Returns same element that was provided in `editable` param.
  104. */
  105. export function toWidgetEditable( editable ) {
  106. editable.addClass( 'ck-editable' );
  107. // Set initial contenteditable value.
  108. editable.setAttribute( 'contenteditable', !editable.isReadOnly );
  109. // Bind contenteditable property to element#isReadOnly.
  110. editable.on( 'change:isReadOnly', ( evt, property, is ) => {
  111. editable.setAttribute( 'contenteditable', !is );
  112. } );
  113. editable.on( 'change:isFocused', ( evt, property, is ) => {
  114. if ( is ) {
  115. editable.addClass( 'ck-editable_focused' );
  116. } else {
  117. editable.removeClass( 'ck-editable_focused' );
  118. }
  119. } );
  120. return editable;
  121. }
  122. // Default filler offset function applied to all widget elements.
  123. //
  124. // @returns {null}
  125. function getFillerOffset() {
  126. return null;
  127. }