table-properties.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module table/utils/ui/table-properties
  7. */
  8. import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
  9. import Collection from '@ckeditor/ckeditor5-utils/src/collection';
  10. import Model from '@ckeditor/ckeditor5-ui/src/model';
  11. import ColorInputView from '../../ui/colorinputview';
  12. import { isColor, isLength, isPercentage } from '@ckeditor/ckeditor5-engine/src/view/styles/utils';
  13. const isEmpty = val => val === '';
  14. /**
  15. * Returns an object containing pairs of CSS border style values and their localized UI
  16. * labels. Used by {@link module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView}
  17. * and {@link module:table/tableproperties/ui/tablepropertiesview~TablePropertiesView}.
  18. *
  19. * @param {module:utils/locale~Locale#t} t The "t" function provided by the editor
  20. * that is used to localize strings.
  21. * @returns {Object.<String,String>}
  22. */
  23. export function getBorderStyleLabels( t ) {
  24. return {
  25. none: t( 'None' ),
  26. solid: t( 'Solid' ),
  27. dotted: t( 'Dotted' ),
  28. dashed: t( 'Dashed' ),
  29. double: t( 'Double' ),
  30. groove: t( 'Groove' ),
  31. ridge: t( 'Ridge' ),
  32. inset: t( 'Inset' ),
  33. outset: t( 'Outset' )
  34. };
  35. }
  36. /**
  37. * Returns a localized error string that can be displayed next to color (background, border)
  38. * fields that have an invalid value.
  39. *
  40. * @param {module:utils/locale~Locale#t} t The "t" function provided by the editor
  41. * that is used to localize strings.
  42. * @returns {String}
  43. */
  44. export function getLocalizedColorErrorText( t ) {
  45. return t( 'The color is invalid. Try "#FF0000" or "rgb(255,0,0)" or "red".' );
  46. }
  47. /**
  48. * Returns a localized error string that can be displayed next to length (padding, border width)
  49. * fields that have an invalid value.
  50. *
  51. * @param {module:utils/locale~Locale#t} t The "t" function provided by the editor
  52. * that is used to localize strings.
  53. * @returns {String}
  54. */
  55. export function getLocalizedLengthErrorText( t ) {
  56. return t( 'The value is invalid. Try "10px" or "2em" or simply "2".' );
  57. }
  58. /**
  59. * Returns `true` when the passed value is an empty string or a valid CSS color expression.
  60. * Otherwise, `false` is returned.
  61. *
  62. * See {@link module:engine/view/styles/utils~isColor}.
  63. *
  64. * @param {String} value
  65. * @returns {Boolean}
  66. */
  67. export function colorFieldValidator( value ) {
  68. value = value.trim();
  69. return isEmpty( value ) || isColor( value );
  70. }
  71. /**
  72. * Returns `true` when the passed value is an empty string, a number without a unit or a valid CSS length expression.
  73. * Otherwise, `false` is returned.
  74. *
  75. * See {@link module:engine/view/styles/utils~isLength}.
  76. * See {@link module:engine/view/styles/utils~isPercentage}.
  77. *
  78. * @param {String} value
  79. * @returns {Boolean}
  80. */
  81. export function lengthFieldValidator( value ) {
  82. value = value.trim();
  83. return isEmpty( value ) || isNumberString( value ) || isLength( value ) || isPercentage( value );
  84. }
  85. /**
  86. * Returns `true` when the passed value is an empty string, a number without a unit or a valid CSS length expression.
  87. * Otherwise, `false` is returned.
  88. *
  89. * See {@link module:engine/view/styles/utils~isLength}.
  90. *
  91. * @param {String} value
  92. * @returns {Boolean}
  93. */
  94. export function lineWidthFieldValidator( value ) {
  95. value = value.trim();
  96. return isEmpty( value ) || isNumberString( value ) || isLength( value );
  97. }
  98. /**
  99. * Generates item definitions for a UI dropdown that allows changing the border style of a table or a table cell.
  100. *
  101. * @param {module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView|
  102. * module:table/tableproperties/ui/tablepropertiesview~TablePropertiesView} view
  103. * @returns {Iterable.<module:ui/dropdown/utils~ListDropdownItemDefinition>}
  104. */
  105. export function getBorderStyleDefinitions( view ) {
  106. const itemDefinitions = new Collection();
  107. const styleLabels = getBorderStyleLabels( view.t );
  108. for ( const style in styleLabels ) {
  109. const definition = {
  110. type: 'button',
  111. model: new Model( {
  112. _borderStyleValue: style === 'none' ? '' : style,
  113. label: styleLabels[ style ],
  114. withText: true
  115. } )
  116. };
  117. if ( style === 'none' ) {
  118. definition.model.bind( 'isOn' ).to( view, 'borderStyle', value => !value );
  119. } else {
  120. definition.model.bind( 'isOn' ).to( view, 'borderStyle', value => {
  121. return value === style;
  122. } );
  123. }
  124. itemDefinitions.add( definition );
  125. }
  126. return itemDefinitions;
  127. }
  128. /**
  129. * A helper that fills a toolbar with buttons that:
  130. *
  131. * * have some labels,
  132. * * have some icons,
  133. * * set a certain UI view property value upon execution.
  134. *
  135. * @param {Object} options
  136. * @param {module:table/tablecellproperties/ui/tablecellpropertiesview~TableCellPropertiesView|
  137. * module:table/tableproperties/ui/tablepropertiesview~TablePropertiesView} options.view
  138. * @param {Array.<String>} options.icons
  139. * @param {module:ui/toolbar/toolbarview~ToolbarView} options.toolbar
  140. * @param {Object.<String,String>} labels
  141. * @param {String} propertyName
  142. * @param {Function} nameToValue A function that maps a button name to a value. By default names are the same as values.
  143. */
  144. export function fillToolbar( { view, icons, toolbar, labels, propertyName, nameToValue } ) {
  145. for ( const name in labels ) {
  146. const button = new ButtonView( view.locale );
  147. button.set( {
  148. label: labels[ name ],
  149. icon: icons[ name ],
  150. tooltip: labels[ name ]
  151. } );
  152. button.bind( 'isOn' ).to( view, propertyName, value => {
  153. return value === nameToValue( name );
  154. } );
  155. button.on( 'execute', () => {
  156. view[ propertyName ] = nameToValue( name );
  157. } );
  158. toolbar.items.add( button );
  159. }
  160. }
  161. /**
  162. * A default color palette used by various user interfaces related to tables, for instance,
  163. * by {@link module:table/tablecellproperties/tablecellpropertiesui~TableCellPropertiesUI} or
  164. * {@link module:table/tableproperties/tablepropertiesui~TablePropertiesUI}.
  165. *
  166. * The color palette follows the {@link module:table/table~TableColorConfig table color configuration format}
  167. * and contains the following color definitions:
  168. *
  169. * const defaultColors = [
  170. * {
  171. * color: 'hsl(0, 0%, 0%)',
  172. * label: 'Black'
  173. * },
  174. * {
  175. * color: 'hsl(0, 0%, 30%)',
  176. * label: 'Dim grey'
  177. * },
  178. * {
  179. * color: 'hsl(0, 0%, 60%)',
  180. * label: 'Grey'
  181. * },
  182. * {
  183. * color: 'hsl(0, 0%, 90%)',
  184. * label: 'Light grey'
  185. * },
  186. * {
  187. * color: 'hsl(0, 0%, 100%)',
  188. * label: 'White',
  189. * hasBorder: true
  190. * },
  191. * {
  192. * color: 'hsl(0, 75%, 60%)',
  193. * label: 'Red'
  194. * },
  195. * {
  196. * color: 'hsl(30, 75%, 60%)',
  197. * label: 'Orange'
  198. * },
  199. * {
  200. * color: 'hsl(60, 75%, 60%)',
  201. * label: 'Yellow'
  202. * },
  203. * {
  204. * color: 'hsl(90, 75%, 60%)',
  205. * label: 'Light green'
  206. * },
  207. * {
  208. * color: 'hsl(120, 75%, 60%)',
  209. * label: 'Green'
  210. * },
  211. * {
  212. * color: 'hsl(150, 75%, 60%)',
  213. * label: 'Aquamarine'
  214. * },
  215. * {
  216. * color: 'hsl(180, 75%, 60%)',
  217. * label: 'Turquoise'
  218. * },
  219. * {
  220. * color: 'hsl(210, 75%, 60%)',
  221. * label: 'Light blue'
  222. * },
  223. * {
  224. * color: 'hsl(240, 75%, 60%)',
  225. * label: 'Blue'
  226. * },
  227. * {
  228. * color: 'hsl(270, 75%, 60%)',
  229. * label: 'Purple'
  230. * }
  231. * ];
  232. */
  233. export const defaultColors = [
  234. {
  235. color: 'hsl(0, 0%, 0%)',
  236. label: 'Black'
  237. },
  238. {
  239. color: 'hsl(0, 0%, 30%)',
  240. label: 'Dim grey'
  241. },
  242. {
  243. color: 'hsl(0, 0%, 60%)',
  244. label: 'Grey'
  245. },
  246. {
  247. color: 'hsl(0, 0%, 90%)',
  248. label: 'Light grey'
  249. },
  250. {
  251. color: 'hsl(0, 0%, 100%)',
  252. label: 'White',
  253. hasBorder: true
  254. },
  255. {
  256. color: 'hsl(0, 75%, 60%)',
  257. label: 'Red'
  258. },
  259. {
  260. color: 'hsl(30, 75%, 60%)',
  261. label: 'Orange'
  262. },
  263. {
  264. color: 'hsl(60, 75%, 60%)',
  265. label: 'Yellow'
  266. },
  267. {
  268. color: 'hsl(90, 75%, 60%)',
  269. label: 'Light green'
  270. },
  271. {
  272. color: 'hsl(120, 75%, 60%)',
  273. label: 'Green'
  274. },
  275. {
  276. color: 'hsl(150, 75%, 60%)',
  277. label: 'Aquamarine'
  278. },
  279. {
  280. color: 'hsl(180, 75%, 60%)',
  281. label: 'Turquoise'
  282. },
  283. {
  284. color: 'hsl(210, 75%, 60%)',
  285. label: 'Light blue'
  286. },
  287. {
  288. color: 'hsl(240, 75%, 60%)',
  289. label: 'Blue'
  290. },
  291. {
  292. color: 'hsl(270, 75%, 60%)',
  293. label: 'Purple'
  294. }
  295. ];
  296. /**
  297. * Returns a creator for a color input with a label.
  298. *
  299. * For given options, it returns a function that creates an instance of a
  300. * {@link module:table/ui/colorinputview~ColorInputView color input} logically related to
  301. * a {@link module:ui/labeledfield/labeledfieldview~LabeledFieldView labeled view} in the DOM.
  302. *
  303. * The helper does the following:
  304. *
  305. * * It sets the color input `id` and `ariaDescribedById` attributes.
  306. * * It binds the color input `isReadOnly` to the labeled view.
  307. * * It binds the color input `hasError` to the labeled view.
  308. * * It enables a logic that cleans up the error when the user starts typing in the color input.
  309. *
  310. * Usage:
  311. *
  312. * const colorInputCreator = getLabeledColorInputCreator( {
  313. * colorConfig: [ ... ],
  314. * columns: 3,
  315. * } );
  316. *
  317. * const labeledInputView = new LabeledFieldView( locale, colorInputCreator );
  318. * console.log( labeledInputView.view ); // A color input instance.
  319. *
  320. * @private
  321. * @param options Color input options.
  322. * @param {module:table/table~TableColorConfig} options.colorConfig The configuration of the color palette
  323. * displayed in the input's dropdown.
  324. * @param {Number} options.columns The configuration of the number of columns the color palette consists of
  325. * in the input's dropdown.
  326. * @returns {Function}
  327. */
  328. export function getLabeledColorInputCreator( options ) {
  329. return ( labeledFieldView, viewUid, statusUid ) => {
  330. const inputView = new ColorInputView( labeledFieldView.locale, {
  331. colorDefinitions: colorConfigToColorGridDefinitions( options.colorConfig ),
  332. columns: options.columns
  333. } );
  334. inputView.set( {
  335. id: viewUid,
  336. ariaDescribedById: statusUid
  337. } );
  338. inputView.bind( 'isReadOnly' ).to( labeledFieldView, 'isEnabled', value => !value );
  339. inputView.bind( 'errorText' ).to( labeledFieldView );
  340. inputView.on( 'input', () => {
  341. // UX: Make the error text disappear and disable the error indicator as the user
  342. // starts fixing the errors.
  343. labeledFieldView.errorText = null;
  344. } );
  345. return inputView;
  346. };
  347. }
  348. // A simple helper method to detect number strings.
  349. // I allows full number notation, so omitting 0 is not allowed:
  350. function isNumberString( value ) {
  351. const parsedValue = parseFloat( value );
  352. return !Number.isNaN( parsedValue ) && value === String( parsedValue );
  353. }
  354. // @param {Array.<Object>} colorConfig
  355. // @returns {Array.<module:ui/colorgrid/colorgrid~ColorDefinition>}
  356. function colorConfigToColorGridDefinitions( colorConfig ) {
  357. return colorConfig.map( item => ( {
  358. color: item.model,
  359. label: item.label,
  360. options: {
  361. hasBorder: item.hasBorder
  362. }
  363. } ) );
  364. }