@atlaskit/editor-plugin-table 7.16.11 → 7.16.13

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 (254) hide show
  1. package/.eslintrc.js +3 -3
  2. package/CHANGELOG.md +16 -0
  3. package/dist/cjs/commands/misc.js +3 -3
  4. package/dist/cjs/nodeviews/TableCell.js +10 -10
  5. package/dist/cjs/nodeviews/TableContainer.js +83 -27
  6. package/dist/cjs/nodeviews/TableResizer.js +40 -19
  7. package/dist/cjs/nodeviews/TableRow.js +23 -23
  8. package/dist/cjs/pm-plugins/table-resizing/plugin.js +3 -3
  9. package/dist/cjs/pm-plugins/table-resizing/utils/resize-state.js +4 -4
  10. package/dist/cjs/pm-plugins/table-resizing/utils/scale-table.js +3 -3
  11. package/dist/cjs/ui/FloatingContextualMenu/styles.js +1 -1
  12. package/dist/cjs/ui/FloatingDragMenu/styles.js +1 -1
  13. package/dist/cjs/ui/common-styles.js +13 -13
  14. package/dist/cjs/ui/ui-styles.js +25 -25
  15. package/dist/cjs/utils/guidelines.js +7 -4
  16. package/dist/cjs/utils/merged-cells.js +3 -3
  17. package/dist/cjs/utils/snapping.js +7 -8
  18. package/dist/es2019/commands/misc.js +3 -3
  19. package/dist/es2019/nodeviews/TableContainer.js +70 -9
  20. package/dist/es2019/nodeviews/TableResizer.js +42 -21
  21. package/dist/es2019/nodeviews/TableRow.js +21 -21
  22. package/dist/es2019/pm-plugins/table-resizing/plugin.js +3 -3
  23. package/dist/es2019/pm-plugins/table-resizing/utils/resize-state.js +4 -4
  24. package/dist/es2019/pm-plugins/table-resizing/utils/scale-table.js +3 -3
  25. package/dist/es2019/ui/FloatingContextualMenu/styles.js +47 -47
  26. package/dist/es2019/ui/FloatingDragMenu/styles.js +30 -30
  27. package/dist/es2019/ui/common-styles.js +802 -816
  28. package/dist/es2019/ui/ui-styles.js +665 -678
  29. package/dist/es2019/utils/guidelines.js +5 -2
  30. package/dist/es2019/utils/merged-cells.js +3 -3
  31. package/dist/es2019/utils/snapping.js +5 -6
  32. package/dist/esm/commands/misc.js +3 -3
  33. package/dist/esm/nodeviews/TableCell.js +10 -10
  34. package/dist/esm/nodeviews/TableContainer.js +85 -29
  35. package/dist/esm/nodeviews/TableResizer.js +42 -21
  36. package/dist/esm/nodeviews/TableRow.js +23 -23
  37. package/dist/esm/pm-plugins/table-resizing/plugin.js +3 -3
  38. package/dist/esm/pm-plugins/table-resizing/utils/resize-state.js +4 -4
  39. package/dist/esm/pm-plugins/table-resizing/utils/scale-table.js +3 -3
  40. package/dist/esm/ui/FloatingContextualMenu/styles.js +1 -1
  41. package/dist/esm/ui/FloatingDragMenu/styles.js +1 -1
  42. package/dist/esm/ui/common-styles.js +13 -13
  43. package/dist/esm/ui/ui-styles.js +25 -25
  44. package/dist/esm/utils/guidelines.js +6 -3
  45. package/dist/esm/utils/merged-cells.js +3 -3
  46. package/dist/esm/utils/snapping.js +6 -7
  47. package/dist/types/nodeviews/TableResizer.d.ts +2 -1
  48. package/dist/types/pm-plugins/decorations/utils/index.d.ts +1 -1
  49. package/dist/types/pm-plugins/drag-and-drop/utils/autoscrollers.d.ts +1 -1
  50. package/dist/types/pm-plugins/drag-and-drop/utils/getDragBehaviour.d.ts +1 -1
  51. package/dist/types/pm-plugins/table-resizing/utils/index.d.ts +1 -1
  52. package/dist/types/ui/ColumnResizeWidget/index.d.ts +1 -1
  53. package/dist/types/ui/FloatingAlignmentButtons/FloatingAlignmentButtons.d.ts +1 -1
  54. package/dist/types/ui/FloatingToolbarLabel/FloatingToolbarLabel.d.ts +1 -1
  55. package/dist/types/ui/TableFloatingColumnControls/ColumnDropTargets/ColumnDropTarget.d.ts +1 -1
  56. package/dist/types/ui/TableFloatingControls/CornerControls/index.d.ts +1 -1
  57. package/dist/types/ui/icons/DragHandleDisabledIcon.d.ts +1 -1
  58. package/dist/types/utils/guidelines.d.ts +2 -1
  59. package/dist/types/utils/snapping.d.ts +3 -2
  60. package/dist/types-ts4.5/nodeviews/TableResizer.d.ts +2 -1
  61. package/dist/types-ts4.5/pm-plugins/decorations/utils/index.d.ts +1 -1
  62. package/dist/types-ts4.5/pm-plugins/drag-and-drop/utils/autoscrollers.d.ts +1 -1
  63. package/dist/types-ts4.5/pm-plugins/drag-and-drop/utils/getDragBehaviour.d.ts +1 -1
  64. package/dist/types-ts4.5/pm-plugins/table-resizing/utils/index.d.ts +1 -1
  65. package/dist/types-ts4.5/ui/ColumnResizeWidget/index.d.ts +1 -1
  66. package/dist/types-ts4.5/ui/FloatingAlignmentButtons/FloatingAlignmentButtons.d.ts +1 -1
  67. package/dist/types-ts4.5/ui/FloatingToolbarLabel/FloatingToolbarLabel.d.ts +1 -1
  68. package/dist/types-ts4.5/ui/TableFloatingColumnControls/ColumnDropTargets/ColumnDropTarget.d.ts +1 -1
  69. package/dist/types-ts4.5/ui/TableFloatingControls/CornerControls/index.d.ts +1 -1
  70. package/dist/types-ts4.5/ui/icons/DragHandleDisabledIcon.d.ts +1 -1
  71. package/dist/types-ts4.5/utils/guidelines.d.ts +2 -1
  72. package/dist/types-ts4.5/utils/snapping.d.ts +3 -2
  73. package/docs/0-intro.tsx +9 -7
  74. package/package.json +3 -3
  75. package/report.api.md +67 -66
  76. package/src/commands/clear.ts +36 -44
  77. package/src/commands/collapse.ts +8 -8
  78. package/src/commands/column-resize.ts +412 -452
  79. package/src/commands/delete.ts +14 -14
  80. package/src/commands/display-mode.ts +10 -11
  81. package/src/commands/go-to-next-cell.ts +48 -54
  82. package/src/commands/hover.ts +210 -227
  83. package/src/commands/index.ts +35 -35
  84. package/src/commands/insert.ts +208 -235
  85. package/src/commands/misc.ts +655 -748
  86. package/src/commands/referentiality.ts +9 -9
  87. package/src/commands/selection.ts +433 -563
  88. package/src/commands/sort.ts +68 -86
  89. package/src/commands/split-cell.ts +14 -14
  90. package/src/commands/toggle.ts +69 -67
  91. package/src/commands-with-analytics.ts +570 -639
  92. package/src/create-plugin-config.ts +13 -13
  93. package/src/event-handlers.ts +513 -612
  94. package/src/handlers.ts +120 -133
  95. package/src/nodeviews/ExternalDropTargets.tsx +68 -73
  96. package/src/nodeviews/OverflowShadowsObserver.ts +148 -157
  97. package/src/nodeviews/TableCell.ts +47 -54
  98. package/src/nodeviews/TableComponent.tsx +1018 -1112
  99. package/src/nodeviews/TableComponentWithSharedState.tsx +91 -94
  100. package/src/nodeviews/TableContainer.tsx +384 -340
  101. package/src/nodeviews/TableNodeViewBase.ts +19 -24
  102. package/src/nodeviews/TableResizer.tsx +642 -653
  103. package/src/nodeviews/TableRow.ts +580 -629
  104. package/src/nodeviews/TableStickyScrollbar.ts +173 -190
  105. package/src/nodeviews/__mocks__/OverflowShadowsObserver.ts +8 -8
  106. package/src/nodeviews/__mocks__/OverridableMock.ts +14 -15
  107. package/src/nodeviews/table.tsx +345 -375
  108. package/src/nodeviews/types.ts +21 -24
  109. package/src/nodeviews/update-overflow-shadows.ts +8 -14
  110. package/src/plugin.tsx +578 -603
  111. package/src/pm-plugins/analytics/actions.ts +10 -12
  112. package/src/pm-plugins/analytics/commands.ts +31 -37
  113. package/src/pm-plugins/analytics/plugin-factory.ts +4 -2
  114. package/src/pm-plugins/analytics/plugin-key.ts +1 -3
  115. package/src/pm-plugins/analytics/plugin.ts +60 -70
  116. package/src/pm-plugins/analytics/reducer.ts +19 -19
  117. package/src/pm-plugins/analytics/types.ts +10 -10
  118. package/src/pm-plugins/analytics/utils/moved-event.ts +38 -38
  119. package/src/pm-plugins/decorations/plugin.ts +58 -77
  120. package/src/pm-plugins/decorations/utils/column-controls.ts +59 -71
  121. package/src/pm-plugins/decorations/utils/column-resizing.ts +50 -57
  122. package/src/pm-plugins/decorations/utils/compose-decorations.ts +6 -6
  123. package/src/pm-plugins/decorations/utils/index.ts +3 -6
  124. package/src/pm-plugins/decorations/utils/types.ts +7 -12
  125. package/src/pm-plugins/default-table-selection.ts +3 -3
  126. package/src/pm-plugins/drag-and-drop/actions.ts +25 -25
  127. package/src/pm-plugins/drag-and-drop/commands-with-analytics.ts +158 -190
  128. package/src/pm-plugins/drag-and-drop/commands.ts +154 -170
  129. package/src/pm-plugins/drag-and-drop/consts.ts +4 -5
  130. package/src/pm-plugins/drag-and-drop/plugin-factory.ts +23 -20
  131. package/src/pm-plugins/drag-and-drop/plugin-key.ts +1 -3
  132. package/src/pm-plugins/drag-and-drop/plugin.ts +329 -383
  133. package/src/pm-plugins/drag-and-drop/reducer.ts +30 -30
  134. package/src/pm-plugins/drag-and-drop/types.ts +8 -8
  135. package/src/pm-plugins/drag-and-drop/utils/autoscrollers.ts +38 -41
  136. package/src/pm-plugins/drag-and-drop/utils/getDragBehaviour.ts +3 -6
  137. package/src/pm-plugins/drag-and-drop/utils/monitor.ts +57 -70
  138. package/src/pm-plugins/keymap.ts +208 -220
  139. package/src/pm-plugins/main.ts +348 -400
  140. package/src/pm-plugins/plugin-factory.ts +32 -34
  141. package/src/pm-plugins/safari-delete-composition-text-issue-workaround.ts +83 -97
  142. package/src/pm-plugins/sticky-headers/commands.ts +2 -6
  143. package/src/pm-plugins/sticky-headers/plugin-key.ts +1 -3
  144. package/src/pm-plugins/sticky-headers/plugin-state.ts +41 -44
  145. package/src/pm-plugins/sticky-headers/plugin.ts +4 -4
  146. package/src/pm-plugins/sticky-headers/types.ts +8 -8
  147. package/src/pm-plugins/sticky-headers/util.ts +10 -10
  148. package/src/pm-plugins/table-analytics.ts +70 -72
  149. package/src/pm-plugins/table-local-id.ts +180 -184
  150. package/src/pm-plugins/table-resizing/commands.ts +72 -85
  151. package/src/pm-plugins/table-resizing/event-handlers.ts +298 -317
  152. package/src/pm-plugins/table-resizing/plugin-factory.ts +10 -10
  153. package/src/pm-plugins/table-resizing/plugin-key.ts +1 -3
  154. package/src/pm-plugins/table-resizing/plugin.ts +61 -68
  155. package/src/pm-plugins/table-resizing/reducer.ts +30 -33
  156. package/src/pm-plugins/table-resizing/utils/colgroup.ts +84 -84
  157. package/src/pm-plugins/table-resizing/utils/column-state.ts +78 -81
  158. package/src/pm-plugins/table-resizing/utils/content-width.ts +94 -114
  159. package/src/pm-plugins/table-resizing/utils/dom.ts +93 -110
  160. package/src/pm-plugins/table-resizing/utils/index.ts +29 -34
  161. package/src/pm-plugins/table-resizing/utils/misc.ts +94 -119
  162. package/src/pm-plugins/table-resizing/utils/resize-column.ts +93 -106
  163. package/src/pm-plugins/table-resizing/utils/resize-logic.ts +240 -257
  164. package/src/pm-plugins/table-resizing/utils/resize-state.ts +343 -372
  165. package/src/pm-plugins/table-resizing/utils/scale-table.ts +202 -207
  166. package/src/pm-plugins/table-resizing/utils/types.ts +17 -17
  167. package/src/pm-plugins/table-resizing/utils/unit-to-number.ts +1 -2
  168. package/src/pm-plugins/table-selection-keymap.ts +25 -51
  169. package/src/pm-plugins/table-width.ts +191 -204
  170. package/src/pm-plugins/view-mode-sort/index.ts +223 -227
  171. package/src/pm-plugins/view-mode-sort/plugin-key.ts +3 -2
  172. package/src/pm-plugins/view-mode-sort/types.ts +12 -12
  173. package/src/pm-plugins/view-mode-sort/utils.ts +108 -117
  174. package/src/reducer.ts +139 -155
  175. package/src/toolbar.tsx +815 -905
  176. package/src/transforms/column-width.ts +186 -213
  177. package/src/transforms/delete-columns.ts +208 -222
  178. package/src/transforms/delete-rows.ts +117 -121
  179. package/src/transforms/fix-tables.ts +190 -215
  180. package/src/transforms/merge.ts +263 -269
  181. package/src/transforms/replace-table.ts +27 -43
  182. package/src/transforms/split.ts +65 -75
  183. package/src/types.ts +421 -427
  184. package/src/ui/ColumnResizeWidget/index.tsx +40 -47
  185. package/src/ui/DragHandle/HandleIconComponent.tsx +9 -13
  186. package/src/ui/DragHandle/index.tsx +221 -250
  187. package/src/ui/DragPreview/index.tsx +35 -35
  188. package/src/ui/FloatingAlignmentButtons/FloatingAlignmentButtons.tsx +33 -41
  189. package/src/ui/FloatingContextualButton/FixedButton.tsx +154 -157
  190. package/src/ui/FloatingContextualButton/index.tsx +109 -115
  191. package/src/ui/FloatingContextualButton/styles.ts +43 -46
  192. package/src/ui/FloatingContextualMenu/ContextualMenu.tsx +634 -694
  193. package/src/ui/FloatingContextualMenu/index.tsx +83 -101
  194. package/src/ui/FloatingContextualMenu/styles.ts +57 -65
  195. package/src/ui/FloatingDeleteButton/DeleteButton.tsx +37 -37
  196. package/src/ui/FloatingDeleteButton/getPopUpOptions.ts +47 -57
  197. package/src/ui/FloatingDeleteButton/index.tsx +319 -350
  198. package/src/ui/FloatingDragMenu/DragMenu.tsx +555 -596
  199. package/src/ui/FloatingDragMenu/DropdownMenu.tsx +152 -162
  200. package/src/ui/FloatingDragMenu/index.tsx +88 -102
  201. package/src/ui/FloatingDragMenu/styles.ts +51 -54
  202. package/src/ui/FloatingInsertButton/InsertButton.tsx +204 -217
  203. package/src/ui/FloatingInsertButton/getPopupOptions.ts +100 -115
  204. package/src/ui/FloatingInsertButton/index.tsx +248 -292
  205. package/src/ui/FloatingToolbarLabel/FloatingToolbarLabel.tsx +24 -29
  206. package/src/ui/TableFloatingColumnControls/ColumnControls/index.tsx +308 -329
  207. package/src/ui/TableFloatingColumnControls/ColumnDropTargets/ColumnDropTarget.tsx +85 -94
  208. package/src/ui/TableFloatingColumnControls/ColumnDropTargets/index.tsx +46 -46
  209. package/src/ui/TableFloatingColumnControls/index.tsx +116 -136
  210. package/src/ui/TableFloatingControls/CornerControls/ClassicCornerControls.tsx +79 -91
  211. package/src/ui/TableFloatingControls/CornerControls/DragCornerControls.tsx +95 -102
  212. package/src/ui/TableFloatingControls/CornerControls/index.tsx +1 -4
  213. package/src/ui/TableFloatingControls/CornerControls/types.ts +8 -8
  214. package/src/ui/TableFloatingControls/FloatingControlsWithSelection.tsx +50 -50
  215. package/src/ui/TableFloatingControls/NumberColumn/index.tsx +111 -124
  216. package/src/ui/TableFloatingControls/RowControls/ClassicControls.tsx +86 -105
  217. package/src/ui/TableFloatingControls/RowControls/DragControls.tsx +305 -341
  218. package/src/ui/TableFloatingControls/RowDropTarget/index.tsx +72 -75
  219. package/src/ui/TableFloatingControls/index.tsx +191 -193
  220. package/src/ui/TableFullWidthLabel/index.tsx +20 -20
  221. package/src/ui/common-styles.ts +880 -912
  222. package/src/ui/consts.ts +29 -74
  223. package/src/ui/icons/AddColLeftIcon.tsx +33 -39
  224. package/src/ui/icons/AddColRightIcon.tsx +33 -39
  225. package/src/ui/icons/AddRowAboveIcon.tsx +16 -22
  226. package/src/ui/icons/AddRowBelowIcon.tsx +33 -39
  227. package/src/ui/icons/DisplayModeIcon.tsx +31 -31
  228. package/src/ui/icons/DragHandleDisabledIcon.tsx +19 -21
  229. package/src/ui/icons/DragHandleIcon.tsx +12 -12
  230. package/src/ui/icons/DragInMotionIcon.tsx +45 -52
  231. package/src/ui/icons/MergeCellsIcon.tsx +22 -28
  232. package/src/ui/icons/MinimisedHandle.tsx +9 -9
  233. package/src/ui/icons/SplitCellIcon.tsx +30 -36
  234. package/src/ui/ui-styles.ts +769 -798
  235. package/src/utils/alignment.ts +1 -1
  236. package/src/utils/analytics.ts +192 -208
  237. package/src/utils/collapse.ts +55 -64
  238. package/src/utils/column-controls.ts +237 -254
  239. package/src/utils/create.ts +30 -30
  240. package/src/utils/decoration.ts +482 -502
  241. package/src/utils/dom.ts +127 -134
  242. package/src/utils/drag-menu.ts +322 -373
  243. package/src/utils/get-allow-add-column-custom-step.ts +4 -5
  244. package/src/utils/guidelines.ts +16 -21
  245. package/src/utils/index.ts +68 -68
  246. package/src/utils/merged-cells.ts +245 -254
  247. package/src/utils/nodes.ts +91 -106
  248. package/src/utils/paste.ts +119 -135
  249. package/src/utils/row-controls.ts +199 -213
  250. package/src/utils/selection.ts +77 -87
  251. package/src/utils/snapping.ts +87 -100
  252. package/src/utils/table.ts +44 -44
  253. package/src/utils/transforms.ts +5 -5
  254. package/src/utils/update-plugin-state-decorations.ts +5 -9
