8
0

tablepropertiesview.js 17 KB

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