@plone/volto-slate 19.0.0-alpha.15 → 19.0.0-alpha.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,28 @@
1
+ # AGENTS.md
2
+
3
+ This file applies only to `packages/volto-slate` and its subdirectories.
4
+
5
+ ## What This Package Is
6
+
7
+ - `@plone/volto-slate` provides the Slate-based editor integration used by Volto.
8
+ - It is tightly coupled to Volto editor behavior even though it is versioned as a separate package.
9
+
10
+ ## Editing Rules
11
+
12
+ - Prefer TypeScript for all new modules and features.
13
+ - Refactoring touched code toward TypeScript is welcome, but not required.
14
+ - Treat changes here as editor-platform changes, not isolated component tweaks.
15
+ - Preserve serialization, deserialization, and editor schema expectations.
16
+ - When changing editor behavior, check whether fixtures, Cypress flows, or Volto-side block/editor integrations also need updates.
17
+ - Avoid introducing APIs that bypass existing Volto editor configuration patterns unless the task explicitly calls for it.
18
+
19
+ ## Validation
20
+
21
+ This package does not expose a dedicated local test script in this branch.
22
+ Validate through Volto:
23
+
24
+ ```sh
25
+ pnpm --filter @plone/volto test --run
26
+ pnpm --filter @plone/volto build
27
+ make ci-acceptance-test-run-all
28
+ ```
package/CHANGELOG.md CHANGED
@@ -8,6 +8,18 @@
8
8
 
9
9
  <!-- towncrier release notes start -->
10
10
 
