@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
@@ -10,16 +10,16 @@ import { getPluginState } from '../pm-plugins/plugin-factory';
10
10
  import { pluginKey as tablePluginKey } from '../pm-plugins/plugin-key';
11
11
  import { updateStickyState } from '../pm-plugins/sticky-headers/commands';
12
12
  import {
13
- syncStickyRowToTable,
14
- updateStickyMargins as updateTableMargin,
13
+ syncStickyRowToTable,
14
+ updateStickyMargins as updateTableMargin,
15
15
  } from '../pm-plugins/table-resizing/utils/dom';
16
16
  import type { TablePluginState } from '../types';
17
17
  import { TableCssClassName as ClassName, TableCssClassName } from '../types';
18
18
  import {
19
- stickyHeaderBorderBottomWidth,
20
- stickyRowOffsetTop,
21
- tableControlsSpacing,
22
- tableScrollbarOffset,
19
+ stickyHeaderBorderBottomWidth,
20
+ stickyRowOffsetTop,
21
+ tableControlsSpacing,
22
+ tableScrollbarOffset,
23
23
  } from '../ui/consts';
24
24
  import type { TableDOMElements } from '../utils/dom';
25
25
  import { getTop, getTree } from '../utils/dom';
@@ -28,9 +28,9 @@ import { supportedHeaderRow } from '../utils/nodes';
28
28
  import TableNodeView from './TableNodeViewBase';
29
29
 
30
30
  interface SentinelData {
31
- isIntersecting: boolean;
32
- boundingClientRect: DOMRectReadOnly | null;
33
- rootBounds: DOMRectReadOnly | null;
31
+ isIntersecting: boolean;
32
+ boundingClientRect: DOMRectReadOnly | null;
33
+ rootBounds: DOMRectReadOnly | null;
34
34
  }
35
35
 
36
36
  // limit scroll event calls
@@ -40,367 +40,336 @@ const HEADER_ROW_SCROLL_THROTTLE_TIMEOUT = 200;
40
40
  // if too short it would trigger too many dom updates.
41
41
  const HEADER_ROW_SCROLL_RESET_DEBOUNCE_TIMEOUT = 400;
42
42
 
