tablepropertiesview.js 16 KB

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