@atlaskit/editor-plugin-table 12.2.5 → 12.2.7

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 (207) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/ui/TableFloatingControls/NumberColumn/index.js +24 -2
  3. package/dist/es2019/ui/TableFloatingControls/NumberColumn/index.js +23 -1
  4. package/dist/esm/ui/TableFloatingControls/NumberColumn/index.js +24 -2
  5. package/package.json +4 -4
  6. package/afm-cc/tsconfig.json +0 -123
  7. package/afm-dev-agents/tsconfig.json +0 -123
  8. package/afm-jira/tsconfig.json +0 -123
  9. package/afm-passionfruit/tsconfig.json +0 -123
  10. package/afm-post-office/tsconfig.json +0 -123
  11. package/afm-rovo-extension/tsconfig.json +0 -123
  12. package/afm-townsquare/tsconfig.json +0 -123
  13. package/afm-volt/tsconfig.json +0 -114
  14. package/build/tsconfig.json +0 -23
  15. package/docs/0-intro.tsx +0 -57
  16. package/src/index.ts +0 -21
  17. package/src/nodeviews/ExternalDropTargets.tsx +0 -91
  18. package/src/nodeviews/OverflowShadowsObserver.ts +0 -156
  19. package/src/nodeviews/TableCell.ts +0 -134
  20. package/src/nodeviews/TableComponent.tsx +0 -1590
  21. package/src/nodeviews/TableComponentWithSharedState.tsx +0 -278
  22. package/src/nodeviews/TableContainer.tsx +0 -926
  23. package/src/nodeviews/TableNodeViewBase.ts +0 -29
  24. package/src/nodeviews/TableResizer.tsx +0 -884
  25. package/src/nodeviews/TableRow.ts +0 -830
  26. package/src/nodeviews/TableStickyScrollbar.ts +0 -211
  27. package/src/nodeviews/__mocks__/OverflowShadowsObserver.ts +0 -15
  28. package/src/nodeviews/__mocks__/OverridableMock.ts +0 -26
  29. package/src/nodeviews/table-container-styles.ts +0 -9
  30. package/src/nodeviews/table-node-views.ts +0 -76
  31. package/src/nodeviews/table.tsx +0 -530
  32. package/src/nodeviews/toDOM.ts +0 -244
  33. package/src/nodeviews/types.ts +0 -36
  34. package/src/nodeviews/update-overflow-shadows.ts +0 -11
  35. package/src/pm-plugins/analytics/actions.ts +0 -21
  36. package/src/pm-plugins/analytics/commands.ts +0 -47
  37. package/src/pm-plugins/analytics/plugin-factory.ts +0 -9
  38. package/src/pm-plugins/analytics/plugin-key.ts +0 -5
  39. package/src/pm-plugins/analytics/plugin.ts +0 -80
  40. package/src/pm-plugins/analytics/reducer.ts +0 -27
  41. package/src/pm-plugins/analytics/types.ts +0 -20
  42. package/src/pm-plugins/analytics/utils/moved-event.ts +0 -51
  43. package/src/pm-plugins/commands/clear.ts +0 -43
  44. package/src/pm-plugins/commands/collapse.ts +0 -17
  45. package/src/pm-plugins/commands/column-resize.ts +0 -478
  46. package/src/pm-plugins/commands/commands-with-analytics.ts +0 -715
  47. package/src/pm-plugins/commands/delete.ts +0 -42
  48. package/src/pm-plugins/commands/display-mode.ts +0 -18
  49. package/src/pm-plugins/commands/go-to-next-cell.ts +0 -198
  50. package/src/pm-plugins/commands/hover.ts +0 -242
  51. package/src/pm-plugins/commands/index.ts +0 -51
  52. package/src/pm-plugins/commands/insert.ts +0 -438
  53. package/src/pm-plugins/commands/misc.ts +0 -811
  54. package/src/pm-plugins/commands/referentiality.ts +0 -15
  55. package/src/pm-plugins/commands/selection.ts +0 -537
  56. package/src/pm-plugins/commands/sort.ts +0 -102
  57. package/src/pm-plugins/commands/split-cell.ts +0 -28
  58. package/src/pm-plugins/commands/toggle.ts +0 -109
  59. package/src/pm-plugins/create-plugin-config.ts +0 -17
  60. package/src/pm-plugins/decorations/plugin.ts +0 -107
  61. package/src/pm-plugins/decorations/utils/column-controls.ts +0 -91
  62. package/src/pm-plugins/decorations/utils/column-resizing.ts +0 -71
  63. package/src/pm-plugins/decorations/utils/compose-decorations.ts +0 -9
  64. package/src/pm-plugins/decorations/utils/types.ts +0 -16
  65. package/src/pm-plugins/default-table-selection.ts +0 -14
  66. package/src/pm-plugins/drag-and-drop/actions.ts +0 -48
  67. package/src/pm-plugins/drag-and-drop/commands-with-analytics.ts +0 -222
  68. package/src/pm-plugins/drag-and-drop/commands.ts +0 -194
  69. package/src/pm-plugins/drag-and-drop/consts.ts +0 -7
  70. package/src/pm-plugins/drag-and-drop/plugin-factory.ts +0 -33
  71. package/src/pm-plugins/drag-and-drop/plugin-key.ts +0 -5
  72. package/src/pm-plugins/drag-and-drop/plugin.ts +0 -398
  73. package/src/pm-plugins/drag-and-drop/reducer.ts +0 -38
  74. package/src/pm-plugins/drag-and-drop/types.ts +0 -18
  75. package/src/pm-plugins/drag-and-drop/utils/autoscrollers.ts +0 -49
  76. package/src/pm-plugins/drag-and-drop/utils/getDragBehaviour.ts +0 -9
  77. package/src/pm-plugins/drag-and-drop/utils/monitor.ts +0 -73
  78. package/src/pm-plugins/handlers.ts +0 -161
  79. package/src/pm-plugins/keymap.ts +0 -436
  80. package/src/pm-plugins/main.ts +0 -433
  81. package/src/pm-plugins/plugin-factory.ts +0 -42
  82. package/src/pm-plugins/plugin-key.ts +0 -8
  83. package/src/pm-plugins/reducer.ts +0 -145
  84. package/src/pm-plugins/safari-delete-composition-text-issue-workaround.ts +0 -102
  85. package/src/pm-plugins/sticky-headers/commands.ts +0 -8
  86. package/src/pm-plugins/sticky-headers/plugin-key.ts +0 -5
  87. package/src/pm-plugins/sticky-headers/plugin-state.ts +0 -52
  88. package/src/pm-plugins/sticky-headers/plugin.ts +0 -12
  89. package/src/pm-plugins/sticky-headers/types.ts +0 -20
  90. package/src/pm-plugins/sticky-headers/util.ts +0 -18
  91. package/src/pm-plugins/table-analytics.ts +0 -100
  92. package/src/pm-plugins/table-local-id.ts +0 -213
  93. package/src/pm-plugins/table-resizing/commands.ts +0 -116
  94. package/src/pm-plugins/table-resizing/event-handlers.ts +0 -352
  95. package/src/pm-plugins/table-resizing/plugin-factory.ts +0 -29
  96. package/src/pm-plugins/table-resizing/plugin-key.ts +0 -5
  97. package/src/pm-plugins/table-resizing/plugin.ts +0 -94
  98. package/src/pm-plugins/table-resizing/reducer.ts +0 -37
  99. package/src/pm-plugins/table-resizing/utils/colgroup.ts +0 -306
  100. package/src/pm-plugins/table-resizing/utils/column-state.ts +0 -120
  101. package/src/pm-plugins/table-resizing/utils/consts.ts +0 -11
  102. package/src/pm-plugins/table-resizing/utils/content-width.ts +0 -118
  103. package/src/pm-plugins/table-resizing/utils/dom.ts +0 -132
  104. package/src/pm-plugins/table-resizing/utils/misc.ts +0 -282
  105. package/src/pm-plugins/table-resizing/utils/resize-column.ts +0 -34
  106. package/src/pm-plugins/table-resizing/utils/resize-logic.ts +0 -289
  107. package/src/pm-plugins/table-resizing/utils/resize-state.ts +0 -417
  108. package/src/pm-plugins/table-resizing/utils/scale-table.ts +0 -290
  109. package/src/pm-plugins/table-resizing/utils/types.ts +0 -25
  110. package/src/pm-plugins/table-resizing/utils/unit-to-number.ts +0 -1
  111. package/src/pm-plugins/table-selection-keymap.ts +0 -64
  112. package/src/pm-plugins/table-size-selector.ts +0 -39
  113. package/src/pm-plugins/table-width-in-comment-fix.ts +0 -113
  114. package/src/pm-plugins/table-width.ts +0 -153
  115. package/src/pm-plugins/transforms/column-width.ts +0 -249
  116. package/src/pm-plugins/transforms/delete-columns.ts +0 -281
  117. package/src/pm-plugins/transforms/delete-rows.ts +0 -154
  118. package/src/pm-plugins/transforms/fix-tables.ts +0 -249
  119. package/src/pm-plugins/transforms/merge.ts +0 -301
  120. package/src/pm-plugins/transforms/replace-table.ts +0 -38
  121. package/src/pm-plugins/transforms/split.ts +0 -90
  122. package/src/pm-plugins/utils/alignment.ts +0 -33
  123. package/src/pm-plugins/utils/analytics.ts +0 -238
  124. package/src/pm-plugins/utils/collapse.ts +0 -93
  125. package/src/pm-plugins/utils/column-controls.ts +0 -250
  126. package/src/pm-plugins/utils/create.ts +0 -64
  127. package/src/pm-plugins/utils/decoration.ts +0 -672
  128. package/src/pm-plugins/utils/dom.ts +0 -251
  129. package/src/pm-plugins/utils/drag-menu.tsx +0 -491
  130. package/src/pm-plugins/utils/get-allow-add-column-custom-step.ts +0 -10
  131. package/src/pm-plugins/utils/guidelines.ts +0 -30
  132. package/src/pm-plugins/utils/merged-cells.ts +0 -239
  133. package/src/pm-plugins/utils/nodes.ts +0 -162
  134. package/src/pm-plugins/utils/paste.ts +0 -386
  135. package/src/pm-plugins/utils/row-controls.ts +0 -211
  136. package/src/pm-plugins/utils/selection.ts +0 -17
  137. package/src/pm-plugins/utils/snapping.ts +0 -136
  138. package/src/pm-plugins/utils/table.ts +0 -60
  139. package/src/pm-plugins/utils/update-plugin-state-decorations.ts +0 -13
  140. package/src/pm-plugins/view-mode-sort/consts.ts +0 -3
  141. package/src/pm-plugins/view-mode-sort/index.ts +0 -291
  142. package/src/pm-plugins/view-mode-sort/plugin-key.ts +0 -7
  143. package/src/pm-plugins/view-mode-sort/types.ts +0 -23
  144. package/src/pm-plugins/view-mode-sort/utils.ts +0 -136
  145. package/src/tablePlugin.tsx +0 -971
  146. package/src/tablePluginType.ts +0 -102
  147. package/src/types/index.ts +0 -592
  148. package/src/ui/ColumnResizeWidget/index.tsx +0 -61
  149. package/src/ui/ContentComponent.tsx +0 -311
  150. package/src/ui/DragHandle/HandleIconComponent.tsx +0 -21
  151. package/src/ui/DragHandle/index.tsx +0 -391
  152. package/src/ui/DragPreview/index.tsx +0 -51
  153. package/src/ui/FloatingAlignmentButtons/FloatingAlignmentButtons.tsx +0 -59
  154. package/src/ui/FloatingContextualButton/FixedButton.tsx +0 -203
  155. package/src/ui/FloatingContextualButton/index.tsx +0 -168
  156. package/src/ui/FloatingContextualButton/styles.ts +0 -69
  157. package/src/ui/FloatingContextualMenu/ContextualMenu.tsx +0 -931
  158. package/src/ui/FloatingContextualMenu/index.tsx +0 -141
  159. package/src/ui/FloatingContextualMenu/styles.ts +0 -77
  160. package/src/ui/FloatingDeleteButton/DeleteButton.tsx +0 -54
  161. package/src/ui/FloatingDeleteButton/getPopUpOptions.ts +0 -65
  162. package/src/ui/FloatingDeleteButton/index.tsx +0 -383
  163. package/src/ui/FloatingDeleteButton/types.ts +0 -3
  164. package/src/ui/FloatingDragMenu/DragMenu.tsx +0 -668
  165. package/src/ui/FloatingDragMenu/DropdownMenu.tsx +0 -221
  166. package/src/ui/FloatingDragMenu/index.tsx +0 -136
  167. package/src/ui/FloatingDragMenu/styles.ts +0 -83
  168. package/src/ui/FloatingInsertButton/InsertButton.tsx +0 -263
  169. package/src/ui/FloatingInsertButton/getPopupOptions.ts +0 -131
  170. package/src/ui/FloatingInsertButton/index.tsx +0 -314
  171. package/src/ui/FloatingToolbarLabel/FloatingToolbarLabel.tsx +0 -31
  172. package/src/ui/SizeSelector/index.tsx +0 -74
  173. package/src/ui/TableFloatingColumnControls/ColumnControls/index.tsx +0 -397
  174. package/src/ui/TableFloatingColumnControls/ColumnDropTargets/ColumnDropTarget.tsx +0 -105
  175. package/src/ui/TableFloatingColumnControls/ColumnDropTargets/index.tsx +0 -63
  176. package/src/ui/TableFloatingColumnControls/index.tsx +0 -151
  177. package/src/ui/TableFloatingControls/CornerControls/ClassicCornerControls.tsx +0 -106
  178. package/src/ui/TableFloatingControls/CornerControls/DragCornerControls.tsx +0 -143
  179. package/src/ui/TableFloatingControls/CornerControls/types.ts +0 -12
  180. package/src/ui/TableFloatingControls/FloatingControlsWithSelection.tsx +0 -88
  181. package/src/ui/TableFloatingControls/NumberColumn/index.tsx +0 -150
  182. package/src/ui/TableFloatingControls/RowControls/ClassicControls.tsx +0 -131
  183. package/src/ui/TableFloatingControls/RowControls/DragControls.tsx +0 -429
  184. package/src/ui/TableFloatingControls/RowDropTarget/index.tsx +0 -96
  185. package/src/ui/TableFloatingControls/index.tsx +0 -275
  186. package/src/ui/TableFullWidthLabel/index.tsx +0 -38
  187. package/src/ui/common-styles.ts +0 -1218
  188. package/src/ui/consts.ts +0 -109
  189. package/src/ui/event-handlers.ts +0 -662
  190. package/src/ui/global-styles.tsx +0 -55
  191. package/src/ui/hooks/useInternalTablePluginStateSelector.ts +0 -38
  192. package/src/ui/icons/AddColLeftIcon.tsx +0 -37
  193. package/src/ui/icons/AddColRightIcon.tsx +0 -37
  194. package/src/ui/icons/AddRowAboveIcon.tsx +0 -22
  195. package/src/ui/icons/AddRowBelowIcon.tsx +0 -39
  196. package/src/ui/icons/DragHandleDisabledIcon.tsx +0 -25
  197. package/src/ui/icons/DragHandleIcon.tsx +0 -16
  198. package/src/ui/icons/DragInMotionIcon.tsx +0 -54
  199. package/src/ui/icons/MergeCellsIcon.tsx +0 -26
  200. package/src/ui/icons/MinimisedHandle.tsx +0 -15
  201. package/src/ui/icons/SortingIconWrapper.tsx +0 -43
  202. package/src/ui/icons/SplitCellIcon.tsx +0 -34
  203. package/src/ui/toolbar.tsx +0 -1153
  204. package/src/ui/ui-styles.ts +0 -960
  205. package/tsconfig.app.json +0 -135
  206. package/tsconfig.dev.json +0 -54
  207. package/tsconfig.json +0 -18
