@atlaskit/editor-plugin-table 5.1.0 → 5.2.1

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 (81) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/plugins/table/index.js +3 -2
  3. package/dist/cjs/plugins/table/nodeviews/TableCell.js +111 -0
  4. package/dist/cjs/plugins/table/nodeviews/TableNodeViewBase.js +30 -0
  5. package/dist/cjs/plugins/table/{pm-plugins/sticky-headers/nodeviews/tableRow.js → nodeviews/TableRow.js} +363 -303
  6. package/dist/cjs/plugins/table/pm-plugins/main.js +16 -13
  7. package/dist/cjs/plugins/table/pm-plugins/sticky-headers/index.js +1 -8
  8. package/dist/cjs/plugins/table/pm-plugins/sticky-headers/plugin.js +1 -9
  9. package/dist/cjs/plugins/table/types.js +4 -1
  10. package/dist/cjs/plugins/table/utils/dom.js +31 -1
  11. package/dist/cjs/plugins/table/utils/index.js +12 -0
  12. package/dist/cjs/plugins/table/utils/nodes.js +31 -7
  13. package/dist/es2019/plugins/table/index.js +3 -2
  14. package/dist/es2019/plugins/table/nodeviews/{tableCell.js → TableCell.js} +28 -24
  15. package/dist/es2019/plugins/table/nodeviews/TableNodeViewBase.js +22 -0
  16. package/dist/es2019/plugins/table/{pm-plugins/sticky-headers/nodeviews/tableRow.js → nodeviews/TableRow.js} +324 -280
  17. package/dist/es2019/plugins/table/pm-plugins/main.js +8 -8
  18. package/dist/es2019/plugins/table/pm-plugins/sticky-headers/index.js +1 -2
  19. package/dist/es2019/plugins/table/pm-plugins/sticky-headers/plugin.js +1 -9
  20. package/dist/es2019/plugins/table/types.js +5 -1
  21. package/dist/es2019/plugins/table/utils/dom.js +30 -0
  22. package/dist/es2019/plugins/table/utils/index.js +1 -1
  23. package/dist/es2019/plugins/table/utils/nodes.js +16 -0
  24. package/dist/esm/plugins/table/index.js +3 -2
  25. package/dist/esm/plugins/table/nodeviews/TableCell.js +105 -0
  26. package/dist/esm/plugins/table/nodeviews/TableNodeViewBase.js +24 -0
  27. package/dist/esm/plugins/table/{pm-plugins/sticky-headers/nodeviews/tableRow.js → nodeviews/TableRow.js} +364 -303
  28. package/dist/esm/plugins/table/pm-plugins/main.js +16 -13
  29. package/dist/esm/plugins/table/pm-plugins/sticky-headers/index.js +1 -2
  30. package/dist/esm/plugins/table/pm-plugins/sticky-headers/plugin.js +1 -9
  31. package/dist/esm/plugins/table/types.js +5 -1
  32. package/dist/esm/plugins/table/utils/dom.js +30 -0
  33. package/dist/esm/plugins/table/utils/index.js +1 -1
  34. package/dist/esm/plugins/table/utils/nodes.js +24 -0
  35. package/dist/types/plugins/table/nodeviews/TableCell.d.ts +13 -0
  36. package/dist/types/plugins/table/nodeviews/TableNodeViewBase.d.ts +18 -0
  37. package/dist/types/plugins/table/nodeviews/TableRow.d.ts +62 -0
  38. package/dist/types/plugins/table/pm-plugins/main.d.ts +1 -1
  39. package/dist/types/plugins/table/pm-plugins/sticky-headers/index.d.ts +0 -1
  40. package/dist/types/plugins/table/pm-plugins/sticky-headers/plugin.d.ts +1 -1
  41. package/dist/types/plugins/table/types.d.ts +15 -0
  42. package/dist/types/plugins/table/utils/dom.d.ts +6 -0
  43. package/dist/types/plugins/table/utils/index.d.ts +1 -1
  44. package/dist/types/plugins/table/utils/nodes.d.ts +12 -2
  45. package/dist/types-ts4.5/plugins/table/nodeviews/TableCell.d.ts +13 -0
  46. package/dist/types-ts4.5/plugins/table/nodeviews/TableNodeViewBase.d.ts +18 -0
  47. package/dist/types-ts4.5/plugins/table/nodeviews/TableRow.d.ts +62 -0
  48. package/dist/types-ts4.5/plugins/table/pm-plugins/main.d.ts +1 -1
  49. package/dist/types-ts4.5/plugins/table/pm-plugins/sticky-headers/index.d.ts +0 -1
  50. package/dist/types-ts4.5/plugins/table/pm-plugins/sticky-headers/plugin.d.ts +1 -1
  51. package/dist/types-ts4.5/plugins/table/types.d.ts +15 -0
  52. package/dist/types-ts4.5/plugins/table/utils/dom.d.ts +6 -0
  53. package/dist/types-ts4.5/plugins/table/utils/index.d.ts +1 -1
  54. package/dist/types-ts4.5/plugins/table/utils/nodes.d.ts +12 -2
  55. package/package.json +4 -2
  56. package/src/__tests__/unit/nodeviews/cell.ts +2 -2
  57. package/src/__tests__/unit/pm-plugins/sticky-headers/tableRow.tsx +25 -148
  58. package/src/plugins/table/index.tsx +2 -0
  59. package/src/plugins/table/nodeviews/{tableCell.tsx → TableCell.ts} +41 -46
  60. package/src/plugins/table/nodeviews/TableNodeViewBase.ts +32 -0
  61. package/src/plugins/table/{pm-plugins/sticky-headers/nodeviews/tableRow.ts → nodeviews/TableRow.ts} +246 -246
  62. package/src/plugins/table/pm-plugins/main.ts +10 -19
  63. package/src/plugins/table/pm-plugins/sticky-headers/index.ts +0 -1
  64. package/src/plugins/table/pm-plugins/sticky-headers/plugin.ts +1 -9
  65. package/src/plugins/table/types.ts +18 -0
  66. package/src/plugins/table/utils/dom.ts +38 -0
  67. package/src/plugins/table/utils/index.ts +2 -0
  68. package/src/plugins/table/utils/nodes.ts +30 -2
  69. package/tsconfig.app.json +6 -0
  70. package/dist/cjs/plugins/table/nodeviews/tableCell.js +0 -99
  71. package/dist/cjs/plugins/table/pm-plugins/sticky-headers/nodeviews/dom.js +0 -35
  72. package/dist/es2019/plugins/table/pm-plugins/sticky-headers/nodeviews/dom.js +0 -29
  73. package/dist/esm/plugins/table/nodeviews/tableCell.js +0 -93
  74. package/dist/esm/plugins/table/pm-plugins/sticky-headers/nodeviews/dom.js +0 -29
  75. package/dist/types/plugins/table/nodeviews/tableCell.d.ts +0 -17
  76. package/dist/types/plugins/table/pm-plugins/sticky-headers/nodeviews/dom.d.ts +0 -6
  77. package/dist/types/plugins/table/pm-plugins/sticky-headers/nodeviews/tableRow.d.ts +0 -73
  78. package/dist/types-ts4.5/plugins/table/nodeviews/tableCell.d.ts +0 -17
  79. package/dist/types-ts4.5/plugins/table/pm-plugins/sticky-headers/nodeviews/dom.d.ts +0 -6
  80. package/dist/types-ts4.5/plugins/table/pm-plugins/sticky-headers/nodeviews/tableRow.d.ts +0 -73
  81. package/src/plugins/table/pm-plugins/sticky-headers/nodeviews/dom.ts +0 -37
