@atlaskit/editor-plugin-table 5.8.2 → 5.8.4

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/nodeviews/TableComponent.js +17 -62
  3. package/dist/cjs/pm-plugins/drag-and-drop/plugin.js +153 -123
  4. package/dist/cjs/pm-plugins/drag-and-drop/utils/autoscrollers.js +53 -0
  5. package/dist/cjs/pm-plugins/drag-and-drop/utils/index.js +8 -1
  6. package/dist/cjs/ui/DragHandle/index.js +8 -3
  7. package/dist/cjs/ui/FloatingContextualMenu/ContextualMenu.js +2 -2
  8. package/dist/cjs/ui/FloatingDragMenu/DragMenu.js +103 -12
  9. package/dist/cjs/ui/FloatingDragMenu/index.js +2 -2
  10. package/dist/es2019/nodeviews/TableComponent.js +5 -55
  11. package/dist/es2019/pm-plugins/drag-and-drop/plugin.js +158 -124
  12. package/dist/es2019/pm-plugins/drag-and-drop/utils/autoscrollers.js +50 -0
  13. package/dist/es2019/pm-plugins/drag-and-drop/utils/index.js +2 -1
  14. package/dist/es2019/ui/DragHandle/index.js +10 -3
  15. package/dist/es2019/ui/FloatingContextualMenu/ContextualMenu.js +2 -2
  16. package/dist/es2019/ui/FloatingDragMenu/DragMenu.js +106 -11
  17. package/dist/es2019/ui/FloatingDragMenu/index.js +1 -1
  18. package/dist/esm/nodeviews/TableComponent.js +7 -52
  19. package/dist/esm/pm-plugins/drag-and-drop/plugin.js +152 -122
  20. package/dist/esm/pm-plugins/drag-and-drop/utils/autoscrollers.js +47 -0
  21. package/dist/esm/pm-plugins/drag-and-drop/utils/index.js +2 -1
  22. package/dist/esm/ui/DragHandle/index.js +8 -3
  23. package/dist/esm/ui/FloatingContextualMenu/ContextualMenu.js +2 -2
  24. package/dist/esm/ui/FloatingDragMenu/DragMenu.js +102 -11
  25. package/dist/esm/ui/FloatingDragMenu/index.js +1 -1
  26. package/dist/types/pm-plugins/drag-and-drop/utils/autoscrollers.d.ts +7 -0
  27. package/dist/types/pm-plugins/drag-and-drop/utils/index.d.ts +1 -0
  28. package/dist/types/ui/DragHandle/index.d.ts +5 -1
  29. package/dist/types/ui/FloatingDragMenu/DragMenu.d.ts +7 -2
  30. package/dist/types/utils/drag-menu.d.ts +2 -1
  31. package/dist/types-ts4.5/pm-plugins/drag-and-drop/utils/autoscrollers.d.ts +7 -0
  32. package/dist/types-ts4.5/pm-plugins/drag-and-drop/utils/index.d.ts +1 -0
  33. package/dist/types-ts4.5/ui/DragHandle/index.d.ts +5 -1
  34. package/dist/types-ts4.5/ui/FloatingDragMenu/DragMenu.d.ts +7 -2
  35. package/dist/types-ts4.5/utils/drag-menu.d.ts +2 -1
  36. package/package.json +2 -2
  37. package/src/__tests__/unit/ui/FloatingDragMenu.tsx +129 -105
  38. package/src/nodeviews/TableComponent.tsx +5 -49
  39. package/src/pm-plugins/drag-and-drop/plugin.ts +202 -157
  40. package/src/pm-plugins/drag-and-drop/utils/autoscrollers.ts +52 -0
  41. package/src/pm-plugins/drag-and-drop/utils/index.ts +2 -0
  42. package/src/ui/DragHandle/index.tsx +13 -2
  43. package/src/ui/FloatingContextualMenu/ContextualMenu.tsx +2 -2
  44. package/src/ui/FloatingDragMenu/DragMenu.tsx +137 -12
  45. package/src/ui/FloatingDragMenu/index.tsx +1 -1
  46. package/src/utils/drag-menu.ts +17 -1
