1 |
|
2 |
|
3 |
|
4 | import classnames from 'classnames';
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | import { Fragment, Component } from '@wordpress/element';
|
10 | import {
|
11 | InspectorControls,
|
12 | BlockControls,
|
13 | RichText,
|
14 | PanelColorSettings,
|
15 | createCustomColorsHOC,
|
16 | } from '@wordpress/block-editor';
|
17 | import { __ } from '@wordpress/i18n';
|
18 | import {
|
19 | PanelBody,
|
20 | ToggleControl,
|
21 | TextControl,
|
22 | Button,
|
23 | Toolbar,
|
24 | DropdownMenu,
|
25 | } from '@wordpress/components';
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | import {
|
31 | createTable,
|
32 | updateCellContent,
|
33 | insertRow,
|
34 | deleteRow,
|
35 | insertColumn,
|
36 | deleteColumn,
|
37 | } from './state';
|
38 |
|
39 | const BACKGROUND_COLORS = [
|
40 | {
|
41 | color: '#f3f4f5',
|
42 | name: 'Subtle light gray',
|
43 | slug: 'subtle-light-gray',
|
44 | },
|
45 | {
|
46 | color: '#e9fbe5',
|
47 | name: 'Subtle pale green',
|
48 | slug: 'subtle-pale-green',
|
49 | },
|
50 | {
|
51 | color: '#e7f5fe',
|
52 | name: 'Subtle pale blue',
|
53 | slug: 'subtle-pale-blue',
|
54 | },
|
55 | {
|
56 | color: '#fcf0ef',
|
57 | name: 'Subtle pale pink',
|
58 | slug: 'subtle-pale-pink',
|
59 | },
|
60 | ];
|
61 |
|
62 | const withCustomBackgroundColors = createCustomColorsHOC( BACKGROUND_COLORS );
|
63 |
|
64 | export class TableEdit extends Component {
|
65 | constructor() {
|
66 | super( ...arguments );
|
67 |
|
68 | this.onCreateTable = this.onCreateTable.bind( this );
|
69 | this.onChangeFixedLayout = this.onChangeFixedLayout.bind( this );
|
70 | this.onChange = this.onChange.bind( this );
|
71 | this.onChangeInitialColumnCount = this.onChangeInitialColumnCount.bind( this );
|
72 | this.onChangeInitialRowCount = this.onChangeInitialRowCount.bind( this );
|
73 | this.renderSection = this.renderSection.bind( this );
|
74 | this.getTableControls = this.getTableControls.bind( this );
|
75 | this.onInsertRow = this.onInsertRow.bind( this );
|
76 | this.onInsertRowBefore = this.onInsertRowBefore.bind( this );
|
77 | this.onInsertRowAfter = this.onInsertRowAfter.bind( this );
|
78 | this.onDeleteRow = this.onDeleteRow.bind( this );
|
79 | this.onInsertColumn = this.onInsertColumn.bind( this );
|
80 | this.onInsertColumnBefore = this.onInsertColumnBefore.bind( this );
|
81 | this.onInsertColumnAfter = this.onInsertColumnAfter.bind( this );
|
82 | this.onDeleteColumn = this.onDeleteColumn.bind( this );
|
83 |
|
84 | this.state = {
|
85 | initialRowCount: 2,
|
86 | initialColumnCount: 2,
|
87 | selectedCell: null,
|
88 | };
|
89 | }
|
90 |
|
91 | |
92 |
|
93 |
|
94 |
|
95 |
|
96 | onChangeInitialColumnCount( initialColumnCount ) {
|
97 | this.setState( { initialColumnCount } );
|
98 | }
|
99 |
|
100 | |
101 |
|
102 |
|
103 |
|
104 |
|
105 | onChangeInitialRowCount( initialRowCount ) {
|
106 | this.setState( { initialRowCount } );
|
107 | }
|
108 |
|
109 | |
110 |
|
111 |
|
112 |
|
113 |
|
114 | onCreateTable( event ) {
|
115 | event.preventDefault();
|
116 |
|
117 | const { setAttributes } = this.props;
|
118 | let { initialRowCount, initialColumnCount } = this.state;
|
119 |
|
120 | initialRowCount = parseInt( initialRowCount, 10 ) || 2;
|
121 | initialColumnCount = parseInt( initialColumnCount, 10 ) || 2;
|
122 |
|
123 | setAttributes( createTable( {
|
124 | rowCount: initialRowCount,
|
125 | columnCount: initialColumnCount,
|
126 | } ) );
|
127 | }
|
128 |
|
129 | |
130 |
|
131 |
|
132 | onChangeFixedLayout() {
|
133 | const { attributes, setAttributes } = this.props;
|
134 | const { hasFixedLayout } = attributes;
|
135 |
|
136 | setAttributes( { hasFixedLayout: ! hasFixedLayout } );
|
137 | }
|
138 |
|
139 | |
140 |
|
141 |
|
142 |
|
143 |
|
144 | onChange( content ) {
|
145 | const { selectedCell } = this.state;
|
146 |
|
147 | if ( ! selectedCell ) {
|
148 | return;
|
149 | }
|
150 |
|
151 | const { attributes, setAttributes } = this.props;
|
152 | const { section, rowIndex, columnIndex } = selectedCell;
|
153 |
|
154 | setAttributes( updateCellContent( attributes, {
|
155 | section,
|
156 | rowIndex,
|
157 | columnIndex,
|
158 | content,
|
159 | } ) );
|
160 | }
|
161 |
|
162 | |
163 |
|
164 |
|
165 |
|
166 |
|
167 | onInsertRow( delta ) {
|
168 | const { selectedCell } = this.state;
|
169 |
|
170 | if ( ! selectedCell ) {
|
171 | return;
|
172 | }
|
173 |
|
174 | const { attributes, setAttributes } = this.props;
|
175 | const { section, rowIndex } = selectedCell;
|
176 |
|
177 | this.setState( { selectedCell: null } );
|
178 | setAttributes( insertRow( attributes, {
|
179 | section,
|
180 | rowIndex: rowIndex + delta,
|
181 | } ) );
|
182 | }
|
183 |
|
184 | |
185 |
|
186 |
|
187 | onInsertRowBefore() {
|
188 | this.onInsertRow( 0 );
|
189 | }
|
190 |
|
191 | |
192 |
|
193 |
|
194 | onInsertRowAfter() {
|
195 | this.onInsertRow( 1 );
|
196 | }
|
197 |
|
198 | |
199 |
|
200 |
|
201 | onDeleteRow() {
|
202 | const { selectedCell } = this.state;
|
203 |
|
204 | if ( ! selectedCell ) {
|
205 | return;
|
206 | }
|
207 |
|
208 | const { attributes, setAttributes } = this.props;
|
209 | const { section, rowIndex } = selectedCell;
|
210 |
|
211 | this.setState( { selectedCell: null } );
|
212 | setAttributes( deleteRow( attributes, { section, rowIndex } ) );
|
213 | }
|
214 |
|
215 | |
216 |
|
217 |
|
218 |
|
219 |
|
220 | onInsertColumn( delta = 0 ) {
|
221 | const { selectedCell } = this.state;
|
222 |
|
223 | if ( ! selectedCell ) {
|
224 | return;
|
225 | }
|
226 |
|
227 | const { attributes, setAttributes } = this.props;
|
228 | const { section, columnIndex } = selectedCell;
|
229 |
|
230 | this.setState( { selectedCell: null } );
|
231 | setAttributes( insertColumn( attributes, {
|
232 | section,
|
233 | columnIndex: columnIndex + delta,
|
234 | } ) );
|
235 | }
|
236 |
|
237 | |
238 |
|
239 |
|
240 | onInsertColumnBefore() {
|
241 | this.onInsertColumn( 0 );
|
242 | }
|
243 |
|
244 | |
245 |
|
246 |
|
247 | onInsertColumnAfter() {
|
248 | this.onInsertColumn( 1 );
|
249 | }
|
250 |
|
251 | |
252 |
|
253 |
|
254 | onDeleteColumn() {
|
255 | const { selectedCell } = this.state;
|
256 |
|
257 | if ( ! selectedCell ) {
|
258 | return;
|
259 | }
|
260 |
|
261 | const { attributes, setAttributes } = this.props;
|
262 | const { section, columnIndex } = selectedCell;
|
263 |
|
264 | this.setState( { selectedCell: null } );
|
265 | setAttributes( deleteColumn( attributes, { section, columnIndex } ) );
|
266 | }
|
267 |
|
268 | |
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 | createOnFocus( selectedCell ) {
|
277 | return () => {
|
278 | this.setState( { selectedCell } );
|
279 | };
|
280 | }
|
281 |
|
282 | |
283 |
|
284 |
|
285 |
|
286 |
|
287 | getTableControls() {
|
288 | const { selectedCell } = this.state;
|
289 |
|
290 | return [
|
291 | {
|
292 | icon: 'table-row-before',
|
293 | title: __( 'Add Row Before' ),
|
294 | isDisabled: ! selectedCell,
|
295 | onClick: this.onInsertRowBefore,
|
296 | },
|
297 | {
|
298 | icon: 'table-row-after',
|
299 | title: __( 'Add Row After' ),
|
300 | isDisabled: ! selectedCell,
|
301 | onClick: this.onInsertRowAfter,
|
302 | },
|
303 | {
|
304 | icon: 'table-row-delete',
|
305 | title: __( 'Delete Row' ),
|
306 | isDisabled: ! selectedCell,
|
307 | onClick: this.onDeleteRow,
|
308 | },
|
309 | {
|
310 | icon: 'table-col-before',
|
311 | title: __( 'Add Column Before' ),
|
312 | isDisabled: ! selectedCell,
|
313 | onClick: this.onInsertColumnBefore,
|
314 | },
|
315 | {
|
316 | icon: 'table-col-after',
|
317 | title: __( 'Add Column After' ),
|
318 | isDisabled: ! selectedCell,
|
319 | onClick: this.onInsertColumnAfter,
|
320 | },
|
321 | {
|
322 | icon: 'table-col-delete',
|
323 | title: __( 'Delete Column' ),
|
324 | isDisabled: ! selectedCell,
|
325 | onClick: this.onDeleteColumn,
|
326 | },
|
327 | ];
|
328 | }
|
329 |
|
330 | |
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 | renderSection( { type, rows } ) {
|
339 | if ( ! rows.length ) {
|
340 | return null;
|
341 | }
|
342 |
|
343 | const Tag = `t${ type }`;
|
344 | const { selectedCell } = this.state;
|
345 |
|
346 | return (
|
347 | <Tag>
|
348 | { rows.map( ( { cells }, rowIndex ) => (
|
349 | <tr key={ rowIndex }>
|
350 | { cells.map( ( { content, tag: CellTag }, columnIndex ) => {
|
351 | const isSelected = selectedCell && (
|
352 | type === selectedCell.section &&
|
353 | rowIndex === selectedCell.rowIndex &&
|
354 | columnIndex === selectedCell.columnIndex
|
355 | );
|
356 |
|
357 | const cell = {
|
358 | section: type,
|
359 | rowIndex,
|
360 | columnIndex,
|
361 | };
|
362 |
|
363 | const cellClasses = classnames( { 'is-selected': isSelected } );
|
364 |
|
365 | return (
|
366 | <CellTag
|
367 | key={ columnIndex }
|
368 | className={ cellClasses }
|
369 | >
|
370 | <RichText
|
371 | className="wp-block-table__cell-content"
|
372 | value={ content }
|
373 | onChange={ this.onChange }
|
374 | unstableOnFocus={ this.createOnFocus( cell ) }
|
375 | />
|
376 | </CellTag>
|
377 | );
|
378 | } ) }
|
379 | </tr>
|
380 | ) ) }
|
381 | </Tag>
|
382 | );
|
383 | }
|
384 |
|
385 | componentDidUpdate() {
|
386 | const { isSelected } = this.props;
|
387 | const { selectedCell } = this.state;
|
388 |
|
389 | if ( ! isSelected && selectedCell ) {
|
390 | this.setState( { selectedCell: null } );
|
391 | }
|
392 | }
|
393 |
|
394 | render() {
|
395 | const {
|
396 | attributes,
|
397 | className,
|
398 | backgroundColor,
|
399 | setBackgroundColor,
|
400 | } = this.props;
|
401 | const { initialRowCount, initialColumnCount } = this.state;
|
402 | const { hasFixedLayout, head, body, foot } = attributes;
|
403 | const isEmpty = ! head.length && ! body.length && ! foot.length;
|
404 | const Section = this.renderSection;
|
405 |
|
406 | if ( isEmpty ) {
|
407 | return (
|
408 | <form onSubmit={ this.onCreateTable }>
|
409 | <TextControl
|
410 | type="number"
|
411 | label={ __( 'Column Count' ) }
|
412 | value={ initialColumnCount }
|
413 | onChange={ this.onChangeInitialColumnCount }
|
414 | min="1"
|
415 | />
|
416 | <TextControl
|
417 | type="number"
|
418 | label={ __( 'Row Count' ) }
|
419 | value={ initialRowCount }
|
420 | onChange={ this.onChangeInitialRowCount }
|
421 | min="1"
|
422 | />
|
423 | <Button isPrimary type="submit">{ __( 'Create' ) }</Button>
|
424 | </form>
|
425 | );
|
426 | }
|
427 |
|
428 | const classes = classnames( className, backgroundColor.class, {
|
429 | 'has-fixed-layout': hasFixedLayout,
|
430 | 'has-background': !! backgroundColor.color,
|
431 | } );
|
432 |
|
433 | return (
|
434 | <Fragment>
|
435 | <BlockControls>
|
436 | <Toolbar>
|
437 | <DropdownMenu
|
438 | icon="editor-table"
|
439 | label={ __( 'Edit table' ) }
|
440 | controls={ this.getTableControls() }
|
441 | />
|
442 | </Toolbar>
|
443 | </BlockControls>
|
444 | <InspectorControls>
|
445 | <PanelBody title={ __( 'Table Settings' ) } className="blocks-table-settings">
|
446 | <ToggleControl
|
447 | label={ __( 'Fixed width table cells' ) }
|
448 | checked={ !! hasFixedLayout }
|
449 | onChange={ this.onChangeFixedLayout }
|
450 | />
|
451 | </PanelBody>
|
452 | <PanelColorSettings
|
453 | title={ __( 'Color Settings' ) }
|
454 | initialOpen={ false }
|
455 | colorSettings={ [
|
456 | {
|
457 | value: backgroundColor.color,
|
458 | onChange: setBackgroundColor,
|
459 | label: __( 'Background Color' ),
|
460 | disableCustomColors: true,
|
461 | colors: BACKGROUND_COLORS,
|
462 | },
|
463 | ] }
|
464 | />
|
465 | </InspectorControls>
|
466 | <table className={ classes }>
|
467 | <Section type="head" rows={ head } />
|
468 | <Section type="body" rows={ body } />
|
469 | <Section type="foot" rows={ foot } />
|
470 | </table>
|
471 | </Fragment>
|
472 | );
|
473 | }
|
474 | }
|
475 |
|
476 | export default withCustomBackgroundColors( 'backgroundColor' )( TableEdit );
|