@@ -3,31 +3,33 @@ import throttle from 'lodash/throttle';
3
3
 
4
4
  import type { EventDispatcher } from '@atlaskit/editor-common/event-dispatcher';
5
5
  import { findOverflowScrollParent } from '@atlaskit/editor-common/ui';
6
- import { browser, mapChildren } from '@atlaskit/editor-common/utils';
7
- import type { Node as PmNode } from '@atlaskit/editor-prosemirror/model';
6
+ import { browser } from '@atlaskit/editor-common/utils';
7
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
8
8
  import type { EditorView, NodeView } from '@atlaskit/editor-prosemirror/view';
9
+ import { attachClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/addon/closest-edge';
10
+ import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/adapter/element';
9
11
 
10
- import type { TablePluginState } from '../../../types';
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';
11
15
  import {
12
- TableCssClassName as ClassName,
13
- TableCssClassName,
14
- } from '../../../types';
16
+ syncStickyRowToTable,
17
+ updateStickyMargins as updateTableMargin,
18
+ } from '../pm-plugins/table-resizing/utils/dom';
19
+ import type { DraggableSourceData, TablePluginState } from '../types';
20
+ import { TableCssClassName as ClassName, TableCssClassName } from '../types';
15
21
  import {
16
22
  STICKY_HEADER_TOGGLE_TOLERANCE_MS,
17
23
  stickyHeaderBorderBottomWidth,
18
24
  stickyRowOffsetTop,
19
25
  tableControlsSpacing,
20
26
  tableScrollbarOffset,
21
- } from '../../../ui/consts';
22
- import { pluginKey as tablePluginKey } from '../../plugin-key';
23
- import {
24
- syncStickyRowToTable,
25
- updateStickyMargins as updateTableMargin,
26
- } from '../../table-resizing/utils/dom';
27
- import { updateStickyState } from '../commands';
27
+ } from '../ui/consts';
28
+ import type { TableDOMElements } from '../utils/dom';
29
+ import { getTop, getTree } from '../utils/dom';
30
+ import { supportedHeaderRow } from '../utils/nodes';
28
31
 