@@ -9,7 +9,9 @@ import type { EditorView } from '@atlaskit/editor-prosemirror/view';
9
9
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
10
10
  import { CellSelection } from '@atlaskit/editor-tables/cell-selection';
11
11
  import { getCellsInRow } from '@atlaskit/editor-tables/utils';
12
+ import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
12
13
  import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/adapter/element';
14
+ import { combine } from '@atlaskit/pragmatic-drag-and-drop/util/combine';
13
15
 
14
16
  import type { DraggableSourceData } from '../../types';
15
17
  import {
@@ -32,6 +34,205 @@ import { pluginKey } from './plugin-key';
32
34
  import type { DragAndDropPluginState } from './types';
33
35
  import { getDraggableDataFromEvent } from './utils/monitor';
34
36
 
37
+ const destroyFn = (editorView: EditorView, editorAnalyticsAPI: any) => {
38
+ const editorPageScrollContainer = document.querySelector(
39
+ '.fabric-editor-popup-scroll-parent',
40
+ );
41
+
42
+ const rowAutoScrollers = editorPageScrollContainer
43
+ ? [
44
+ monitorForElements({
45
+ canMonitor({ source }) {
46
+ const { type } = source.data as Partial<DraggableSourceData>;
47
+ return type === 'table-row';
48
+ },
49
+ onDragStart() {
50
+ // auto scroller doesn't work when scroll-behavior: smooth is set, this monitor temporarily removes it via inline styles
51
+ (editorPageScrollContainer as HTMLElement).style.setProperty(
52
+ 'scroll-behavior',
53
+ 'unset',
54
+ );
55
+ },
56
+ onDrop() {
57
+ // 'null' will remove the inline style
58
+ (editorPageScrollContainer as HTMLElement).style.setProperty(
59
+ 'scroll-behavior',
60
+ null,
61
+ );
62
+ },
63
+ }),
64
+ autoScrollForElements({
65
+ element: editorPageScrollContainer as HTMLElement,
66
+ canScroll: ({ source }) => {
67
+ const { type } = source.data as Partial<DraggableSourceData>;
68
+ return type === 'table-row';
69
+ },
70
+ }),
71
+ ]
72
+ : [];
73
+
74
+ return combine(
75
+ ...rowAutoScrollers,
76
+ monitorForElements({
77
+ canMonitor({ source }) {
78
+ const { type, localId, indexes } =
79
+ source.data as Partial<DraggableSourceData>;
80
+
81
+ // First; Perform any quick checks so we can abort early.
82
+ if (
83
+ !indexes ||
84
+ !localId ||
85
+ // FIXME: We currently don't support DragNDrop of multiple elements. For now we will not bother to monitor drags
86
+ // of more then 1 item.
87
+ indexes.length !== 1 ||
88
+ !(type === 'table-row' || type === 'table-column')
89
+ ) {
90
+ return false;
91
+ }
92
+
93
+ const { tableNode } = getTablePluginState(editorView.state);
94
+ // If the draggable localId is the same as the current selected table localId then we will allow the monitor
95
+ // watch for changes
96
+ return localId === tableNode?.attrs.localId;
97
+ },
98
+ onDragStart: ({ location }) => {
99
+ toggleDragMenu(false)(editorView.state, editorView.dispatch);
100
+ },
101
+ onDrag(event) {
102
+ const data = getDraggableDataFromEvent(event);
103
+ // If no data can be found then it's most like we do not want to perform any drag actions
104
+ if (!data) {
105
+ clearDropTarget()(editorView.state, editorView.dispatch);
106
+ return;
107
+ }
108
+
109
+ // TODO: as we drag an element around we are going to want to update the state to acurately reflect the current
110
+ // insert location as to where the draggable will most likely be go. For example;
111
+ const { sourceType, targetAdjustedIndex } = data;
112
+ const dropTargetType =
113
+ sourceType === 'table-row'
114
+ ? DropTargetType.ROW
115
+ : DropTargetType.COLUMN;
116
+
117
+ const hasMergedCells = hasMergedCellsInBetween(
118
+ [targetAdjustedIndex - 1, targetAdjustedIndex],
119
+ dropTargetType,
120
+ )(editorView.state.selection);
121
+
122
+ setDropTarget(
123
+ dropTargetType,
124
+ targetAdjustedIndex,
125
+ hasMergedCells,
126
+ )(editorView.state, editorView.dispatch);
127
+ },
128
+ onDrop(event) {
129
+ const data = getDraggableDataFromEvent(event);
130
+
131
+ // On Drop we need to update the table main plugin hoveredCell value with the current row/col that the mouse is
132
+ // over. This is so the drag handles update their positions to correctly align with the users mouse. Unfortunately
133
+ // at this point in time and during the drag opertation, the drop targets are eating all the mouse events so
134
+ // it's not possible to know what row/col the mouse is over (via mouse events). This attempts to locate the nearest cell and
135
+ // then tries to update the main table hoveredCell value by piggy-backing the transaction onto the command
136
+ // triggered by this on drop event.
137
+ const { hoveredCell } = getTablePluginState(editorView.state);
138
+
139
+ const cell = findNearestCellIndexToPoint(
140
+ event.location.current.input.clientX,
141
+ event.location.current.input.clientY,
142
+ );
143
+ const tr = editorView.state.tr;
144
+ const action = {
145
+ type: 'HOVER_CELL',
146
+ data: {
147
+ hoveredCell: {
148
+ rowIndex: cell?.row ?? hoveredCell.rowIndex,
149
+ colIndex: cell?.col ?? hoveredCell.colIndex,
150
+ },
151
+ },
152
+ };
153
+ tr.setMeta(tablePluginKey, action);
154
+
155
+ // If no data can be found then it's most like we do not want to perform any drop action
156
+ if (!data) {
157
+ // If we're able to determine the source type of the dropped element then we should report to analytics that
158
+ // the drop event was cancelled. Otherwise we will cancel silently.
159
+ if (
160
+ event?.source?.data?.type === 'table-row' ||
161
+ event?.source?.data?.type === 'table-column'
162
+ ) {
163
+ return clearDropTargetWithAnalytics(editorAnalyticsAPI)(
164
+ INPUT_METHOD.DRAG_AND_DROP,
165
+ event.source.data.type,
166
+ TABLE_STATUS.CANCELLED,
167
+ tr,
168
+ )(editorView.state, editorView.dispatch);
169
+ }
170
+ return clearDropTarget(tr)(editorView.state, editorView.dispatch);
171
+ }
172
+
173
+ const {
174
+ sourceType,
175
+ sourceIndexes,
176
+ targetIndex,
177
+ targetAdjustedIndex,
178
+ direction,
179
+ } = data;
180
+
181
+ // When we drop on a target we will know the targets row/col index for certain,
182
+ if (sourceType === 'table-row') {
183
+ action.data.hoveredCell.rowIndex = targetIndex;
184
+ } else {
185
+ action.data.hoveredCell.colIndex = targetIndex;
186
+ }
187
+
188
+ // If the drop target index contains merged cells then we should not allow the drop to occur.
189
+ if (
190
+ hasMergedCellsInBetween(
191
+ [targetAdjustedIndex - 1, targetAdjustedIndex],
192
+ sourceType === 'table-row'
193
+ ? DropTargetType.ROW
194
+ : DropTargetType.COLUMN,
195
+ )(editorView.state.selection)
196
+ ) {
197
+ clearDropTargetWithAnalytics(editorAnalyticsAPI)(
198
+ INPUT_METHOD.DRAG_AND_DROP,
199
+ sourceType,
200
+ // This event is mrked as invalid because the user is attempting to drop an element in an area which has merged cells.
201
+ TABLE_STATUS.INVALID,
202
+ tr,
203
+ )(editorView.state, editorView.dispatch);
204
+ return;
205
+ }
206
+
207
+ const [sourceIndex] = sourceIndexes;
208
+
209
+ requestAnimationFrame(() => {
210
+ moveSourceWithAnalytics(editorAnalyticsAPI)(
211
+ INPUT_METHOD.DRAG_AND_DROP,
212
+ sourceType,
213
+ sourceIndex,
214
+ targetAdjustedIndex + (direction === -1 ? 0 : -1),
215
+ tr,
216
+ )(editorView.state, editorView.dispatch);
217
+
218
+ // force a colgroup update here, otherwise dropped columns don't have
219
+ // the correct width immediately after the drop
220
+ if (sourceType === 'table-column') {
221
+ const { tableRef, tableNode } = getTablePluginState(
222
+ editorView.state,
223
+ );
224
+ if (tableRef && tableNode) {
225
+ insertColgroupFromNode(tableRef, tableNode);
226
+ }
227
+ }
228
+
229
+ editorView.focus();
230
+ });
231
+ },
232
+ }),
233
+ );
234
+ };
235
+
35
236
  export const createPlugin = (
36
237
  dispatch: Dispatch,
37
238
  eventDispatcher: EventDispatcher,
@@ -122,163 +323,7 @@ export const createPlugin = (
122
323
  },
123
324
  view: (editorView: EditorView) => {
124
325
  return {
125
- destroy: monitorForElements({
126
- canMonitor({ source }) {
127
- const { type, localId, indexes } =
128
- source.data as Partial<DraggableSourceData>;
129
-
130
- // First; Perform any quick checks so we can abort early.
131
- if (
132
- !indexes ||
133
- !localId ||
134
- // FIXME: We currently don't support DragNDrop of multiple elements. For now we will not bother to monitor drags
135
- // of more then 1 item.
136
- indexes.length !== 1 ||
137
- !(type === 'table-row' || type === 'table-column')
138
- ) {
139
- return false;
140
- }
141
-
142
- const { tableNode } = getTablePluginState(editorView.state);
143
- // If the draggable localId is the same as the current selected table localId then we will allow the monitor
144
- // watch for changes
145
- return localId === tableNode?.attrs.localId;
146
- },
147
- onDragStart: ({ location }) => {
148
- toggleDragMenu(false)(editorView.state, editorView.dispatch);
149
- },
150
- onDrag(event) {
151
- const data = getDraggableDataFromEvent(event);
152
- // If no data can be found then it's most like we do not want to perform any drag actions
153
- if (!data) {
154
- clearDropTarget()(editorView.state, editorView.dispatch);
155
- return;
156
- }
157
-
158
- // TODO: as we drag an element around we are going to want to update the state to acurately reflect the current
159
- // insert location as to where the draggable will most likely be go. For example;
160
- const { sourceType, targetAdjustedIndex } = data;
161
- const dropTargetType =
162
- sourceType === 'table-row'
163
- ? DropTargetType.ROW
164
- : DropTargetType.COLUMN;
165
-
166
- const hasMergedCells = hasMergedCellsInBetween(
167
- [targetAdjustedIndex - 1, targetAdjustedIndex],
168
- dropTargetType,
169
- )(editorView.state.selection);
170
-
171
- setDropTarget(
172
- dropTargetType,
173
- targetAdjustedIndex,
174
- hasMergedCells,
175
- )(editorView.state, editorView.dispatch);
176
- },
177
- onDrop(event) {
178
- const data = getDraggableDataFromEvent(event);
179
-
180
- // On Drop we need to update the table main plugin hoveredCell value with the current row/col that the mouse is
181
- // over. This is so the drag handles update their positions to correctly align with the users mouse. Unfortunately
182
- // at this point in time and during the drag opertation, the drop targets are eating all the mouse events so
183
- // it's not possible to know what row/col the mouse is over (via mouse events). This attempts to locate the nearest cell and
184
- // then tries to update the main table hoveredCell value by piggy-backing the transaction onto the command
185
- // triggered by this on drop event.
186
- const { hoveredCell } = getTablePluginState(editorView.state);
187
-
188
- const cell = findNearestCellIndexToPoint(
189
- event.location.current.input.clientX,
190
- event.location.current.input.clientY,
191
- );
192
- const tr = editorView.state.tr;
193
- const action = {
194
- type: 'HOVER_CELL',
195
- data: {
196
- hoveredCell: {
197
- rowIndex: cell?.row ?? hoveredCell.rowIndex,
198
- colIndex: cell?.col ?? hoveredCell.colIndex,
199
- },
200
- },
201
- };
202
- tr.setMeta(tablePluginKey, action);
203
-
204
- // If no data can be found then it's most like we do not want to perform any drop action
205
- if (!data) {
206
- // If we're able to determine the source type of the dropped element then we should report to analytics that
207
- // the drop event was cancelled. Otherwise we will cancel silently.
208
- if (
209
- event?.source?.data?.type === 'table-row' ||
210
- event?.source?.data?.type === 'table-column'
211
- ) {
212
- return clearDropTargetWithAnalytics(editorAnalyticsAPI)(
213
- INPUT_METHOD.DRAG_AND_DROP,
214
- event.source.data.type,
215
- TABLE_STATUS.CANCELLED,
216
- tr,
217
- )(editorView.state, editorView.dispatch);
218
- }
219
- return clearDropTarget(tr)(editorView.state, editorView.dispatch);
220
- }
221
-
222
- const {
223
- sourceType,
224
- sourceIndexes,
225
- targetIndex,
226
- targetAdjustedIndex,
227
- direction,
228
- } = data;
229
-
230
- // When we drop on a target we will know the targets row/col index for certain,
231
- if (sourceType === 'table-row') {
232
- action.data.hoveredCell.rowIndex = targetIndex;
233
- } else {
234
- action.data.hoveredCell.colIndex = targetIndex;
235
- }
236
-
237
- // If the drop target index contains merged cells then we should not allow the drop to occur.
238
- if (
239
- hasMergedCellsInBetween(
240
- [targetAdjustedIndex - 1, targetAdjustedIndex],
241
- sourceType === 'table-row'
242
- ? DropTargetType.ROW
243
- : DropTargetType.COLUMN,
244
- )(editorView.state.selection)
245
- ) {
246
- clearDropTargetWithAnalytics(editorAnalyticsAPI)(
247
- INPUT_METHOD.DRAG_AND_DROP,
248
- sourceType,
249
- // This event is mrked as invalid because the user is attempting to drop an element in an area which has merged cells.
250
- TABLE_STATUS.INVALID,
251
- tr,
252
- )(editorView.state, editorView.dispatch);
253
- return;
254
- }
255
-
256
- const [sourceIndex] = sourceIndexes;
257
-
258
- requestAnimationFrame(() => {
259
- moveSourceWithAnalytics(editorAnalyticsAPI)(
260
- INPUT_METHOD.DRAG_AND_DROP,
261
- sourceType,
262
- sourceIndex,
263
- targetAdjustedIndex + (direction === -1 ? 0 : -1),
264
- tr,
265
- )(editorView.state, editorView.dispatch);
266
-
267
- // force a colgroup update here, otherwise dropped columns don't have
268
- // the correct width immediately after the drop
269
- if (sourceType === 'table-column') {
270
- const { tableRef, tableNode } = getTablePluginState(
271
- editorView.state,
272
- );
273
- if (tableRef && tableNode) {
274
- insertColgroupFromNode(tableRef, tableNode);
275
- }
276
- }
277
-
278
- editorView.focus();
279
- });
280
- },
281
- }),
326
+ destroy: destroyFn(editorView, editorAnalyticsAPI),
282
327
  };
283
328
  },
284
329
  props: {
@@ -0,0 +1,52 @@
1
+ import type { Node as PmNode } from '@atlaskit/editor-prosemirror/model';
2
+ import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
3
+ import { unsafeOverflowAutoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/unsafe-overflow/element';
4
+
5
+ import type { DraggableSourceData } from '../../../types';
6
+ import { dropTargetExtendedWidth } from '../../../ui/consts';
7
+
8
+ type AutoScrollerFactory = {
9
+ tableWrapper: HTMLElement;
10
+ getNode: () => PmNode;
11
+ };
12
+
13
+ export const autoScrollerFactory = ({
14
+ tableWrapper,
15
+ getNode,
16
+ }: AutoScrollerFactory) => {
17
+ return [
18
+ autoScrollForElements({
19
+ element: tableWrapper,
20
+ canScroll: ({ source }) => {
21
+ const { localId, type } = source.data as Partial<DraggableSourceData>;
22
+ const node = getNode();
23
+ return localId === node?.attrs.localId && type === 'table-column';
24
+ },
25
+ }),
26
+ unsafeOverflowAutoScrollForElements({
27
+ element: tableWrapper,
28
+ canScroll: ({ source }) => {
29
+ const { localId, type } = source.data as Partial<DraggableSourceData>;
30
+ const node = getNode();
31
+ return localId === node?.attrs.localId && type === 'table-column';
32
+ },
33
+ getOverflow: () => ({
34
+ fromTopEdge: {
35
+ top: dropTargetExtendedWidth,
36
+ right: dropTargetExtendedWidth,
37
+ left: dropTargetExtendedWidth,
38
+ },
39
+ fromRightEdge: {
40
+ right: dropTargetExtendedWidth,
41
+ top: dropTargetExtendedWidth,
42
+ bottom: dropTargetExtendedWidth,
43
+ },
44
+ fromLeftEdge: {
45
+ top: dropTargetExtendedWidth,
46
+ left: dropTargetExtendedWidth,
47
+ bottom: dropTargetExtendedWidth,
48
+ },
49
+ }),
50
+ }),
51
+ ];
52
+ };
@@ -1 +1,3 @@
1
1
  export { getDraggableDataFromEvent } from './monitor';
2
+
3
+ export { autoScrollerFactory } from './autoscrollers';
@@ -3,7 +3,10 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
3
3
 
4
4
  import classnames from 'classnames';
5
5
  import ReactDOM from 'react-dom';
6
+ import type { WrappedComponentProps } from 'react-intl-next';
7
+ import { injectIntl } from 'react-intl-next';
6
8
 
9
+ import { tableMessages as messages } from '@atlaskit/editor-common/messages';
7
10
  import { browser } from '@atlaskit/editor-common/utils';
8
11
  import type { EditorView } from '@atlaskit/editor-prosemirror/view';
9
12
  import { draggable } from '@atlaskit/pragmatic-drag-and-drop/adapter/element';
@@ -36,7 +39,7 @@ type DragHandleProps = {
36
39
  isDragMenuTarget: boolean; // this is identify which current handle component is
37
40
  };
38
41
 
39
- export const DragHandle = ({
42
+ const DragHandleComponent = ({
40
43
  isDragMenuTarget,
41
44
  tableLocalId,
42
45
  direction = 'row',
@@ -51,7 +54,8 @@ export const DragHandle = ({
51
54
  onClick,
52
55
  editorView,
53
56
  canDrag = false,
54
- }: DragHandleProps) => {
57
+ intl: { formatMessage },
58
+ }: DragHandleProps & WrappedComponentProps) => {
55
59
  const dragHandleDivRef = useRef<HTMLButtonElement>(null);
56
60
  const [previewContainer, setPreviewContainer] = useState<HTMLElement | null>(
57
61
  null,
@@ -154,6 +158,11 @@ export const DragHandle = ({
154
158
  transform: direction === 'column' ? 'none' : 'rotate(90deg)',
155
159
  }}
156
160
  data-testid="table-drag-handle-button"
161
+ aria-label={formatMessage(
162
+ direction === 'row'
163
+ ? messages.rowDragHandle
164
+ : messages.columnDragHandle,
165
+ )}
157
166
  onMouseOver={onMouseOver}
158
167
  onMouseOut={onMouseOut}
159
168
  onMouseUp={(e) => {
@@ -187,3 +196,5 @@ export const DragHandle = ({
187
196
  </>
188
197
  );
189
198
  };
199
+
200
+ export const DragHandle = injectIntl(DragHandleComponent);
@@ -303,7 +303,7 @@ export class ContextualMenu extends Component<
303
303
  content: formatMessage(
304
304
  isDragAndDropEnabled &&
305
305
  getBooleanFF('platform.editor.table.new-cell-context-menu-styling')
306
- ? messages.addColumn
306
+ ? messages.addColumnRight
307
307
  : messages.insertColumn,
308
308
  ),
309
309
  value: { name: 'insert_column' },
@@ -327,7 +327,7 @@ export class ContextualMenu extends Component<
327
327
  content: formatMessage(
328
328
  isDragAndDropEnabled &&
329
329
  getBooleanFF('platform.editor.table.new-cell-context-menu-styling')
330
- ? messages.addRow
330
+ ? messages.addRowBelow
331
331
  : messages.insertRow,
332
332
  ),
333
333
  value: { name: 'insert_row' },