43
- export default class TableRow
44
- extends TableNodeView<HTMLTableRowElement>
45
- implements NodeView
46
- {
47
- constructor(
48
- node: PMNode,
49
- view: EditorView,
50
- getPos: () => number | undefined,
51
- eventDispatcher: EventDispatcher,
52
- ) {
53
- super(node, view, getPos, eventDispatcher);
54
-
55
- this.isHeaderRow = supportedHeaderRow(node);
56
- this.isSticky = false;
57
-
58
- const { pluginConfig } = getPluginState(view.state);
59
-
60
- this.isStickyHeaderEnabled = !!pluginConfig.stickyHeaders;
61
-
62
- if (this.isHeaderRow) {
63
- this.dom.setAttribute('data-header-row', 'true');
64
- if (this.isStickyHeaderEnabled) {
65
- this.subscribe();
66
- }
67
- }
68
- }
69
-
70
- /**
71
- * Variables
72
- */
73
- private isHeaderRow: boolean;
74
- private isStickyHeaderEnabled: boolean;
75
- private editorScrollableElement?: HTMLElement | Window;
76
- private colControlsOffset = 0;
77
- private focused = false;
78
- private topPosEditorElement = 0;
79
- private isSticky: boolean;
80
- private intersectionObserver?: IntersectionObserver;
81
- private resizeObserver?: ResizeObserver;
82
- private sentinels: {
83
- top?: HTMLElement | null;
84
- bottom?: HTMLElement | null;
85
- } = {};
86
- private sentinelData: {
87
- top: SentinelData;
88
- bottom: SentinelData;
89
- } = {
90
- top: {
91
- isIntersecting: false,
92
- boundingClientRect: null,
93
- rootBounds: null,
94
- },
95
- bottom: {
96
- isIntersecting: false,
97
- boundingClientRect: null,
98
- rootBounds: null,
99
- },
100
- };
101
- private stickyRowHeight?: number;
102
- private listening = false;
103
- private padding: number = 0;
104
- private top: number = 0;
105
-
106
- /**
107
- * Methods: Nodeview Lifecycle
108
- */
109
- update(node: PMNode, ..._args: any[]) {
110
- // do nothing if nodes were identical
111
- if (node === this.node) {
112
- return true;
113
- }
114
-
115
- // see if we're changing into a header row or
116
- // changing away from one
117
- const newNodeIsHeaderRow = supportedHeaderRow(node);
118
- if (this.isHeaderRow !== newNodeIsHeaderRow) {
119
- return false; // re-create nodeview
120
- }
121
-
122
- // node is different but no need to re-create nodeview
123
- this.node = node;
124
-
125
- // don't do anything if we're just a regular tr
126
- if (!this.isHeaderRow) {
127
- return true;
128
- }
129
-
130
- // something changed, sync widths
131
- if (this.isStickyHeaderEnabled) {
132
- const tbody = this.dom.parentElement;
133
- const table = tbody && tbody.parentElement;
134
- syncStickyRowToTable(table);
135
- }
136
-
137
- return true;
138
- }
139
-
140
- destroy() {
141
- if (this.isStickyHeaderEnabled) {
142
- this.unsubscribe();
143
-
144
- const tree = getTree(this.dom);
145
- if (tree) {
146
- this.makeRowHeaderNotSticky(tree.table, true);
147
- }
148
-
149
- this.emitOff(true);
150
- }
151
- }
152
-
153
- ignoreMutation(
154
- mutationRecord: MutationRecord | { type: 'selection'; target: Element },
155
- ) {
156
- /* tableRows are not directly editable by the user
157
- * so it should be safe to ignore mutations that we cause
158
- * by updating styles and classnames on this DOM element
159
- *
160
- * Update: should not ignore mutations for row selection to avoid known issue with table selection highlight in firefox
161
- * Related bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=1289673
162
- * */
163
- const isTableSelection =
164
- mutationRecord.type === 'selection' &&
165
- mutationRecord.target.nodeName === 'TR';
166
- /**
167
- * Update: should not ignore mutations when an node is added, as this interferes with
168
- * prosemirrors handling of some language inputs in Safari (ie. Pinyin, Hiragana).
169
- *
170
- * In paticular, when a composition occurs at the start of the first node inside a table cell, if the resulting mutation
171
- * from the composition end is ignored than prosemirror will end up with; invalid table markup nesting and a misplaced
172
- * selection and insertion.
173
- */
174
- const isNodeInsertion =
175
- mutationRecord.type === 'childList' &&
176
- mutationRecord.target.nodeName === 'TR' &&
177
- mutationRecord.addedNodes.length;
178
-
179
- if (isTableSelection || isNodeInsertion) {
180
- return false;
181
- }
182
-
183
- return true;
184
- }
185
-
186
- /**
187
- * Methods
188
- */
189
- private headerRowMouseScrollEnd = debounce(() => {
190
- this.dom.classList.remove('no-pointer-events');
191
- }, HEADER_ROW_SCROLL_RESET_DEBOUNCE_TIMEOUT);
192
-
193
- // When the header is sticky, the header row is set to position: fixed
194
- // This prevents mouse wheel scrolling on the scroll-parent div when user's mouse is hovering the header row.
195
- // This fix sets pointer-events: none on the header row briefly to avoid this behaviour
196
- private headerRowMouseScroll = throttle(() => {
197
- if (this.isSticky) {
198
- this.dom.classList.add('no-pointer-events');
199
- this.headerRowMouseScrollEnd();
200
- }
201
- }, HEADER_ROW_SCROLL_THROTTLE_TIMEOUT);
202
-
203
- private subscribe() {
204
- this.editorScrollableElement =
205
- findOverflowScrollParent(this.view.dom as HTMLElement) || window;
206
-
207
- if (this.editorScrollableElement) {
208
- this.initObservers();
209
- this.topPosEditorElement = getTop(this.editorScrollableElement);
210
- }
211
-
212
- this.eventDispatcher.on(
213
- 'widthPlugin',
214
- this.updateStickyHeaderWidth.bind(this),
215
- );
216
-
217
- this.eventDispatcher.on(
218
- (tablePluginKey as any).key,
219
- this.onTablePluginState.bind(this),
220
- );
221
-
222
- this.listening = true;
223
-
224
- this.dom.addEventListener('wheel', this.headerRowMouseScroll.bind(this), {
225
- passive: true,
226
- });
227
- this.dom.addEventListener(
228
- 'touchmove',
229
- this.headerRowMouseScroll.bind(this),
230
- { passive: true },
231
- );
232
- }
233
-
234
- private unsubscribe() {
235
- if (!this.listening) {
236
- return;
237
- }
238
- if (this.intersectionObserver) {
239
- this.intersectionObserver.disconnect();
240
- // ED-16211 Once intersection observer is disconnected, we need to remove the isObserved from the sentinels
241
- // Otherwise when newer intersection observer is created it will not observe because it thinks its already being observed
242
- [this.sentinels.top, this.sentinels.bottom].forEach((el) => {
243
- if (el) {
244
- delete el.dataset.isObserved;
245
- }
246
- });
247
- }
248
-
249
- if (this.resizeObserver) {
250
- this.resizeObserver.disconnect();
251
- }
252
-
253
- this.eventDispatcher.off('widthPlugin', this.updateStickyHeaderWidth);
254
- this.eventDispatcher.off(
255
- (tablePluginKey as any).key,
256
- this.onTablePluginState,
257
- );
258
-
259
- this.listening = false;
260
-
261
- this.dom.removeEventListener('wheel', this.headerRowMouseScroll);
262
- this.dom.removeEventListener('touchmove', this.headerRowMouseScroll);
263
- }
264
-
265
- // initialize intersection observer to track if table is within scroll area
266
- private initObservers() {
267
- if (!this.dom || this.dom.dataset.isObserved) {
268
- return;
269
- }
270
- this.dom.dataset.isObserved = 'true';
271
- this.createIntersectionObserver();
272
- this.createResizeObserver();
273
-
274
- if (!this.intersectionObserver || !this.resizeObserver) {
275
- return;
276
- }
277
-
278
- this.resizeObserver.observe(this.dom);
279
- if (this.editorScrollableElement) {
280
- this.resizeObserver.observe(this.editorScrollableElement as HTMLElement);
281
- }
282
-
283
- window.requestAnimationFrame(() => {
284
- // we expect tree to be defined after animation frame
285
- const tableContainer = getTree(this.dom)?.wrapper.closest(
286
- `.${TableCssClassName.NODEVIEW_WRAPPER}`,
287
- );
288
- if (tableContainer) {
289
- this.sentinels.top = tableContainer
290
- .getElementsByClassName(ClassName.TABLE_STICKY_SENTINEL_TOP)
291
- .item(0) as HTMLElement;
292
- this.sentinels.bottom = tableContainer
293
- .getElementsByClassName(ClassName.TABLE_STICKY_SENTINEL_BOTTOM)
294
- .item(0) as HTMLElement;
295
- [this.sentinels.top, this.sentinels.bottom].forEach((el) => {
296
- // skip if already observed for another row on this table
297
- if (el && !el.dataset.isObserved) {
298
- el.dataset.isObserved = 'true';
299
- this.intersectionObserver!.observe(el);
300
- }
301
- });
302
- }
303
- });
304
- }
305
-
306
- // updating bottom sentinel position if sticky header height changes
307
- // to allocate for new header height
308
- private createResizeObserver() {
309
- this.resizeObserver = new ResizeObserver((entries) => {
310
- const tree = getTree(this.dom);
311
- if (!tree) {
312
- return;
313
- }
314
- const { table } = tree;
315
- entries.forEach((entry) => {
316
- // On resize of the parent scroll element we need to adjust the width
317
- // of the sticky header
318
- if (
319
- entry.target.className ===
320
- (this.editorScrollableElement as HTMLElement)?.className
321
- ) {
322
- this.updateStickyHeaderWidth();
323
- } else {
324
- const newHeight = entry.contentRect
325
- ? entry.contentRect.height
326
- : (entry.target as HTMLElement).offsetHeight;
327
-
328
- if (
329
- this.sentinels.bottom &&
330
- // When the table header is sticky, it would be taller by a 1px (border-bottom),
331
- // So we adding this check to allow a 1px difference.
332
- Math.abs(newHeight - (this.stickyRowHeight || 0)) >
333
- stickyHeaderBorderBottomWidth
334
- ) {
335
- this.stickyRowHeight = newHeight;
336
- this.sentinels.bottom.style.bottom = `${
337
- tableScrollbarOffset + stickyRowOffsetTop + newHeight
338
- }px`;
339
-
340
- updateTableMargin(table);
341
- }
342
- }
343
- });
344
- });
345
- }
346
-
347
- private createIntersectionObserver() {
348
- this.intersectionObserver = new IntersectionObserver(
349
- (entries: IntersectionObserverEntry[], _: IntersectionObserver) => {
350
- // IMPORTANT: please try and avoid using entry.rootBounds it's terribly inconsistent. For example; sometimes it may return
351
- // 0 height. In safari it will multiply all values by the window scale factor, however chrome & firfox won't.
352
- // This is why i just get the scroll view bounding rect here and use it, and fallback to the entry.rootBounds if needed.
353
- const rootBounds = (
354
- this.editorScrollableElement as Element
355
- )?.getBoundingClientRect?.();
356
-
357
- entries.forEach((entry) => {
358
- const { target, isIntersecting, boundingClientRect } = entry;
359
- // This observer only every looks at the top/bottom sentinels, we can assume if it's not one then it's the other.
360
- const targetKey = target.classList.contains(
361
- ClassName.TABLE_STICKY_SENTINEL_TOP,
362
- )
363
- ? 'top'
364
- : 'bottom';
365
- // Cache the latest sentinel information
366
- this.sentinelData[targetKey] = {
367
- isIntersecting,
368
- boundingClientRect,
369
- rootBounds: rootBounds ?? entry.rootBounds,
370
- };
371
- // Keep the other sentinels rootBounds in sync with this latest one
372
- this.sentinelData[
373
- targetKey === 'top' ? 'bottom' : targetKey
374
- ].rootBounds = rootBounds ?? entry.rootBounds;
375
- });
376
- this.refreshStickyState();
377
- },
378
- { threshold: 0, root: this.editorScrollableElement as Element },
379
- );
380
- }
381
-
382
- private refreshStickyState() {
383
- const tree = getTree(this.dom);
384
- if (!tree) {
385
- return;
386
- }
387
- const { table } = tree;
388
- const shouldStick = this.isHeaderSticky();
389
- if (this.isSticky !== shouldStick) {
390
- if (shouldStick) {
391
- // The rootRect is kept in sync across sentinels so it doesn't matter which one we use.
392
- const rootRect =
393
- this.sentinelData.top.rootBounds ??
394
- this.sentinelData.bottom.rootBounds;
395
- this.makeHeaderRowSticky(tree, rootRect?.top);
396
- } else {
397
- this.makeRowHeaderNotSticky(table);
398
- }
399
- }
400
- }
401
-
402
- private isHeaderSticky() {
403
- /*
43
+ export default class TableRow extends TableNodeView<HTMLTableRowElement> implements NodeView {
44
+ constructor(
45
+ node: PMNode,
46
+ view: EditorView,
47
+ getPos: () => number | undefined,
48
+ eventDispatcher: EventDispatcher,
49
+ ) {
50
+ super(node, view, getPos, eventDispatcher);
51
+
52
+ this.isHeaderRow = supportedHeaderRow(node);
53
+ this.isSticky = false;
54
+
55
+ const { pluginConfig } = getPluginState(view.state);
56
+
57
+ this.isStickyHeaderEnabled = !!pluginConfig.stickyHeaders;
58
+
59
+ if (this.isHeaderRow) {
60
+ this.dom.setAttribute('data-header-row', 'true');
61
+ if (this.isStickyHeaderEnabled) {
62
+ this.subscribe();
63
+ }
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Variables
69
+ */
70
+ private isHeaderRow: boolean;
71
+ private isStickyHeaderEnabled: boolean;
72
+ private editorScrollableElement?: HTMLElement | Window;
73
+ private colControlsOffset = 0;
74
+ private focused = false;
75
+ private topPosEditorElement = 0;
76
+ private isSticky: boolean;
77
+ private intersectionObserver?: IntersectionObserver;
78
+ private resizeObserver?: ResizeObserver;
79
+ private sentinels: {
80
+ top?: HTMLElement | null;
81
+ bottom?: HTMLElement | null;
82
+ } = {};
83
+ private sentinelData: {
84
+ top: SentinelData;
85
+ bottom: SentinelData;
86
+ } = {
87
+ top: {
88
+ isIntersecting: false,
89
+ boundingClientRect: null,
90
+ rootBounds: null,
91
+ },
92
+ bottom: {
93
+ isIntersecting: false,
94
+ boundingClientRect: null,
95
+ rootBounds: null,
96
+ },
97
+ };
98
+ private stickyRowHeight?: number;
99
+ private listening = false;
100
+ private padding: number = 0;
101
+ private top: number = 0;
102
+
103
+ /**
104
+ * Methods: Nodeview Lifecycle
105
+ */
106
+ update(node: PMNode, ..._args: any[]) {
107
+ // do nothing if nodes were identical
108
+ if (node === this.node) {
109
+ return true;
110
+ }
111
+
112
+ // see if we're changing into a header row or
113
+ // changing away from one
114
+ const newNodeIsHeaderRow = supportedHeaderRow(node);
115
+ if (this.isHeaderRow !== newNodeIsHeaderRow) {
116
+ return false; // re-create nodeview
117
+ }
118
+
119
+ // node is different but no need to re-create nodeview
120
+ this.node = node;
121
+
122
+ // don't do anything if we're just a regular tr
123
+ if (!this.isHeaderRow) {
124
+ return true;
125
+ }
126
+
127
+ // something changed, sync widths
128
+ if (this.isStickyHeaderEnabled) {
129
+ const tbody = this.dom.parentElement;
130
+ const table = tbody && tbody.parentElement;
131
+ syncStickyRowToTable(table);
132
+ }
133
+
134
+ return true;
135
+ }
136
+
137
+ destroy() {
138
+ if (this.isStickyHeaderEnabled) {
139
+ this.unsubscribe();
140
+
141
+ const tree = getTree(this.dom);
142
+ if (tree) {
143
+ this.makeRowHeaderNotSticky(tree.table, true);
144
+ }
145
+
146
+ this.emitOff(true);
147
+ }
148
+ }
149
+
150
+ ignoreMutation(mutationRecord: MutationRecord | { type: 'selection'; target: Element }) {
151
+ /* tableRows are not directly editable by the user
152
+ * so it should be safe to ignore mutations that we cause
153
+ * by updating styles and classnames on this DOM element
154
+ *
155
+ * Update: should not ignore mutations for row selection to avoid known issue with table selection highlight in firefox
156
+ * Related bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=1289673
157
+ * */
158
+ const isTableSelection =
159
+ mutationRecord.type === 'selection' && mutationRecord.target.nodeName === 'TR';
160
+ /**
161
+ * Update: should not ignore mutations when an node is added, as this interferes with
162
+ * prosemirrors handling of some language inputs in Safari (ie. Pinyin, Hiragana).
163
+ *
164
+ * In paticular, when a composition occurs at the start of the first node inside a table cell, if the resulting mutation
165
+ * from the composition end is ignored than prosemirror will end up with; invalid table markup nesting and a misplaced
166
+ * selection and insertion.
167
+ */
168
+ const isNodeInsertion =
169
+ mutationRecord.type === 'childList' &&
170
+ mutationRecord.target.nodeName === 'TR' &&
171
+ mutationRecord.addedNodes.length;
172
+
173
+ if (isTableSelection || isNodeInsertion) {
174
+ return false;
175
+ }
176
+
177
+ return true;
178
+ }
179
+
180
+ /**
181
+ * Methods
182
+ */
183
+ private headerRowMouseScrollEnd = debounce(() => {
184
+ this.dom.classList.remove('no-pointer-events');
185
+ }, HEADER_ROW_SCROLL_RESET_DEBOUNCE_TIMEOUT);
186
+
187
+ // When the header is sticky, the header row is set to position: fixed
188
+ // This prevents mouse wheel scrolling on the scroll-parent div when user's mouse is hovering the header row.
189
+ // This fix sets pointer-events: none on the header row briefly to avoid this behaviour
190
+ private headerRowMouseScroll = throttle(() => {
191
+ if (this.isSticky) {
192
+ this.dom.classList.add('no-pointer-events');
193
+ this.headerRowMouseScrollEnd();
194
+ }
195
+ }, HEADER_ROW_SCROLL_THROTTLE_TIMEOUT);
196
+
197
+ private subscribe() {
198
+ this.editorScrollableElement = findOverflowScrollParent(this.view.dom as HTMLElement) || window;
199
+
200
+ if (this.editorScrollableElement) {
201
+ this.initObservers();
202
+ this.topPosEditorElement = getTop(this.editorScrollableElement);
203
+ }
204
+
205
+ this.eventDispatcher.on('widthPlugin', this.updateStickyHeaderWidth.bind(this));
206
+
207
+ this.eventDispatcher.on((tablePluginKey as any).key, this.onTablePluginState.bind(this));
208
+
209
+ this.listening = true;
210
+
211
+ this.dom.addEventListener('wheel', this.headerRowMouseScroll.bind(this), {
212
+ passive: true,
213
+ });
214
+ this.dom.addEventListener('touchmove', this.headerRowMouseScroll.bind(this), { passive: true });
215
+ }
216
+
217
+ private unsubscribe() {
218
+ if (!this.listening) {
219
+ return;
220
+ }
221
+ if (this.intersectionObserver) {
222
+ this.intersectionObserver.disconnect();
223
+ // ED-16211 Once intersection observer is disconnected, we need to remove the isObserved from the sentinels
224
+ // Otherwise when newer intersection observer is created it will not observe because it thinks its already being observed
225
+ [this.sentinels.top, this.sentinels.bottom].forEach((el) => {
226
+ if (el) {
227
+ delete el.dataset.isObserved;
228
+ }
229
+ });
230
+ }
231
+
232
+ if (this.resizeObserver) {
233
+ this.resizeObserver.disconnect();
234
+ }
235
+
236
+ this.eventDispatcher.off('widthPlugin', this.updateStickyHeaderWidth);
237
+ this.eventDispatcher.off((tablePluginKey as any).key, this.onTablePluginState);
238
+
239
+ this.listening = false;
240
+
241
+ this.dom.removeEventListener('wheel', this.headerRowMouseScroll);
242
+ this.dom.removeEventListener('touchmove', this.headerRowMouseScroll);
243
+ }
244
+
245
+ // initialize intersection observer to track if table is within scroll area
246
+ private initObservers() {
247
+ if (!this.dom || this.dom.dataset.isObserved) {
248
+ return;
249
+ }
250
+ this.dom.dataset.isObserved = 'true';
251
+ this.createIntersectionObserver();
252
+ this.createResizeObserver();
253
+
254
+ if (!this.intersectionObserver || !this.resizeObserver) {
255
+ return;
256
+ }
257
+
258
+ this.resizeObserver.observe(this.dom);
259
+ if (this.editorScrollableElement) {
260
+ this.resizeObserver.observe(this.editorScrollableElement as HTMLElement);
261
+ }
262
+
263
+ window.requestAnimationFrame(() => {
264
+ // we expect tree to be defined after animation frame
265
+ const tableContainer = getTree(this.dom)?.wrapper.closest(
266
+ `.${TableCssClassName.NODEVIEW_WRAPPER}`,
267
+ );
268
+ if (tableContainer) {
269
+ this.sentinels.top = tableContainer
270
+ .getElementsByClassName(ClassName.TABLE_STICKY_SENTINEL_TOP)
271
+ .item(0) as HTMLElement;
272
+ this.sentinels.bottom = tableContainer
273
+ .getElementsByClassName(ClassName.TABLE_STICKY_SENTINEL_BOTTOM)
274
+ .item(0) as HTMLElement;
275
+ [this.sentinels.top, this.sentinels.bottom].forEach((el) => {
276
+ // skip if already observed for another row on this table
277
+ if (el && !el.dataset.isObserved) {
278
+ el.dataset.isObserved = 'true';
279
+ this.intersectionObserver!.observe(el);
280
+ }
281
+ });
282
+ }
283
+ });
284
+ }
285
+
286
+ // updating bottom sentinel position if sticky header height changes
287
+ // to allocate for new header height
288
+ private createResizeObserver() {
289
+ this.resizeObserver = new ResizeObserver((entries) => {
290
+ const tree = getTree(this.dom);
291
+ if (!tree) {
292
+ return;
293
+ }
294
+ const { table } = tree;
295
+ entries.forEach((entry) => {
296
+ // On resize of the parent scroll element we need to adjust the width
297
+ // of the sticky header
298
+ if (entry.target.className === (this.editorScrollableElement as HTMLElement)?.className) {
299
+ this.updateStickyHeaderWidth();
300
+ } else {
301
+ const newHeight = entry.contentRect
302
+ ? entry.contentRect.height
303
+ : (entry.target as HTMLElement).offsetHeight;
304
+
305
+ if (
306
+ this.sentinels.bottom &&
307
+ // When the table header is sticky, it would be taller by a 1px (border-bottom),
308
+ // So we adding this check to allow a 1px difference.
309
+ Math.abs(newHeight - (this.stickyRowHeight || 0)) > stickyHeaderBorderBottomWidth
310
+ ) {
311
+ this.stickyRowHeight = newHeight;
312
+ this.sentinels.bottom.style.bottom = `${
313
+ tableScrollbarOffset + stickyRowOffsetTop + newHeight
314
+ }px`;
315
+
316
+ updateTableMargin(table);
317
+ }
318
+ }
319
+ });
320
+ });
321
+ }
322
+
323
+ private createIntersectionObserver() {
324
+ this.intersectionObserver = new IntersectionObserver(
325
+ (entries: IntersectionObserverEntry[], _: IntersectionObserver) => {
326
+ // IMPORTANT: please try and avoid using entry.rootBounds it's terribly inconsistent. For example; sometimes it may return
327
+ // 0 height. In safari it will multiply all values by the window scale factor, however chrome & firfox won't.
328
+ // This is why i just get the scroll view bounding rect here and use it, and fallback to the entry.rootBounds if needed.
329
+ const rootBounds = (this.editorScrollableElement as Element)?.getBoundingClientRect?.();
330
+
331
+ entries.forEach((entry) => {
332
+ const { target, isIntersecting, boundingClientRect } = entry;
333
+ // This observer only every looks at the top/bottom sentinels, we can assume if it's not one then it's the other.
334
+ const targetKey = target.classList.contains(ClassName.TABLE_STICKY_SENTINEL_TOP)
335
+ ? 'top'
336
+ : 'bottom';
337
+ // Cache the latest sentinel information
338
+ this.sentinelData[targetKey] = {
339
+ isIntersecting,
340
+ boundingClientRect,
341
+ rootBounds: rootBounds ?? entry.rootBounds,
342
+ };
343
+ // Keep the other sentinels rootBounds in sync with this latest one
344
+ this.sentinelData[targetKey === 'top' ? 'bottom' : targetKey].rootBounds =
345
+ rootBounds ?? entry.rootBounds;
346
+ });
347
+ this.refreshStickyState();
348
+ },
349
+ { threshold: 0, root: this.editorScrollableElement as Element },
350
+ );
351
+ }
352
+
353
+ private refreshStickyState() {
354
+ const tree = getTree(this.dom);
355
+ if (!tree) {
356
+ return;
357
+ }
358
+ const { table } = tree;
359
+ const shouldStick = this.isHeaderSticky();
360
+ if (this.isSticky !== shouldStick) {
361
+ if (shouldStick) {
362
+ // The rootRect is kept in sync across sentinels so it doesn't matter which one we use.
363
+ const rootRect = this.sentinelData.top.rootBounds ?? this.sentinelData.bottom.rootBounds;
364
+ this.makeHeaderRowSticky(tree, rootRect?.top);
365
+ } else {
366
+ this.makeRowHeaderNotSticky(table);
367
+ }
368
+ }
369
+ }
370
+
371
+ private isHeaderSticky() {
372
+ /*
404
373
  # Overview
405
374
  I'm going to list all the view states associated with the sentinels and when they should trigger sticky headers.
406
375
  The format of the states are; {top|bottom}:{in|above|below}
@@ -427,263 +396,245 @@ export default class TableRow
427
396
  is in or below it.
428
397
  */
429
398
 
430
- const { top: sentinelTop, bottom: sentinelBottom } = this.sentinelData;
431
- // The rootRect is kept in sync across sentinels so it doesn't matter which one we use.
432
- const rootBounds = sentinelTop.rootBounds ?? sentinelBottom.rootBounds;
433
-
434
- if (
435
- !rootBounds ||
436
- !sentinelTop.boundingClientRect ||
437
- !sentinelBottom.boundingClientRect
438
- ) {
439
- return false;
440
- }
441
-
442
- // Normalize the sentinels to y points
443
- // Since the sentinels are actually rects 1px high we want make sure we normalise the inner most values closest to the table.
444
- const sentinelTopY = sentinelTop.boundingClientRect.bottom;
445
- const sentinelBottomY = sentinelBottom.boundingClientRect.top;
446
-
447
- // If header row height is more than 50% of viewport height don't do this
448
- const isRowHeaderTooLarge =
449
- this.stickyRowHeight && this.stickyRowHeight > window.innerHeight * 0.5;
450
-
451
- const isTopSentinelAboveScrollArea =
452
- !sentinelTop.isIntersecting && sentinelTopY <= rootBounds.top;
453
-
454
- const isBottomSentinelInOrBelowScrollArea =
455
- sentinelBottom.isIntersecting || sentinelBottomY > rootBounds.bottom;
456
-
457
- // This makes sure that the top sentinel is actually above the bottom sentinel, and that they havn't inverted.
458
- const isTopSentinelAboveBottomSentinel = sentinelTopY < sentinelBottomY;
459
-
460
- return (
461
- isTopSentinelAboveScrollArea &&
462
- isBottomSentinelInOrBelowScrollArea &&
463
- isTopSentinelAboveBottomSentinel &&
464
- !isRowHeaderTooLarge
465
- );
466
- }
467
-
468
- /* receive external events */
469
-
470
- private onTablePluginState(state: TablePluginState) {
471
- const tableRef = state.tableRef;
472
-
473
- const tree = getTree(this.dom);
474
- if (!tree) {
475
- return;
476
- }
477
-
478
- // when header rows are toggled off - mark sentinels as unobserved
479
- if (!state.isHeaderRowEnabled) {
480
- [this.sentinels.top, this.sentinels.bottom].forEach((el) => {
481
- if (el) {
482
- delete el.dataset.isObserved;
483
- }
484
- });
485
- }
486
-
487
- const isCurrentTableSelected = tableRef === tree.table;
488
-
489
- // If current table selected and header row is toggled off, turn off sticky header
490
- if (isCurrentTableSelected && !state.isHeaderRowEnabled && tree) {
491
- this.makeRowHeaderNotSticky(tree.table);
492
- }
493
- this.focused = isCurrentTableSelected;
494
-
495
- const { wrapper } = tree;
496
-
497
- const tableContainer = wrapper.parentElement;
498
- const tableContentWrapper = tableContainer?.parentElement;
499
-
500
- const layoutContainer =
501
- tableContentWrapper && tableContentWrapper.parentElement;
502
-
503
- if (isCurrentTableSelected) {
504
- this.colControlsOffset = tableControlsSpacing;
505
-
506
- if (
507
- layoutContainer &&
508
- layoutContainer.getAttribute('data-layout-content')
509
- ) {
510
- // move table a little out of the way
511
- // to provide spacing for table controls
512
- tableContentWrapper!.style.paddingLeft = '11px';
513
- }
514
- } else {
515
- this.colControlsOffset = 0;
516
- if (
517
- layoutContainer &&
518
- layoutContainer.getAttribute('data-layout-content')
519
- ) {
520
- tableContentWrapper!.style.removeProperty('padding-left');
521
- }
522
- }
523
-
524
- // run after table style changes have been committed
525
- setTimeout(() => {
526
- syncStickyRowToTable(tree.table);
527
- }, 0);
528
- }
529
-
530
- private updateStickyHeaderWidth() {
531
- // table width might have changed, sync that back to sticky row
532
- const tree = getTree(this.dom);
533
- if (!tree) {
534
- return;
535
- }
536
-
537
- syncStickyRowToTable(tree.table);
538
- }
539
-
540
- /**
541
- * Manually refire the intersection observers.
542
- * Useful when the header may have detached from the table.
543
- */
544
- private refireIntersectionObservers() {
545
- if (this.isSticky) {
546
- [this.sentinels.top, this.sentinels.bottom].forEach((el) => {
547
- if (el && this.intersectionObserver) {
548
- this.intersectionObserver.unobserve(el);
549
- this.intersectionObserver.observe(el);
550
- }
551
- });
552
- }
553
- }
554
-
555
- private makeHeaderRowSticky(tree: TableDOMElements, scrollTop?: number) {
556
- // If header row height is more than 50% of viewport height don't do this
557
- if (
558
- this.isSticky ||
559
- (this.stickyRowHeight && this.stickyRowHeight > window.innerHeight / 2)
560
- ) {
561
- return;
562
- }
563
-
564
- const { table, wrapper } = tree;
565
-
566
- // ED-16035 Make sure sticky header is only applied to first row
567
- const tbody = this.dom.parentElement;
568
- const isFirstHeader = tbody?.firstChild?.isEqualNode(this.dom);
569
- if (!isFirstHeader) {
570
- return;
571
- }
572
-
573
- const currentTableTop = this.getCurrentTableTop(tree);
574
-
575
- if (!scrollTop) {
576
- scrollTop = getTop(this.editorScrollableElement);
577
- }
578
-
579
- const domTop =
580
- currentTableTop > 0 ? scrollTop : scrollTop + currentTableTop;
581
-
582
- if (!this.isSticky) {
583
- syncStickyRowToTable(table);
584
- this.dom.classList.add('sticky');
585
- table.classList.add(ClassName.TABLE_STICKY);
586
-
587
- this.isSticky = true;
588
-
589
- /**
590
- * The logic below is not desirable, but acts as a fail safe for scenarios where the sticky header
591
- * detaches from the table. This typically happens during a fast scroll by the user which causes
592
- * the intersection observer logic to not fire as expected.
593
- */
594
- this.editorScrollableElement?.addEventListener(
595
- 'scrollend',
596
- this.refireIntersectionObservers,
597
- { passive: true, once: true },
598
- );
599
-
600
- const fastScrollThresholdMs = 500;
601
- setTimeout(() => {
602
- this.refireIntersectionObservers();
603
- }, fastScrollThresholdMs);
604
- }
605
-
606
- this.dom.style.top = `${domTop}px`;
607
- updateTableMargin(table);
608
- this.dom.scrollLeft = wrapper.scrollLeft;
609
-
610
- this.emitOn(domTop, this.colControlsOffset);
611
- }
612
-
613
- private makeRowHeaderNotSticky(
614
- table: HTMLElement,
615
- isEditorDestroyed: boolean = false,
616
- ) {
617
- if (!this.isSticky || !table || !this.dom) {
618
- return;
619
- }
620
-
621
- this.dom.style.removeProperty('width');
622
- this.dom.classList.remove('sticky');
623
- table.classList.remove(ClassName.TABLE_STICKY);
624
-
625
- this.isSticky = false;
626
- this.dom.style.top = '';
627
- table.style.removeProperty('margin-top');
628
-
629
- this.emitOff(isEditorDestroyed);
630
- }
631
-
632
- private getWrapperoffset(inverse: boolean = false): number {
633
- const focusValue = inverse ? !this.focused : this.focused;
634
- return focusValue ? 0 : tableControlsSpacing;
635
- }
636
-
637
- private getWrapperRefTop(wrapper: HTMLElement): number {
638
- return Math.round(getTop(wrapper)) + this.getWrapperoffset();
639
- }
640
-
641
- // TODO: rename!
642
- private getScrolledTableTop(wrapper: HTMLElement): number {
643
- return this.getWrapperRefTop(wrapper) - this.topPosEditorElement;
644
- }
645
-
646
- private getCurrentTableTop(tree: TableDOMElements): number {
647
- return this.getScrolledTableTop(tree.wrapper) + tree.table.clientHeight;
648
- }
649
-
650
- /* emit external events */
651
-
652
- private emitOn(top: number, padding: number) {
653
- if (top === this.top && padding === this.padding) {
654
- return;
655
- }
656
-
657
- this.top = top;
658
- this.padding = padding;
659
- const pos = this.getPos()!;
660
-
661
- if (Number.isFinite(pos)) {
662
- updateStickyState({
663
- pos,
664
- top,
665
- sticky: true,
666
- padding,
667
- })(this.view.state, this.view.dispatch, this.view);
668
- }
669
- }
670
-
671
- private emitOff(isEditorDestroyed: boolean) {
672
- if (this.top === 0 && this.padding === 0) {
673
- return;
674
- }
675
-
676
- this.top = 0;
677
- this.padding = 0;
678
- const pos = this.getPos()!;
679
-
680
- if (!isEditorDestroyed && Number.isFinite(pos)) {
681
- updateStickyState({
682
- pos,
683
- sticky: false,
684
- top: this.top,
685
- padding: this.padding,
686
- })(this.view.state, this.view.dispatch, this.view);
687
- }
688
- }
399
+ const { top: sentinelTop, bottom: sentinelBottom } = this.sentinelData;
400
+ // The rootRect is kept in sync across sentinels so it doesn't matter which one we use.
401
+ const rootBounds = sentinelTop.rootBounds ?? sentinelBottom.rootBounds;
402
+
403
+ if (!rootBounds || !sentinelTop.boundingClientRect || !sentinelBottom.boundingClientRect) {
404
+ return false;
405
+ }
406
+
407
+ // Normalize the sentinels to y points
408
+ // Since the sentinels are actually rects 1px high we want make sure we normalise the inner most values closest to the table.
409
+ const sentinelTopY = sentinelTop.boundingClientRect.bottom;
410
+ const sentinelBottomY = sentinelBottom.boundingClientRect.top;
411
+
412
+ // If header row height is more than 50% of viewport height don't do this
413
+ const isRowHeaderTooLarge =
414
+ this.stickyRowHeight && this.stickyRowHeight > window.innerHeight * 0.5;
415
+
416
+ const isTopSentinelAboveScrollArea =
417
+ !sentinelTop.isIntersecting && sentinelTopY <= rootBounds.top;
418
+
419
+ const isBottomSentinelInOrBelowScrollArea =
420
+ sentinelBottom.isIntersecting || sentinelBottomY > rootBounds.bottom;
421
+
422
+ // This makes sure that the top sentinel is actually above the bottom sentinel, and that they havn't inverted.
423
+ const isTopSentinelAboveBottomSentinel = sentinelTopY < sentinelBottomY;
424
+
425
+ return (
426
+ isTopSentinelAboveScrollArea &&
427
+ isBottomSentinelInOrBelowScrollArea &&
428
+ isTopSentinelAboveBottomSentinel &&
429
+ !isRowHeaderTooLarge
430
+ );
431
+ }
432
+
433
+ /* receive external events */
434
+
435
+ private onTablePluginState(state: TablePluginState) {
436
+ const tableRef = state.tableRef;
437
+
438
+ const tree = getTree(this.dom);
439
+ if (!tree) {
440
+ return;
441
+ }
442
+
443
+ // when header rows are toggled off - mark sentinels as unobserved
444
+ if (!state.isHeaderRowEnabled) {
445
+ [this.sentinels.top, this.sentinels.bottom].forEach((el) => {
446
+ if (el) {
447
+ delete el.dataset.isObserved;
448
+ }
449
+ });
450
+ }
451
+
452
+ const isCurrentTableSelected = tableRef === tree.table;
453
+
454
+ // If current table selected and header row is toggled off, turn off sticky header
455
+ if (isCurrentTableSelected && !state.isHeaderRowEnabled && tree) {
456
+ this.makeRowHeaderNotSticky(tree.table);
457
+ }
458
+ this.focused = isCurrentTableSelected;
459
+
460
+ const { wrapper } = tree;
461
+
462
+ const tableContainer = wrapper.parentElement;
463
+ const tableContentWrapper = tableContainer?.parentElement;
464
+
465
+ const layoutContainer = tableContentWrapper && tableContentWrapper.parentElement;
466
+
467
+ if (isCurrentTableSelected) {
468
+ this.colControlsOffset = tableControlsSpacing;
469
+
470
+ if (layoutContainer && layoutContainer.getAttribute('data-layout-content')) {
471
+ // move table a little out of the way
472
+ // to provide spacing for table controls
473
+ tableContentWrapper!.style.paddingLeft = '11px';
474
+ }
475
+ } else {
476
+ this.colControlsOffset = 0;
477
+ if (layoutContainer && layoutContainer.getAttribute('data-layout-content')) {
478
+ tableContentWrapper!.style.removeProperty('padding-left');
479
+ }
480
+ }
481
+
482
+ // run after table style changes have been committed
483
+ setTimeout(() => {
484
+ syncStickyRowToTable(tree.table);
485
+ }, 0);
486
+ }
487
+
488
+ private updateStickyHeaderWidth() {
489
+ // table width might have changed, sync that back to sticky row
490
+ const tree = getTree(this.dom);
491
+ if (!tree) {
492
+ return;
493
+ }
494
+
495
+ syncStickyRowToTable(tree.table);
496
+ }
497
+
498
+ /**
499
+ * Manually refire the intersection observers.
500
+ * Useful when the header may have detached from the table.
501
+ */
502
+ private refireIntersectionObservers() {
503
+ if (this.isSticky) {
504
+ [this.sentinels.top, this.sentinels.bottom].forEach((el) => {
505
+ if (el && this.intersectionObserver) {
506
+ this.intersectionObserver.unobserve(el);
507
+ this.intersectionObserver.observe(el);
508
+ }
509
+ });
510
+ }
511
+ }
512
+
513
+ private makeHeaderRowSticky(tree: TableDOMElements, scrollTop?: number) {
514
+ // If header row height is more than 50% of viewport height don't do this
515
+ if (this.isSticky || (this.stickyRowHeight && this.stickyRowHeight > window.innerHeight / 2)) {
516
+ return;
517
+ }
518
+
519
+ const { table, wrapper } = tree;
520
+
521
+ // ED-16035 Make sure sticky header is only applied to first row
522
+ const tbody = this.dom.parentElement;
523
+ const isFirstHeader = tbody?.firstChild?.isEqualNode(this.dom);
524
+ if (!isFirstHeader) {
525
+ return;
526
+ }
527
+
528
+ const currentTableTop = this.getCurrentTableTop(tree);
529
+
530
+ if (!scrollTop) {
531
+ scrollTop = getTop(this.editorScrollableElement);
532
+ }
533
+
534
+ const domTop = currentTableTop > 0 ? scrollTop : scrollTop + currentTableTop;
535
+
536
+ if (!this.isSticky) {
537
+ syncStickyRowToTable(table);
538
+ this.dom.classList.add('sticky');
539
+ table.classList.add(ClassName.TABLE_STICKY);
540
+
541
+ this.isSticky = true;
542
+
543
+ /**
544
+ * The logic below is not desirable, but acts as a fail safe for scenarios where the sticky header
545
+ * detaches from the table. This typically happens during a fast scroll by the user which causes
546
+ * the intersection observer logic to not fire as expected.
547
+ */
548
+ this.editorScrollableElement?.addEventListener(
549
+ 'scrollend',
550
+ this.refireIntersectionObservers,
551
+ { passive: true, once: true },
552
+ );
553
+
554
+ const fastScrollThresholdMs = 500;
555
+ setTimeout(() => {
556
+ this.refireIntersectionObservers();
557
+ }, fastScrollThresholdMs);
558
+ }
559
+
560
+ this.dom.style.top = `${domTop}px`;
561
+ updateTableMargin(table);
562
+ this.dom.scrollLeft = wrapper.scrollLeft;
563
+
564
+ this.emitOn(domTop, this.colControlsOffset);
565
+ }
566
+
567
+ private makeRowHeaderNotSticky(table: HTMLElement, isEditorDestroyed: boolean = false) {
568
+ if (!this.isSticky || !table || !this.dom) {
569
+ return;
570
+ }
571
+
572
+ this.dom.style.removeProperty('width');
573
+ this.dom.classList.remove('sticky');
574
+ table.classList.remove(ClassName.TABLE_STICKY);
575
+
576
+ this.isSticky = false;
577
+ this.dom.style.top = '';
578
+ table.style.removeProperty('margin-top');
579
+
580
+ this.emitOff(isEditorDestroyed);
581
+ }
582
+
583
+ private getWrapperoffset(inverse: boolean = false): number {
584
+ const focusValue = inverse ? !this.focused : this.focused;
585
+ return focusValue ? 0 : tableControlsSpacing;
586
+ }
587
+
588
+ private getWrapperRefTop(wrapper: HTMLElement): number {
589
+ return Math.round(getTop(wrapper)) + this.getWrapperoffset();
590
+ }
591
+
592
+ // TODO: rename!
593
+ private getScrolledTableTop(wrapper: HTMLElement): number {
594
+ return this.getWrapperRefTop(wrapper) - this.topPosEditorElement;
595
+ }
596
+
597
+ private getCurrentTableTop(tree: TableDOMElements): number {
598
+ return this.getScrolledTableTop(tree.wrapper) + tree.table.clientHeight;
599
+ }
600
+
601
+ /* emit external events */
602
+
603
+ private emitOn(top: number, padding: number) {
604
+ if (top === this.top && padding === this.padding) {
605
+ return;
606
+ }
607
+
608
+ this.top = top;
609
+ this.padding = padding;
610
+ const pos = this.getPos()!;
611
+
612
+ if (Number.isFinite(pos)) {
613
+ updateStickyState({
614
+ pos,
615
+ top,
616
+ sticky: true,
617
+ padding,
618
+ })(this.view.state, this.view.dispatch, this.view);
619
+ }
620
+ }
621
+
622
+ private emitOff(isEditorDestroyed: boolean) {
623
+ if (this.top === 0 && this.padding === 0) {
624
+ return;
625
+ }
626
+
627
+ this.top = 0;
628
+ this.padding = 0;
629
+ const pos = this.getPos()!;
630
+
631
+ if (!isEditorDestroyed && Number.isFinite(pos)) {
632
+ updateStickyState({
633
+ pos,
634
+ sticky: false,
635
+ top: this.top,
636
+ padding: this.padding,
637
+ })(this.view.state, this.view.dispatch, this.view);
638
+ }
639
+ }
689
640
  }