29
- import type { TableDOMElements } from './dom';
30
- import { getTop, getTree } from './dom';
32
+ import TableNodeView from './TableNodeViewBase';
31
33
 
32
34
  // limit scroll event calls
33
35
  const HEADER_ROW_SCROLL_THROTTLE_TIMEOUT = 200;
@@ -36,48 +38,51 @@ const HEADER_ROW_SCROLL_THROTTLE_TIMEOUT = 200;
36
38
  // if too short it would trigger too many dom updates.
37
39
  const HEADER_ROW_SCROLL_RESET_DEBOUNCE_TIMEOUT = 400;
38
40
 
39
- const anyChildCellMergedAcrossRow = (node: PmNode) =>
40
- mapChildren(node, (child) => child.attrs.rowspan || 0).some(
41
- (rowspan) => rowspan > 1,
42
- );
43
-
44
- /**
45
- * Check if a given node is a header row with this definition:
46
- * - all children are tableHeader cells
47
- * - no table cells have been have merged with other table row cells
48
- *
49
- * @param node ProseMirror node
50
- * @return boolean if it meets definition
51
- */
52
- export const supportedHeaderRow = (node: PmNode) => {
53
- const allHeaders = mapChildren(
54
- node,
55
- (child) => child.type.name === 'tableHeader',
56
- ).every(Boolean);
57
-
58
- const someMerged = anyChildCellMergedAcrossRow(node);
59
-
60
- return allHeaders && !someMerged;
61
- };
62
-
63
- export class TableRowNodeView implements NodeView {
64
- view: EditorView;
65
- node: PmNode;
66
- getPos: () => number;
67
- eventDispatcher: EventDispatcher;
68
-
69
- dom: HTMLTableRowElement; // this is the sticky header table row
70
- contentDOM: HTMLElement;
71
-
72
- isHeaderRow: boolean;
73
- editorScrollableElement?: HTMLElement | Window;
74
- colControlsOffset = 0;
75
- focused = false;
76
- topPosEditorElement = 0;
77
- isSticky: boolean;
78
- lastStickyTimestamp: number | undefined;
79
- lastTimePainted: number;
41
+ export default class TableRow
42
+ extends TableNodeView<HTMLTableRowElement>
43
+ implements NodeView
44
+ {
45
+ constructor(
46
+ node: PMNode,
47
+ view: EditorView,
48
+ getPos: () => number | undefined,
49
+ eventDispatcher: EventDispatcher,
50
+ ) {
51
+ super(node, view, getPos, eventDispatcher);
52
+
53
+ this.isHeaderRow = supportedHeaderRow(node);
54
+ this.isSticky = false;
55
+
56
+ const { pluginConfig, isDragAndDropEnabled } = getPluginState(view.state);
80
57
 
58
+ this.isStickyHeaderEnabled = !!pluginConfig.stickyHeaders;
59
+ this.isDragAndDropEnabled = !!isDragAndDropEnabled;
60
+
61
+ if (this.isHeaderRow) {
62
+ this.dom.setAttribute('data-header-row', 'true');
63
+ if (this.isStickyHeaderEnabled) {
64
+ this.subscribe();
65
+ }
66
+ }
67
+
68
+ if (this.isDragAndDropEnabled) {
69
+ this.addDropTarget(this.contentDOM);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Variables
75
+ */
76
+ private isHeaderRow: boolean;
77
+ private isStickyHeaderEnabled: boolean;
78
+ // @ts-ignore
79
+ private isDragAndDropEnabled: boolean;
80
+ private editorScrollableElement?: HTMLElement | Window;
81
+ private colControlsOffset = 0;
82
+ private focused = false;
83
+ private topPosEditorElement = 0;
84
+ private isSticky: boolean;
85
+ private lastStickyTimestamp: number | undefined;
81
86
  private intersectionObserver?: IntersectionObserver;
82
87
  private resizeObserver?: ResizeObserver;
83
88
  private sentinels: {
@@ -85,54 +90,156 @@ export class TableRowNodeView implements NodeView {
85
90
  bottom?: HTMLElement | null;
86
91
  } = {};
87
92
  private stickyRowHeight?: number;
93
+ private listening = false;
94
+ private padding: number = 0;
95
+ private top: number = 0;
96
+ private dropTargetCleanup?: () => void;
88
97
 
89
- get tree(): TableDOMElements | null | undefined {
90
- return getTree(this.dom);
91
- }
98
+ /**
99
+ * Methods: Nodeview Lifecycle
100
+ */
101
+ update(node: PMNode, ..._args: any[]) {
102
+ // do nothing if nodes were identical
103
+ if (node === this.node) {
104
+ return true;
105
+ }
92
106
 
93
- constructor(
94
- node: PmNode,
95
- view: EditorView,
96
- getPos: any,
97
- eventDispatcher: EventDispatcher,
98
- ) {
99
- this.view = view;
107
+ // see if we're changing into a header row or
108
+ // changing away from one
109
+ const newNodeIsHeaderRow = supportedHeaderRow(node);
110
+ if (this.isHeaderRow !== newNodeIsHeaderRow) {
111
+ return false; // re-create nodeview
112
+ }
113
+
114
+ // node is different but no need to re-create nodeview
100
115
  this.node = node;
101
- this.getPos = getPos;
102
- this.eventDispatcher = eventDispatcher;
103
116
 
104
- this.dom = document.createElement('tr');
105
- this.contentDOM = this.dom;
117
+ // don't do anything if we're just a regular tr
118
+ if (!this.isHeaderRow) {
119
+ return true;
120
+ }
106
121
 
107
- this.lastTimePainted = 0;
108
- this.isHeaderRow = supportedHeaderRow(node);
109
- this.isSticky = false;
110
- this.lastStickyTimestamp = undefined;
122
+ // something changed, sync widths
123
+ if (this.isStickyHeaderEnabled) {
124
+ const tbody = this.dom.parentElement;
125
+ const table = tbody && tbody.parentElement;
126
+ syncStickyRowToTable(table);
127
+ }
111
128
 
112
- if (this.isHeaderRow) {
113
- this.dom.setAttribute('data-header-row', 'true');
114
- this.subscribe();
129
+ return true;
130
+ }
131
+
132
+ destroy() {
133
+ if (this.isStickyHeaderEnabled) {
134
+ this.unsubscribe();
135
+
136
+ const tree = getTree(this.dom);
137
+ if (tree) {
138
+ this.makeRowHeaderNotSticky(tree.table, true);
139
+ }
140
+
141
+ this.emitOff(true);
115
142
  }
143
+
144
+ // If a drop target cleanup method has been set then we should call it.
145
+ this.dropTargetCleanup?.();
116
146
  }
117
147
 
118
- /* external events */
119
- listening = false;
148
+ ignoreMutation(
149
+ mutationRecord: MutationRecord | { type: 'selection'; target: Element },
150
+ ) {
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' &&
160
+ mutationRecord.target.nodeName === 'TR';
161
+ /**
162
+ * Update: should not ignore mutations when an node is added, as this interferes with
163
+ * prosemirrors handling of some language inputs in Safari (ie. Pinyin, Hiragana).
164
+ *
165
+ * In paticular, when a composition occurs at the start of the first node inside a table cell, if the resulting mutation
166
+ * from the composition end is ignored than prosemirror will end up with; invalid table markup nesting and a misplaced
167
+ * selection and insertion.
168
+ */
169
+ const isNodeInsertion =
170
+ mutationRecord.type === 'childList' &&
171
+ mutationRecord.target.nodeName === 'TR' &&
172
+ mutationRecord.addedNodes.length;
173
+
174
+ if (isTableSelection || isNodeInsertion) {
175
+ return false;
176
+ }
177
+
178
+ return true;
179
+ }
180
+
181
+ /**
182
+ * Methods
183
+ */
184
+
185
+ private addDropTarget(element: HTMLElement) {
186
+ const pos = this.getPos()!;
187
+ if (!Number.isFinite(pos)) {
188
+ return;
189
+ }
190
+
191
+ if (this.dropTargetCleanup) {
192
+ this.dropTargetCleanup();
193
+ }
194
+
195
+ const resolvedPos = this.view.state.doc.resolve(pos);
196
+ const targetIndex = resolvedPos.index();
197
+ const localId = resolvedPos.parent.attrs.localId;
198
+
199
+ this.dropTargetCleanup = dropTargetForElements({
200
+ element: element,
201
+ canDrop({ source }) {
202
+ const data = source.data as DraggableSourceData;
203
+ return (
204
+ // Only draggables of row type can be dropped on this target
205
+ data.type === 'table-row' &&
206
+ // Only draggables which came from the same table can be dropped on this target
207
+ data.localId === localId &&
208
+ // Only draggables which DO NOT include this drop targets index can be dropped
209
+ !!data.indexes?.length &&
210
+ data.indexes?.indexOf(targetIndex) === -1
211
+ );
212
+ },
213
+ getData({ input, element }) {
214
+ const data = {
215
+ localId,
216
+ type: 'table-row',
217
+ targetIndex,
218
+ };
219
+ return attachClosestEdge(data, {
220
+ input,
221
+ element,
222
+ allowedEdges: ['top', 'bottom'],
223
+ });
224
+ },
225
+ });
226
+ }
120
227
 
121
- headerRowMouseScrollEnd = debounce(() => {
228
+ private headerRowMouseScrollEnd = debounce(() => {
122
229
  this.dom.classList.remove('no-pointer-events');
123
230
  }, HEADER_ROW_SCROLL_RESET_DEBOUNCE_TIMEOUT);
124
231
 
125
232
  // When the header is sticky, the header row is set to position: fixed
126
233
  // This prevents mouse wheel scrolling on the scroll-parent div when user's mouse is hovering the header row.
127
234
  // This fix sets pointer-events: none on the header row briefly to avoid this behaviour
128
- headerRowMouseScroll = throttle(() => {
235
+ private headerRowMouseScroll = throttle(() => {
129
236
  if (this.isSticky) {
130
237
  this.dom.classList.add('no-pointer-events');
131
238
  this.headerRowMouseScrollEnd();
132
239
  }
133
240
  }, HEADER_ROW_SCROLL_THROTTLE_TIMEOUT);
134
241
 
135
- subscribe() {
242
+ private subscribe() {
136
243
  this.editorScrollableElement =
137
244
  findOverflowScrollParent(this.view.dom as HTMLElement) || window;
138
245
 
@@ -141,11 +248,14 @@ export class TableRowNodeView implements NodeView {
141
248
  this.topPosEditorElement = getTop(this.editorScrollableElement);
142
249
  }
143
250
 
144
- this.eventDispatcher.on('widthPlugin', this.updateStickyHeaderWidth);
251
+ this.eventDispatcher.on(
252
+ 'widthPlugin',
253
+ this.updateStickyHeaderWidth.bind(this),
254
+ );
145
255
 
146
256
  this.eventDispatcher.on(
147
257
  (tablePluginKey as any).key,
148
- this.onTablePluginState,
258
+ this.onTablePluginState.bind(this),
149
259
  );
150
260
 
151
261
  this.listening = true;
@@ -160,7 +270,7 @@ export class TableRowNodeView implements NodeView {
160
270
  );
161
271
  }
162
272
 
163
- unsubscribe() {
273
+ private unsubscribe() {
164
274
  if (!this.listening) {
165
275
  return;
166
276
  }
@@ -211,7 +321,7 @@ export class TableRowNodeView implements NodeView {
211
321
 
212
322
  window.requestAnimationFrame(() => {
213
323
  // we expect tree to be defined after animation frame
214
- const tableContainer = this.tree?.wrapper.closest(
324
+ const tableContainer = getTree(this.dom)?.wrapper.closest(
215
325
  `.${TableCssClassName.NODEVIEW_WRAPPER}`,
216
326
  );
217
327
  if (tableContainer) {
@@ -236,10 +346,11 @@ export class TableRowNodeView implements NodeView {
236
346
  // to allocate for new header height
237
347
  private createResizeObserver() {
238
348
  this.resizeObserver = new ResizeObserver((entries) => {
239
- if (!this.tree) {
349
+ const tree = getTree(this.dom);
350
+ if (!tree) {
240
351
  return;
241
352
  }
242
- const { table } = this.tree;
353
+ const { table } = tree;
243
354
  entries.forEach((entry) => {
244
355
  // On resize of the parent scroll element we need to adjust the width
245
356
  // of the sticky header
@@ -275,10 +386,11 @@ export class TableRowNodeView implements NodeView {
275
386
  private createIntersectionObserver() {
276
387
  this.intersectionObserver = new IntersectionObserver(
277
388
  (entries: IntersectionObserverEntry[], _: IntersectionObserver) => {
278
- if (!this.tree) {
389
+ const tree = getTree(this.dom);
390
+ if (!tree) {
279
391
  return;
280
392
  }
281
- const { table } = this.tree;
393
+ const { table } = tree;
282
394
 
283
395
  if (table.rows.length < 2) {
284
396
  // ED-19307 - When there's only one row in a table the top & bottom sentinels become inverted. This creates some nasty visiblity
@@ -299,8 +411,7 @@ export class TableRowNodeView implements NodeView {
299
411
  (entry.rootBounds?.bottom || 0) < entry.boundingClientRect.bottom;
300
412
 
301
413
  if (!entry.isIntersecting && !sentinelIsBelowScrollArea) {
302
- this.tree &&
303
- this.makeHeaderRowSticky(this.tree, entry.rootBounds?.top);
414
+ tree && this.makeHeaderRowSticky(tree, entry.rootBounds?.top);
304
415
  this.lastStickyTimestamp = Date.now();
305
416
  } else {
306
417
  table && this.makeRowHeaderNotSticky(table);
@@ -328,8 +439,7 @@ export class TableRowNodeView implements NodeView {
328
439
  this.makeRowHeaderNotSticky(table);
329
440
  }
330
441
  } else if (entry.isIntersecting && sentinelIsAboveScrollArea) {
331
- this.tree &&
332
- this.makeHeaderRowSticky(this.tree, entry?.rootBounds?.top);
442
+ tree && this.makeHeaderRowSticky(tree, entry?.rootBounds?.top);
333
443
  this.lastStickyTimestamp = Date.now();
334
444
  }
335
445
  }
@@ -339,94 +449,12 @@ export class TableRowNodeView implements NodeView {
339
449
  { root: this.editorScrollableElement as Element },
340
450
  );
341
451
  }
342
-
343
- /* paint/update loop */
344
- previousDomTop: number | undefined;
345
- previousPadding: number | undefined;
346
-
347
- latestDomTop: number | undefined;
348
-
349
- nextFrame: number | undefined;
350
-
351
- /* nodeview lifecycle */
352
- update(node: PmNode, ..._args: any[]) {
353
- // do nothing if nodes were identical
354
- if (node === this.node) {
355
- return true;
356
- }
357
-
358
- // see if we're changing into a header row or
359
- // changing away from one
360
- const newNodeIsHeaderRow = supportedHeaderRow(node);
361
- if (this.isHeaderRow !== newNodeIsHeaderRow) {
362
- return false; // re-create nodeview
363
- }
364
-
365
- // node is different but no need to re-create nodeview
366
- this.node = node;
367
-
368
- // don't do anything if we're just a regular tr
369
- if (!this.isHeaderRow) {
370
- return true;
371
- }
372
-
373
- // something changed, sync widths
374
- const tbody = this.dom.parentElement;
375
- const table = tbody && tbody.parentElement;
376
- syncStickyRowToTable(table);
377
-
378
- return true;
379
- }
380
-
381
- destroy() {
382
- this.unsubscribe();
383
-
384
- if (this.tree) {
385
- this.makeRowHeaderNotSticky(this.tree.table, true);
386
- }
387
-
388
- this.emitOff(true);
389
- }
390
-
391
- ignoreMutation(
392
- mutationRecord: MutationRecord | { type: 'selection'; target: Element },
393
- ) {
394
- /* tableRows are not directly editable by the user
395
- * so it should be safe to ignore mutations that we cause
396
- * by updating styles and classnames on this DOM element
397
- *
398
- * Update: should not ignore mutations for row selection to avoid known issue with table selection highlight in firefox
399
- * Related bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=1289673
400
- * */
401
- const isTableSelection =
402
- mutationRecord.type === 'selection' &&
403
- mutationRecord.target.nodeName === 'TR';
404
- /**
405
- * Update: should not ignore mutations when an node is added, as this interferes with
406
- * prosemirrors handling of some language inputs in Safari (ie. Pinyin, Hiragana).
407
- *
408
- * In paticular, when a composition occurs at the start of the first node inside a table cell, if the resulting mutation
409
- * from the composition end is ignored than prosemirror will end up with; invalid table markup nesting and a misplaced
410
- * selection and insertion.
411
- */
412
- const isNodeInsertion =
413
- mutationRecord.type === 'childList' &&
414
- mutationRecord.target.nodeName === 'TR' &&
415
- mutationRecord.addedNodes.length;
416
-
417
- if (isTableSelection || isNodeInsertion) {
418
- return false;
419
- }
420
-
421
- return true;
422
- }
423
-
424
452
  /* receive external events */
425
453
 
426
- onTablePluginState = (state: TablePluginState) => {
454
+ private onTablePluginState(state: TablePluginState) {
427
455
  const tableRef = state.tableRef;
428
456
 
429
- const tree = this.tree;
457
+ const tree = getTree(this.dom);
430
458
  if (!tree) {
431
459
  return;
432
460
  }
@@ -443,8 +471,8 @@ export class TableRowNodeView implements NodeView {
443
471
  const isCurrentTableSelected = tableRef === tree.table;
444
472
 
445
473
  // If current table selected and header row is toggled off, turn off sticky header
446
- if (isCurrentTableSelected && !state.isHeaderRowEnabled && this.tree) {
447
- this.makeRowHeaderNotSticky(this.tree.table);
474
+ if (isCurrentTableSelected && !state.isHeaderRowEnabled && tree) {
475
+ this.makeRowHeaderNotSticky(tree.table);
448
476
  }
449
477
  this.focused = isCurrentTableSelected;
450
478
 
@@ -481,55 +509,23 @@ export class TableRowNodeView implements NodeView {
481
509
  setTimeout(() => {
482
510
  syncStickyRowToTable(tree.table);
483
511
  }, 0);
484
- };
512
+ }
485
513
 
486
- updateStickyHeaderWidth = () => {
514
+ private updateStickyHeaderWidth() {
487
515
  // table width might have changed, sync that back to sticky row
488
- const tree = this.tree;
516
+ const tree = getTree(this.dom);
489
517
  if (!tree) {
490
518
  return;
491
519
  }
492
520
 
493
521
  syncStickyRowToTable(tree.table);
494
- };
495
-
496
- shouldHeaderStick = (tree: TableDOMElements): boolean => {
497
- const { wrapper } = tree;
498
- const tableWrapperRect = wrapper.getBoundingClientRect();
499
- const editorAreaRect = (
500
- this.editorScrollableElement as HTMLElement
501
- ).getBoundingClientRect();
502
-
503
- const stickyHeaderRect = this.contentDOM.getBoundingClientRect();
504
- const firstHeaderRow = !this.dom.previousElementSibling;
505
- const subsequentRows = !!this.dom.nextElementSibling;
506
- const isHeaderValid = firstHeaderRow && subsequentRows;
507
-
508
- // if the table wrapper is less than the editor top pos then make it sticky
509
- // Make header sticky if table wrapper top is outside viewport
510
- // but bottom is still in the viewport.
511
- if (
512
- tableWrapperRect.top < editorAreaRect.top &&
513
- tableWrapperRect.bottom > editorAreaRect.top &&
514
- isHeaderValid
515
- ) {
516
- return true;
517
- }
518
-
519
- // if the sticky header is below the editor area make it non-sticky
520
- if (stickyHeaderRect.top > editorAreaRect.top) {
521
- return false;
522
- }
523
-
524
- // otherwise make it non-sticky
525
- return false;
526
- };
522
+ }
527
523
 
528
524
  /**
529
525
  * Manually refire the intersection observers.
530
526
  * Useful when the header may have detached from the table.
531
527
  */
532
- refireIntersectionObservers = () => {
528
+ private refireIntersectionObservers() {
533
529
  if (this.isSticky) {
534
530
  [this.sentinels.top, this.sentinels.bottom].forEach((el) => {
535
531
  if (el && this.intersectionObserver) {
@@ -538,9 +534,9 @@ export class TableRowNodeView implements NodeView {
538
534
  }
539
535
  });
540
536
  }
541
- };
537
+ }
542
538
 
543
- makeHeaderRowSticky = (tree: TableDOMElements, scrollTop?: number) => {
539
+ private makeHeaderRowSticky(tree: TableDOMElements, scrollTop?: number) {
544
540
  // If header row height is more than 50% of viewport height don't do this
545
541
  if (
546
542
  this.isSticky ||
@@ -596,12 +592,12 @@ export class TableRowNodeView implements NodeView {
596
592
  this.dom.scrollLeft = wrapper.scrollLeft;
597
593
 
598
594
  this.emitOn(domTop, this.colControlsOffset);
599
- };
595
+ }
600
596
 
601
- makeRowHeaderNotSticky = (
597
+ private makeRowHeaderNotSticky(
602
598
  table: HTMLElement,
603
599
  isEditorDestroyed: boolean = false,
604
- ) => {
600
+ ) {
605
601
  if (!this.isSticky || !table || !this.dom) {
606
602
  return;
607
603
  }
@@ -615,59 +611,63 @@ export class TableRowNodeView implements NodeView {
615
611
  table.style.removeProperty('margin-top');
616
612
 
617
613
  this.emitOff(isEditorDestroyed);
618
- };
614
+ }
619
615
 
620
- getWrapperoffset = (inverse: boolean = false): number => {
616
+ private getWrapperoffset(inverse: boolean = false): number {
621
617
  const focusValue = inverse ? !this.focused : this.focused;
622
618
  return focusValue ? 0 : tableControlsSpacing;
623
- };
619
+ }
624
620
 
625
- getWrapperRefTop = (wrapper: HTMLElement): number =>
626
- Math.round(getTop(wrapper)) + this.getWrapperoffset();
621
+ private getWrapperRefTop(wrapper: HTMLElement): number {
622
+ return Math.round(getTop(wrapper)) + this.getWrapperoffset();
623
+ }
627
624
 
628
625
  // TODO: rename!
629
- getScrolledTableTop = (wrapper: HTMLElement): number =>
630
- this.getWrapperRefTop(wrapper) - this.topPosEditorElement;
626
+ private getScrolledTableTop(wrapper: HTMLElement): number {
627
+ return this.getWrapperRefTop(wrapper) - this.topPosEditorElement;
628
+ }
631
629
 
632
- getCurrentTableTop = (tree: TableDOMElements): number =>
633
- this.getScrolledTableTop(tree.wrapper) + tree.table.clientHeight;
630
+ private getCurrentTableTop(tree: TableDOMElements): number {
631
+ return this.getScrolledTableTop(tree.wrapper) + tree.table.clientHeight;
632
+ }
634
633
 
635
634
  /* emit external events */
636
635
 
637
- padding = 0;
638
- top = 0;
639
-
640
- emitOn = (top: number, padding: number) => {
636
+ private emitOn(top: number, padding: number) {
641
637
  if (top === this.top && padding === this.padding) {
642
638
  return;
643
639
  }
644
640
 
645
641
  this.top = top;
646
642
  this.padding = padding;
643
+ const pos = this.getPos()!;
647
644
 
648
- updateStickyState({
649
- pos: this.getPos(),
650
- top,
651
- sticky: true,
652
- padding,
653
- })(this.view.state, this.view.dispatch, this.view);
654
- };
645
+ if (Number.isFinite(pos)) {
646
+ updateStickyState({
647
+ pos,
648
+ top,
649
+ sticky: true,
650
+ padding,
651
+ })(this.view.state, this.view.dispatch, this.view);
652
+ }
653
+ }
655
654
 
656
- emitOff = (isEditorDestroyed: boolean) => {
655
+ private emitOff(isEditorDestroyed: boolean) {
657
656
  if (this.top === 0 && this.padding === 0) {
658
657
  return;
659
658
  }
660
659
 
661
660
  this.top = 0;
662
661
  this.padding = 0;
662
+ const pos = this.getPos()!;
663
663
 
664
- if (!isEditorDestroyed) {
664
+ if (!isEditorDestroyed && Number.isFinite(pos)) {
665
665
  updateStickyState({
666
- pos: this.getPos(),
666
+ pos,
667
667
  sticky: false,
668
668
  top: this.top,
669
669
  padding: this.padding,
670
670
  })(this.view.state, this.view.dispatch, this.view);
671
671
  }
672
- };
672
+ }
673
673
  }