@@ -1,830 +0,0 @@
1
- import debounce from 'lodash/debounce';
2
- import throttle from 'lodash/throttle';
3
-
4
- import type { EventDispatcher } from '@atlaskit/editor-common/event-dispatcher';
5
- import { getParentOfTypeCount } from '@atlaskit/editor-common/nesting';
6
- import { nodeVisibilityManager } from '@atlaskit/editor-common/node-visibility';
7
- import { findOverflowScrollParent } from '@atlaskit/editor-common/ui';
8
- import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
9
- import { findParentNodeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
10
- import type { EditorView, NodeView } from '@atlaskit/editor-prosemirror/view';
11
-
12
- import { getPluginState } from '../pm-plugins/plugin-factory';
13
- import { pluginKey as tablePluginKey } from '../pm-plugins/plugin-key';
14
- import { updateStickyState } from '../pm-plugins/sticky-headers/commands';
15
- import {
16
- syncStickyRowToTable,
17
- updateStickyMargins as updateTableMargin,
18
- } from '../pm-plugins/table-resizing/utils/dom';
19
- import type { TableDOMElements } from '../pm-plugins/utils/dom';
20
- import { getTop, getTree } from '../pm-plugins/utils/dom';
21
- import { supportedHeaderRow } from '../pm-plugins/utils/nodes';
22
- import type { TablePluginState } from '../types';
23
- import {
24
- TableCssClassName as ClassName,
25
- TableCssClassName,
26
- type PluginInjectionAPI,
27
- } from '../types';
28
- import {
29
- stickyHeaderBorderBottomWidth,
30
- stickyRowOffsetTop,
31
- tableControlsSpacing,
32
- tableScrollbarOffset,
33
- } from '../ui/consts';
34
-
35
- import TableNodeView from './TableNodeViewBase';
36
-
37
- interface SentinelData {
38
- isIntersecting: boolean;
39
- boundingClientRect: DOMRectReadOnly | null;
40
- rootBounds: DOMRectReadOnly | null;
41
- }
42
-
43
- // limit scroll event calls
44
- const HEADER_ROW_SCROLL_THROTTLE_TIMEOUT = 200;
45
-
46
- // timeout for resetting the scroll class - if it's too long then users won't be able to click on the header cells,
47
- // if too short it would trigger too many dom updates.
48
- const HEADER_ROW_SCROLL_RESET_DEBOUNCE_TIMEOUT = 400;
49
-
50
- export default class TableRow extends TableNodeView<HTMLTableRowElement> implements NodeView {
51
- private nodeVisibilityObserverCleanupFn?: () => void;
52
-
53
- cleanup = () => {
54
- if (this.isStickyHeaderEnabled) {
55
- this.unsubscribe();
56
-
57
- this.nodeVisibilityObserverCleanupFn && this.nodeVisibilityObserverCleanupFn();
58
-
59
- const tree = getTree(this.dom);
60
- if (tree) {
61
- this.makeRowHeaderNotSticky(tree.table, true);
62
- }
63
-
64
- this.emitOff(false);
65
- }
66
-
67
- if (this.tableContainerObserver) {
68
- this.tableContainerObserver.disconnect();
69
- }
70
- };
71
-
72
- constructor(
73
- node: PMNode,
74
- view: EditorView,
75
- getPos: () => number | undefined,
76
- eventDispatcher: EventDispatcher,
77
- api?: PluginInjectionAPI,
78
- ) {
79
- super(node, view, getPos, eventDispatcher);
80
-
81
- this.isHeaderRow = supportedHeaderRow(node);
82
- this.isSticky = false;
83
-
84
- const { pluginConfig } = getPluginState(view.state);
85
-
86
- this.isStickyHeaderEnabled = !!pluginConfig.stickyHeaders;
87
-
88
- if (
89
- api?.limitedMode?.sharedState.currentState()?.limitedModePluginKey.getState(view.state)
90
- ?.documentSizeBreachesThreshold
91
- ) {
92
- this.isStickyHeaderEnabled = false;
93
- // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
94
- document.addEventListener('limited-mode-activated', this.cleanup);
95
- }
96
-
97
- const pos = this.getPos();
98
- this.isInNestedTable = false;
99
- if (pos) {
100
- this.isInNestedTable =
101
- getParentOfTypeCount(view.state.schema.nodes.table)(view.state.doc.resolve(pos)) > 1;
102
- }
103
-
104
- if (this.isHeaderRow) {
105
- this.dom.setAttribute('data-vc-nvs', 'true');
106
- const { observe } = nodeVisibilityManager(view.dom);
107
- this.nodeVisibilityObserverCleanupFn = observe({
108
- element: this.contentDOM,
109
- onFirstVisible: () => {
110
- this.subscribeWhenRowVisible();
111
- },
112
- });
113
- }
114
- }
115
-
116
- subscribeWhenRowVisible() {
117
- if (this.listening) {
118
- return;
119
- }
120
-
121
- this.dom.setAttribute('data-header-row', 'true');
122
- if (this.isStickyHeaderEnabled) {
123
- this.subscribe();
124
- }
125
- }
126
-
127
- /**
128
- * Variables
129
- */
130
- private isHeaderRow: boolean;
131
- private isInNestedTable: boolean;
132
- private isStickyHeaderEnabled: boolean;
133
- private editorScrollableElement?: HTMLElement | Window;
134
- private colControlsOffset = 0;
135
- private focused = false;
136
- private topPosEditorElement = 0;
137
- private isSticky: boolean;
138
- private intersectionObserver?: IntersectionObserver;
139
- private resizeObserver?: ResizeObserver;
140
- private tableContainerObserver?: MutationObserver;
141
- private sentinels: {
142
- top?: HTMLElement | null;
143
- bottom?: HTMLElement | null;
144
- } = {};
145
- private sentinelData: {
146
- top: SentinelData;
147
- bottom: SentinelData;
148
- } = {
149
- top: {
150
- isIntersecting: false,
151
- boundingClientRect: null,
152
- rootBounds: null,
153
- },
154
- bottom: {
155
- isIntersecting: false,
156
- boundingClientRect: null,
157
- rootBounds: null,
158
- },
159
- };
160
- private stickyRowHeight?: number;
161
- private listening = false;
162
- private padding: number = 0;
163
- private top: number = 0;
164
-
165
- /**
166
- * Methods: Nodeview Lifecycle
167
- */
168
- // Ignored via go/ees005
169
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
170
- update(node: PMNode, ..._args: any[]) {
171
- // do nothing if nodes were identical
172
- if (node === this.node) {
173
- return true;
174
- }
175
-
176
- // see if we're changing into a header row or
177
- // changing away from one
178
- const newNodeIsHeaderRow = supportedHeaderRow(node);
179
- if (this.isHeaderRow !== newNodeIsHeaderRow) {
180
- return false; // re-create nodeview
181
- }
182
-
183
- // node is different but no need to re-create nodeview
184
- this.node = node;
185
-
186
- // don't do anything if we're just a regular tr
187
- if (!this.isHeaderRow) {
188
- return true;
189
- }
190
-
191
- // something changed, sync widths
192
- if (this.isStickyHeaderEnabled) {
193
- const tbody = this.dom.parentElement;
194
- const table = tbody && tbody.parentElement;
195
- syncStickyRowToTable(table);
196
- }
197
-
198
- return true;
199
- }
200
-
201
- destroy() {
202
- if (this.isStickyHeaderEnabled) {
203
- this.unsubscribe();
204
-
205
- this.nodeVisibilityObserverCleanupFn && this.nodeVisibilityObserverCleanupFn();
206
-
207
- const tree = getTree(this.dom);
208
- if (tree) {
209
- this.makeRowHeaderNotSticky(tree.table, true);
210
- }
211
-
212
- this.emitOff(true);
213
- }
214
-
215
- // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
216
- document.removeEventListener('limited-mode-activated', this.cleanup);
217
-
218
- if (this.tableContainerObserver) {
219
- this.tableContainerObserver.disconnect();
220
- }
221
- }
222
-
223
- ignoreMutation(mutationRecord: MutationRecord | { type: 'selection'; target: Node }) {
224
- /* tableRows are not directly editable by the user
225
- * so it should be safe to ignore mutations that we cause
226
- * by updating styles and classnames on this DOM element
227
- *
228
- * Update: should not ignore mutations for row selection to avoid known issue with table selection highlight in firefox
229
- * Related bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=1289673
230
- * */
231
- const isTableSelection =
232
- mutationRecord.type === 'selection' && mutationRecord.target.nodeName === 'TR';
233
- /**
234
- * Update: should not ignore mutations when an node is added, as this interferes with
235
- * prosemirrors handling of some language inputs in Safari (ie. Pinyin, Hiragana).
236
- *
237
- * In paticular, when a composition occurs at the start of the first node inside a table cell, if the resulting mutation
238
- * from the composition end is ignored than prosemirror will end up with; invalid table markup nesting and a misplaced
239
- * selection and insertion.
240
- */
241
- const isNodeInsertion =
242
- mutationRecord.type === 'childList' &&
243
- mutationRecord.target.nodeName === 'TR' &&
244
- mutationRecord.addedNodes.length;
245
-
246
- if (isTableSelection || isNodeInsertion) {
247
- return false;
248
- }
249
-
250
- return true;
251
- }
252
-
253
- /**
254
- * Methods
255
- */
256
- private headerRowMouseScrollEnd = debounce(() => {
257
- this.dom.classList.remove('no-pointer-events');
258
- }, HEADER_ROW_SCROLL_RESET_DEBOUNCE_TIMEOUT);
259
-
260
- // When the header is sticky, the header row is set to position: fixed
261
- // This prevents mouse wheel scrolling on the scroll-parent div when user's mouse is hovering the header row.
262
- // This fix sets pointer-events: none on the header row briefly to avoid this behaviour
263
- private headerRowMouseScroll = throttle(() => {
264
- if (this.isSticky) {
265
- this.dom.classList.add('no-pointer-events');
266
- this.headerRowMouseScrollEnd();
267
- }
268
- }, HEADER_ROW_SCROLL_THROTTLE_TIMEOUT);
269
-
270
- private subscribe() {
271
- // Ignored via go/ees005
272
- // eslint-disable-next-line @atlaskit/editor/no-as-casting
273
- this.editorScrollableElement = findOverflowScrollParent(this.view.dom as HTMLElement) || window;
274
-
275
- if (this.editorScrollableElement) {
276
- this.initObservers();
277
- this.topPosEditorElement = getTop(this.editorScrollableElement);
278
- }
279
-
280
- this.eventDispatcher.on('widthPlugin', this.updateStickyHeaderWidth.bind(this));
281
-
282
- // Ignored via go/ees005
283
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
284
- this.eventDispatcher.on((tablePluginKey as any).key, this.onTablePluginState.bind(this));
285
-
286
- this.listening = true;
287
-
288
- // Ignored via go/ees005
289
- // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
290
- this.dom.addEventListener('wheel', this.headerRowMouseScroll.bind(this), {
291
- passive: true,
292
- });
293
- // Ignored via go/ees005
294
- // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
295
- this.dom.addEventListener('touchmove', this.headerRowMouseScroll.bind(this), { passive: true });
296
- }
297
-
298
- private unsubscribe() {
299
- if (!this.listening) {
300
- return;
301
- }
302
- if (this.intersectionObserver) {
303
- this.intersectionObserver.disconnect();
304
- // ED-16211 Once intersection observer is disconnected, we need to remove the isObserved from the sentinels
305
- // Otherwise when newer intersection observer is created it will not observe because it thinks its already being observed
306
- [this.sentinels.top, this.sentinels.bottom].forEach((el) => {
307
- if (el) {
308
- delete el.dataset.isObserved;
309
- }
310
- });
311
- }
312
-
313
- if (this.resizeObserver) {
314
- this.resizeObserver.disconnect();
315
- }
316
-
317
- this.eventDispatcher.off('widthPlugin', this.updateStickyHeaderWidth);
318
- // Ignored via go/ees005
319
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
320
- this.eventDispatcher.off((tablePluginKey as any).key, this.onTablePluginState);
321
-
322
- this.listening = false;
323
-
324
- // Ignored via go/ees005
325
- // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
326
- this.dom.removeEventListener('wheel', this.headerRowMouseScroll);
327
- // Ignored via go/ees005
328
- // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
329
- this.dom.removeEventListener('touchmove', this.headerRowMouseScroll);
330
- }
331
-
332
- // initialize intersection observer to track if table is within scroll area
333
- private initObservers() {
334
- if (!this.dom || this.dom.dataset.isObserved) {
335
- return;
336
- }
337
- this.dom.dataset.isObserved = 'true';
338
- this.createIntersectionObserver();
339
- this.createResizeObserver();
340
-
341
- if (!this.intersectionObserver || !this.resizeObserver) {
342
- return;
343
- }
344
-
345
- this.resizeObserver.observe(this.dom);
346
- if (this.editorScrollableElement) {
347
- // Ignored via go/ees005
348
- // eslint-disable-next-line @atlaskit/editor/no-as-casting
349
- this.resizeObserver.observe(this.editorScrollableElement as HTMLElement);
350
- }
351
-
352
- window.requestAnimationFrame(() => {
353
- const getTableContainer = () =>
354
- getTree(this.dom)?.wrapper.closest(`.${TableCssClassName.NODEVIEW_WRAPPER}`);
355
-
356
- // we expect tree to be defined after animation frame
357
- let tableContainer = getTableContainer();
358
-
359
- if (tableContainer) {
360
- const getSentinelTop = () =>
361
- tableContainer &&
362
- // Ignored via go/ees005
363
- // eslint-disable-next-line @atlaskit/editor/no-as-casting
364
- (tableContainer
365
- .getElementsByClassName(ClassName.TABLE_STICKY_SENTINEL_TOP)
366
- .item(0) as HTMLElement);
367
- const getSentinelBottom = () => {
368
- // Multiple bottom sentinels may be found if there are nested tables.
369
- // We need to make sure we get the last one which will belong to the parent table.
370
-
371
- const bottomSentinels =
372
- tableContainer &&
373
- tableContainer.getElementsByClassName(ClassName.TABLE_STICKY_SENTINEL_BOTTOM);
374
-
375
- return (
376
- // eslint-disable-next-line @atlaskit/editor/no-as-casting
377
- bottomSentinels && (bottomSentinels.item(bottomSentinels.length - 1) as HTMLElement)
378
- );
379
- };
380
-
381
- const sentinelsInDom = () => getSentinelTop() !== null && getSentinelBottom() !== null;
382
-
383
- const observeStickySentinels = () => {
384
- this.sentinels.top = getSentinelTop();
385
- this.sentinels.bottom = getSentinelBottom();
386
- [this.sentinels.top, this.sentinels.bottom].forEach((el) => {
387
- // skip if already observed for another row on this table
388
- if (el && !el.dataset.isObserved) {
389
- el.dataset.isObserved = 'true';
390
-
391
- // Ignored via go/ees005
392
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
393
- this.intersectionObserver!.observe(el);
394
- }
395
- });
396
- };
397
-
398
- const isInitialProsemirrorToDomRender = tableContainer.hasAttribute(
399
- 'data-prosemirror-initial-toDOM-render',
400
- );
401
-
402
- // Sentinels may be in the DOM but they're part of the prosemirror placeholder structure which is replaced with the fully rendered React node.
403
- if (sentinelsInDom() && !isInitialProsemirrorToDomRender) {
404
- // great - DOM ready, observe as normal
405
- observeStickySentinels();
406
- } else {
407
- // concurrent loading issue - here TableRow is too eager trying to
408
- // observe sentinels before they are in the DOM, use MutationObserver
409
- // to wait for sentinels to be added to the parent Table node DOM
410
- // then attach the IntersectionObserver
411
- this.tableContainerObserver = new MutationObserver(() => {
412
- // Check if the tableContainer is still connected to the DOM. It can become disconnected when the placholder
413
- // prosemirror node is replaced with the fully rendered React node (see _handleTableRef).
414
-
415
- if (!tableContainer || !tableContainer.isConnected) {
416
- tableContainer = getTableContainer();
417
- }
418
- if (sentinelsInDom()) {
419
- observeStickySentinels();
420
- this.tableContainerObserver?.disconnect();
421
- }
422
- });
423
-
424
- const mutatingNode = tableContainer;
425
- if (mutatingNode && this.tableContainerObserver) {
426
- this.tableContainerObserver.observe(mutatingNode, { subtree: true, childList: true });
427
- }
428
- }
429
- }
430
- });
431
- }
432
-
433
- // updating bottom sentinel position if sticky header height changes
434
- // to allocate for new header height
435
- private createResizeObserver() {
436
- this.resizeObserver = new ResizeObserver((entries) => {
437
- const tree = getTree(this.dom);
438
- if (!tree) {
439
- return;
440
- }
441
- const { table } = tree;
442
- entries.forEach((entry) => {
443
- // On resize of the parent scroll element we need to adjust the width
444
- // of the sticky header
445
- // Ignored via go/ees005
446
- // eslint-disable-next-line @atlaskit/editor/no-as-casting
447
- if (entry.target.className === (this.editorScrollableElement as HTMLElement)?.className) {
448
- this.updateStickyHeaderWidth();
449
- } else {
450
- const newHeight = entry.contentRect
451
- ? entry.contentRect.height
452
- : // Ignored via go/ees005
453
- // eslint-disable-next-line @atlaskit/editor/no-as-casting
454
- (entry.target as HTMLElement).offsetHeight;
455
-
456
- if (
457
- this.sentinels.bottom &&
458
- // When the table header is sticky, it would be taller by a 1px (border-bottom),
459
- // So we adding this check to allow a 1px difference.
460
- Math.abs(newHeight - (this.stickyRowHeight || 0)) > stickyHeaderBorderBottomWidth
461
- ) {
462
- this.stickyRowHeight = newHeight;
463
- this.sentinels.bottom.style.bottom = `${
464
- tableScrollbarOffset + stickyRowOffsetTop + newHeight
465
- }px`;
466
-
467
- updateTableMargin(table);
468
- }
469
- }
470
- });
471
- });
472
- }
473
-
474
- private createIntersectionObserver() {
475
- this.intersectionObserver = new IntersectionObserver(
476
- (entries: IntersectionObserverEntry[], _: IntersectionObserver) => {
477
- // IMPORTANT: please try and avoid using entry.rootBounds it's terribly inconsistent. For example; sometimes it may return
478
- // 0 height. In safari it will multiply all values by the window scale factor, however chrome & firfox won't.
479
- // This is why i just get the scroll view bounding rect here and use it, and fallback to the entry.rootBounds if needed.
480
- const rootBounds = (this.editorScrollableElement as Element)?.getBoundingClientRect?.();
481
-
482
- entries.forEach((entry) => {
483
- const { target, isIntersecting, boundingClientRect } = entry;
484
- // This observer only every looks at the top/bottom sentinels, we can assume if it's not one then it's the other.
485
- const targetKey = target.classList.contains(ClassName.TABLE_STICKY_SENTINEL_TOP)
486
- ? 'top'
487
- : 'bottom';
488
- // Cache the latest sentinel information
489
- this.sentinelData[targetKey] = {
490
- isIntersecting,
491
- boundingClientRect,
492
- rootBounds: rootBounds ?? entry.rootBounds,
493
- };
494
- // Keep the other sentinels rootBounds in sync with this latest one
495
- this.sentinelData[targetKey === 'top' ? 'bottom' : targetKey].rootBounds =
496
- rootBounds ?? entry.rootBounds;
497
- });
498
- this.refreshStickyState();
499
- },
500
- { threshold: 0, root: this.editorScrollableElement as Element },
501
- );
502
- }
503
-
504
- private refreshStickyState() {
505
- const tree = getTree(this.dom);
506
- if (!tree) {
507
- return;
508
- }
509
- const { table } = tree;
510
- const shouldStick = this.shouldSticky();
511
- if (this.isSticky !== shouldStick) {
512
- if (shouldStick) {
513
- // The rootRect is kept in sync across sentinels so it doesn't matter which one we use.
514
- const rootRect = this.sentinelData.top.rootBounds ?? this.sentinelData.bottom.rootBounds;
515
- this.makeHeaderRowSticky(tree, rootRect?.top);
516
- } else {
517
- this.makeRowHeaderNotSticky(table);
518
- }
519
- }
520
- }
521
-
522
- private shouldSticky() {
523
- if (
524
- // is Safari
525
- navigator.userAgent.includes('AppleWebKit') &&
526
- !navigator.userAgent.includes('Chrome')
527
- ) {
528
- const pos = this.getPos();
529
- if (typeof pos === 'number') {
530
- const $tableRowPos = this.view.state.doc.resolve(pos);
531
-
532
- // layout -> layout column -> table -> table row
533
- if ($tableRowPos.depth >= 3) {
534
- const isInsideLayout = findParentNodeClosestToPos($tableRowPos, (node) => {
535
- return node.type.name === 'layoutColumn';
536
- })?.node;
537
-
538
- if (isInsideLayout) {
539
- return false;
540
- }
541
- }
542
- }
543
- }
544
-
545
- return this.isHeaderSticky();
546
- }
547
-
548
- private isHeaderSticky() {
549
- /*
550
- # Overview
551
- I'm going to list all the view states associated with the sentinels and when they should trigger sticky headers.
552
- The format of the states are; {top|bottom}:{in|above|below}
553
- ie sentinel:view-position -- both "above" and "below" are equal to out of the viewport
554
-
555
- For example; "top:in" means top sentinel is within the viewport. "bottom:above" means the bottom sentinel is
556
- above and out of the viewport
557
-
558
- This will hopefully simplify things and make it easier to determine when sticky should/shouldn't be triggered.
559
-
560
- # States
561
- top:in / bottom:in - NOT sticky
562
- top:in / bottom:above - NOT sticky - NOTE: This is an inversion clause
563
- top:in / bottom:below - NOT sticky
564
- top:above / bottom:in - STICKY
565
- top:above / bottom:above - NOT sticky
566
- top:above / bottom:below - STICKY
567
- top:below / bottom:in - NOT sticky - NOTE: This is an inversion clause
568
- top:below / bottom:above - NOT sticky - NOTE: This is an inversion clause
569
- top:below / bottom:below - NOT sticky
570
-
571
- # Summary
572
- The only time the header should be sticky is when the top sentinel is above the view and the bottom sentinel
573
- is in or below it.
574
- */
575
-
576
- const { top: sentinelTop, bottom: sentinelBottom } = this.sentinelData;
577
- // The rootRect is kept in sync across sentinels so it doesn't matter which one we use.
578
- const rootBounds = sentinelTop.rootBounds ?? sentinelBottom.rootBounds;
579
-
580
- if (!rootBounds || !sentinelTop.boundingClientRect || !sentinelBottom.boundingClientRect) {
581
- return false;
582
- }
583
-
584
- // Normalize the sentinels to y points
585
- // Since the sentinels are actually rects 1px high we want make sure we normalise the inner most values closest to the table.
586
- const sentinelTopY = sentinelTop.boundingClientRect.bottom;
587
- const sentinelBottomY = sentinelBottom.boundingClientRect.top;
588
-
589
- // If header row height is more than 50% of viewport height don't do this
590
- const isRowHeaderTooLarge =
591
- this.stickyRowHeight && this.stickyRowHeight > window.innerHeight * 0.5;
592
-
593
- const isTopSentinelAboveScrollArea =
594
- !sentinelTop.isIntersecting && sentinelTopY <= rootBounds.top;
595
-
596
- const isBottomSentinelInOrBelowScrollArea =
597
- sentinelBottom.isIntersecting || sentinelBottomY > rootBounds.bottom;
598
-
599
- // This makes sure that the top sentinel is actually above the bottom sentinel, and that they havn't inverted.
600
- const isTopSentinelAboveBottomSentinel = sentinelTopY < sentinelBottomY;
601
-
602
- return (
603
- isTopSentinelAboveScrollArea &&
604
- isBottomSentinelInOrBelowScrollArea &&
605
- isTopSentinelAboveBottomSentinel &&
606
- !isRowHeaderTooLarge
607
- );
608
- }
609
-
610
- /* receive external events */
611
-
612
- private onTablePluginState(state: TablePluginState) {
613
- const tableRef = state.tableRef;
614
-
615
- const tree = getTree(this.dom);
616
- if (!tree) {
617
- return;
618
- }
619
-
620
- // when header rows are toggled off - mark sentinels as unobserved
621
- if (!state.isHeaderRowEnabled) {
622
- [this.sentinels.top, this.sentinels.bottom].forEach((el) => {
623
- if (el) {
624
- delete el.dataset.isObserved;
625
- }
626
- });
627
- }
628
-
629
- const isCurrentTableSelected = tableRef === tree.table;
630
-
631
- // If current table selected and header row is toggled off, turn off sticky header
632
- if (isCurrentTableSelected && !state.isHeaderRowEnabled && tree) {
633
- this.makeRowHeaderNotSticky(tree.table);
634
- }
635
- this.focused = isCurrentTableSelected;
636
-
637
- const { wrapper } = tree;
638
-
639
- const tableContainer = wrapper.parentElement;
640
- const tableContentWrapper = tableContainer?.parentElement;
641
-
642
- const parentContainer = tableContentWrapper && tableContentWrapper.parentElement;
643
-
644
- const isTableInsideLayout =
645
- parentContainer && parentContainer.getAttribute('data-layout-content');
646
-
647
- if (tableContentWrapper) {
648
- if (isCurrentTableSelected) {
649
- this.colControlsOffset = tableControlsSpacing;
650
-
651
- // move table a little out of the way
652
- // to provide spacing for table controls
653
- if (isTableInsideLayout) {
654
- tableContentWrapper.style.paddingLeft = '11px';
655
- }
656
- } else {
657
- this.colControlsOffset = 0;
658
- if (isTableInsideLayout) {
659
- tableContentWrapper.style.removeProperty('padding-left');
660
- }
661
- }
662
- }
663
-
664
- // run after table style changes have been committed
665
- setTimeout(() => {
666
- syncStickyRowToTable(tree.table);
667
- }, 0);
668
- }
669
-
670
- private updateStickyHeaderWidth() {
671
- // table width might have changed, sync that back to sticky row
672
- const tree = getTree(this.dom);
673
- if (!tree) {
674
- return;
675
- }
676
-
677
- syncStickyRowToTable(tree.table);
678
- }
679
-
680
- /**
681
- * Manually refire the intersection observers.
682
- * Useful when the header may have detached from the table.
683
- */
684
- private refireIntersectionObservers() {
685
- if (this.isSticky) {
686
- [this.sentinels.top, this.sentinels.bottom].forEach((el) => {
687
- if (el && this.intersectionObserver) {
688
- this.intersectionObserver.unobserve(el);
689
- this.intersectionObserver.observe(el);
690
- }
691
- });
692
- }
693
- }
694
-
695
- private makeHeaderRowSticky(tree: TableDOMElements, scrollTop?: number) {
696
- // If header row height is more than 50% of viewport height don't do this
697
- if (
698
- this.isSticky ||
699
- (this.stickyRowHeight && this.stickyRowHeight > window.innerHeight / 2) ||
700
- this.isInNestedTable
701
- ) {
702
- return;
703
- }
704
-
705
- const { table, wrapper } = tree;
706
-
707
- // TODO: ED-16035 - Make sure sticky header is only applied to first row
708
- const tbody = this.dom.parentElement;
709
- const isFirstHeader = tbody?.firstChild?.isEqualNode(this.dom);
710
- if (!isFirstHeader) {
711
- return;
712
- }
713
-
714
- const currentTableTop = this.getCurrentTableTop(tree);
715
-
716
- if (!scrollTop) {
717
- scrollTop = getTop(this.editorScrollableElement);
718
- }
719
-
720
- const domTop = currentTableTop > 0 ? scrollTop : scrollTop + currentTableTop;
721
-
722
- if (!this.isSticky) {
723
- syncStickyRowToTable(table);
724
- this.dom.classList.add('sticky');
725
- table.classList.add(ClassName.TABLE_STICKY);
726
-
727
- this.isSticky = true;
728
-
729
- /**
730
- * The logic below is not desirable, but acts as a fail safe for scenarios where the sticky header
731
- * detaches from the table. This typically happens during a fast scroll by the user which causes
732
- * the intersection observer logic to not fire as expected.
733
- */
734
- // Ignored via go/ees005
735
- // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
736
- this.editorScrollableElement?.addEventListener(
737
- 'scrollend',
738
- this.refireIntersectionObservers,
739
- { passive: true, once: true },
740
- );
741
-
742
- const fastScrollThresholdMs = 500;
743
- setTimeout(() => {
744
- this.refireIntersectionObservers();
745
- }, fastScrollThresholdMs);
746
- }
747
- this.dom.style.top = `0px`;
748
- updateTableMargin(table);
749
- this.dom.scrollLeft = wrapper.scrollLeft;
750
-
751
- this.emitOn(domTop, this.colControlsOffset);
752
- }
753
-
754
- private makeRowHeaderNotSticky(table: HTMLElement, isEditorDestroyed: boolean = false) {
755
- if (!this.isSticky || !table || !this.dom) {
756
- return;
757
- }
758
-
759
- this.dom.style.removeProperty('width');
760
- this.dom.classList.remove('sticky');
761
- table.classList.remove(ClassName.TABLE_STICKY);
762
-
763
- this.isSticky = false;
764
- this.dom.style.top = '';
765
- table.style.removeProperty('margin-top');
766
-
767
- this.emitOff(isEditorDestroyed);
768
- }
769
-
770
- private getWrapperoffset(inverse: boolean = false): number {
771
- const focusValue = inverse ? !this.focused : this.focused;
772
- return focusValue ? 0 : tableControlsSpacing;
773
- }
774
-
775
- private getWrapperRefTop(wrapper: HTMLElement): number {
776
- return Math.round(getTop(wrapper)) + this.getWrapperoffset();
777
- }
778
-
779
- private getScrolledTableTop(wrapper: HTMLElement): number {
780
- return this.getWrapperRefTop(wrapper) - this.topPosEditorElement;
781
- }
782
-
783
- private getCurrentTableTop(tree: TableDOMElements): number {
784
- return this.getScrolledTableTop(tree.wrapper) + tree.table.clientHeight;
785
- }
786
-
787
- /* emit external events */
788
-
789
- private emitOn(top: number, padding: number) {
790
- if (top === this.top && padding === this.padding) {
791
- return;
792
- }
793
-
794
- this.top = top;
795
- this.padding = padding;
796
- // Ignored via go/ees005
797
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
798
- const pos = this.getPos()!;
799
-
800
- if (Number.isFinite(pos)) {
801
- updateStickyState({
802
- pos,
803
- top,
804
- sticky: true,
805
- padding,
806
- })(this.view.state, this.view.dispatch, this.view);
807
- }
808
- }
809
-
810
- private emitOff(isEditorDestroyed: boolean) {
811
- if (this.top === 0 && this.padding === 0) {
812
- return;
813
- }
814
-
815
- this.top = 0;
816
- this.padding = 0;
817
- // Ignored via go/ees005
818
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
819
- const pos = this.getPos()!;
820
-
821
- if (!isEditorDestroyed && Number.isFinite(pos)) {
822
- updateStickyState({
823
- pos,
824
- sticky: false,
825
- top: this.top,
826
- padding: this.padding,
827
- })(this.view.state, this.view.dispatch, this.view);
828
- }
829
- }
830
- }