11
+ ## 19.0.0-alpha.17 (2026-05-12)
12
+
13
+ ### Internal
14
+
15
+ - Refactored the `TableBlockEdit` component from a class-based component to a modern functional component using React hooks. @Manik-Khajuria-5 [#7760](https://github.com/plone/volto/issues/7760)
16
+
17
+ ## 19.0.0-alpha.16 (2026-05-07)
18
+
19
+ ### Documentation
20
+
21
+ - Added package-specific `AGENTS.md` contributor guidance for `@plone/volto-slate` maintainers.
22
+
11
23
  ## 19.0.0-alpha.15 (2026-04-27)
12
24
 
13
25
  ### Bugfix
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plone/volto-slate",
3
- "version": "19.0.0-alpha.15",
3
+ "version": "19.0.0-alpha.17",
4
4
  "description": "Slate.js integration with Volto",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -1,16 +1,12 @@
1
- /**
2
- * Slate Table block editor.
3
- * @module volto-slate/blocks/Table/Edit
4
- */
5
-
6
- import React, { Component } from 'react';
1
+ import { useState, useEffect, useCallback, useMemo } from 'react';
7
2
  import PropTypes from 'prop-types';
8
3
  import isEmpty from 'lodash/isEmpty';
9
4
  import map from 'lodash/map';
10
5
  import remove from 'lodash/remove';
11
6
  import { Button, Table } from 'semantic-ui-react';
12
7
  import cx from 'classnames';
13
- import { defineMessages, injectIntl } from 'react-intl';
8
+ import { defineMessages, useIntl } from 'react-intl';
9
+ import { useClient } from '@plone/volto/hooks/client/useClient';
14
10
 
15
11
  import Cell from './Cell';
16
12
  import Icon from '@plone/volto/components/theme/Icon/Icon';
@@ -162,532 +158,389 @@ const messages = defineMessages({
162
158
 
163
159
  /**
164
160
  * Edit component for the Slate Table block type in Volto.
165
- * @class Edit
166
- * @extends Component
161
+ * @function Edit
162
+ * @param {Object} props
163
+ * @returns {JSX.Element}
167
164
  */
168
- class Edit extends Component {
169
- /**
170
- * Property types.
171
- * @property {Object} propTypes Property types.
172
- * @static
173
- */
174
- static propTypes = {
175
- data: PropTypes.objectOf(PropTypes.any).isRequired,
176
- detached: PropTypes.bool,
177
- index: PropTypes.number.isRequired,
178
- selected: PropTypes.bool.isRequired,
179
- block: PropTypes.string.isRequired,
180
- onAddBlock: PropTypes.func.isRequired,
181
- onChangeBlock: PropTypes.func.isRequired,
182
- onDeleteBlock: PropTypes.func.isRequired,
183
- onInsertBlock: PropTypes.func.isRequired,
184
- onMutateBlock: PropTypes.func.isRequired,
185
- onFocusPreviousBlock: PropTypes.func.isRequired,
186
- onFocusNextBlock: PropTypes.func.isRequired,
187
- onSelectBlock: PropTypes.func.isRequired,
188
- };
189
-
190
- /**
191
- * Default properties
192
- * @property {Object} defaultProps Default properties.
193
- * @static
194
- */
195
- static defaultProps = {
196
- detached: false,
197
- };
198
-
199
- /**
200
- * Constructor
201
- * @method constructor
202
- * @param {Object} props Component properties
203
- * @constructs WysiwygEditor
204
- */
205
- constructor(props) {
206
- super(props);
207
- this.state = {
208
- headers: [],
209
- rows: {},
210
- selected: {
211
- row: 0,
212
- cell: 0,
213
- },
214
- isClient: false,
215
- };
216
- this.onChange = this.onChange.bind(this);
217
- this.onSelectCell = this.onSelectCell.bind(this);
218
- this.onInsertRowBefore = this.onInsertRowBefore.bind(this);
219
- this.onInsertRowAfter = this.onInsertRowAfter.bind(this);
220
- this.onInsertColBefore = this.onInsertColBefore.bind(this);
221
- this.onInsertColAfter = this.onInsertColAfter.bind(this);
222
- this.onDeleteRow = this.onDeleteRow.bind(this);
223
- this.onDeleteCol = this.onDeleteCol.bind(this);
224
- this.onChangeCell = this.onChangeCell.bind(this);
225
- this.toggleCellType = this.toggleCellType.bind(this);
226
- }
227
-
228
- /**
229
- * Component did mount lifecycle method
230
- * @method componentDidMount
231
- * @returns {undefined}
232
- */
233
- componentDidMount() {
234
- if (!this.props.data.table || isEmpty(this.props.data.table)) {
235
- this.props.onChangeBlock(this.props.block, {
236
- ...this.props.data,
237
- table: initialTable,
238
- });
165
+ const Edit = (props) => {
166
+ const {
167
+ data,
168
+
169
+ index,
170
+ selected,
171
+ block,
172
+ onAddBlock,
173
+ onChangeBlock,
174
+ onSelectBlock,
175
+ blocksConfig,
176
+ } = props;
177
+
178
+ const intl = useIntl();
179
+ const isClient = useClient();
180
+
181
+ const [selectedCell, setSelectedCell] = useState({
182
+ row: 0,
183
+ cell: 0,
184
+ });
185
+ // If selected prop is unset, update the state
186
+ useEffect(() => {
187
+ if (!selected) {
188
+ setSelectedCell(null);
239
189
  }
240
- this.setState({ isClient: true });
241
- }
190
+ }, [selected]);
242
191
 
243
- /**
244
- * Component will receive props lifecycle method
245
- * @method componentWillReceiveProps
246
- * @param {Object} nextProps Next properties
247
- * @returns {undefined}
248
- */
249
- UNSAFE_componentWillReceiveProps(nextProps) {
250
- if (!nextProps.data.table || isEmpty(nextProps.data.table)) {
251
- this.props.onChangeBlock(nextProps.block, {
252
- ...nextProps.data,
192
+ useEffect(() => {
193
+ if (!data.table || isEmpty(data.table)) {
194
+ onChangeBlock(block, {
195
+ ...data,
253
196
  table: initialTable,
254
197
  });
255
198
  }
256
- }
257
-
258
- /**
259
- * On change
260
- * @method onChange
261
- * @param {string} id Id of modified property.
262
- * @param {any} value New value of modified property.
263
- * @returns {undefined}
264
- */
265
- onChange(id, value) {
266
- const table = this.props.data.table;
267
- this.props.onChangeBlock(this.props.block, {
268
- ...this.props.data,
269
- table: {
270
- ...table,
271
- [id]: value,
272
- },
273
- });
274
- }
275
-
276
- /**
277
- * Select cell handler
278
- * @method onSelectCell
279
- * @param {Number} row Row index.
280
- * @param {Number} cell Cell index.
281
- * @returns {undefined}
282
- */
283
- onSelectCell(row, cell) {
284
- this.setState({ selected: { row, cell } });
285
- }
286
-
287
- /**
288
- * Change cell handler
289
- * @param {Number} row Row index.
290
- * @param {Number} cell Cell index.
291
- * @param {Array} slateValue Value of the `SlateEditor` in the cell.
292
- * @returns {undefined}
293
- */
294
- onChangeCell(row, cell, slateValue) {
295
- const table = JSON.parse(JSON.stringify(this.props.data.table));
296
- table.rows[row].cells[cell] = {
297
- ...table.rows[row].cells[cell],
298
- value: JSON.parse(JSON.stringify(slateValue)),
299
- };
300
- this.props.onChangeBlock(this.props.block, {
301
- ...this.props.data,
302
- table,
303
- });
304
- }
305
-
306
- /**
307
- * Toggle cell type (from header to data or reverse)
308
- * @method toggleCellType
309
- * @returns {undefined}
310
- */
311
- toggleCellType() {
312
- const table = { ...this.props.data.table };
313
- let type =
314
- table.rows[this.state.selected.row].cells[this.state.selected.cell].type;
315
- table.rows[this.state.selected.row].cells[this.state.selected.cell].type =
316
- type === 'header' ? 'data' : 'header';
317
- this.props.onChangeBlock(this.props.block, {
318
- ...this.props.data,
319
- table,
320
- });
321
- }
199
+ }, [data.table, data, block, onChangeBlock]);
200
+
201
+ const headers = useMemo(
202
+ () => data.table?.rows?.[0]?.cells || [],
203
+ [data.table],
204
+ );
205
+
206
+ const rows = useMemo(
207
+ () => data.table?.rows?.filter((_, index) => index > 0) || [],
208
+ [data.table],
209
+ );
210
+
211
+ const schema = useMemo(() => TableSchema({ ...props, intl }), [props, intl]);
212
+
213
+ const onSelectCell = useCallback((row, cell) => {
214
+ setSelectedCell({ row, cell });
215
+ }, []);
216
+
217
+ const onChangeCell = useCallback(
218
+ (row, cell, slateValue) => {
219
+ const table = JSON.parse(JSON.stringify(data.table));
220
+ table.rows[row].cells[cell] = {
221
+ ...table.rows[row].cells[cell],
222
+ value: JSON.parse(JSON.stringify(slateValue)),
223
+ };
224
+ onChangeBlock(block, {
225
+ ...data,
226
+ table,
227
+ });
228
+ },
229
+ [data, block, onChangeBlock],
230
+ );
322
231
 
323
- /**
324
- * Insert row before handler. Keeps the selected cell as selected after the
325
- * operation is done.
326
- * @returns {undefined}
327
- */
328
- onInsertRowBefore() {
329
- const table = this.props.data.table;
330
- this.props.onChangeBlock(this.props.block, {
331
- ...this.props.data,
232
+ const onInsertRowBefore = useCallback(() => {
233
+ const table = data.table;
234
+ onChangeBlock(block, {
235
+ ...data,
332
236
  table: {
333
237
  ...table,
334
238
  rows: [
335
- ...table.rows.slice(0, this.state.selected.row),
239
+ ...table.rows.slice(0, selectedCell.row),
336
240
  emptyRow(table.rows[0].cells),
337
- ...table.rows.slice(this.state.selected.row),
241
+ ...table.rows.slice(selectedCell.row),
338
242
  ],
339
243
  },
340
244
  });
341
- this.setState({
342
- selected: {
343
- row: this.state.selected.row + 1,
344
- cell: this.state.selected.cell,
345
- },
245
+ setSelectedCell({
246
+ row: selectedCell.row + 1,
247
+ cell: selectedCell.cell,
346
248
  });
347
- }
249
+ }, [data, block, onChangeBlock, selectedCell]);
348
250
 
349
- /**
350
- * Insert row after handler
351
- * @returns {undefined}
352
- */
353
- onInsertRowAfter() {
354
- const table = this.props.data.table;
355
- this.props.onChangeBlock(this.props.block, {
356
- ...this.props.data,
251
+ const onInsertRowAfter = useCallback(() => {
252
+ const table = data.table;
253
+ onChangeBlock(block, {
254
+ ...data,
357
255
  table: {
358
256
  ...table,
359
257
  rows: [
360
- ...table.rows.slice(0, this.state.selected.row + 1),
258
+ ...table.rows.slice(0, selectedCell.row + 1),
361
259
  emptyRow(table.rows[0].cells),
362
- ...table.rows.slice(this.state.selected.row + 1),
260
+ ...table.rows.slice(selectedCell.row + 1),
363
261
  ],
364
262
  },
365
263
  });
366
- }
264
+ }, [data, block, onChangeBlock, selectedCell]);
367
265
 
368
- /**
369
- * Insert column before handler. Keeps the selected cell as selected after the
370
- * operation is done.
371
- * @returns {undefined}
372
- */
373
- onInsertColBefore() {
374
- const table = this.props.data.table;
375
- this.props.onChangeBlock(this.props.block, {
376
- ...this.props.data,
266
+ const onInsertColBefore = useCallback(() => {
267
+ const table = data.table;
268
+ onChangeBlock(block, {
269
+ ...data,
377
270
  table: {
378
271
  ...table,
379
272
  rows: map(table.rows, (row, index) => ({
380
273
  ...row,
381
274
  cells: [
382
- ...row.cells.slice(0, this.state.selected.cell),
383
- emptyCell(table.rows[index].cells[this.state.selected.cell].type),
384
- ...row.cells.slice(this.state.selected.cell),
275
+ ...row.cells.slice(0, selectedCell.cell),
276
+ emptyCell(table.rows[index].cells[selectedCell.cell].type),
277
+ ...row.cells.slice(selectedCell.cell),
385
278
  ],
386
279
  })),
387
280
  },
388
281
  });
389
- this.setState({
390
- selected: {
391
- row: this.state.selected.row,
392
- cell: this.state.selected.cell + 1,
393
- },
282
+ setSelectedCell({
283
+ row: selectedCell.row,
284
+ cell: selectedCell.cell + 1,
394
285
  });
395
- }
286
+ }, [data, block, onChangeBlock, selectedCell]);
396
287
 
397
- /**
398
- * Insert column after handler
399
- * @returns {undefined}
400
- */
401
- onInsertColAfter() {
402
- const table = this.props.data.table;
403
- this.props.onChangeBlock(this.props.block, {
404
- ...this.props.data,
288
+ const onInsertColAfter = useCallback(() => {
289
+ const table = data.table;
290
+ onChangeBlock(block, {
291
+ ...data,
405
292
  table: {
406
293
  ...table,
407
294
  rows: map(table.rows, (row, index) => ({
408
295
  ...row,
409
296
  cells: [
410
- ...row.cells.slice(0, this.state.selected.cell + 1),
411
- emptyCell(table.rows[index].cells[this.state.selected.cell].type),
412
- ...row.cells.slice(this.state.selected.cell + 1),
297
+ ...row.cells.slice(0, selectedCell.cell + 1),
298
+ emptyCell(table.rows[index].cells[selectedCell.cell].type),
299
+ ...row.cells.slice(selectedCell.cell + 1),
413
300
  ],
414
301
  })),
415
302
  },
416
303
  });
417
- }
304
+ }, [data, block, onChangeBlock, selectedCell]);
418
305
 
419
- /**
420
- * Delete column handler. Changes the selected cell if the last table column
421
- * is selected.
422
- * @returns {undefined}
423
- */
424
- onDeleteCol() {
425
- const table = this.props.data.table;
306
+ const onDeleteCol = useCallback(() => {
307
+ const table = data.table;
426
308
 
427
- if (this.state.selected.cell === table.rows[0].cells.length - 1) {
428
- this.setState({
429
- selected: {
430
- row: this.state.selected.row,
431
- cell: this.state.selected.cell - 1,
432
- },
309
+ if (selectedCell.cell === table.rows[0].cells.length - 1) {
310
+ setSelectedCell({
311
+ row: selectedCell.row,
312
+ cell: selectedCell.cell - 1,
433
313
  });
434
314
  }
435
315
 
436
- this.props.onChangeBlock(this.props.block, {
437
- ...this.props.data,
316
+ onChangeBlock(block, {
317
+ ...data,
438
318
  table: {
439
319
  ...table,
440
320
  rows: map(table.rows, (row) => ({
441
321
  ...row,
442
322
  cells: remove(
443
323
  row.cells,
444
- (cell, index) => index !== this.state.selected.cell,
324
+ (cell, index) => index !== selectedCell.cell,
445
325
  ),
446
326
  })),
447
327
  },
448
328
  });
449
- }
329
+ }, [data, block, onChangeBlock, selectedCell]);
450
330
 
451
- /**
452
- * Delete row handler. Changes the selected cell if the last table row is
453
- * selected.
454
- * @method onDeleteRow
455
- * @returns {undefined}
456
- */
457
- onDeleteRow() {
458
- const table = this.props.data.table;
331
+ const onDeleteRow = useCallback(() => {
332
+ const table = data.table;
459
333
 
460
- if (this.state.selected.row === table.rows.length - 1) {
461
- this.setState({
462
- selected: {
463
- row: this.state.selected.row - 1,
464
- cell: this.state.selected.cell,
465
- },
334
+ if (selectedCell.row === table.rows.length - 1) {
335
+ setSelectedCell({
336
+ row: selectedCell.row - 1,
337
+ cell: selectedCell.cell,
466
338
  });
467
339
  }
468
340
 
469
- this.props.onChangeBlock(this.props.block, {
470
- ...this.props.data,
341
+ onChangeBlock(block, {
342
+ ...data,
471
343
  table: {
472
344
  ...table,
473
- rows: remove(
474
- table.rows,
475
- (row, index) => index !== this.state.selected.row,
476
- ),
345
+ rows: remove(table.rows, (row, index) => index !== selectedCell.row),
477
346
  },
478
347
  });
479
- }
480
-
481
- componentDidUpdate(prevProps) {
482
- if (prevProps.selected && !this.props.selected) {
483
- this.setState({ selected: null });
484
- }
485
- }
486
-
487
- /**
488
- * Render method.
489
- * @method render
490
- * @returns {string} Markup for the component.
491
- */
492
- render() {
493
- const headers = this.props.data.table?.rows?.[0]?.cells || [];
494
- const rows =
495
- this.props.data.table?.rows?.filter((_, index) => index > 0) || [];
496
- const schema = TableSchema(this.props);
497
-
498
- return (
499
- // TODO: use slate-table instead of table, but first copy the CSS styles
500
- // to the new name
501
- <div className={cx('block table', { selected: this.props.selected })}>
502
- {this.props.selected && (
503
- <div className="toolbar">
504
- <Button.Group>
505
- <Button
506
- icon
507
- basic
508
- onClick={this.onInsertRowBefore}
509
- title={this.props.intl.formatMessage(messages.insertRowBefore)}
510
- aria-label={this.props.intl.formatMessage(
511
- messages.insertRowBefore,
512
- )}
513
- >
514
- <Icon name={rowBeforeSVG} size="24px" />
515
- </Button>
516
- </Button.Group>
517
- <Button.Group>
518
- <Button
519
- icon
520
- basic
521
- onClick={this.onInsertRowAfter}
522
- title={this.props.intl.formatMessage(messages.insertRowAfter)}
523
- aria-label={this.props.intl.formatMessage(
524
- messages.insertRowAfter,
525
- )}
526
- >
527
- <Icon name={rowAfterSVG} size="24px" />
528
- </Button>
529
- </Button.Group>
530
- <Button.Group>
531
- <Button
532
- icon
533
- basic
534
- onClick={this.onDeleteRow}
535
- disabled={this.props.data.table?.rows?.length === 1}
536
- title={this.props.intl.formatMessage(messages.deleteRow)}
537
- aria-label={this.props.intl.formatMessage(messages.deleteRow)}
538
- >
539
- <Icon name={rowDeleteSVG} size="24px" />
540
- </Button>
541
- </Button.Group>
542
- <Button.Group>
543
- <Button
544
- icon
545
- basic
546
- onClick={this.onInsertColBefore}
547
- title={this.props.intl.formatMessage(messages.insertColBefore)}
548
- aria-label={this.props.intl.formatMessage(
549
- messages.insertColBefore,
550
- )}
551
- >
552
- <Icon name={colBeforeSVG} size="24px" />
553
- </Button>
554
- </Button.Group>
555
- <Button.Group>
556
- <Button
557
- icon
558
- basic
559
- onClick={this.onInsertColAfter}
560
- title={this.props.intl.formatMessage(messages.insertColAfter)}
561
- aria-label={this.props.intl.formatMessage(
562
- messages.insertColAfter,
563
- )}
564
- >
565
- <Icon name={colAfterSVG} size="24px" />
566
- </Button>
567
- </Button.Group>
568
- <Button.Group>
569
- <Button
570
- icon
571
- basic
572
- onClick={this.onDeleteCol}
573
- disabled={this.props.data.table?.rows?.[0].cells.length === 1}
574
- title={this.props.intl.formatMessage(messages.deleteCol)}
575
- aria-label={this.props.intl.formatMessage(messages.deleteCol)}
576
- >
577
- <Icon name={colDeleteSVG} size="24px" />
578
- </Button>
579
- </Button.Group>
580
- </div>
581
- )}
582
- {this.props.data.table && (
583
- <Table
584
- fixed={this.props.data.table.fixed}
585
- compact={this.props.data.table.compact}
586
- basic={this.props.data.table.basic ? 'very' : false}
587
- celled={this.props.data.table.celled}
588
- inverted={this.props.data.table.inverted}
589
- striped={this.props.data.table.striped}
590
- className="slate-table-block"
591
- unstackable
592
- >
593
- {!this.props.data.table.hideHeaders ? (
594
- <Table.Header>
595
- <Table.Row textAlign="left">
596
- {headers.map((cell, cellIndex) => (
597
- <Table.HeaderCell
598
- key={cell.key}
599
- textAlign="left"
600
- verticalAlign="middle"
601
- >
602
- <Cell
603
- value={cell.value}
604
- row={0}
605
- cell={cellIndex}
606
- onSelectCell={this.onSelectCell}
607
- selected={
608
- this.props.selected &&
609
- this.state.selected &&
610
- 0 === this.state.selected.row &&
611
- cellIndex === this.state.selected.cell
612
- }
613
- selectedCell={this.state.selected}
614
- isTableBlockSelected={this.props.selected}
615
- onAddBlock={this.props.onAddBlock}
616
- onSelectBlock={this.props.onSelectBlock}
617
- onChange={this.onChangeCell}
618
- index={this.props.index}
619
- />
620
- </Table.HeaderCell>
621
- ))}
622
- </Table.Row>
623
- </Table.Header>
624
- ) : (
625
- ''
626
- )}
627
- <Table.Body>
628
- {map(rows, (row, rowIndex) => (
629
- <Table.Row key={row.key}>
630
- {map(row.cells, (cell, cellIndex) => (
631
- <Table.Cell
632
- key={cell.key}
633
- textAlign="left"
634
- verticalAlign="middle"
635
- className={
636
- this.props.selected &&
637
- this.state.selected &&
638
- rowIndex + 1 === this.state.selected.row &&
639
- cellIndex === this.state.selected.cell &&
640
- this.props.selected
641
- ? 'selected'
642
- : ''
348
+ }, [data, block, onChangeBlock, selectedCell]);
349
+
350
+ return (
351
+ <div className={cx('block table', { selected })}>
352
+ {selected && (
353
+ <div className="toolbar">
354
+ <Button.Group>
355
+ <Button
356
+ icon
357
+ basic
358
+ onClick={onInsertRowBefore}
359
+ title={intl.formatMessage(messages.insertRowBefore)}
360
+ aria-label={intl.formatMessage(messages.insertRowBefore)}
361
+ >
362
+ <Icon name={rowBeforeSVG} size="24px" />
363
+ </Button>
364
+ </Button.Group>
365
+ <Button.Group>
366
+ <Button
367
+ icon
368
+ basic
369
+ onClick={onInsertRowAfter}
370
+ title={intl.formatMessage(messages.insertRowAfter)}
371
+ aria-label={intl.formatMessage(messages.insertRowAfter)}
372
+ >
373
+ <Icon name={rowAfterSVG} size="24px" />
374
+ </Button>
375
+ </Button.Group>
376
+ <Button.Group>
377
+ <Button
378
+ icon
379
+ basic
380
+ onClick={onDeleteRow}
381
+ disabled={data.table?.rows?.length === 1}
382
+ title={intl.formatMessage(messages.deleteRow)}
383
+ aria-label={intl.formatMessage(messages.deleteRow)}
384
+ >
385
+ <Icon name={rowDeleteSVG} size="24px" />
386
+ </Button>
387
+ </Button.Group>
388
+ <Button.Group>
389
+ <Button
390
+ icon
391
+ basic
392
+ onClick={onInsertColBefore}
393
+ title={intl.formatMessage(messages.insertColBefore)}
394
+ aria-label={intl.formatMessage(messages.insertColBefore)}
395
+ >
396
+ <Icon name={colBeforeSVG} size="24px" />
397
+ </Button>
398
+ </Button.Group>
399
+ <Button.Group>
400
+ <Button
401
+ icon
402
+ basic
403
+ onClick={onInsertColAfter}
404
+ title={intl.formatMessage(messages.insertColAfter)}
405
+ aria-label={intl.formatMessage(messages.insertColAfter)}
406
+ >
407
+ <Icon name={colAfterSVG} size="24px" />
408
+ </Button>
409
+ </Button.Group>
410
+ <Button.Group>
411
+ <Button
412
+ icon
413
+ basic
414
+ onClick={onDeleteCol}
415
+ disabled={data.table?.rows?.[0].cells.length === 1}
416
+ title={intl.formatMessage(messages.deleteCol)}
417
+ aria-label={intl.formatMessage(messages.deleteCol)}
418
+ >
419
+ <Icon name={colDeleteSVG} size="24px" />
420
+ </Button>
421
+ </Button.Group>
422
+ </div>
423
+ )}
424
+ {data.table && (
425
+ <Table
426
+ fixed={data.table.fixed}
427
+ compact={data.table.compact}
428
+ basic={data.table.basic ? 'very' : false}
429
+ celled={data.table.celled}
430
+ inverted={data.table.inverted}
431
+ striped={data.table.striped}
432
+ className="slate-table-block"
433
+ unstackable
434
+ >
435
+ {!data.table.hideHeaders ? (
436
+ <Table.Header>
437
+ <Table.Row textAlign="left">
438
+ {headers.map((cell, cellIndex) => (
439
+ <Table.HeaderCell
440
+ key={cell.key}
441
+ textAlign="left"
442
+ verticalAlign="middle"
443
+ >
444
+ <Cell
445
+ value={cell.value}
446
+ row={0}
447
+ cell={cellIndex}
448
+ onSelectCell={onSelectCell}
449
+ selected={
450
+ selected &&
451
+ selectedCell &&
452
+ 0 === selectedCell.row &&
453
+ cellIndex === selectedCell.cell
643
454
  }
644
- >
645
- <Cell
646
- value={cell.value}
647
- row={rowIndex + 1}
648
- cell={cellIndex}
649
- onSelectCell={this.onSelectCell}
650
- selected={
651
- this.props.selected &&
652
- this.state.selected &&
653
- rowIndex + 1 === this.state.selected.row &&
654
- cellIndex === this.state.selected.cell
655
- }
656
- selectedCell={this.state.selected}
657
- isTableBlockSelected={this.props.selected}
658
- onAddBlock={this.props.onAddBlock}
659
- onSelectBlock={this.props.onSelectBlock}
660
- onChange={this.onChangeCell}
661
- index={this.props.index}
662
- />
663
- </Table.Cell>
664
- ))}
665
- </Table.Row>
666
- ))}
667
- </Table.Body>
668
- </Table>
669
- )}
670
- {this.props.selected && this.state.selected && this.state.isClient && (
671
- <SidebarPortal selected={this.props.selected}>
672
- <BlockDataForm
673
- schema={schema}
674
- title={schema.title}
675
- onChangeField={(id, value) => {
676
- this.props.onChangeBlock(this.props.block, {
677
- ...this.props.data,
678
- [id]: value,
679
- });
680
- }}
681
- onChangeBlock={this.props.onChangeBlock}
682
- formData={this.props.data}
683
- block={this.props.block}
684
- blocksConfig={this.props.blocksConfig}
685
- />
686
- </SidebarPortal>
687
- )}
688
- </div>
689
- );
690
- }
691
- }
455
+ selectedCell={selectedCell}
456
+ isTableBlockSelected={selected}
457
+ onAddBlock={onAddBlock}
458
+ onSelectBlock={onSelectBlock}
459
+ onChange={onChangeCell}
460
+ index={index}
461
+ />
462
+ </Table.HeaderCell>
463
+ ))}
464
+ </Table.Row>
465
+ </Table.Header>
466
+ ) : (
467
+ ''
468
+ )}
469
+ <Table.Body>
470
+ {map(rows, (row, rowIndex) => (
471
+ <Table.Row key={row.key}>
472
+ {map(row.cells, (cell, cellIndex) => (
473
+ <Table.Cell
474
+ key={cell.key}
475
+ textAlign="left"
476
+ verticalAlign="middle"
477
+ className={
478
+ selected &&
479
+ selectedCell &&
480
+ rowIndex + 1 === selectedCell.row &&
481
+ cellIndex === selectedCell.cell &&
482
+ selected
483
+ ? 'selected'
484
+ : ''
485
+ }
486
+ >
487
+ <Cell
488
+ value={cell.value}
489
+ row={rowIndex + 1}
490
+ cell={cellIndex}
491
+ onSelectCell={onSelectCell}
492
+ selected={
493
+ selected &&
494
+ selectedCell &&
495
+ rowIndex + 1 === selectedCell.row &&
496
+ cellIndex === selectedCell.cell
497
+ }
498
+ selectedCell={selectedCell}
499
+ isTableBlockSelected={selected}
500
+ onAddBlock={onAddBlock}
501
+ onSelectBlock={onSelectBlock}
502
+ onChange={onChangeCell}
503
+ index={index}
504
+ />
505
+ </Table.Cell>
506
+ ))}
507
+ </Table.Row>
508
+ ))}
509
+ </Table.Body>
510
+ </Table>
511
+ )}
512
+ {selected && selectedCell && isClient && (
513
+ <SidebarPortal selected={selected}>
514
+ <BlockDataForm
515
+ schema={schema}
516
+ title={schema.title}
517
+ onChangeField={(id, value) => {
518
+ onChangeBlock(block, {
519
+ ...data,
520
+ [id]: value,
521
+ });
522
+ }}
523
+ onChangeBlock={onChangeBlock}
524
+ formData={data}
525
+ block={block}
526
+ blocksConfig={blocksConfig}
527
+ />
528
+ </SidebarPortal>
529
+ )}
530
+ </div>
531
+ );
532
+ };
533
+
534
+ Edit.propTypes = {
535
+ data: PropTypes.objectOf(PropTypes.any).isRequired,
536
+ detached: PropTypes.bool,
537
+ index: PropTypes.number.isRequired,
538
+ selected: PropTypes.bool.isRequired,
539
+ block: PropTypes.string.isRequired,
540
+ onAddBlock: PropTypes.func.isRequired,
541
+ onChangeBlock: PropTypes.func.isRequired,
542
+ onSelectBlock: PropTypes.func.isRequired,
543
+ blocksConfig: PropTypes.object,
544
+ };
692
545
 
693
- export default injectIntl(Edit);
546
+ export default Edit;
@@ -25,12 +25,6 @@ test('renders an edit table block component', () => {
25
25
  onAddBlock={() => {}}
26
26
  onChangeBlock={() => {}}
27
27
  onSelectBlock={() => {}}
28
- onDeleteBlock={() => {}}
29
- onInsertBlock={() => {}}
30
- onFocusPreviousBlock={() => {}}
31
- onFocusNextBlock={() => {}}
32
- handleKeyDown={() => {}}
33
- onMutateBlock={() => {}}
34
28
  index={1}
35
29
  />
36
30
  </Provider>,