@@ -8,22 +8,12 @@ import type { IntlShape } from 'react-intl-next';
8
8
  import { injectIntl } from 'react-intl-next';
9
9
 
10
10
  import type { TableColumnOrdering } from '@atlaskit/custom-steps';
11
- import {
12
- ACTION_SUBJECT,
13
- EVENT_TYPE,
14
- TABLE_ACTION,
15
- } from '@atlaskit/editor-common/analytics';
11
+ import { ACTION_SUBJECT, EVENT_TYPE, TABLE_ACTION } from '@atlaskit/editor-common/analytics';
16
12
  import type { DispatchAnalyticsEvent } from '@atlaskit/editor-common/analytics';
17
13
  import type { EventDispatcher } from '@atlaskit/editor-common/event-dispatcher';
18
- import {
19
- getParentNodeWidth,
20
- getTableContainerWidth,
21
- } from '@atlaskit/editor-common/node-width';
14
+ import { getParentNodeWidth, getTableContainerWidth } from '@atlaskit/editor-common/node-width';
22
15
  import { tableMarginSides } from '@atlaskit/editor-common/styles';
23
- import type {
24
- EditorContainerWidth,
25
- GetEditorFeatureFlags,
26
- } from '@atlaskit/editor-common/types';
16
+ import type { EditorContainerWidth, GetEditorFeatureFlags } from '@atlaskit/editor-common/types';
27
17
  import { browser, isValidPosition } from '@atlaskit/editor-common/utils';
28
18
  import type { Node as PmNode } from '@atlaskit/editor-prosemirror/model';
29
19
  import type { EditorView } from '@atlaskit/editor-prosemirror/view';
@@ -37,22 +27,19 @@ import { token } from '@atlaskit/tokens';
37
27
  import { autoSizeTable, clearHoverSelection } from '../commands';
38
28
  import { autoScrollerFactory } from '../pm-plugins/drag-and-drop/utils';
39
29
  import { getPluginState } from '../pm-plugins/plugin-factory';
40
- import type {
41
- RowStickyState,
42
- StickyPluginState,
43
- } from '../pm-plugins/sticky-headers';
30
+ import type { RowStickyState, StickyPluginState } from '../pm-plugins/sticky-headers';
44
31
  import {
45
- findStickyHeaderForTable,
46
- pluginKey as stickyHeadersPluginKey,
32
+ findStickyHeaderForTable,
33
+ pluginKey as stickyHeadersPluginKey,
47
34
  } from '../pm-plugins/sticky-headers';
48
35
  import { META_KEYS } from '../pm-plugins/table-analytics';
49
36
  import {
50
- COLUMN_MIN_WIDTH,
51
- getLayoutSize,
52
- getResizeState,
53
- insertColgroupFromNode,
54
- scaleTable,
55
- updateColgroup,
37
+ COLUMN_MIN_WIDTH,
38
+ getLayoutSize,
39
+ getResizeState,
40
+ insertColgroupFromNode,
41
+ scaleTable,
42
+ updateColgroup,
56
43
  } from '../pm-plugins/table-resizing/utils';
57
44
  import { hasTableBeenResized } from '../pm-plugins/table-resizing/utils/colgroup';
58
45
  import { TABLE_EDITOR_MARGIN } from '../pm-plugins/table-resizing/utils/consts';
@@ -62,12 +49,12 @@ import { TableCssClassName as ClassName, ShadowEvent } from '../types';
62
49
  import TableFloatingColumnControls from '../ui/TableFloatingColumnControls';
63
50
  import TableFloatingControls from '../ui/TableFloatingControls';
64
51
  import {
65
- containsHeaderRow,
66
- getAssistiveMessage,
67
- isTableNested,
68
- tablesHaveDifferentColumnWidths,
69
- tablesHaveDifferentNoOfColumns,
70
- tablesHaveDifferentNoOfRows,
52
+ containsHeaderRow,
53
+ getAssistiveMessage,
54
+ isTableNested,
55
+ tablesHaveDifferentColumnWidths,
56
+ tablesHaveDifferentNoOfColumns,
57
+ tablesHaveDifferentNoOfRows,
71
58
  } from '../utils';
72
59
 
73
60
  import { ExternalDropTargets } from './ExternalDropTargets';
@@ -89,1093 +76,1012 @@ const initialOverflowCaptureTimeroutDelay = 300;
89
76
  const isOverflowAnalyticsEnabled = false;
90
77
 
91
78
  export interface ComponentProps {
92
- view: EditorView;
93
- getNode: () => PmNode;
94
- allowColumnResizing?: boolean;
95
- eventDispatcher: EventDispatcher;
96
- getPos: () => number | undefined;
97
- options?: TableOptions;
98
-
99
- contentDOM: (node: HTMLElement | null) => void;
100
- containerWidth: EditorContainerWidth;
101
- allowControls: boolean;
102
- isHeaderRowEnabled: boolean;
103
- isHeaderColumnEnabled: boolean;
104
- isMediaFullscreen?: boolean;
105
- isDragAndDropEnabled?: boolean;
106
- isTableScalingEnabled?: boolean;
107
- isTableAlignmentEnabled?: boolean;
108
- tableActive: boolean;
109
- ordering?: TableColumnOrdering;
110
- isResizing?: boolean;
111
- getEditorFeatureFlags: GetEditorFeatureFlags;
112
- dispatchAnalyticsEvent: DispatchAnalyticsEvent;
113
- pluginInjectionApi?: PluginInjectionAPI;
114
- intl: IntlShape;
115
-
116
- // marking props as option to ensure backward compatibility when platform.editor.table.use-shared-state-hook disabled
117
- isInDanger?: boolean;
118
- hoveredRows?: number[];
119
- hoveredCell?: CellHoverMeta;
120
- isTableHovered?: boolean;
121
- isWholeTableInDanger?: boolean;
79
+ view: EditorView;
80
+ getNode: () => PmNode;
81
+ allowColumnResizing?: boolean;
82
+ eventDispatcher: EventDispatcher;
83
+ getPos: () => number | undefined;
84
+ options?: TableOptions;
85
+
86
+ contentDOM: (node: HTMLElement | null) => void;
87
+ containerWidth: EditorContainerWidth;
88
+ allowControls: boolean;
89
+ isHeaderRowEnabled: boolean;
90
+ isHeaderColumnEnabled: boolean;
91
+ isMediaFullscreen?: boolean;
92
+ isDragAndDropEnabled?: boolean;
93
+ isTableScalingEnabled?: boolean;
94
+ isTableAlignmentEnabled?: boolean;
95
+ tableActive: boolean;
96
+ ordering?: TableColumnOrdering;
97
+ isResizing?: boolean;
98
+ getEditorFeatureFlags: GetEditorFeatureFlags;
99
+ dispatchAnalyticsEvent: DispatchAnalyticsEvent;
100
+ pluginInjectionApi?: PluginInjectionAPI;
101
+ intl: IntlShape;
102
+
103
+ // marking props as option to ensure backward compatibility when platform.editor.table.use-shared-state-hook disabled
104
+ isInDanger?: boolean;
105
+ hoveredRows?: number[];
106
+ hoveredCell?: CellHoverMeta;
107
+ isTableHovered?: boolean;
108
+ isWholeTableInDanger?: boolean;
122
109
  }
