tablecellpropertiesview.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  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/tablecellproperties/ui/tablecellpropertiesview
  7. */
  8. import View from '@ckeditor/ckeditor5-ui/src/view';
  9. import ViewCollection from '@ckeditor/ckeditor5-ui/src/viewcollection';
  10. import submitHandler from '@ckeditor/ckeditor5-ui/src/bindings/submithandler';
  11. import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler';
  12. import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
  13. import FocusCycler from '@ckeditor/ckeditor5-ui/src/focuscycler';
  14. import LabeledView from '@ckeditor/ckeditor5-ui/src/labeledview/labeledview';
  15. import { createLabeledDropdown, createLabeledInputText } from '@ckeditor/ckeditor5-ui/src/labeledview/utils';
  16. import LabelView from '@ckeditor/ckeditor5-ui/src/label/labelview';
  17. import { addListToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
  18. import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview';
  19. import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
  20. import { fillToolbar, getBorderStyleDefinitions, getBorderStyleLabels } from '../../ui/utils';
  21. import FormRowView from '../../ui/formrowview';
  22. import FormHeaderView from '../../ui/formheaderview';
  23. import checkIcon from '@ckeditor/ckeditor5-core/theme/icons/check.svg';
  24. import cancelIcon from '@ckeditor/ckeditor5-core/theme/icons/cancel.svg';
  25. import alignLeftIcon from '@ckeditor/ckeditor5-core/theme/icons/align-left.svg';
  26. import alignRightIcon from '@ckeditor/ckeditor5-core/theme/icons/align-right.svg';
  27. import alignCenterIcon from '@ckeditor/ckeditor5-core/theme/icons/align-center.svg';
  28. import alignJustifyIcon from '@ckeditor/ckeditor5-core/theme/icons/align-justify.svg';
  29. import alignTopIcon from '@ckeditor/ckeditor5-core/theme/icons/align-top.svg';
  30. import alignMiddleIcon from '@ckeditor/ckeditor5-core/theme/icons/align-middle.svg';
  31. import alignBottomIcon from '@ckeditor/ckeditor5-core/theme/icons/align-bottom.svg';
  32. import '../../../theme/form.css';
  33. import '../../../theme/tableform.css';
  34. import '../../../theme/tablecellproperties.css';
  35. const ALIGNMENT_ICONS = {
  36. left: alignLeftIcon,
  37. center: alignCenterIcon,
  38. right: alignRightIcon,
  39. justify: alignJustifyIcon,
  40. top: alignTopIcon,
  41. middle: alignMiddleIcon,
  42. bottom: alignBottomIcon
  43. };
  44. /**
  45. * The class representing a table cell properties form, allowing users to customize
  46. * certain style aspects of a table cell, for instance, border, padding, text alignment, etc..
  47. *
  48. * @extends module:ui/view~View
  49. */
  50. export default class TableCellPropertiesView extends View {
  51. /**
  52. * @inheritDoc
  53. */
  54. constructor( locale ) {
  55. super( locale );
  56. const { borderStyleDropdown, borderWidthInput, borderColorInput, borderRowLabel } = this._createBorderFields();
  57. const { horizontalAlignmentToolbar, verticalAlignmentToolbar, alignmentLabel } = this._createAlignmentFields();
  58. /**
  59. * Tracks information about the DOM focus in the form.
  60. *
  61. * @readonly
  62. * @member {module:utils/focustracker~FocusTracker}
  63. */
  64. this.focusTracker = new FocusTracker();
  65. /**
  66. * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
  67. *
  68. * @readonly
  69. * @member {module:utils/keystrokehandler~KeystrokeHandler}
  70. */
  71. this.keystrokes = new KeystrokeHandler();
  72. /**
  73. * A collection of child views in the form.
  74. *
  75. * @readonly
  76. * @type {module:ui/viewcollection~ViewCollection}
  77. */
  78. this.children = this.createCollection();
  79. this.set( {
  80. /**
  81. * The value of the cell border style.
  82. *
  83. * @observable
  84. * @default 'none'
  85. * @member #borderStyle
  86. */
  87. borderStyle: 'none',
  88. /**
  89. * The value of the cell border width style.
  90. *
  91. * @observable
  92. * @default ''
  93. * @member #borderWidth
  94. */
  95. borderWidth: '',
  96. /**
  97. * The value of the cell border color style.
  98. *
  99. * @observable
  100. * @default ''
  101. * @member #borderColor
  102. */
  103. borderColor: '',
  104. /**
  105. * The value of the cell padding style.
  106. *
  107. * @observable
  108. * @default ''
  109. * @member #padding
  110. */
  111. padding: '',
  112. /**
  113. * The value of the cell background color style.
  114. *
  115. * @observable
  116. * @default ''
  117. * @member #backgroundColor
  118. */
  119. backgroundColor: '',
  120. /**
  121. * The value of the horizontal text alignment style.
  122. *
  123. * @observable
  124. * @default 'left'
  125. * @member #horizontalAlignment
  126. */
  127. horizontalAlignment: 'left',
  128. /**
  129. * The value of the vertical text alignment style.
  130. *
  131. * @observable
  132. * @default 'middle'
  133. * @member #verticalAlignment
  134. */
  135. verticalAlignment: 'middle'
  136. } );
  137. /**
  138. * A dropdown that allows selecting the style of the table cell border.
  139. *
  140. * @readonly
  141. * @member {module:ui/dropdown/dropdownview~DropdownView}
  142. */
  143. this.borderStyleDropdown = borderStyleDropdown;
  144. /**
  145. * An input that allows specifying the width of the table cell border.
  146. *
  147. * @readonly
  148. * @member {module:ui/inputtext/inputtextview~InputTextView}
  149. */
  150. this.borderWidthInput = borderWidthInput;
  151. /**
  152. * An input that allows specifying the color of the table cell border.
  153. *
  154. * @readonly
  155. * @member {module:ui/inputtext/inputtextview~InputTextView}
  156. */
  157. this.borderColorInput = borderColorInput;
  158. /**
  159. * An input that allows specifying the table cell background color.
  160. *
  161. * @readonly
  162. * @member {module:ui/inputtext/inputtextview~InputTextView}
  163. */
  164. this.backgroundInput = this._createBackgroundField();
  165. /**
  166. * An input that allows specifying the table cell padding.
  167. *
  168. * @readonly
  169. * @member {module:ui/inputtext/inputtextview~InputTextView}
  170. */
  171. this.paddingInput = this._createPaddingField();
  172. /**
  173. * A toolbar with buttons that allow changing the horizontal text alignment in a table cell.
  174. *
  175. * @readonly
  176. * @member {module:ui/toolbar/toolbar~ToolbarView}
  177. */
  178. this.horizontalAlignmentToolbar = horizontalAlignmentToolbar;
  179. /**
  180. * A toolbar with buttons that allow changing the vertical text alignment in a table cell.
  181. *
  182. * @readonly
  183. * @member {module:ui/toolbar/toolbar~ToolbarView}
  184. */
  185. this.verticalAlignmentToolbar = verticalAlignmentToolbar;
  186. // Defer creating to make sure other fields are present and the Save button can
  187. // bind its #isEnabled to their error messages so there's no way to save unless all
  188. // fields are valid.
  189. const { saveButtonView, cancelButtonView } = this._createActionButtons();
  190. /**
  191. * The "Save" button view.
  192. *
  193. * @member {module:ui/button/buttonview~ButtonView}
  194. */
  195. this.saveButtonView = saveButtonView;
  196. /**
  197. * The "Cancel" button view.
  198. *
  199. * @member {module:ui/button/buttonview~ButtonView}
  200. */
  201. this.cancelButtonView = cancelButtonView;
  202. /**
  203. * A collection of views that can be focused in the form.
  204. *
  205. * @readonly
  206. * @protected
  207. * @member {module:ui/viewcollection~ViewCollection}
  208. */
  209. this._focusables = new ViewCollection();
  210. /**
  211. * Helps cycling over {@link #_focusables} in the form.
  212. *
  213. * @readonly
  214. * @protected
  215. * @member {module:ui/focuscycler~FocusCycler}
  216. */
  217. this._focusCycler = new FocusCycler( {
  218. focusables: this._focusables,
  219. focusTracker: this.focusTracker,
  220. keystrokeHandler: this.keystrokes,
  221. actions: {
  222. // Navigate form fields backwards using the Shift + Tab keystroke.
  223. focusPrevious: 'shift + tab',
  224. // Navigate form fields forwards using the Tab key.
  225. focusNext: 'tab'
  226. }
  227. } );
  228. // Form header.
  229. this.children.add( new FormHeaderView( locale, {
  230. label: this.t( 'Cell properties' )
  231. } ) );
  232. // Border row.
  233. this.children.add( new FormRowView( locale, {
  234. labelView: borderRowLabel,
  235. children: [
  236. borderRowLabel,
  237. borderStyleDropdown,
  238. borderColorInput,
  239. borderWidthInput
  240. ],
  241. class: 'ck-table-form__border-row'
  242. } ) );
  243. // Background and padding row.
  244. this.children.add( new FormRowView( locale, {
  245. children: [
  246. this.backgroundInput,
  247. this.paddingInput,
  248. ]
  249. } ) );
  250. // Text alignment row.
  251. this.children.add( new FormRowView( locale, {
  252. labelView: alignmentLabel,
  253. children: [
  254. alignmentLabel,
  255. horizontalAlignmentToolbar,
  256. verticalAlignmentToolbar,
  257. ],
  258. class: 'ck-table-cell-properties-form__alignment-row'
  259. } ) );
  260. // Action row.
  261. this.children.add( new FormRowView( locale, {
  262. children: [
  263. this.saveButtonView,
  264. this.cancelButtonView,
  265. ],
  266. class: 'ck-table-form__action-row'
  267. } ) );
  268. this.setTemplate( {
  269. tag: 'form',
  270. attributes: {
  271. class: [
  272. 'ck',
  273. 'ck-form',
  274. 'ck-table-form',
  275. 'ck-table-cell-properties-form'
  276. ],
  277. // https://github.com/ckeditor/ckeditor5-link/issues/90
  278. tabindex: '-1'
  279. },
  280. children: this.children
  281. } );
  282. }
  283. /**
  284. * @inheritDoc
  285. */
  286. render() {
  287. super.render();
  288. // Enable the "submit" event for this view. It can be triggered by the #saveButtonView
  289. // which is of the "submit" DOM "type".
  290. submitHandler( {
  291. view: this
  292. } );
  293. [
  294. this.borderStyleDropdown,
  295. this.borderColorInput,
  296. this.borderWidthInput,
  297. this.backgroundInput,
  298. this.paddingInput,
  299. this.horizontalAlignmentToolbar,
  300. this.verticalAlignmentToolbar,
  301. this.saveButtonView,
  302. this.cancelButtonView
  303. ].forEach( view => {
  304. // Register the view as focusable.
  305. this._focusables.add( view );
  306. // Register the view in the focus tracker.
  307. this.focusTracker.add( view.element );
  308. } );
  309. // Mainly for closing using "Esc" and navigation using "Tab".
  310. this.keystrokes.listenTo( this.element );
  311. }
  312. /**
  313. * Focuses the fist focusable field in the form.
  314. */
  315. focus() {
  316. this._focusCycler.focusFirst();
  317. }
  318. /**
  319. * Creates the following form fields:
  320. *
  321. * * {@link #borderStyleDropdown},
  322. * * {@link #borderWidthInput},
  323. * * {@link #borderColorInput}.
  324. *
  325. * @private
  326. * @returns {Object.<String,module:ui/view~View>}
  327. */
  328. _createBorderFields() {
  329. const locale = this.locale;
  330. const t = this.t;
  331. // -- Group label ---------------------------------------------
  332. const borderRowLabel = new LabelView( locale );
  333. borderRowLabel.text = t( 'Border' );
  334. // -- Style ---------------------------------------------------
  335. const styleLabels = getBorderStyleLabels( t );
  336. const borderStyleDropdown = new LabeledView( locale, createLabeledDropdown );
  337. borderStyleDropdown.set( {
  338. label: t( 'Style' ),
  339. class: 'ck-table-form__border-style'
  340. } );
  341. borderStyleDropdown.view.buttonView.set( {
  342. isOn: false,
  343. withText: true,
  344. tooltip: t( 'Style' )
  345. } );
  346. borderStyleDropdown.view.buttonView.bind( 'label' ).to( this, 'borderStyle', value => {
  347. return styleLabels[ value ];
  348. } );
  349. borderStyleDropdown.view.on( 'execute', evt => {
  350. this.borderStyle = evt.source._borderStyleValue;
  351. } );
  352. addListToDropdown( borderStyleDropdown.view, getBorderStyleDefinitions( this ) );
  353. // -- Width ---------------------------------------------------
  354. const borderWidthInput = new LabeledView( locale, createLabeledInputText );
  355. borderWidthInput.set( {
  356. label: t( 'Width' ),
  357. class: 'ck-table-form__border-width',
  358. } );
  359. borderWidthInput.view.bind( 'value' ).to( this, 'borderWidth' );
  360. borderWidthInput.bind( 'isEnabled' ).to( this, 'borderStyle', value => {
  361. return value !== 'none';
  362. } );
  363. borderWidthInput.view.on( 'input', () => {
  364. this.borderWidth = borderWidthInput.view.element.value;
  365. } );
  366. // -- Color ---------------------------------------------------
  367. const borderColorInput = new LabeledView( locale, createLabeledInputText );
  368. borderColorInput.label = t( 'Color' );
  369. borderColorInput.view.bind( 'value' ).to( this, 'borderColor' );
  370. borderColorInput.bind( 'isEnabled' ).to( this, 'borderStyle', value => {
  371. return value !== 'none';
  372. } );
  373. borderColorInput.view.on( 'input', () => {
  374. this.borderColor = borderColorInput.view.element.value;
  375. } );
  376. return {
  377. borderRowLabel,
  378. borderStyleDropdown,
  379. borderColorInput,
  380. borderWidthInput,
  381. };
  382. }
  383. /**
  384. * Creates the following form fields:
  385. *
  386. * * {@link #backgroundInput}.
  387. *
  388. * @private
  389. * @returns {module:ui/labeledview/labeledview~LabeledView}
  390. */
  391. _createBackgroundField() {
  392. const locale = this.locale;
  393. const t = this.t;
  394. const backgroundInput = new LabeledView( locale, createLabeledInputText );
  395. backgroundInput.set( {
  396. label: t( 'Background' ),
  397. class: 'ck-table-cell-properties-form__background',
  398. } );
  399. backgroundInput.view.bind( 'value' ).to( this, 'backgroundColor' );
  400. backgroundInput.view.on( 'input', () => {
  401. this.backgroundColor = backgroundInput.view.element.value;
  402. } );
  403. return backgroundInput;
  404. }
  405. /**
  406. * Creates the following form fields:
  407. *
  408. * * {@link #paddingInput}.
  409. *
  410. * @private
  411. * @returns {module:ui/labeledview/labeledview~LabeledView}
  412. */
  413. _createPaddingField() {
  414. const locale = this.locale;
  415. const t = this.t;
  416. const paddingInput = new LabeledView( locale, createLabeledInputText );
  417. paddingInput.set( {
  418. label: t( 'Padding' ),
  419. class: 'ck-table-cell-properties-form__padding',
  420. } );
  421. paddingInput.view.bind( 'value' ).to( this, 'padding' );
  422. paddingInput.view.on( 'input', () => {
  423. this.padding = paddingInput.view.element.value;
  424. } );
  425. return paddingInput;
  426. }
  427. /**
  428. * Creates the following form fields:
  429. *
  430. * * {@link #horizontalAlignmentToolbar},
  431. * * {@link #verticalAlignmentToolbar}.
  432. *
  433. * @private
  434. * @returns {Object.<String,module:ui/view~View>}
  435. */
  436. _createAlignmentFields() {
  437. const locale = this.locale;
  438. const t = this.t;
  439. const alignmentLabel = new LabelView( locale );
  440. alignmentLabel.text = t( 'Text alignment' );
  441. // -- Horizontal ---------------------------------------------------
  442. const horizontalAlignmentToolbar = new ToolbarView( locale );
  443. horizontalAlignmentToolbar.set( {
  444. isCompact: true,
  445. ariaLabel: t( 'Horizontal text alignment toolbar' )
  446. } );
  447. fillToolbar( {
  448. view: this,
  449. icons: ALIGNMENT_ICONS,
  450. toolbar: horizontalAlignmentToolbar,
  451. labels: this._horizontalAlignmentLabels,
  452. propertyName: 'horizontalAlignment'
  453. } );
  454. // -- Vertical -----------------------------------------------------
  455. const verticalAlignmentToolbar = new ToolbarView( locale );
  456. verticalAlignmentToolbar.set( {
  457. isCompact: true,
  458. ariaLabel: t( 'Vertical text alignment toolbar' )
  459. } );
  460. fillToolbar( {
  461. view: this,
  462. icons: ALIGNMENT_ICONS,
  463. toolbar: verticalAlignmentToolbar,
  464. labels: this._verticalAlignmentLabels,
  465. propertyName: 'verticalAlignment'
  466. } );
  467. return {
  468. horizontalAlignmentToolbar,
  469. verticalAlignmentToolbar,
  470. alignmentLabel
  471. };
  472. }
  473. /**
  474. * Creates the following form controls:
  475. *
  476. * * {@link #saveButtonView},
  477. * * {@link #cancelButtonView}.
  478. *
  479. * @private
  480. * @returns {Object.<String,module:ui/view~View>}
  481. */
  482. _createActionButtons() {
  483. const locale = this.locale;
  484. const t = this.t;
  485. const saveButtonView = new ButtonView( locale );
  486. const cancelButtonView = new ButtonView( locale );
  487. const fieldsThatShouldValidateToSave = [
  488. this.borderWidthInput,
  489. this.borderColorInput,
  490. this.backgroundInput,
  491. this.paddingInput
  492. ];
  493. saveButtonView.set( {
  494. label: t( 'Save' ),
  495. icon: checkIcon,
  496. class: 'ck-button-save',
  497. type: 'submit',
  498. withText: true,
  499. } );
  500. saveButtonView.bind( 'isEnabled' ).toMany( fieldsThatShouldValidateToSave, 'errorText', ( ...errorTexts ) => {
  501. return errorTexts.every( errorText => !errorText );
  502. } );
  503. cancelButtonView.set( {
  504. label: t( 'Cancel' ),
  505. icon: cancelIcon,
  506. class: 'ck-button-cancel',
  507. type: 'cancel',
  508. withText: true,
  509. } );
  510. cancelButtonView.delegate( 'execute' ).to( this, 'cancel' );
  511. return {
  512. saveButtonView, cancelButtonView
  513. };
  514. }
  515. /**
  516. * Provides localized labels for {@link #horizontalAlignmentToolbar} buttons.
  517. *
  518. * @private
  519. * @type {Object.<String,String>}
  520. */
  521. get _horizontalAlignmentLabels() {
  522. const t = this.t;
  523. return {
  524. left: t( 'Align cell text to the left' ),
  525. center: t( 'Align cell text to the center' ),
  526. right: t( 'Align cell text to the right' ),
  527. justify: t( 'Justify cell text' ),
  528. };
  529. }
  530. /**
  531. * Provides localized labels for {@link #verticalAlignmentToolbar} buttons.
  532. *
  533. * @private
  534. * @type {Object.<String,String>}
  535. */
  536. get _verticalAlignmentLabels() {
  537. const t = this.t;
  538. return {
  539. top: t( 'Align cell text to the top' ),
  540. middle: t( 'Align cell text to the middle' ),
  541. bottom: t( 'Align cell text to the bottom' )
  542. };
  543. }
  544. }