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 'none'
  54. * @member #borderStyle
  55. */
  56. borderStyle: 'none',
  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 ];
  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', value => {
  367. return value !== 'none';
  368. } );
  369. borderWidthInput.view.on( 'input', () => {
  370. this.borderWidth = borderWidthInput.view.element.value;
  371. } );
  372. // -- Color ---------------------------------------------------
  373. const borderColorInput = new LabeledView( locale, createLabeledInputText );
  374. borderColorInput.label = t( 'Color' );
  375. borderColorInput.view.bind( 'value' ).to( this, 'borderColor' );
  376. borderColorInput.bind( 'isEnabled' ).to( this, 'borderStyle', value => {
  377. return value !== 'none';
  378. } );
  379. borderColorInput.view.on( 'input', () => {
  380. this.borderColor = borderColorInput.view.element.value;
  381. } );
  382. // Reset the border color and width fields when style is "none".
  383. // https://github.com/ckeditor/ckeditor5/issues/6227
  384. this.on( 'change:borderStyle', ( evt, name, value ) => {
  385. if ( value === 'none' ) {
  386. this.borderColor = '';
  387. this.borderWidth = '';
  388. }
  389. } );
  390. return {
  391. borderRowLabel,
  392. borderStyleDropdown,
  393. borderColorInput,
  394. borderWidthInput
  395. };
  396. }
  397. /**
  398. * Creates the following form fields:
  399. *
  400. * * {@link #backgroundInput}.
  401. *
  402. * @private
  403. * @returns {module:ui/labeledview/labeledview~LabeledView}
  404. */
  405. _createBackgroundField() {
  406. const locale = this.locale;
  407. const t = this.t;
  408. const backgroundInput = new LabeledView( locale, createLabeledInputText );
  409. backgroundInput.set( {
  410. label: t( 'Background' ),
  411. class: 'ck-table-properties-form__background',
  412. } );
  413. backgroundInput.view.bind( 'value' ).to( this, 'backgroundColor' );
  414. backgroundInput.view.on( 'input', () => {
  415. this.backgroundColor = backgroundInput.view.element.value;
  416. } );
  417. return backgroundInput;
  418. }
  419. /**
  420. * Creates the following form fields:
  421. *
  422. * * {@link #widthInput}.
  423. * * {@link #heightInput}.
  424. *
  425. * @private
  426. * @returns {module:ui/labeledview/labeledview~LabeledView}
  427. */
  428. _createDimensionFields() {
  429. const locale = this.locale;
  430. const t = this.t;
  431. // -- Label ---------------------------------------------------
  432. const dimensionsLabel = new LabelView( locale );
  433. dimensionsLabel.text = t( 'Dimensions' );
  434. // -- Width ---------------------------------------------------
  435. const widthInput = new LabeledView( locale, createLabeledInputText );
  436. widthInput.set( {
  437. label: t( 'Width' ),
  438. class: 'ck-table-properties-form__width',
  439. } );
  440. widthInput.view.bind( 'value' ).to( this, 'width' );
  441. widthInput.view.on( 'input', () => {
  442. this.width = widthInput.view.element.value;
  443. } );
  444. // -- Operator ---------------------------------------------------
  445. const operatorLabel = new View( locale );
  446. operatorLabel.setTemplate( {
  447. tag: 'span',
  448. attributes: {
  449. class: [
  450. 'ck-table-form__dimension-operator'
  451. ]
  452. },
  453. children: [
  454. { text: '×' }
  455. ]
  456. } );
  457. // -- Height ---------------------------------------------------
  458. const heightInput = new LabeledView( locale, createLabeledInputText );
  459. heightInput.set( {
  460. label: t( 'Height' ),
  461. class: 'ck-table-properties-form__height',
  462. } );
  463. heightInput.view.bind( 'value' ).to( this, 'height' );
  464. heightInput.view.on( 'input', () => {
  465. this.height = heightInput.view.element.value;
  466. } );
  467. return {
  468. dimensionsLabel,
  469. widthInput,
  470. operatorLabel,
  471. heightInput
  472. };
  473. }
  474. /**
  475. * Creates the following form fields:
  476. *
  477. * * {@link #alignmentToolbar},
  478. *
  479. * @private
  480. * @returns {Object.<String,module:ui/view~View>}
  481. */
  482. _createAlignmentFields() {
  483. const locale = this.locale;
  484. const t = this.t;
  485. // -- Label ---------------------------------------------------
  486. const alignmentLabel = new LabelView( locale );
  487. alignmentLabel.text = t( 'Alignment' );
  488. // -- Toolbar ---------------------------------------------------
  489. const alignmentToolbar = new ToolbarView( locale );
  490. alignmentToolbar.set( {
  491. isCompact: true,
  492. ariaLabel: t( 'Table alignment toolbar' )
  493. } );
  494. fillToolbar( {
  495. view: this,
  496. icons: ALIGNMENT_ICONS,
  497. toolbar: alignmentToolbar,
  498. labels: this._alignmentLabels,
  499. propertyName: 'alignment',
  500. nameToValue: name => {
  501. return name === 'center' ? '' : name;
  502. }
  503. } );
  504. return {
  505. alignmentLabel,
  506. alignmentToolbar
  507. };
  508. }
  509. /**
  510. * Creates the following form controls:
  511. *
  512. * * {@link #saveButtonView},
  513. * * {@link #cancelButtonView}.
  514. *
  515. * @private
  516. * @returns {Object.<String,module:ui/view~View>}
  517. */
  518. _createActionButtons() {
  519. const locale = this.locale;
  520. const t = this.t;
  521. const saveButtonView = new ButtonView( locale );
  522. const cancelButtonView = new ButtonView( locale );
  523. const fieldsThatShouldValidateToSave = [
  524. this.borderWidthInput,
  525. this.borderColorInput,
  526. this.backgroundInput,
  527. this.widthInput,
  528. this.heightInput
  529. ];
  530. saveButtonView.set( {
  531. label: t( 'Save' ),
  532. icon: checkIcon,
  533. class: 'ck-button-save',
  534. type: 'submit',
  535. withText: true,
  536. } );
  537. saveButtonView.bind( 'isEnabled' ).toMany( fieldsThatShouldValidateToSave, 'errorText', ( ...errorTexts ) => {
  538. return errorTexts.every( errorText => !errorText );
  539. } );
  540. cancelButtonView.set( {
  541. label: t( 'Cancel' ),
  542. icon: cancelIcon,
  543. class: 'ck-button-cancel',
  544. type: 'cancel',
  545. withText: true,
  546. } );
  547. cancelButtonView.delegate( 'execute' ).to( this, 'cancel' );
  548. return {
  549. saveButtonView, cancelButtonView
  550. };
  551. }
  552. /**
  553. * Provides localized labels for {@link #alignmentToolbar} buttons.
  554. *
  555. * @private
  556. * @type {Object.<String,String>}
  557. */
  558. get _alignmentLabels() {
  559. const t = this.t;
  560. return {
  561. left: t( 'Align table to the left' ),
  562. center: t( 'Center table' ),
  563. right: t( 'Align table to the right' )
  564. };
  565. }
  566. }