123
110
 
124
111
  interface TableState {
125
- scroll: number;
126
- parentWidth?: number;
127
- stickyHeader?: RowStickyState;
128
- [ShadowEvent.SHOW_BEFORE_SHADOW]: boolean;
129
- [ShadowEvent.SHOW_AFTER_SHADOW]: boolean;
130
- tableWrapperWidth?: number;
131
- tableWrapperHeight?: number;
112
+ scroll: number;
113
+ parentWidth?: number;
114
+ stickyHeader?: RowStickyState;
115
+ [ShadowEvent.SHOW_BEFORE_SHADOW]: boolean;
116
+ [ShadowEvent.SHOW_AFTER_SHADOW]: boolean;
117
+ tableWrapperWidth?: number;
118
+ tableWrapperHeight?: number;
132
119
  }
133
120
 
134
121
  class TableComponent extends React.Component<ComponentProps, TableState> {
135
- static displayName = 'TableComponent';
136
-
137
- state: TableState = {
138
- scroll: 0,
139
- parentWidth: undefined,
140
- [ShadowEvent.SHOW_BEFORE_SHADOW]: false,
141
- [ShadowEvent.SHOW_AFTER_SHADOW]: false,
142
- tableWrapperWidth: undefined,
143
- tableWrapperHeight: undefined,
144
- };
145
-
146
- private wrapper?: HTMLDivElement | null;
147
- private table?: HTMLTableElement | null;
148
- private node: PmNode;
149
- private containerWidth?: EditorContainerWidth;
150
- private wasResizing?: boolean;
151
- private tableNodeWidth?: number;
152
- private layoutSize?: number;
153
- private overflowShadowsObserver?: OverflowShadowsObserver;
154
- private stickyScrollbar?: TableStickyScrollbar;
155
-
156
- private isInitialOverflowSent: boolean;
157
- private initialOverflowCaptureTimerId?: ReturnType<typeof setTimeout>;
158
- private resizeObserver?: ResizeObserver;
159
-
160
- private dragAndDropCleanupFn?: CleanupFn;
161
-
162
- constructor(props: ComponentProps) {
163
- super(props);
164
- const { options, containerWidth, getNode } = props;
165
- this.node = getNode();
166
- this.containerWidth = containerWidth;
167
- this.isInitialOverflowSent = false;
168
-
169
- // store table size using previous full-width mode so can detect if it has changed.
170
- const isFullWidthModeEnabled = options
171
- ? options.wasFullWidthModeEnabled
172
- : false;
173
- this.layoutSize = this.tableNodeLayoutSize(
174
- this.node,
175
- containerWidth.width,
176
- {
177
- isFullWidthModeEnabled,
178
- },
179
- );
180
-
181
- this.resizeObserver = new ResizeObserver((entries) => {
182
- for (let entry of entries) {
183
- this.setState((prev) => {
184
- return prev?.tableWrapperWidth === entry.contentRect?.width &&
185
- prev?.tableWrapperHeight === entry.contentRect?.height
186
- ? prev
187
- : {
188
- ...prev,
189
- tableWrapperWidth: entry.contentRect.width,
190
- tableWrapperHeight: entry.contentRect.height,
191
- };
192
- });
193
- }
194
- });
195
-
196
- // Disable inline table editing and resizing controls in Firefox
197
- // https://github.com/ProseMirror/prosemirror/issues/432
198
- if ('execCommand' in document) {
199
- ['enableObjectResizing', 'enableInlineTableEditing'].forEach((cmd) => {
200
- if (document.queryCommandSupported(cmd)) {
201
- document.execCommand(cmd, false, 'false');
202
- }
203
- });
204
- }
205
- }
206
-
207
- componentDidMount() {
208
- const {
209
- allowColumnResizing,
210
- eventDispatcher,
211
- options,
212
- isDragAndDropEnabled,
213
- getNode,
214
- getEditorFeatureFlags,
215
- isTableScalingEnabled,
216
- } = this.props;
217
-
218
- if (getBooleanFF('platform.editor.table.live-pages-sorting_4malx')) {
219
- const { mode } =
220
- this.props.pluginInjectionApi?.editorViewMode?.sharedState.currentState() ||
221
- {};
222
- if (mode === 'view') {
223
- this?.table?.addEventListener('mouseenter', this.handleMouseEnter);
224
- }
225
- }
226
-
227
- if (
228
- isTableScalingEnabled &&
229
- !getBooleanFF('platform.editor.table.preserve-widths-with-lock-button')
230
- ) {
231
- this.handleColgroupUpdates(true);
232
- }
233
-
234
- if (
235
- isTableScalingEnabled &&
236
- getBooleanFF('platform.editor.table.preserve-widths-with-lock-button') &&
237
- getNode().attrs.displayMode !== 'fixed'
238
- ) {
239
- this.handleColgroupUpdates(true);
240
- }
241
-
242
- if (allowColumnResizing && this.wrapper && !isIE11) {
243
- this.wrapper.addEventListener('scroll', this.handleScrollDebounced, {
244
- passive: true,
245
- });
246
-
247
- const { stickyScrollbar } = getEditorFeatureFlags();
248
-
249
- if (stickyScrollbar) {
250
- if (this.table) {
251
- this.stickyScrollbar = new TableStickyScrollbar(
252
- this.wrapper,
253
- this.props.view,
254
- );
255
- }
256
- }
257
-
258
- if (isDragAndDropEnabled) {
259
- this.dragAndDropCleanupFn = combine(
260
- ...autoScrollerFactory({
261
- tableWrapper: this.wrapper,
262
- getNode,
263
- }),
264
- );
265
- }
266
- }
267
-
268
- if (allowColumnResizing) {
269
- /**
270
- * We no longer use `containerWidth` as a variable to determine an update for table resizing (avoids unnecessary updates).
271
- * Instead we use the resize event to only trigger updates when necessary.
272
- */
273
- if (!options?.isTableResizingEnabled) {
274
- window.addEventListener('resize', this.handleWindowResizeDebounced);
275
- }
276
- this.handleTableResizingDebounced();
277
- }
278
-
279
- const currentStickyState = stickyHeadersPluginKey.getState(
280
- this.props.view.state,
281
- );
282
-
283
- if (currentStickyState) {
284
- this.onStickyState(currentStickyState);
285
- }
286
-
287
- eventDispatcher.on((stickyHeadersPluginKey as any).key, this.onStickyState);
288
-
289
- if (isOverflowAnalyticsEnabled) {
290
- const initialIsOveflowing =
291
- this.state[ShadowEvent.SHOW_BEFORE_SHADOW] ||
292
- this.state[ShadowEvent.SHOW_AFTER_SHADOW];
293
-
294
- this.setTimerToSendInitialOverflowCaptured(initialIsOveflowing);
295
- }
296
- }
297
-
298
- componentWillUnmount() {
299
- const {
300
- allowColumnResizing,
301
- eventDispatcher,
302
- options,
303
- isDragAndDropEnabled,
304
- getEditorFeatureFlags,
305
- } = this.props;
306
- if (this.wrapper && !isIE11) {
307
- this.wrapper.removeEventListener('scroll', this.handleScrollDebounced);
308
- }
309
-
310
- if (isDragAndDropEnabled && this.dragAndDropCleanupFn) {
311
- this.dragAndDropCleanupFn();
312
- }
313
-
314
- this.resizeObserver?.disconnect();
315
-
316
- const { stickyScrollbar } = getEditorFeatureFlags();
317
-
318
- if (stickyScrollbar) {
319
- if (this.stickyScrollbar) {
320
- this.stickyScrollbar.dispose();
321
- }
322
- }
323
-
324
- this.handleScrollDebounced.cancel();
325
- this.scaleTableDebounced.cancel();
326
- this.handleTableResizingDebounced.cancel();
327
- this.handleAutoSizeDebounced.cancel();
328
- if (!options?.isTableResizingEnabled) {
329
- this.handleWindowResizeDebounced.cancel();
330
- }
331
-
332
- if (!options?.isTableResizingEnabled && allowColumnResizing) {
333
- window.removeEventListener('resize', this.handleWindowResizeDebounced);
334
- }
335
-
336
- if (getBooleanFF('platform.editor.table.live-pages-sorting_4malx')) {
337
- this?.table?.removeEventListener('mouseenter', this.handleMouseEnter);
338
- }
339
-
340
- if (this.overflowShadowsObserver) {
341
- this.overflowShadowsObserver.dispose();
342
- }
343
-
344
- eventDispatcher.off(
345
- (stickyHeadersPluginKey as any).key,
346
- this.onStickyState,
347
- );
348
-
349
- if (this.initialOverflowCaptureTimerId) {
350
- clearTimeout(this.initialOverflowCaptureTimerId);
351
- }
352
- }
353
-
354
- handleMouseEnter = () => {
355
- const node = this.props.getNode();
356
- const pos = this.props.getPos();
357
- const tr = this.props.view.state.tr;
358
- const tableId = node.attrs.localId;
359
- tr.setMeta('mouseEnterTable', [tableId, node, pos]);
360
- this.props.view.dispatch(tr);
361
- };
362
-
363
- handleColgroupUpdates(force = false) {
364
- const { getNode, containerWidth, isResizing, view, getPos } = this.props;
365
-
366
- if (!this.table) {
367
- return;
368
- }
369
-
370
- // Remove any widths styles after resizing preview is completed
371
- this.table.style.width = '';
372
-
373
- const tableRenderWidth = containerWidth.width - TABLE_EDITOR_MARGIN;
374
- const tableNode = getNode();
375
- const start = getPos() || 0;
376
- const depth = view.state.doc.resolve(start).depth;
377
-
378
- if (depth !== 0) {
379
- return;
380
- }
381
-
382
- const tableNodeWidth = getTableContainerWidth(tableNode);
383
- const isTableResizedFullWidth =
384
- tableNodeWidth === 1800 && this.wasResizing && !isResizing;
385
- // Needed for undo / redo
386
- const isTableWidthChanged = tableNodeWidth !== this.tableNodeWidth;
387
- const isTableSquashed = tableRenderWidth < tableNodeWidth;
388
- const isNumberColumnChanged =
389
- tableNode.attrs.isNumberColumnEnabled !==
390
- this.node.attrs.isNumberColumnEnabled;
391
- const isNumberOfColumnsChanged = tablesHaveDifferentNoOfColumns(
392
- tableNode,
393
- this.node,
394
- );
395
-
396
- const maybeScale =
397
- isTableSquashed ||
398
- isTableWidthChanged ||
399
- isTableResizedFullWidth ||
400
- isNumberColumnChanged ||
401
- isNumberOfColumnsChanged;
402
-
403
- if (force || maybeScale) {
404
- const { width: containerWidthValue } = containerWidth;
405
- const isWidthChanged = this.containerWidth?.width !== containerWidthValue;
406
- const wasTableResized = hasTableBeenResized(this.node);
407
- const isTableResized = hasTableBeenResized(tableNode);
408
- const isColumnsDistributed = wasTableResized && !isTableResized;
409
- const isTableDisplayModeChanged =
410
- this.node.attrs.displayMode !== tableNode.attrs.displayMode;
411
-
412
- const shouldUpdateColgroup =
413
- isWidthChanged ||
414
- isColumnsDistributed ||
415
- isTableResizedFullWidth ||
416
- isTableWidthChanged ||
417
- isTableDisplayModeChanged ||
418
- isNumberColumnChanged ||
419
- isNumberOfColumnsChanged;
420
-
421
- if (force || (!isResizing && shouldUpdateColgroup)) {
422
- const resizeState = getResizeState({
423
- minWidth: COLUMN_MIN_WIDTH,
424
- maxSize: tableRenderWidth,
425
- table: tableNode,
426
- tableRef: this.table,
427
- start,
428
- domAtPos: view.domAtPos.bind(view),
429
- isTableScalingEnabled: true,
430
- });
431
-
432
- let shouldScaleOnColgroupUpdate = false;
433
- if (
434
- this.props.options?.isTableScalingEnabled &&
435
- !getBooleanFF(
436
- 'platform.editor.table.preserve-widths-with-lock-button',
437
- )
438
- ) {
439
- shouldScaleOnColgroupUpdate = true;
440
- }
441
-
442
- if (
443
- this.props.options?.isTableScalingEnabled &&
444
- getBooleanFF(
445
- 'platform.editor.table.preserve-widths-with-lock-button',
446
- ) &&
447
- tableNode.attrs.displayMode !== 'fixed'
448
- ) {
449
- shouldScaleOnColgroupUpdate = true;
450
- }
451
-
452
- // Request animation frame required for Firefox
453
- requestAnimationFrame(() => {
454
- updateColgroup(
455
- resizeState,
456
- this.table!,
457
- tableNode,
458
- shouldScaleOnColgroupUpdate,
459
- );
460
- });
461
- }
462
- }
463
- this.tableNodeWidth = tableNodeWidth;
464
- this.wasResizing = isResizing;
465
- this.containerWidth = containerWidth;
466
- }
467
-
468
- componentDidUpdate(_: any, prevState: TableState) {
469
- const {
470
- view,
471
- getNode,
472
- isMediaFullscreen,
473
- allowColumnResizing,
474
- isResizing,
475
- options,
476
- isTableScalingEnabled, // we could use options.isTableScalingEnabled here
477
- getPos,
478
- } = this.props;
479
- let { isInDanger } = this.props;
480
-
481
- const table = findTable(view.state.selection);
482
-
483
- if (!getBooleanFF('platform.editor.table.use-shared-state-hook')) {
484
- const pluginState = getPluginState(view.state);
485
- isInDanger = pluginState.isInDanger;
486
- }
487
-
488
- let shouldScale = false;
489
- let shouldHandleColgroupUpdates = false;
490
-
491
- if (
492
- isTableScalingEnabled &&
493
- !getBooleanFF('platform.editor.table.preserve-widths-with-lock-button')
494
- ) {
495
- shouldScale = true;
496
- shouldHandleColgroupUpdates = true;
497
- }
498
-
499
- if (
500
- isTableScalingEnabled &&
501
- getBooleanFF('platform.editor.table.preserve-widths-with-lock-button') &&
502
- getNode().attrs.displayMode !== 'fixed'
503
- ) {
504
- shouldScale = true;
505
- shouldHandleColgroupUpdates = true;
506
- }
507
-
508
- if (shouldHandleColgroupUpdates) {
509
- this.handleColgroupUpdates();
510
- }
511
-
512
- if (isInDanger && !table) {
513
- clearHoverSelection()(view.state, view.dispatch);
514
- }
515
-
516
- if (
517
- this.wrapper?.parentElement &&
518
- this.table &&
519
- !this.overflowShadowsObserver
520
- ) {
521
- if (this.props.isDragAndDropEnabled) {
522
- // requestAnimationFrame is used here to fix a race condition issue
523
- // that happens when a table is nested in expand and expand's width is
524
- // changed via breakout button
525
- window.requestAnimationFrame(() => {
526
- this.overflowShadowsObserver = new OverflowShadowsObserver(
527
- this.updateShadowState,
528
- this.table as HTMLElement,
529
- this.wrapper as HTMLDivElement,
530
- );
531
- });
532
- } else {
533
- this.overflowShadowsObserver = new OverflowShadowsObserver(
534
- this.updateShadowState,
535
- this.table,
536
- this.wrapper,
537
- );
538
- }
539
- }
540
-
541
- if (this.overflowShadowsObserver) {
542
- this.overflowShadowsObserver.observeShadowSentinels(
543
- this.state.stickyHeader?.sticky,
544
- );
545
- }
546
-
547
- const currentTable = getNode();
548
- const previousTable = this.node;
549
- const isNoOfColumnsChanged = tablesHaveDifferentNoOfColumns(
550
- currentTable,
551
- previousTable,
552
- );
553
- const isNoOfRowsChanged = tablesHaveDifferentNoOfRows(
554
- currentTable,
555
- previousTable,
556
- );
557
- if (isNoOfColumnsChanged || isNoOfRowsChanged) {
558
- this.props.pluginInjectionApi?.accessibilityUtils?.actions.ariaNotify(
559
- getAssistiveMessage(previousTable, currentTable, this.props.intl),
560
- { priority: 'important' },
561
- );
562
- }
563
- if (currentTable.attrs.__autoSize) {
564
- // Wait for next tick to handle auto sizing, gives the browser time to do layout calc etc.
565
- this.handleAutoSizeDebounced();
566
- }
567
- // re-drawing will cause media component get unmounted that will exit fullscreen mode if media is in fullscreen mode
568
- // see https://product-fabric.atlassian.net/browse/MEX-1290
569
- else if (allowColumnResizing && this.table && !isMediaFullscreen) {
570
- // If col widths (e.g. via collab) or number of columns (e.g. delete a column) have changed,
571
- // re-draw colgroup.
572
- if (
573
- tablesHaveDifferentColumnWidths(currentTable, previousTable) ||
574
- isNoOfColumnsChanged
575
- ) {
576
- const { view } = this.props;
577
- const shouldRecreateResizeCols =
578
- !options?.isTableResizingEnabled ||
579
- !isResizing ||
580
- (isNoOfColumnsChanged && isResizing);
581
-
582
- if (shouldRecreateResizeCols) {
583
- const start = getPos() || 0;
584
- const depth = view.state.doc.resolve(start).depth;
585
- shouldScale = depth === 0 && shouldScale;
586
-
587
- insertColgroupFromNode(this.table, currentTable, shouldScale);
588
- }
589
-
590
- updateControls()(view.state);
591
- }
592
-
593
- this.handleTableResizingDebounced();
594
- }
595
- if (isOverflowAnalyticsEnabled) {
596
- const newIsOverflowing =
597
- this.state[ShadowEvent.SHOW_BEFORE_SHADOW] ||
598
- this.state[ShadowEvent.SHOW_AFTER_SHADOW];
599
-
600
- const prevIsOverflowing =
601
- prevState[ShadowEvent.SHOW_BEFORE_SHADOW] ||
602
- prevState[ShadowEvent.SHOW_AFTER_SHADOW];
603
-
604
- if (this.initialOverflowCaptureTimerId) {
605
- clearTimeout(this.initialOverflowCaptureTimerId);
606
- }
607
-
608
- if (!this.isInitialOverflowSent) {
609
- this.setTimerToSendInitialOverflowCaptured(newIsOverflowing);
610
- }
611
-
612
- if (
613
- this.isInitialOverflowSent &&
614
- prevIsOverflowing !== newIsOverflowing
615
- ) {
616
- const {
617
- dispatch,
618
- state: { tr },
619
- } = this.props.view;
620
-
621
- dispatch(
622
- tr.setMeta(META_KEYS.OVERFLOW_STATE_CHANGED, {
623
- isOverflowing: newIsOverflowing,
624
- wasOverflowing: prevIsOverflowing,
625
- editorWidth: this.props.containerWidth.width || 0,
626
- width: this.node.attrs.width || 0,
627
- parentWidth: this.state?.parentWidth || 0,
628
- }),
629
- );
630
- }
631
- }
632
- }
633
-
634
- private updateShadowState = (shadowKey: ShadowEvent, value: boolean) => {
635
- if (this.state[shadowKey] === value) {
636
- return;
637
- }
638
- this.setState({ [shadowKey]: value } as Pick<TableState, typeof shadowKey>);
639
- };
640
-
641
- private createShadowSentinels = (table: HTMLTableElement | null) => {
642
- if (table) {
643
- const shadowSentinelLeft = document.createElement('span');
644
- shadowSentinelLeft.className = ClassName.TABLE_SHADOW_SENTINEL_LEFT;
645
- const shadowSentinelRight = document.createElement('span');
646
- shadowSentinelRight.className = ClassName.TABLE_SHADOW_SENTINEL_RIGHT;
647
- table.prepend(shadowSentinelLeft);
648
- table.prepend(shadowSentinelRight);
649
- }
650
- };
651
-
652
- private observeTable(table: HTMLTableElement | null) {
653
- if (table) {
654
- this.resizeObserver?.observe(table);
655
- }
656
- }
657
-
658
- onStickyState = (state: StickyPluginState) => {
659
- const pos = this.props.getPos();
660
- if (!isValidPosition(pos, this.props.view.state)) {
661
- return;
662
- }
663
- const stickyHeader = findStickyHeaderForTable(state, pos);
664
- if (stickyHeader !== this.state.stickyHeader) {
665
- this.setState({ stickyHeader });
666
- if (this.overflowShadowsObserver) {
667
- this.overflowShadowsObserver.updateStickyShadows();
668
- }
669
- }
670
- };
671
-
672
- prevTableState: any = null;
673
-
674
- render() {
675
- const {
676
- view,
677
- getNode,
678
- isResizing,
679
- allowControls = true,
680
- isHeaderRowEnabled,
681
- ordering,
682
- isHeaderColumnEnabled,
683
- tableActive,
684
- containerWidth,
685
- options,
686
- getPos,
687
- pluginInjectionApi,
688
- isDragAndDropEnabled,
689
- getEditorFeatureFlags,
690
- isTableScalingEnabled, // here we can use options.isTableScalingEnabled
691
- isTableAlignmentEnabled,
692
- } = this.props;
693
-
694
- let {
695
- isInDanger,
696
- hoveredRows,
697
- hoveredCell,
698
- isTableHovered,
699
- isWholeTableInDanger,
700
- } = this.props;
701
-
702
- const { showBeforeShadow, showAfterShadow } = this.state;
703
- const node = getNode();
704
-
705
- if (!getBooleanFF('platform.editor.table.use-shared-state-hook')) {
706
- const pluginState = getPluginState(view.state);
707
- isInDanger = pluginState.isInDanger;
708
- hoveredRows = pluginState.hoveredRows;
709
- hoveredCell = pluginState.hoveredCell;
710
- isTableHovered = pluginState.isTableHovered;
711
- isWholeTableInDanger = pluginState.isWholeTableInDanger;
712
- }
713
-
714
- const tableRef = this.table || undefined;
715
- const headerRow = tableRef
716
- ? tableRef.querySelector<HTMLTableRowElement>('tr[data-header-row]')
717
- : undefined;
718
-
719
- const hasHeaderRow = containsHeaderRow(node);
720
- const rowControls = (
721
- <TableFloatingControls
722
- editorView={view}
723
- tableRef={tableRef}
724
- tableNode={node}
725
- tableActive={tableActive}
726
- hoveredRows={hoveredRows}
727
- hoveredCell={hoveredCell}
728
- isTableHovered={isTableHovered}
729
- isInDanger={isInDanger}
730
- isResizing={isResizing}
731
- isNumberColumnEnabled={node.attrs.isNumberColumnEnabled}
732
- isHeaderRowEnabled={isHeaderRowEnabled}
733
- isDragAndDropEnabled={isDragAndDropEnabled}
734
- ordering={ordering}
735
- isHeaderColumnEnabled={isHeaderColumnEnabled}
736
- hasHeaderRow={hasHeaderRow}
737
- // pass `selection` and `tableHeight` to control re-render
738
- selection={view.state.selection}
739
- headerRowHeight={headerRow ? headerRow.offsetHeight : undefined}
740
- stickyHeader={this.state.stickyHeader}
741
- tableWrapperWidth={this.state.tableWrapperWidth}
742
- api={pluginInjectionApi}
743
- />
744
- );
745
- const tableContainerWidth = getTableContainerWidth(node);
746
- const colControls = isDragAndDropEnabled ? (
747
- <TableFloatingColumnControls
748
- editorView={view}
749
- tableRef={tableRef}
750
- getNode={getNode}
751
- tableActive={tableActive}
752
- isInDanger={isInDanger}
753
- hoveredRows={hoveredRows}
754
- hoveredCell={hoveredCell}
755
- isTableHovered={isTableHovered}
756
- isResizing={isResizing}
757
- ordering={ordering}
758
- hasHeaderRow={hasHeaderRow}
759
- // pass `selection` to control re-render
760
- selection={view.state.selection}
761
- headerRowHeight={headerRow ? headerRow.offsetHeight : undefined}
762
- stickyHeader={this.state.stickyHeader}
763
- getEditorFeatureFlags={getEditorFeatureFlags}
764
- tableContainerWidth={tableContainerWidth}
765
- isNumberColumnEnabled={node.attrs.isNumberColumnEnabled}
766
- getScrollOffset={() => this.wrapper?.scrollLeft || 0}
767
- tableWrapperHeight={this.state.tableWrapperHeight}
768
- api={pluginInjectionApi}
769
- />
770
- ) : null;
771
-
772
- const shadowPadding =
773
- allowControls && tableActive ? -tableToolbarSize : tableMarginSides;
774
-
775
- const shadowStyle = memoizeOne(
776
- (visible) =>
777
- ({ visibility: visible ? 'visible' : 'hidden' }) as CSSProperties,
778
- );
779
-
780
- /**
781
- * ED-19838
782
- * There is a getPos issue coming from this code. We need to apply this workaround for now and apply a patch
783
- * before CR6 lands in production
784
- */
785
- let tablePos: number | undefined;
786
- try {
787
- tablePos = getPos ? getPos() : undefined;
788
- } catch (e) {
789
- tablePos = undefined;
790
- }
791
-
792
- const isNested = isTableNested(view.state, tablePos);
793
-
794
- const topStickyShadowPosition = isDragAndDropEnabled
795
- ? this.state.stickyHeader &&
796
- this.state.stickyHeader.top + this.state.stickyHeader.padding + 2
797
- : this.state.stickyHeader &&
798
- this.state.stickyHeader.top +
799
- this.state.stickyHeader.padding +
800
- shadowPadding +
801
- 2;
802
-
803
- const { stickyScrollbar } = getEditorFeatureFlags();
804
-
805
- return (
806
- <TableContainer
807
- // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
808
- className={classnames(ClassName.TABLE_CONTAINER, {
809
- [ClassName.WITH_CONTROLS]: allowControls && tableActive,
810
- [ClassName.TABLE_STICKY]: this.state.stickyHeader && hasHeaderRow,
811
- [ClassName.HOVERED_DELETE_BUTTON]: isInDanger,
812
- [ClassName.TABLE_SELECTED]: isTableSelected(view.state.selection),
813
- })}
814
- editorView={view}
815
- getPos={getPos}
816
- node={node}
817
- tableRef={tableRef!}
818
- containerWidth={containerWidth}
819
- isNested={isNested}
820
- pluginInjectionApi={pluginInjectionApi}
821
- tableWrapperHeight={this.state.tableWrapperHeight}
822
- isTableResizingEnabled={options?.isTableResizingEnabled}
823
- isResizing={isResizing}
824
- isTableScalingEnabled={isTableScalingEnabled}
825
- isWholeTableInDanger={isWholeTableInDanger}
826
- isTableAlignmentEnabled={isTableAlignmentEnabled}
827
- >
828
- <div
829
- // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
830
- className={ClassName.TABLE_STICKY_SENTINEL_TOP}
831
- data-testid="sticky-sentinel-top"
832
- />
833
- {stickyScrollbar && (
834
- <div
835
- // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
836
- className={ClassName.TABLE_STICKY_SCROLLBAR_SENTINEL_TOP}
837
- data-testid="sticky-scrollbar-sentinel-top"
838
- />
839
- )}
840
-
841
- {allowControls && rowControls}
842
- {isDragAndDropEnabled && (
843
- <ExternalDropTargets
844
- editorView={view}
845
- node={node}
846
- getScrollOffset={() => {
847
- return this.wrapper?.scrollLeft || 0;
848
- }}
849
- getTableWrapperWidth={() => {
850
- return this.wrapper?.clientWidth || 760;
851
- }}
852
- />
853
- )}
854
- <div
855
- contentEditable={false}
856
- // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
857
- style={shadowStyle(showBeforeShadow)}
858
- // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
859
- className={ClassName.TABLE_LEFT_SHADOW}
860
- />
861
- {this.state.stickyHeader && (
862
- <div
863
- // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
864
- className={`${ClassName.TABLE_LEFT_SHADOW} ${ClassName.TABLE_STICKY_SHADOW}`}
865
- style={{
866
- visibility:
867
- showBeforeShadow && hasHeaderRow ? 'visible' : 'hidden',
868
- top: `${topStickyShadowPosition}px`,
869
- paddingBottom: `${
870
- isDragAndDropEnabled && token('space.025', '2px')
871
- }`,
872
- }}
873
- />
874
- )}
875
- <div
876
- // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
877
- className={classnames(ClassName.TABLE_NODE_WRAPPER)}
878
- ref={(elem) => {
879
- this.wrapper = elem;
880
- if (elem) {
881
- this.props.contentDOM(elem);
882
- const tableElement = elem.querySelector('table');
883
- if (tableElement !== this.table) {
884
- this.table = tableElement;
885
- this.createShadowSentinels(this.table);
886
- this.observeTable(this.table);
887
- }
888
- }
889
- }}
890
- >
891
- {allowControls && colControls}
892
- </div>
893
- {stickyScrollbar && (
894
- <div
895
- // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
896
- className={ClassName.TABLE_STICKY_SCROLLBAR_CONTAINER}
897
- style={{
898
- // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
899
- height: token('space.250', '20px'), // MAX_BROWSER_SCROLLBAR_HEIGHT
900
- // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
901
- display: 'none',
902
- // prevent unwanted scroll during table resize without removing scrollbar container from the dom
903
- width: isResizing ? token('space.0', '0px') : '100%',
904
- }}
905
- >
906
- <div
907
- style={{
908
- width: tableRef?.clientWidth,
909
- // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
910
- height: '100%',
911
- }}
912
- ></div>
913
- </div>
914
- )}
915
- <div
916
- contentEditable={false}
917
- // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
918
- style={shadowStyle(showAfterShadow)}
919
- // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
920
- className={ClassName.TABLE_RIGHT_SHADOW}
921
- />
922
- {this.state.stickyHeader && (
923
- <div
924
- style={{
925
- // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
926
- position: 'absolute',
927
- // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
928
- right: token('space.400', '32px'), // tableOverflowShadowWidthWide
929
- }}
930
- >
931
- <div
932
- // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
933
- className={`${ClassName.TABLE_RIGHT_SHADOW} ${ClassName.TABLE_STICKY_SHADOW}`}
934
- style={{
935
- visibility:
936
- showAfterShadow && hasHeaderRow ? 'visible' : 'hidden',
937
- top: `${topStickyShadowPosition}px`,
938
- paddingBottom: `${
939
- isDragAndDropEnabled && token('space.025', '2px')
940
- }`,
941
- }}
942
- />
943
- </div>
944
- )}
945
- <div
946
- // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
947
- className={ClassName.TABLE_STICKY_SENTINEL_BOTTOM}
948
- data-testid="sticky-sentinel-bottom"
949
- />
950
- {stickyScrollbar && (
951
- <div
952
- // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
953
- className={ClassName.TABLE_STICKY_SCROLLBAR_SENTINEL_BOTTOM}
954
- data-testid="sticky-scrollbar-sentinel-bottom"
955
- />
956
- )}
957
- </TableContainer>
958
- );
959
- }
960
-
961
- private handleScroll = (event: Event) => {
962
- if (!this.wrapper || event.target !== this.wrapper) {
963
- return;
964
- }
965
-
966
- const { stickyScrollbar } = this.props.getEditorFeatureFlags();
967
-
968
- if (stickyScrollbar) {
969
- if (this.stickyScrollbar) {
970
- this.stickyScrollbar.scrollLeft(this.wrapper.scrollLeft);
971
- }
972
- }
973
-
974
- if (this.table) {
975
- // sync sticky header row to table scroll
976
- const headers = this.table.querySelectorAll('tr[data-header-row]');
977
- for (let i = 0; i < headers.length; i++) {
978
- const header = headers[i] as HTMLElement;
979
-
980
- header.scrollLeft = this.wrapper.scrollLeft;
981
- header.style.marginRight = '2px';
982
- }
983
- }
984
-
985
- this.setState({
986
- [ShadowEvent.SHOW_BEFORE_SHADOW]: this.wrapper.scrollLeft !== 0,
987
- });
988
- };
989
-
990
- private handleTableResizing = () => {
991
- const { getNode, containerWidth, options } = this.props;
992
- const prevNode = this.node!;
993
- const node = getNode();
994
- const prevAttrs = prevNode.attrs;
995
-
996
- const isNested = isTableNested(this.props.view.state, this.props.getPos());
997
-
998
- const parentWidth = this.getParentNodeWidth();
999
- const parentWidthChanged =
1000
- parentWidth && parentWidth !== this.state.parentWidth;
1001
-
1002
- const layoutSize = this.tableNodeLayoutSize(
1003
- node,
1004
- containerWidth.width,
1005
- options,
1006
- );
1007
-
1008
- const hasNumberedColumnChanged =
1009
- prevAttrs.isNumberColumnEnabled !== node.attrs.isNumberColumnEnabled;
1010
-
1011
- const noOfColumnsChanged = tablesHaveDifferentNoOfColumns(node, prevNode);
1012
-
1013
- if (
1014
- // We need to react if our parent changes
1015
- // Scales the cols widths relative to the new parent width.
1016
- parentWidthChanged ||
1017
- // Enabling / disabling this feature reduces or adds size to the table.
1018
- hasNumberedColumnChanged ||
1019
- // This last check is also to cater for dynamic text sizing changing the 'default' layout width
1020
- // Usually happens on window resize.
1021
- layoutSize !== this.layoutSize ||
1022
- noOfColumnsChanged
1023
- ) {
1024
- const shouldScaleTable =
1025
- (!options?.isTableResizingEnabled ||
1026
- (options?.isTableResizingEnabled && isNested)) &&
1027
- !hasNumberedColumnChanged &&
1028
- !noOfColumnsChanged;
1029
-
1030
- // If column has been inserted/deleted avoid multi dispatch
1031
- if (shouldScaleTable) {
1032
- this.scaleTable({
1033
- parentWidth,
1034
- });
1035
- }
1036
-
1037
- // only when table resizing is enabled and toggle numbered column to run scaleTable
1038
- if (options?.isTableResizingEnabled && hasNumberedColumnChanged) {
1039
- if (!hasTableBeenResized(prevNode)) {
1040
- this.scaleTable({
1041
- parentWidth: node.attrs.width,
1042
- });
1043
- }
1044
- }
1045
-
1046
- this.updateParentWidth(parentWidth);
1047
- }
1048
-
1049
- this.node = node;
1050
- this.containerWidth = containerWidth;
1051
- this.layoutSize = layoutSize;
1052
- };
1053
-
1054
- private scaleTable = (scaleOptions: { parentWidth?: number }) => {
1055
- const { view, getNode, getPos, containerWidth, options } = this.props;
1056
- const node = getNode();
1057
- const { state, dispatch } = view;
1058
- const pos = getPos();
1059
-
1060
- if (typeof pos !== 'number' || !isValidPosition(pos, state)) {
1061
- return;
1062
- }
1063
- const domAtPos = view.domAtPos.bind(view);
1064
- const { width } = containerWidth;
1065
-
1066
- this.scaleTableDebounced.cancel();
1067
-
1068
- const tr = scaleTable(
1069
- this.table,
1070
- {
1071
- ...scaleOptions,
1072
- node,
1073
- prevNode: this.node || node,
1074
- start: pos + 1,
1075
- containerWidth: width,
1076
- previousContainerWidth: this.containerWidth!.width || width,
1077
- ...options,
1078
- },
1079
- domAtPos,
1080
- false,
1081
- )(state.tr);
1082
-
1083
- dispatch(tr);
1084
- };
1085
-
1086
- private setTimerToSendInitialOverflowCaptured = (isOverflowing: boolean) => {
1087
- const { dispatchAnalyticsEvent, containerWidth, options } = this.props;
1088
- const parentWidth = this.state?.parentWidth || 0;
1089
-
1090
- this.initialOverflowCaptureTimerId = setTimeout(() => {
1091
- dispatchAnalyticsEvent({
1092
- action: TABLE_ACTION.INITIAL_OVERFLOW_CAPTURED,
1093
- actionSubject: ACTION_SUBJECT.TABLE,
1094
- actionSubjectId: null,
1095
- eventType: EVENT_TYPE.TRACK,
1096
- attributes: {
1097
- editorWidth: containerWidth.width || 0,
1098
- isOverflowing,
1099
- tableResizingEnabled: options?.isTableResizingEnabled || false,
1100
- width: this.node.attrs.width || 0,
1101
- parentWidth,
1102
- },
1103
- });
1104
-
1105
- this.isInitialOverflowSent = true;
1106
- }, initialOverflowCaptureTimeroutDelay);
1107
- };
1108
-
1109
- private handleAutoSize = () => {
1110
- if (this.table) {
1111
- const { view, getNode, getPos, containerWidth } = this.props;
1112
- const node = getNode();
1113
- const pos = getPos();
1114
- if (!isValidPosition(pos, view.state)) {
1115
- return;
1116
- }
1117
- autoSizeTable(view, node, this.table, pos, {
1118
- containerWidth: containerWidth.width,
1119
- });
1120
- }
1121
- };
1122
-
1123
- private handleWindowResize = () => {
1124
- const { getNode, containerWidth } = this.props;
1125
- const node = getNode();
1126
- const layoutSize = this.tableNodeLayoutSize(node);
1127
-
1128
- if (containerWidth.width > layoutSize) {
1129
- return;
1130
- }
1131
-
1132
- const parentWidth = this.getParentNodeWidth();
1133
- this.scaleTableDebounced({
1134
- parentWidth: parentWidth,
1135
- });
1136
- };
1137
-
1138
- private getParentNodeWidth = () => {
1139
- const {
1140
- getPos,
1141
- containerWidth,
1142
- options,
1143
- view: { state },
1144
- } = this.props;
1145
- const pos = getPos();
1146
- if (!isValidPosition(pos, state)) {
1147
- return;
1148
- }
1149
- const parentNodeWith = getParentNodeWidth(
1150
- pos,
1151
- state,
1152
- containerWidth,
1153
- options && options.isFullWidthModeEnabled,
1154
- );
1155
-
1156
- return parentNodeWith;
1157
- };
1158
-
1159
- private updateParentWidth = (width?: number) => {
1160
- this.setState({ parentWidth: width });
1161
- };
1162
-
1163
- private tableNodeLayoutSize = (
1164
- node: PmNode,
1165
- containerWidth?: number,
1166
- options?: TableOptions,
1167
- ) =>
1168
- getLayoutSize(
1169
- node.attrs.layout,
1170
- containerWidth || this.props.containerWidth.width,
1171
- options || this.props.options || {},
1172
- );
1173
-
1174
- private scaleTableDebounced = rafSchedule(this.scaleTable);
1175
- private handleTableResizingDebounced = rafSchedule(this.handleTableResizing);
1176
- private handleScrollDebounced = rafSchedule(this.handleScroll);
1177
- private handleAutoSizeDebounced = rafSchedule(this.handleAutoSize);
1178
- private handleWindowResizeDebounced = rafSchedule(this.handleWindowResize);
122
+ static displayName = 'TableComponent';
123
+
124
+ state: TableState = {
125
+ scroll: 0,
126
+ parentWidth: undefined,
127
+ [ShadowEvent.SHOW_BEFORE_SHADOW]: false,
128
+ [ShadowEvent.SHOW_AFTER_SHADOW]: false,
129
+ tableWrapperWidth: undefined,
130
+ tableWrapperHeight: undefined,
131
+ };
132
+
133
+ private wrapper?: HTMLDivElement | null;
134
+ private table?: HTMLTableElement | null;
135
+ private node: PmNode;
136
+ private containerWidth?: EditorContainerWidth;
137
+ private wasResizing?: boolean;
138
+ private tableNodeWidth?: number;
139
+ private layoutSize?: number;
140
+ private overflowShadowsObserver?: OverflowShadowsObserver;
141
+ private stickyScrollbar?: TableStickyScrollbar;
142
+
143
+ private isInitialOverflowSent: boolean;
144
+ private initialOverflowCaptureTimerId?: ReturnType<typeof setTimeout>;
145
+ private resizeObserver?: ResizeObserver;
146
+
147
+ private dragAndDropCleanupFn?: CleanupFn;
148
+
149
+ constructor(props: ComponentProps) {
150
+ super(props);
151
+ const { options, containerWidth, getNode } = props;
152
+ this.node = getNode();
153
+ this.containerWidth = containerWidth;
154
+ this.isInitialOverflowSent = false;
155
+
156
+ // store table size using previous full-width mode so can detect if it has changed.
157
+ const isFullWidthModeEnabled = options ? options.wasFullWidthModeEnabled : false;
158
+ this.layoutSize = this.tableNodeLayoutSize(this.node, containerWidth.width, {
159
+ isFullWidthModeEnabled,
160
+ });
161
+
162
+ this.resizeObserver = new ResizeObserver((entries) => {
163
+ for (let entry of entries) {
164
+ this.setState((prev) => {
165
+ return prev?.tableWrapperWidth === entry.contentRect?.width &&
166
+ prev?.tableWrapperHeight === entry.contentRect?.height
167
+ ? prev
168
+ : {
169
+ ...prev,
170
+ tableWrapperWidth: entry.contentRect.width,
171
+ tableWrapperHeight: entry.contentRect.height,
172
+ };
173
+ });
174
+ }
175
+ });
176
+
177
+ // Disable inline table editing and resizing controls in Firefox
178
+ // https://github.com/ProseMirror/prosemirror/issues/432
179
+ if ('execCommand' in document) {
180
+ ['enableObjectResizing', 'enableInlineTableEditing'].forEach((cmd) => {
181
+ if (document.queryCommandSupported(cmd)) {
182
+ document.execCommand(cmd, false, 'false');
183
+ }
184
+ });
185
+ }
186
+ }
187
+
188
+ componentDidMount() {
189
+ const {
190
+ allowColumnResizing,
191
+ eventDispatcher,
192
+ options,
193
+ isDragAndDropEnabled,
194
+ getNode,
195
+ getEditorFeatureFlags,
196
+ isTableScalingEnabled,
197
+ } = this.props;
198
+
199
+ if (getBooleanFF('platform.editor.table.live-pages-sorting_4malx')) {
200
+ const { mode } =
201
+ this.props.pluginInjectionApi?.editorViewMode?.sharedState.currentState() || {};
202
+ if (mode === 'view') {
203
+ this?.table?.addEventListener('mouseenter', this.handleMouseEnter);
204
+ }
205
+ }
206
+
207
+ if (
208
+ isTableScalingEnabled &&
209
+ !getBooleanFF('platform.editor.table.preserve-widths-with-lock-button')
210
+ ) {
211
+ this.handleColgroupUpdates(true);
212
+ }
213
+
214
+ if (
215
+ isTableScalingEnabled &&
216
+ getBooleanFF('platform.editor.table.preserve-widths-with-lock-button') &&
217
+ getNode().attrs.displayMode !== 'fixed'
218
+ ) {
219
+ this.handleColgroupUpdates(true);
220
+ }
221
+
222
+ if (allowColumnResizing && this.wrapper && !isIE11) {
223
+ this.wrapper.addEventListener('scroll', this.handleScrollDebounced, {
224
+ passive: true,
225
+ });
226
+
227
+ const { stickyScrollbar } = getEditorFeatureFlags();
228
+
229
+ if (stickyScrollbar) {
230
+ if (this.table) {
231
+ this.stickyScrollbar = new TableStickyScrollbar(this.wrapper, this.props.view);
232
+ }
233
+ }
234
+
235
+ if (isDragAndDropEnabled) {
236
+ this.dragAndDropCleanupFn = combine(
237
+ ...autoScrollerFactory({
238
+ tableWrapper: this.wrapper,
239
+ getNode,
240
+ }),
241
+ );
242
+ }
243
+ }
244
+
245
+ if (allowColumnResizing) {
246
+ /**
247
+ * We no longer use `containerWidth` as a variable to determine an update for table resizing (avoids unnecessary updates).
248
+ * Instead we use the resize event to only trigger updates when necessary.
249
+ */
250
+ if (!options?.isTableResizingEnabled) {
251
+ window.addEventListener('resize', this.handleWindowResizeDebounced);
252
+ }
253
+ this.handleTableResizingDebounced();
254
+ }
255
+
256
+ const currentStickyState = stickyHeadersPluginKey.getState(this.props.view.state);
257
+
258
+ if (currentStickyState) {
259
+ this.onStickyState(currentStickyState);
260
+ }
261
+
262
+ eventDispatcher.on((stickyHeadersPluginKey as any).key, this.onStickyState);
263
+
264
+ if (isOverflowAnalyticsEnabled) {
265
+ const initialIsOveflowing =
266
+ this.state[ShadowEvent.SHOW_BEFORE_SHADOW] || this.state[ShadowEvent.SHOW_AFTER_SHADOW];
267
+
268
+ this.setTimerToSendInitialOverflowCaptured(initialIsOveflowing);
269
+ }
270
+ }
271
+
272
+ componentWillUnmount() {
273
+ const {
274
+ allowColumnResizing,
275
+ eventDispatcher,
276
+ options,
277
+ isDragAndDropEnabled,
278
+ getEditorFeatureFlags,
279
+ } = this.props;
280
+ if (this.wrapper && !isIE11) {
281
+ this.wrapper.removeEventListener('scroll', this.handleScrollDebounced);
282
+ }
283
+
284
+ if (isDragAndDropEnabled && this.dragAndDropCleanupFn) {
285
+ this.dragAndDropCleanupFn();
286
+ }
287
+
288
+ this.resizeObserver?.disconnect();
289
+
290
+ const { stickyScrollbar } = getEditorFeatureFlags();
291
+
292
+ if (stickyScrollbar) {
293
+ if (this.stickyScrollbar) {
294
+ this.stickyScrollbar.dispose();
295
+ }
296
+ }
297
+
298
+ this.handleScrollDebounced.cancel();
299
+ this.scaleTableDebounced.cancel();
300
+ this.handleTableResizingDebounced.cancel();
301
+ this.handleAutoSizeDebounced.cancel();
302
+ if (!options?.isTableResizingEnabled) {
303
+ this.handleWindowResizeDebounced.cancel();
304
+ }
305
+
306
+ if (!options?.isTableResizingEnabled && allowColumnResizing) {
307
+ window.removeEventListener('resize', this.handleWindowResizeDebounced);
308
+ }
309
+
310
+ if (getBooleanFF('platform.editor.table.live-pages-sorting_4malx')) {
311
+ this?.table?.removeEventListener('mouseenter', this.handleMouseEnter);
312
+ }
313
+
314
+ if (this.overflowShadowsObserver) {
315
+ this.overflowShadowsObserver.dispose();
316
+ }
317
+
318
+ eventDispatcher.off((stickyHeadersPluginKey as any).key, this.onStickyState);
319
+
320
+ if (this.initialOverflowCaptureTimerId) {
321
+ clearTimeout(this.initialOverflowCaptureTimerId);
322
+ }
323
+ }
324
+
325
+ handleMouseEnter = () => {
326
+ const node = this.props.getNode();
327
+ const pos = this.props.getPos();
328
+ const tr = this.props.view.state.tr;
329
+ const tableId = node.attrs.localId;
330
+ tr.setMeta('mouseEnterTable', [tableId, node, pos]);
331
+ this.props.view.dispatch(tr);
332
+ };
333
+
334
+ handleColgroupUpdates(force = false) {
335
+ const { getNode, containerWidth, isResizing, view, getPos } = this.props;
336
+
337
+ if (!this.table) {
338
+ return;
339
+ }
340
+
341
+ // Remove any widths styles after resizing preview is completed
342
+ this.table.style.width = '';
343
+
344
+ const tableRenderWidth = containerWidth.width - TABLE_EDITOR_MARGIN;
345
+ const tableNode = getNode();
346
+ const start = getPos() || 0;
347
+ const depth = view.state.doc.resolve(start).depth;
348
+
349
+ if (depth !== 0) {
350
+ return;
351
+ }
352
+
353
+ const tableNodeWidth = getTableContainerWidth(tableNode);
354
+ const isTableResizedFullWidth = tableNodeWidth === 1800 && this.wasResizing && !isResizing;
355
+ // Needed for undo / redo
356
+ const isTableWidthChanged = tableNodeWidth !== this.tableNodeWidth;
357
+ const isTableSquashed = tableRenderWidth < tableNodeWidth;
358
+ const isNumberColumnChanged =
359
+ tableNode.attrs.isNumberColumnEnabled !== this.node.attrs.isNumberColumnEnabled;
360
+ const isNumberOfColumnsChanged = tablesHaveDifferentNoOfColumns(tableNode, this.node);
361
+
362
+ const maybeScale =
363
+ isTableSquashed ||
364
+ isTableWidthChanged ||
365
+ isTableResizedFullWidth ||
366
+ isNumberColumnChanged ||
367
+ isNumberOfColumnsChanged;
368
+
369
+ if (force || maybeScale) {
370
+ const { width: containerWidthValue } = containerWidth;
371
+ const isWidthChanged = this.containerWidth?.width !== containerWidthValue;
372
+ const wasTableResized = hasTableBeenResized(this.node);
373
+ const isTableResized = hasTableBeenResized(tableNode);
374
+ const isColumnsDistributed = wasTableResized && !isTableResized;
375
+ const isTableDisplayModeChanged = this.node.attrs.displayMode !== tableNode.attrs.displayMode;
376
+
377
+ const shouldUpdateColgroup =
378
+ isWidthChanged ||
379
+ isColumnsDistributed ||
380
+ isTableResizedFullWidth ||
381
+ isTableWidthChanged ||
382
+ isTableDisplayModeChanged ||
383
+ isNumberColumnChanged ||
384
+ isNumberOfColumnsChanged;
385
+
386
+ if (force || (!isResizing && shouldUpdateColgroup)) {
387
+ const resizeState = getResizeState({
388
+ minWidth: COLUMN_MIN_WIDTH,
389
+ maxSize: tableRenderWidth,
390
+ table: tableNode,
391
+ tableRef: this.table,
392
+ start,
393
+ domAtPos: view.domAtPos.bind(view),
394
+ isTableScalingEnabled: true,
395
+ });
396
+
397
+ let shouldScaleOnColgroupUpdate = false;
398
+ if (
399
+ this.props.options?.isTableScalingEnabled &&
400
+ !getBooleanFF('platform.editor.table.preserve-widths-with-lock-button')
401
+ ) {
402
+ shouldScaleOnColgroupUpdate = true;
403
+ }
404
+
405
+ if (
406
+ this.props.options?.isTableScalingEnabled &&
407
+ getBooleanFF('platform.editor.table.preserve-widths-with-lock-button') &&
408
+ tableNode.attrs.displayMode !== 'fixed'
409
+ ) {
410
+ shouldScaleOnColgroupUpdate = true;
411
+ }
412
+
413
+ // Request animation frame required for Firefox
414
+ requestAnimationFrame(() => {
415
+ updateColgroup(resizeState, this.table!, tableNode, shouldScaleOnColgroupUpdate);
416
+ });
417
+ }
418
+ }
419
+ this.tableNodeWidth = tableNodeWidth;
420
+ this.wasResizing = isResizing;
421
+ this.containerWidth = containerWidth;
422
+ }
423
+
424
+ componentDidUpdate(_: any, prevState: TableState) {
425
+ const {
426
+ view,
427
+ getNode,
428
+ isMediaFullscreen,
429
+ allowColumnResizing,
430
+ isResizing,
431
+ options,
432
+ isTableScalingEnabled, // we could use options.isTableScalingEnabled here
433
+ getPos,
434
+ } = this.props;
435
+ let { isInDanger } = this.props;
436
+
437
+ const table = findTable(view.state.selection);
438
+
439
+ if (!getBooleanFF('platform.editor.table.use-shared-state-hook')) {
440
+ const pluginState = getPluginState(view.state);
441
+ isInDanger = pluginState.isInDanger;
442
+ }
443
+
444
+ let shouldScale = false;
445
+ let shouldHandleColgroupUpdates = false;
446
+
447
+ if (
448
+ isTableScalingEnabled &&
449
+ !getBooleanFF('platform.editor.table.preserve-widths-with-lock-button')
450
+ ) {
451
+ shouldScale = true;
452
+ shouldHandleColgroupUpdates = true;
453
+ }
454
+
455
+ if (
456
+ isTableScalingEnabled &&
457
+ getBooleanFF('platform.editor.table.preserve-widths-with-lock-button') &&
458
+ getNode().attrs.displayMode !== 'fixed'
459
+ ) {
460
+ shouldScale = true;
461
+ shouldHandleColgroupUpdates = true;
462
+ }
463
+
464
+ if (shouldHandleColgroupUpdates) {
465
+ this.handleColgroupUpdates();
466
+ }
467
+
468
+ if (isInDanger && !table) {
469
+ clearHoverSelection()(view.state, view.dispatch);
470
+ }
471
+
472
+ if (this.wrapper?.parentElement && this.table && !this.overflowShadowsObserver) {
473
+ if (this.props.isDragAndDropEnabled) {
474
+ // requestAnimationFrame is used here to fix a race condition issue
475
+ // that happens when a table is nested in expand and expand's width is
476
+ // changed via breakout button
477
+ window.requestAnimationFrame(() => {
478
+ this.overflowShadowsObserver = new OverflowShadowsObserver(
479
+ this.updateShadowState,
480
+ this.table as HTMLElement,
481
+ this.wrapper as HTMLDivElement,
482
+ );
483
+ });
484
+ } else {
485
+ this.overflowShadowsObserver = new OverflowShadowsObserver(
486
+ this.updateShadowState,
487
+ this.table,
488
+ this.wrapper,
489
+ );
490
+ }
491
+ }
492
+
493
+ if (this.overflowShadowsObserver) {
494
+ this.overflowShadowsObserver.observeShadowSentinels(this.state.stickyHeader?.sticky);
495
+ }
496
+
497
+ const currentTable = getNode();
498
+ const previousTable = this.node;
499
+ const isNoOfColumnsChanged = tablesHaveDifferentNoOfColumns(currentTable, previousTable);
500
+ const isNoOfRowsChanged = tablesHaveDifferentNoOfRows(currentTable, previousTable);
501
+ if (isNoOfColumnsChanged || isNoOfRowsChanged) {
502
+ this.props.pluginInjectionApi?.accessibilityUtils?.actions.ariaNotify(
503
+ getAssistiveMessage(previousTable, currentTable, this.props.intl),
504
+ { priority: 'important' },
505
+ );
506
+ }
507
+ if (currentTable.attrs.__autoSize) {
508
+ // Wait for next tick to handle auto sizing, gives the browser time to do layout calc etc.
509
+ this.handleAutoSizeDebounced();
510
+ }
511
+ // re-drawing will cause media component get unmounted that will exit fullscreen mode if media is in fullscreen mode
512
+ // see https://product-fabric.atlassian.net/browse/MEX-1290
513
+ else if (allowColumnResizing && this.table && !isMediaFullscreen) {
514
+ // If col widths (e.g. via collab) or number of columns (e.g. delete a column) have changed,
515
+ // re-draw colgroup.
516
+ if (tablesHaveDifferentColumnWidths(currentTable, previousTable) || isNoOfColumnsChanged) {
517
+ const { view } = this.props;
518
+ const shouldRecreateResizeCols =
519
+ !options?.isTableResizingEnabled || !isResizing || (isNoOfColumnsChanged && isResizing);
520
+
521
+ if (shouldRecreateResizeCols) {
522
+ const start = getPos() || 0;
523
+ const depth = view.state.doc.resolve(start).depth;
524
+ shouldScale = depth === 0 && shouldScale;
525
+
526
+ insertColgroupFromNode(this.table, currentTable, shouldScale);
527
+ }
528
+
529
+ updateControls()(view.state);
530
+ }
531
+
532
+ this.handleTableResizingDebounced();
533
+ }
534
+ if (isOverflowAnalyticsEnabled) {
535
+ const newIsOverflowing =
536
+ this.state[ShadowEvent.SHOW_BEFORE_SHADOW] || this.state[ShadowEvent.SHOW_AFTER_SHADOW];
537
+
538
+ const prevIsOverflowing =
539
+ prevState[ShadowEvent.SHOW_BEFORE_SHADOW] || prevState[ShadowEvent.SHOW_AFTER_SHADOW];
540
+
541
+ if (this.initialOverflowCaptureTimerId) {
542
+ clearTimeout(this.initialOverflowCaptureTimerId);
543
+ }
544
+
545
+ if (!this.isInitialOverflowSent) {
546
+ this.setTimerToSendInitialOverflowCaptured(newIsOverflowing);
547
+ }
548
+
549
+ if (this.isInitialOverflowSent && prevIsOverflowing !== newIsOverflowing) {
550
+ const {
551
+ dispatch,
552
+ state: { tr },
553
+ } = this.props.view;
554
+
555
+ dispatch(
556
+ tr.setMeta(META_KEYS.OVERFLOW_STATE_CHANGED, {
557
+ isOverflowing: newIsOverflowing,
558
+ wasOverflowing: prevIsOverflowing,
559
+ editorWidth: this.props.containerWidth.width || 0,
560
+ width: this.node.attrs.width || 0,
561
+ parentWidth: this.state?.parentWidth || 0,
562
+ }),
563
+ );
564
+ }
565
+ }
566
+ }
567
+
568
+ private updateShadowState = (shadowKey: ShadowEvent, value: boolean) => {
569
+ if (this.state[shadowKey] === value) {
570
+ return;
571
+ }
572
+ this.setState({ [shadowKey]: value } as Pick<TableState, typeof shadowKey>);
573
+ };
574
+
575
+ private createShadowSentinels = (table: HTMLTableElement | null) => {
576
+ if (table) {
577
+ const shadowSentinelLeft = document.createElement('span');
578
+ shadowSentinelLeft.className = ClassName.TABLE_SHADOW_SENTINEL_LEFT;
579
+ const shadowSentinelRight = document.createElement('span');
580
+ shadowSentinelRight.className = ClassName.TABLE_SHADOW_SENTINEL_RIGHT;
581
+ table.prepend(shadowSentinelLeft);
582
+ table.prepend(shadowSentinelRight);
583
+ }
584
+ };
585
+
586
+ private observeTable(table: HTMLTableElement | null) {
587
+ if (table) {
588
+ this.resizeObserver?.observe(table);
589
+ }
590
+ }
591
+
592
+ onStickyState = (state: StickyPluginState) => {
593
+ const pos = this.props.getPos();
594
+ if (!isValidPosition(pos, this.props.view.state)) {
595
+ return;
596
+ }
597
+ const stickyHeader = findStickyHeaderForTable(state, pos);
598
+ if (stickyHeader !== this.state.stickyHeader) {
599
+ this.setState({ stickyHeader });
600
+ if (this.overflowShadowsObserver) {
601
+ this.overflowShadowsObserver.updateStickyShadows();
602
+ }
603
+ }
604
+ };
605
+
606
+ prevTableState: any = null;
607
+
608
+ render() {
609
+ const {
610
+ view,
611
+ getNode,
612
+ isResizing,
613
+ allowControls = true,
614
+ isHeaderRowEnabled,
615
+ ordering,
616
+ isHeaderColumnEnabled,
617
+ tableActive,
618
+ containerWidth,
619
+ options,
620
+ getPos,
621
+ pluginInjectionApi,
622
+ isDragAndDropEnabled,
623
+ getEditorFeatureFlags,
624
+ isTableScalingEnabled, // here we can use options.isTableScalingEnabled
625
+ isTableAlignmentEnabled,
626
+ } = this.props;
627
+
628
+ let { isInDanger, hoveredRows, hoveredCell, isTableHovered, isWholeTableInDanger } = this.props;
629
+
630
+ const { showBeforeShadow, showAfterShadow } = this.state;
631
+ const node = getNode();
632
+
633
+ if (!getBooleanFF('platform.editor.table.use-shared-state-hook')) {
634
+ const pluginState = getPluginState(view.state);
635
+ isInDanger = pluginState.isInDanger;
636
+ hoveredRows = pluginState.hoveredRows;
637
+ hoveredCell = pluginState.hoveredCell;
638
+ isTableHovered = pluginState.isTableHovered;
639
+ isWholeTableInDanger = pluginState.isWholeTableInDanger;
640
+ }
641
+
642
+ const tableRef = this.table || undefined;
643
+ const headerRow = tableRef
644
+ ? tableRef.querySelector<HTMLTableRowElement>('tr[data-header-row]')
645
+ : undefined;
646
+
647
+ const hasHeaderRow = containsHeaderRow(node);
648
+ const rowControls = (
649
+ <TableFloatingControls
650
+ editorView={view}
651
+ tableRef={tableRef}
652
+ tableNode={node}
653
+ tableActive={tableActive}
654
+ hoveredRows={hoveredRows}
655
+ hoveredCell={hoveredCell}
656
+ isTableHovered={isTableHovered}
657
+ isInDanger={isInDanger}
658
+ isResizing={isResizing}
659
+ isNumberColumnEnabled={node.attrs.isNumberColumnEnabled}
660
+ isHeaderRowEnabled={isHeaderRowEnabled}
661
+ isDragAndDropEnabled={isDragAndDropEnabled}
662
+ ordering={ordering}
663
+ isHeaderColumnEnabled={isHeaderColumnEnabled}
664
+ hasHeaderRow={hasHeaderRow}
665
+ // pass `selection` and `tableHeight` to control re-render
666
+ selection={view.state.selection}
667
+ headerRowHeight={headerRow ? headerRow.offsetHeight : undefined}
668
+ stickyHeader={this.state.stickyHeader}
669
+ tableWrapperWidth={this.state.tableWrapperWidth}
670
+ api={pluginInjectionApi}
671
+ />
672
+ );
673
+ const tableContainerWidth = getTableContainerWidth(node);
674
+ const colControls = isDragAndDropEnabled ? (
675
+ <TableFloatingColumnControls
676
+ editorView={view}
677
+ tableRef={tableRef}
678
+ getNode={getNode}
679
+ tableActive={tableActive}
680
+ isInDanger={isInDanger}
681
+ hoveredRows={hoveredRows}
682
+ hoveredCell={hoveredCell}
683
+ isTableHovered={isTableHovered}
684
+ isResizing={isResizing}
685
+ ordering={ordering}
686
+ hasHeaderRow={hasHeaderRow}
687
+ // pass `selection` to control re-render
688
+ selection={view.state.selection}
689
+ headerRowHeight={headerRow ? headerRow.offsetHeight : undefined}
690
+ stickyHeader={this.state.stickyHeader}
691
+ getEditorFeatureFlags={getEditorFeatureFlags}
692
+ tableContainerWidth={tableContainerWidth}
693
+ isNumberColumnEnabled={node.attrs.isNumberColumnEnabled}
694
+ getScrollOffset={() => this.wrapper?.scrollLeft || 0}
695
+ tableWrapperHeight={this.state.tableWrapperHeight}
696
+ api={pluginInjectionApi}
697
+ />
698
+ ) : null;
699
+
700
+ const shadowPadding = allowControls && tableActive ? -tableToolbarSize : tableMarginSides;
701
+
702
+ const shadowStyle = memoizeOne(
703
+ (visible) => ({ visibility: visible ? 'visible' : 'hidden' }) as CSSProperties,
704
+ );
705
+
706
+ /**
707
+ * ED-19838
708
+ * There is a getPos issue coming from this code. We need to apply this workaround for now and apply a patch
709
+ * before CR6 lands in production
710
+ */
711
+ let tablePos: number | undefined;
712
+ try {
713
+ tablePos = getPos ? getPos() : undefined;
714
+ } catch (e) {
715
+ tablePos = undefined;
716
+ }
717
+
718
+ const isNested = isTableNested(view.state, tablePos);
719
+
720
+ const topStickyShadowPosition = isDragAndDropEnabled
721
+ ? this.state.stickyHeader && this.state.stickyHeader.top + this.state.stickyHeader.padding + 2
722
+ : this.state.stickyHeader &&
723
+ this.state.stickyHeader.top + this.state.stickyHeader.padding + shadowPadding + 2;
724
+
725
+ const { stickyScrollbar } = getEditorFeatureFlags();
726
+
727
+ return (
728
+ <TableContainer
729
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
730
+ className={classnames(ClassName.TABLE_CONTAINER, {
731
+ [ClassName.WITH_CONTROLS]: allowControls && tableActive,
732
+ [ClassName.TABLE_STICKY]: this.state.stickyHeader && hasHeaderRow,
733
+ [ClassName.HOVERED_DELETE_BUTTON]: isInDanger,
734
+ [ClassName.TABLE_SELECTED]: isTableSelected(view.state.selection),
735
+ })}
736
+ editorView={view}
737
+ getPos={getPos}
738
+ node={node}
739
+ tableRef={tableRef!}
740
+ containerWidth={containerWidth}
741
+ isNested={isNested}
742
+ pluginInjectionApi={pluginInjectionApi}
743
+ tableWrapperHeight={this.state.tableWrapperHeight}
744
+ isTableResizingEnabled={options?.isTableResizingEnabled}
745
+ isResizing={isResizing}
746
+ isTableScalingEnabled={isTableScalingEnabled}
747
+ isWholeTableInDanger={isWholeTableInDanger}
748
+ isTableAlignmentEnabled={isTableAlignmentEnabled}
749
+ >
750
+ <div
751
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
752
+ className={ClassName.TABLE_STICKY_SENTINEL_TOP}
753
+ data-testid="sticky-sentinel-top"
754
+ />
755
+ {stickyScrollbar && (
756
+ <div
757
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
758
+ className={ClassName.TABLE_STICKY_SCROLLBAR_SENTINEL_TOP}
759
+ data-testid="sticky-scrollbar-sentinel-top"
760
+ />
761
+ )}
762
+
763
+ {allowControls && rowControls}
764
+ {isDragAndDropEnabled && (
765
+ <ExternalDropTargets
766
+ editorView={view}
767
+ node={node}
768
+ getScrollOffset={() => {
769
+ return this.wrapper?.scrollLeft || 0;
770
+ }}
771
+ getTableWrapperWidth={() => {
772
+ return this.wrapper?.clientWidth || 760;
773
+ }}
774
+ />
775
+ )}
776
+ <div
777
+ contentEditable={false}
778
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
779
+ style={shadowStyle(showBeforeShadow)}
780
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
781
+ className={ClassName.TABLE_LEFT_SHADOW}
782
+ />
783
+ {this.state.stickyHeader && (
784
+ <div
785
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
786
+ className={`${ClassName.TABLE_LEFT_SHADOW} ${ClassName.TABLE_STICKY_SHADOW}`}
787
+ style={{
788
+ visibility: showBeforeShadow && hasHeaderRow ? 'visible' : 'hidden',
789
+ top: `${topStickyShadowPosition}px`,
790
+ paddingBottom: `${isDragAndDropEnabled && token('space.025', '2px')}`,
791
+ }}
792
+ />
793
+ )}
794
+ <div
795
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
796
+ className={classnames(ClassName.TABLE_NODE_WRAPPER)}
797
+ ref={(elem) => {
798
+ this.wrapper = elem;
799
+ if (elem) {
800
+ this.props.contentDOM(elem);
801
+ const tableElement = elem.querySelector('table');
802
+ if (tableElement !== this.table) {
803
+ this.table = tableElement;
804
+ this.createShadowSentinels(this.table);
805
+ this.observeTable(this.table);
806
+ }
807
+ }
808
+ }}
809
+ >
810
+ {allowControls && colControls}
811
+ </div>
812
+ {stickyScrollbar && (
813
+ <div
814
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
815
+ className={ClassName.TABLE_STICKY_SCROLLBAR_CONTAINER}
816
+ style={{
817
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
818
+ height: token('space.250', '20px'), // MAX_BROWSER_SCROLLBAR_HEIGHT
819
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
820
+ display: 'none',
821
+ // prevent unwanted scroll during table resize without removing scrollbar container from the dom
822
+ width: isResizing ? token('space.0', '0px') : '100%',
823
+ }}
824
+ >
825
+ <div
826
+ style={{
827
+ width: tableRef?.clientWidth,
828
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
829
+ height: '100%',
830
+ }}
831
+ ></div>
832
+ </div>
833
+ )}
834
+ <div
835
+ contentEditable={false}
836
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
837
+ style={shadowStyle(showAfterShadow)}
838
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
839
+ className={ClassName.TABLE_RIGHT_SHADOW}
840
+ />
841
+ {this.state.stickyHeader && (
842
+ <div
843
+ style={{
844
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
845
+ position: 'absolute',
846
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
847
+ right: token('space.400', '32px'), // tableOverflowShadowWidthWide
848
+ }}
849
+ >
850
+ <div
851
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
852
+ className={`${ClassName.TABLE_RIGHT_SHADOW} ${ClassName.TABLE_STICKY_SHADOW}`}
853
+ style={{
854
+ visibility: showAfterShadow && hasHeaderRow ? 'visible' : 'hidden',
855
+ top: `${topStickyShadowPosition}px`,
856
+ paddingBottom: `${isDragAndDropEnabled && token('space.025', '2px')}`,
857
+ }}
858
+ />
859
+ </div>
860
+ )}
861
+ <div
862
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
863
+ className={ClassName.TABLE_STICKY_SENTINEL_BOTTOM}
864
+ data-testid="sticky-sentinel-bottom"
865
+ />
866
+ {stickyScrollbar && (
867
+ <div
868
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
869
+ className={ClassName.TABLE_STICKY_SCROLLBAR_SENTINEL_BOTTOM}
870
+ data-testid="sticky-scrollbar-sentinel-bottom"
871
+ />
872
+ )}
873
+ </TableContainer>
874
+ );
875
+ }
876
+
877
+ private handleScroll = (event: Event) => {
878
+ if (!this.wrapper || event.target !== this.wrapper) {
879
+ return;
880
+ }
881
+
882
+ const { stickyScrollbar } = this.props.getEditorFeatureFlags();
883
+
884
+ if (stickyScrollbar) {
885
+ if (this.stickyScrollbar) {
886
+ this.stickyScrollbar.scrollLeft(this.wrapper.scrollLeft);
887
+ }
888
+ }
889
+
890
+ if (this.table) {
891
+ // sync sticky header row to table scroll
892
+ const headers = this.table.querySelectorAll('tr[data-header-row]');
893
+ for (let i = 0; i < headers.length; i++) {
894
+ const header = headers[i] as HTMLElement;
895
+
896
+ header.scrollLeft = this.wrapper.scrollLeft;
897
+ header.style.marginRight = '2px';
898
+ }
899
+ }
900
+
901
+ this.setState({
902
+ [ShadowEvent.SHOW_BEFORE_SHADOW]: this.wrapper.scrollLeft !== 0,
903
+ });
904
+ };
905
+
906
+ private handleTableResizing = () => {
907
+ const { getNode, containerWidth, options } = this.props;
908
+ const prevNode = this.node!;
909
+ const node = getNode();
910
+ const prevAttrs = prevNode.attrs;
911
+
912
+ const isNested = isTableNested(this.props.view.state, this.props.getPos());
913
+
914
+ const parentWidth = this.getParentNodeWidth();
915
+ const parentWidthChanged = parentWidth && parentWidth !== this.state.parentWidth;
916
+
917
+ const layoutSize = this.tableNodeLayoutSize(node, containerWidth.width, options);
918
+
919
+ const hasNumberedColumnChanged =
920
+ prevAttrs.isNumberColumnEnabled !== node.attrs.isNumberColumnEnabled;
921
+
922
+ const noOfColumnsChanged = tablesHaveDifferentNoOfColumns(node, prevNode);
923
+
924
+ if (
925
+ // We need to react if our parent changes
926
+ // Scales the cols widths relative to the new parent width.
927
+ parentWidthChanged ||
928
+ // Enabling / disabling this feature reduces or adds size to the table.
929
+ hasNumberedColumnChanged ||
930
+ // This last check is also to cater for dynamic text sizing changing the 'default' layout width
931
+ // Usually happens on window resize.
932
+ layoutSize !== this.layoutSize ||
933
+ noOfColumnsChanged
934
+ ) {
935
+ const shouldScaleTable =
936
+ (!options?.isTableResizingEnabled || (options?.isTableResizingEnabled && isNested)) &&
937
+ !hasNumberedColumnChanged &&
938
+ !noOfColumnsChanged;
939
+
940
+ // If column has been inserted/deleted avoid multi dispatch
941
+ if (shouldScaleTable) {
942
+ this.scaleTable({
943
+ parentWidth,
944
+ });
945
+ }
946
+
947
+ // only when table resizing is enabled and toggle numbered column to run scaleTable
948
+ if (options?.isTableResizingEnabled && hasNumberedColumnChanged) {
949
+ if (!hasTableBeenResized(prevNode)) {
950
+ this.scaleTable({
951
+ parentWidth: node.attrs.width,
952
+ });
953
+ }
954
+ }
955
+
956
+ this.updateParentWidth(parentWidth);
957
+ }
958
+
959
+ this.node = node;
960
+ this.containerWidth = containerWidth;
961
+ this.layoutSize = layoutSize;
962
+ };
963
+
964
+ private scaleTable = (scaleOptions: { parentWidth?: number }) => {
965
+ const { view, getNode, getPos, containerWidth, options } = this.props;
966
+ const node = getNode();
967
+ const { state, dispatch } = view;
968
+ const pos = getPos();
969
+
970
+ if (typeof pos !== 'number' || !isValidPosition(pos, state)) {
971
+ return;
972
+ }
973
+ const domAtPos = view.domAtPos.bind(view);
974
+ const { width } = containerWidth;
975
+
976
+ this.scaleTableDebounced.cancel();
977
+
978
+ const tr = scaleTable(
979
+ this.table,
980
+ {
981
+ ...scaleOptions,
982
+ node,
983
+ prevNode: this.node || node,
984
+ start: pos + 1,
985
+ containerWidth: width,
986
+ previousContainerWidth: this.containerWidth!.width || width,
987
+ ...options,
988
+ },
989
+ domAtPos,
990
+ false,
991
+ )(state.tr);
992
+
993
+ dispatch(tr);
994
+ };
995
+
996
+ private setTimerToSendInitialOverflowCaptured = (isOverflowing: boolean) => {
997
+ const { dispatchAnalyticsEvent, containerWidth, options } = this.props;
998
+ const parentWidth = this.state?.parentWidth || 0;
999
+
1000
+ this.initialOverflowCaptureTimerId = setTimeout(() => {
1001
+ dispatchAnalyticsEvent({
1002
+ action: TABLE_ACTION.INITIAL_OVERFLOW_CAPTURED,
1003
+ actionSubject: ACTION_SUBJECT.TABLE,
1004
+ actionSubjectId: null,
1005
+ eventType: EVENT_TYPE.TRACK,
1006
+ attributes: {
1007
+ editorWidth: containerWidth.width || 0,
1008
+ isOverflowing,
1009
+ tableResizingEnabled: options?.isTableResizingEnabled || false,
1010
+ width: this.node.attrs.width || 0,
1011
+ parentWidth,
1012
+ },
1013
+ });
1014
+
1015
+ this.isInitialOverflowSent = true;
1016
+ }, initialOverflowCaptureTimeroutDelay);
1017
+ };
1018
+
1019
+ private handleAutoSize = () => {
1020
+ if (this.table) {
1021
+ const { view, getNode, getPos, containerWidth } = this.props;
1022
+ const node = getNode();
1023
+ const pos = getPos();
1024
+ if (!isValidPosition(pos, view.state)) {
1025
+ return;
1026
+ }
1027
+ autoSizeTable(view, node, this.table, pos, {
1028
+ containerWidth: containerWidth.width,
1029
+ });
1030
+ }
1031
+ };
1032
+
1033
+ private handleWindowResize = () => {
1034
+ const { getNode, containerWidth } = this.props;
1035
+ const node = getNode();
1036
+ const layoutSize = this.tableNodeLayoutSize(node);
1037
+
1038
+ if (containerWidth.width > layoutSize) {
1039
+ return;
1040
+ }
1041
+
1042
+ const parentWidth = this.getParentNodeWidth();
1043
+ this.scaleTableDebounced({
1044
+ parentWidth: parentWidth,
1045
+ });
1046
+ };
1047
+
1048
+ private getParentNodeWidth = () => {
1049
+ const {
1050
+ getPos,
1051
+ containerWidth,
1052
+ options,
1053
+ view: { state },
1054
+ } = this.props;
1055
+ const pos = getPos();
1056
+ if (!isValidPosition(pos, state)) {
1057
+ return;
1058
+ }
1059
+ const parentNodeWith = getParentNodeWidth(
1060
+ pos,
1061
+ state,
1062
+ containerWidth,
1063
+ options && options.isFullWidthModeEnabled,
1064
+ );
1065
+
1066
+ return parentNodeWith;
1067
+ };
1068
+
1069
+ private updateParentWidth = (width?: number) => {
1070
+ this.setState({ parentWidth: width });
1071
+ };
1072
+
1073
+ private tableNodeLayoutSize = (node: PmNode, containerWidth?: number, options?: TableOptions) =>
1074
+ getLayoutSize(
1075
+ node.attrs.layout,
1076
+ containerWidth || this.props.containerWidth.width,
1077
+ options || this.props.options || {},
1078
+ );
1079
+
1080
+ private scaleTableDebounced = rafSchedule(this.scaleTable);
1081
+ private handleTableResizingDebounced = rafSchedule(this.handleTableResizing);
1082
+ private handleScrollDebounced = rafSchedule(this.handleScroll);
1083
+ private handleAutoSizeDebounced = rafSchedule(this.handleAutoSize);
1084
+ private handleWindowResizeDebounced = rafSchedule(this.handleWindowResize);
1179
1085
  }
1180
1086
 
1181
1087
  export default injectIntl(TableComponent);