@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
@@ -2,13 +2,18 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import debounce from 'lodash/debounce';
3
3
  import throttle from 'lodash/throttle';
4
4
  import { findOverflowScrollParent } from '@atlaskit/editor-common/ui';
5
- import { browser, mapChildren } from '@atlaskit/editor-common/utils';
6
- import { TableCssClassName as ClassName, TableCssClassName } from '../../../types';
7
- import { STICKY_HEADER_TOGGLE_TOLERANCE_MS, stickyHeaderBorderBottomWidth, stickyRowOffsetTop, tableControlsSpacing, tableScrollbarOffset } from '../../../ui/consts';
8
- import { pluginKey as tablePluginKey } from '../../plugin-key';
9
- import { syncStickyRowToTable, updateStickyMargins as updateTableMargin } from '../../table-resizing/utils/dom';
10
- import { updateStickyState } from '../commands';
11
- import { getTop, getTree } from './dom';
5
+ import { browser } from '@atlaskit/editor-common/utils';
6
+ import { attachClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/addon/closest-edge';
7
+ import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/adapter/element';
8
+ import { getPluginState } from '../pm-plugins/plugin-factory';
9
+ import { pluginKey as tablePluginKey } from '../pm-plugins/plugin-key';
10
+ import { updateStickyState } from '../pm-plugins/sticky-headers/commands';
11
+ import { syncStickyRowToTable, updateStickyMargins as updateTableMargin } from '../pm-plugins/table-resizing/utils/dom';
12
+ import { TableCssClassName as ClassName, TableCssClassName } from '../types';
13
+ import { STICKY_HEADER_TOGGLE_TOLERANCE_MS, stickyHeaderBorderBottomWidth, stickyRowOffsetTop, tableControlsSpacing, tableScrollbarOffset } from '../ui/consts';
14
+ import { getTop, getTree } from '../utils/dom';
15
+ import { supportedHeaderRow } from '../utils/nodes';
16
+ import TableNodeView from './TableNodeViewBase';
12
17
 
13
18
  // limit scroll event calls
14
19
  const HEADER_ROW_SCROLL_THROTTLE_TIMEOUT = 200;
@@ -16,33 +21,16 @@ const HEADER_ROW_SCROLL_THROTTLE_TIMEOUT = 200;
16
21
  // timeout for resetting the scroll class - if it’s too long then users won’t be able to click on the header cells,
17
22
  // if too short it would trigger too many dom updates.
18
23
  const HEADER_ROW_SCROLL_RESET_DEBOUNCE_TIMEOUT = 400;
19
- const anyChildCellMergedAcrossRow = node => mapChildren(node, child => child.attrs.rowspan || 0).some(rowspan => rowspan > 1);
20
-
21
- /**
22
- * Check if a given node is a header row with this definition:
23
- * - all children are tableHeader cells
24
- * - no table cells have been have merged with other table row cells
25
- *
26
- * @param node ProseMirror node
27
- * @return boolean if it meets definition
28
- */
29
- export const supportedHeaderRow = node => {
30
- const allHeaders = mapChildren(node, child => child.type.name === 'tableHeader').every(Boolean);
31
- const someMerged = anyChildCellMergedAcrossRow(node);
32
- return allHeaders && !someMerged;
33
- };
34
- export class TableRowNodeView {
35
- get tree() {
36
- return getTree(this.dom);
37
- }
24
+ export default class TableRow extends TableNodeView {
38
25
  constructor(node, view, getPos, eventDispatcher) {
39
- // this is the sticky header table row
26
+ super(node, view, getPos, eventDispatcher);
40
27
  _defineProperty(this, "colControlsOffset", 0);
41
28
  _defineProperty(this, "focused", false);
42
29
  _defineProperty(this, "topPosEditorElement", 0);
43
30
  _defineProperty(this, "sentinels", {});
44
- /* external events */
45
31
  _defineProperty(this, "listening", false);
32
+ _defineProperty(this, "padding", 0);
33
+ _defineProperty(this, "top", 0);
46
34
  _defineProperty(this, "headerRowMouseScrollEnd", debounce(() => {
47
35
  this.dom.classList.remove('no-pointer-events');
48
36
  }, HEADER_ROW_SCROLL_RESET_DEBOUNCE_TIMEOUT));
@@ -55,215 +43,148 @@ export class TableRowNodeView {
55
43
  this.headerRowMouseScrollEnd();
56
44
  }
57
45
  }, HEADER_ROW_SCROLL_THROTTLE_TIMEOUT));
58
- /* receive external events */
59
- _defineProperty(this, "onTablePluginState", state => {
60
- const tableRef = state.tableRef;
61
- const tree = this.tree;
62
- if (!tree) {
63
- return;
46
+ this.isHeaderRow = supportedHeaderRow(node);
47
+ this.isSticky = false;
48
+ const {
49
+ pluginConfig,
50
+ isDragAndDropEnabled
51
+ } = getPluginState(view.state);
52
+ this.isStickyHeaderEnabled = !!pluginConfig.stickyHeaders;
53
+ this.isDragAndDropEnabled = !!isDragAndDropEnabled;
54
+ if (this.isHeaderRow) {
55
+ this.dom.setAttribute('data-header-row', 'true');
56
+ if (this.isStickyHeaderEnabled) {
57
+ this.subscribe();
64
58
  }
59
+ }
60
+ if (this.isDragAndDropEnabled) {
61
+ this.addDropTarget(this.contentDOM);
62
+ }
63
+ }
65
64
 
66
- // when header rows are toggled off - mark sentinels as unobserved
67
- if (!state.isHeaderRowEnabled) {
68
- [this.sentinels.top, this.sentinels.bottom].forEach(el => {
69
- if (el) {
70
- delete el.dataset.isObserved;
71
- }
72
- });
73
- }
74
- const isCurrentTableSelected = tableRef === tree.table;
65
+ /**
66
+ * Variables
67
+ */
75
68
 
76
- // If current table selected and header row is toggled off, turn off sticky header
77
- if (isCurrentTableSelected && !state.isHeaderRowEnabled && this.tree) {
78
- this.makeRowHeaderNotSticky(this.tree.table);
79
- }
80
- this.focused = isCurrentTableSelected;
81
- const {
82
- wrapper
83
- } = tree;
84
- const tableContainer = wrapper.parentElement;
85
- const tableContentWrapper = tableContainer.parentElement;
86
- const layoutContainer = tableContentWrapper && tableContentWrapper.parentElement;
87
- if (isCurrentTableSelected) {
88
- this.colControlsOffset = tableControlsSpacing;
89
- if (layoutContainer && layoutContainer.getAttribute('data-layout-content')) {
90
- // move table a little out of the way
91
- // to provide spacing for table controls
92
- tableContentWrapper.style.paddingLeft = '11px';
93
- }
94
- } else {
95
- this.colControlsOffset = 0;
96
- if (layoutContainer && layoutContainer.getAttribute('data-layout-content')) {
97
- tableContentWrapper.style.removeProperty('padding-left');
98
- }
99
- }
69
+ // @ts-ignore
100
70
 
101
- // run after table style changes have been committed
102
- setTimeout(() => {
103
- syncStickyRowToTable(tree.table);
104
- }, 0);
105
- });
106
- _defineProperty(this, "updateStickyHeaderWidth", () => {
107
- // table width might have changed, sync that back to sticky row
108
- const tree = this.tree;
109
- if (!tree) {
110
- return;
111
- }
112
- syncStickyRowToTable(tree.table);
113
- });
114
- _defineProperty(this, "shouldHeaderStick", tree => {
115
- const {
116
- wrapper
117
- } = tree;
118
- const tableWrapperRect = wrapper.getBoundingClientRect();
119
- const editorAreaRect = this.editorScrollableElement.getBoundingClientRect();
120
- const stickyHeaderRect = this.contentDOM.getBoundingClientRect();
121
- const firstHeaderRow = !this.dom.previousElementSibling;
122
- const subsequentRows = !!this.dom.nextElementSibling;
123
- const isHeaderValid = firstHeaderRow && subsequentRows;
71
+ /**
72
+ * Methods: Nodeview Lifecycle
73
+ */
74
+ update(node, ..._args) {
75
+ // do nothing if nodes were identical
76
+ if (node === this.node) {
77
+ return true;
78
+ }
124
79
 
125
- // if the table wrapper is less than the editor top pos then make it sticky
126
- // Make header sticky if table wrapper top is outside viewport
127
- // but bottom is still in the viewport.
128
- if (tableWrapperRect.top < editorAreaRect.top && tableWrapperRect.bottom > editorAreaRect.top && isHeaderValid) {
129
- return true;
130
- }
80
+ // see if we're changing into a header row or
81
+ // changing away from one
82
+ const newNodeIsHeaderRow = supportedHeaderRow(node);
83
+ if (this.isHeaderRow !== newNodeIsHeaderRow) {
84
+ return false; // re-create nodeview
85
+ }
131
86
 
132
- // if the sticky header is below the editor area make it non-sticky
133
- if (stickyHeaderRect.top > editorAreaRect.top) {
134
- return false;
87
+ // node is different but no need to re-create nodeview
88
+ this.node = node;
89
+
90
+ // don't do anything if we're just a regular tr
91
+ if (!this.isHeaderRow) {
92
+ return true;
93
+ }
94
+
95
+ // something changed, sync widths
96
+ if (this.isStickyHeaderEnabled) {
97
+ const tbody = this.dom.parentElement;
98
+ const table = tbody && tbody.parentElement;
99
+ syncStickyRowToTable(table);
100
+ }
101
+ return true;
102
+ }
103
+ destroy() {
104
+ var _this$dropTargetClean;
105
+ if (this.isStickyHeaderEnabled) {
106
+ this.unsubscribe();
107
+ const tree = getTree(this.dom);
108
+ if (tree) {
109
+ this.makeRowHeaderNotSticky(tree.table, true);
135
110
  }
111
+ this.emitOff(true);
112
+ }
136
113
 
137
- // otherwise make it non-sticky
138
- return false;
139
- });
114
+ // If a drop target cleanup method has been set then we should call it.
115
+ (_this$dropTargetClean = this.dropTargetCleanup) === null || _this$dropTargetClean === void 0 ? void 0 : _this$dropTargetClean.call(this);
116
+ }
117
+ ignoreMutation(mutationRecord) {
118
+ /* tableRows are not directly editable by the user
119
+ * so it should be safe to ignore mutations that we cause
120
+ * by updating styles and classnames on this DOM element
121
+ *
122
+ * Update: should not ignore mutations for row selection to avoid known issue with table selection highlight in firefox
123
+ * Related bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=1289673
124
+ * */
125
+ const isTableSelection = mutationRecord.type === 'selection' && mutationRecord.target.nodeName === 'TR';
140
126
  /**
141
- * Manually refire the intersection observers.
142
- * Useful when the header may have detached from the table.
127
+ * Update: should not ignore mutations when an node is added, as this interferes with
128
+ * prosemirrors handling of some language inputs in Safari (ie. Pinyin, Hiragana).
129
+ *
130
+ * In paticular, when a composition occurs at the start of the first node inside a table cell, if the resulting mutation
131
+ * from the composition end is ignored than prosemirror will end up with; invalid table markup nesting and a misplaced
132
+ * selection and insertion.
143
133
  */
144
- _defineProperty(this, "refireIntersectionObservers", () => {
145
- if (this.isSticky) {
146
- [this.sentinels.top, this.sentinels.bottom].forEach(el => {
147
- if (el && this.intersectionObserver) {
148
- this.intersectionObserver.unobserve(el);
149
- this.intersectionObserver.observe(el);
150
- }
151
- });
152
- }
153
- });
154
- _defineProperty(this, "makeHeaderRowSticky", (tree, scrollTop) => {
155
- var _tbody$firstChild;
156
- // If header row height is more than 50% of viewport height don't do this
157
- if (this.isSticky || this.stickyRowHeight && this.stickyRowHeight > window.innerHeight / 2) {
158
- return;
159
- }
160
- const {
161
- table,
162
- wrapper
163
- } = tree;
134
+ const isNodeInsertion = mutationRecord.type === 'childList' && mutationRecord.target.nodeName === 'TR' && mutationRecord.addedNodes.length;
135
+ if (isTableSelection || isNodeInsertion) {
136
+ return false;
137
+ }
138
+ return true;
139
+ }
164
140
 
165
- // ED-16035 Make sure sticky header is only applied to first row
166
- const tbody = this.dom.parentElement;
167
- const isFirstHeader = tbody === null || tbody === void 0 ? void 0 : (_tbody$firstChild = tbody.firstChild) === null || _tbody$firstChild === void 0 ? void 0 : _tbody$firstChild.isEqualNode(this.dom);
168
- if (!isFirstHeader) {
169
- return;
170
- }
171
- const currentTableTop = this.getCurrentTableTop(tree);
172
- if (!scrollTop) {
173
- scrollTop = getTop(this.editorScrollableElement);
174
- }
175
- const domTop = currentTableTop > 0 ? scrollTop : scrollTop + currentTableTop;
176
- if (!this.isSticky) {
177
- var _this$editorScrollabl;
178
- syncStickyRowToTable(table);
179
- this.dom.classList.add('sticky');
180
- table.classList.add(ClassName.TABLE_STICKY);
181
- this.isSticky = true;
141
+ /**
142
+ * Methods
143
+ */
182
144
 
183
- /**
184
- * The logic below is not desirable, but acts as a fail safe for scenarios where the sticky header
185
- * detaches from the table. This typically happens during a fast scroll by the user which causes
186
- * the intersection observer logic to not fire as expected.
187
- */
188
- (_this$editorScrollabl = this.editorScrollableElement) === null || _this$editorScrollabl === void 0 ? void 0 : _this$editorScrollabl.addEventListener('scrollend', this.refireIntersectionObservers, {
189
- passive: true,
190
- once: true
145
+ addDropTarget(element) {
146
+ const pos = this.getPos();
147
+ if (!Number.isFinite(pos)) {
148
+ return;
149
+ }
150
+ if (this.dropTargetCleanup) {
151
+ this.dropTargetCleanup();
152
+ }
153
+ const resolvedPos = this.view.state.doc.resolve(pos);
154
+ const targetIndex = resolvedPos.index();
155
+ const localId = resolvedPos.parent.attrs.localId;
156
+ this.dropTargetCleanup = dropTargetForElements({
157
+ element: element,
158
+ canDrop({
159
+ source
160
+ }) {
161
+ var _data$indexes, _data$indexes2;
162
+ const data = source.data;
163
+ return (
164
+ // Only draggables of row type can be dropped on this target
165
+ data.type === 'table-row' &&
166
+ // Only draggables which came from the same table can be dropped on this target
167
+ data.localId === localId &&
168
+ // Only draggables which DO NOT include this drop targets index can be dropped
169
+ !!((_data$indexes = data.indexes) !== null && _data$indexes !== void 0 && _data$indexes.length) && ((_data$indexes2 = data.indexes) === null || _data$indexes2 === void 0 ? void 0 : _data$indexes2.indexOf(targetIndex)) === -1
170
+ );
171
+ },
172
+ getData({
173
+ input,
174
+ element
175
+ }) {
176
+ const data = {
177
+ localId,
178
+ type: 'table-row',
179
+ targetIndex
180
+ };
181
+ return attachClosestEdge(data, {
182
+ input,
183
+ element,
184
+ allowedEdges: ['top', 'bottom']
191
185
  });
192
- const fastScrollThresholdMs = 500;
193
- setTimeout(() => {
194
- this.refireIntersectionObservers();
195
- }, fastScrollThresholdMs);
196
186
  }
197
- this.dom.style.top = `${domTop}px`;
198
- updateTableMargin(table);
199
- this.dom.scrollLeft = wrapper.scrollLeft;
200
- this.emitOn(domTop, this.colControlsOffset);
201
187
  });
202
- _defineProperty(this, "makeRowHeaderNotSticky", (table, isEditorDestroyed = false) => {
203
- if (!this.isSticky || !table || !this.dom) {
204
- return;
205
- }
206
- this.dom.style.removeProperty('width');
207
- this.dom.classList.remove('sticky');
208
- table.classList.remove(ClassName.TABLE_STICKY);
209
- this.isSticky = false;
210
- this.dom.style.top = '';
211
- table.style.removeProperty('margin-top');
212
- this.emitOff(isEditorDestroyed);
213
- });
214
- _defineProperty(this, "getWrapperoffset", (inverse = false) => {
215
- const focusValue = inverse ? !this.focused : this.focused;
216
- return focusValue ? 0 : tableControlsSpacing;
217
- });
218
- _defineProperty(this, "getWrapperRefTop", wrapper => Math.round(getTop(wrapper)) + this.getWrapperoffset());
219
- // TODO: rename!
220
- _defineProperty(this, "getScrolledTableTop", wrapper => this.getWrapperRefTop(wrapper) - this.topPosEditorElement);
221
- _defineProperty(this, "getCurrentTableTop", tree => this.getScrolledTableTop(tree.wrapper) + tree.table.clientHeight);
222
- /* emit external events */
223
- _defineProperty(this, "padding", 0);
224
- _defineProperty(this, "top", 0);
225
- _defineProperty(this, "emitOn", (top, padding) => {
226
- if (top === this.top && padding === this.padding) {
227
- return;
228
- }
229
- this.top = top;
230
- this.padding = padding;
231
- updateStickyState({
232
- pos: this.getPos(),
233
- top,
234
- sticky: true,
235
- padding
236
- })(this.view.state, this.view.dispatch, this.view);
237
- });
238
- _defineProperty(this, "emitOff", isEditorDestroyed => {
239
- if (this.top === 0 && this.padding === 0) {
240
- return;
241
- }
242
- this.top = 0;
243
- this.padding = 0;
244
- if (!isEditorDestroyed) {
245
- updateStickyState({
246
- pos: this.getPos(),
247
- sticky: false,
248
- top: this.top,
249
- padding: this.padding
250
- })(this.view.state, this.view.dispatch, this.view);
251
- }
252
- });
253
- this.view = view;
254
- this.node = node;
255
- this.getPos = getPos;
256
- this.eventDispatcher = eventDispatcher;
257
- this.dom = document.createElement('tr');
258
- this.contentDOM = this.dom;
259
- this.lastTimePainted = 0;
260
- this.isHeaderRow = supportedHeaderRow(node);
261
- this.isSticky = false;
262
- this.lastStickyTimestamp = undefined;
263
- if (this.isHeaderRow) {
264
- this.dom.setAttribute('data-header-row', 'true');
265
- this.subscribe();
266
- }
267
188
  }
268
189
  subscribe() {
269
190
  this.editorScrollableElement = findOverflowScrollParent(this.view.dom) || window;
@@ -271,8 +192,8 @@ export class TableRowNodeView {
271
192
  this.initObservers();
272
193
  this.topPosEditorElement = getTop(this.editorScrollableElement);
273
194
  }
274
- this.eventDispatcher.on('widthPlugin', this.updateStickyHeaderWidth);
275
- this.eventDispatcher.on(tablePluginKey.key, this.onTablePluginState);
195
+ this.eventDispatcher.on('widthPlugin', this.updateStickyHeaderWidth.bind(this));
196
+ this.eventDispatcher.on(tablePluginKey.key, this.onTablePluginState.bind(this));
276
197
  this.listening = true;
277
198
  this.dom.addEventListener('wheel', this.headerRowMouseScroll.bind(this), {
278
199
  passive: true
@@ -321,9 +242,9 @@ export class TableRowNodeView {
321
242
  this.resizeObserver.observe(this.editorScrollableElement);
322
243
  }
323
244
  window.requestAnimationFrame(() => {
324
- var _this$tree;
245
+ var _getTree;
325
246
  // we expect tree to be defined after animation frame
326
- const tableContainer = (_this$tree = this.tree) === null || _this$tree === void 0 ? void 0 : _this$tree.wrapper.closest(`.${TableCssClassName.NODEVIEW_WRAPPER}`);
247
+ const tableContainer = (_getTree = getTree(this.dom)) === null || _getTree === void 0 ? void 0 : _getTree.wrapper.closest(`.${TableCssClassName.NODEVIEW_WRAPPER}`);
327
248
  if (tableContainer) {
328
249
  this.sentinels.top = tableContainer.getElementsByClassName(ClassName.TABLE_STICKY_SENTINEL_TOP).item(0);
329
250
  this.sentinels.bottom = tableContainer.getElementsByClassName(ClassName.TABLE_STICKY_SENTINEL_BOTTOM).item(0);
@@ -342,17 +263,18 @@ export class TableRowNodeView {
342
263
  // to allocate for new header height
343
264
  createResizeObserver() {
344
265
  this.resizeObserver = new ResizeObserver(entries => {
345
- if (!this.tree) {
266
+ const tree = getTree(this.dom);
267
+ if (!tree) {
346
268
  return;
347
269
  }
348
270
  const {
349
271
  table
350
- } = this.tree;
272
+ } = tree;
351
273
  entries.forEach(entry => {
352
- var _this$editorScrollabl2;
274
+ var _this$editorScrollabl;
353
275
  // On resize of the parent scroll element we need to adjust the width
354
276
  // of the sticky header
355
- if (entry.target.className === ((_this$editorScrollabl2 = this.editorScrollableElement) === null || _this$editorScrollabl2 === void 0 ? void 0 : _this$editorScrollabl2.className)) {
277
+ if (entry.target.className === ((_this$editorScrollabl = this.editorScrollableElement) === null || _this$editorScrollabl === void 0 ? void 0 : _this$editorScrollabl.className)) {
356
278
  this.updateStickyHeaderWidth();
357
279
  } else {
358
280
  const newHeight = entry.contentRect ? entry.contentRect.height : entry.target.offsetHeight;
@@ -370,12 +292,13 @@ export class TableRowNodeView {
370
292
  }
371
293
  createIntersectionObserver() {
372
294
  this.intersectionObserver = new IntersectionObserver((entries, _) => {
373
- if (!this.tree) {
295
+ const tree = getTree(this.dom);
296
+ if (!tree) {
374
297
  return;
375
298
  }
376
299
  const {
377
300
  table
378
- } = this.tree;
301
+ } = tree;
379
302
  if (table.rows.length < 2) {
380
303
  // ED-19307 - When there's only one row in a table the top & bottom sentinels become inverted. This creates some nasty visiblity
381
304
  // toggling side-effects because the intersection observers gets confused.
@@ -394,7 +317,7 @@ export class TableRowNodeView {
394
317
  const sentinelIsBelowScrollArea = (((_entry$rootBounds2 = entry.rootBounds) === null || _entry$rootBounds2 === void 0 ? void 0 : _entry$rootBounds2.bottom) || 0) < entry.boundingClientRect.bottom;
395
318
  if (!entry.isIntersecting && !sentinelIsBelowScrollArea) {
396
319
  var _entry$rootBounds3;
397
- this.tree && this.makeHeaderRowSticky(this.tree, (_entry$rootBounds3 = entry.rootBounds) === null || _entry$rootBounds3 === void 0 ? void 0 : _entry$rootBounds3.top);
320
+ tree && this.makeHeaderRowSticky(tree, (_entry$rootBounds3 = entry.rootBounds) === null || _entry$rootBounds3 === void 0 ? void 0 : _entry$rootBounds3.top);
398
321
  this.lastStickyTimestamp = Date.now();
399
322
  } else {
400
323
  table && this.makeRowHeaderNotSticky(table);
@@ -414,7 +337,7 @@ export class TableRowNodeView {
414
337
  }
415
338
  } else if (entry.isIntersecting && sentinelIsAboveScrollArea) {
416
339
  var _entry$rootBounds5;
417
- this.tree && this.makeHeaderRowSticky(this.tree, entry === null || entry === void 0 ? void 0 : (_entry$rootBounds5 = entry.rootBounds) === null || _entry$rootBounds5 === void 0 ? void 0 : _entry$rootBounds5.top);
340
+ tree && this.makeHeaderRowSticky(tree, entry === null || entry === void 0 ? void 0 : (_entry$rootBounds5 = entry.rootBounds) === null || _entry$rootBounds5 === void 0 ? void 0 : _entry$rootBounds5.top);
418
341
  this.lastStickyTimestamp = Date.now();
419
342
  }
420
343
  }
@@ -424,65 +347,186 @@ export class TableRowNodeView {
424
347
  root: this.editorScrollableElement
425
348
  });
426
349
  }
350
+ /* receive external events */
427
351
 
428
- /* paint/update loop */
352
+ onTablePluginState(state) {
353
+ const tableRef = state.tableRef;
354
+ const tree = getTree(this.dom);
355
+ if (!tree) {
356
+ return;
357
+ }
429
358
 
430
- /* nodeview lifecycle */
431
- update(node, ..._args) {
432
- // do nothing if nodes were identical
433
- if (node === this.node) {
434
- return true;
359
+ // when header rows are toggled off - mark sentinels as unobserved
360
+ if (!state.isHeaderRowEnabled) {
361
+ [this.sentinels.top, this.sentinels.bottom].forEach(el => {
362
+ if (el) {
363
+ delete el.dataset.isObserved;
364
+ }
365
+ });
435
366
  }
367
+ const isCurrentTableSelected = tableRef === tree.table;
436
368
 
437
- // see if we're changing into a header row or
438
- // changing away from one
439
- const newNodeIsHeaderRow = supportedHeaderRow(node);
440
- if (this.isHeaderRow !== newNodeIsHeaderRow) {
441
- return false; // re-create nodeview
369
+ // If current table selected and header row is toggled off, turn off sticky header
370
+ if (isCurrentTableSelected && !state.isHeaderRowEnabled && tree) {
371
+ this.makeRowHeaderNotSticky(tree.table);
372
+ }
373
+ this.focused = isCurrentTableSelected;
374
+ const {
375
+ wrapper
376
+ } = tree;
377
+ const tableContainer = wrapper.parentElement;
378
+ const tableContentWrapper = tableContainer.parentElement;
379
+ const layoutContainer = tableContentWrapper && tableContentWrapper.parentElement;
380
+ if (isCurrentTableSelected) {
381
+ this.colControlsOffset = tableControlsSpacing;
382
+ if (layoutContainer && layoutContainer.getAttribute('data-layout-content')) {
383
+ // move table a little out of the way
384
+ // to provide spacing for table controls
385
+ tableContentWrapper.style.paddingLeft = '11px';
386
+ }
387
+ } else {
388
+ this.colControlsOffset = 0;
389
+ if (layoutContainer && layoutContainer.getAttribute('data-layout-content')) {
390
+ tableContentWrapper.style.removeProperty('padding-left');
391
+ }
442
392
  }
443
393
 
444
- // node is different but no need to re-create nodeview
445
- this.node = node;
394
+ // run after table style changes have been committed
395
+ setTimeout(() => {
396
+ syncStickyRowToTable(tree.table);
397
+ }, 0);
398
+ }
399
+ updateStickyHeaderWidth() {
400
+ // table width might have changed, sync that back to sticky row
401
+ const tree = getTree(this.dom);
402
+ if (!tree) {
403
+ return;
404
+ }
405
+ syncStickyRowToTable(tree.table);
406
+ }
446
407
 
447
- // don't do anything if we're just a regular tr
448
- if (!this.isHeaderRow) {
449
- return true;
408
+ /**
409
+ * Manually refire the intersection observers.
410
+ * Useful when the header may have detached from the table.
411
+ */
412
+ refireIntersectionObservers() {
413
+ if (this.isSticky) {
414
+ [this.sentinels.top, this.sentinels.bottom].forEach(el => {
415
+ if (el && this.intersectionObserver) {
416
+ this.intersectionObserver.unobserve(el);
417
+ this.intersectionObserver.observe(el);
418
+ }
419
+ });
450
420
  }
421
+ }
422
+ makeHeaderRowSticky(tree, scrollTop) {
423
+ var _tbody$firstChild;
424
+ // If header row height is more than 50% of viewport height don't do this
425
+ if (this.isSticky || this.stickyRowHeight && this.stickyRowHeight > window.innerHeight / 2) {
426
+ return;
427
+ }
428
+ const {
429
+ table,
430
+ wrapper
431
+ } = tree;
451
432
 
452
- // something changed, sync widths
433
+ // ED-16035 Make sure sticky header is only applied to first row
453
434
  const tbody = this.dom.parentElement;
454
- const table = tbody && tbody.parentElement;
455
- syncStickyRowToTable(table);
456
- return true;
435
+ const isFirstHeader = tbody === null || tbody === void 0 ? void 0 : (_tbody$firstChild = tbody.firstChild) === null || _tbody$firstChild === void 0 ? void 0 : _tbody$firstChild.isEqualNode(this.dom);
436
+ if (!isFirstHeader) {
437
+ return;
438
+ }
439
+ const currentTableTop = this.getCurrentTableTop(tree);
440
+ if (!scrollTop) {
441
+ scrollTop = getTop(this.editorScrollableElement);
442
+ }
443
+ const domTop = currentTableTop > 0 ? scrollTop : scrollTop + currentTableTop;
444
+ if (!this.isSticky) {
445
+ var _this$editorScrollabl2;
446
+ syncStickyRowToTable(table);
447
+ this.dom.classList.add('sticky');
448
+ table.classList.add(ClassName.TABLE_STICKY);
449
+ this.isSticky = true;
450
+
451
+ /**
452
+ * The logic below is not desirable, but acts as a fail safe for scenarios where the sticky header
453
+ * detaches from the table. This typically happens during a fast scroll by the user which causes
454
+ * the intersection observer logic to not fire as expected.
455
+ */
456
+ (_this$editorScrollabl2 = this.editorScrollableElement) === null || _this$editorScrollabl2 === void 0 ? void 0 : _this$editorScrollabl2.addEventListener('scrollend', this.refireIntersectionObservers, {
457
+ passive: true,
458
+ once: true
459
+ });
460
+ const fastScrollThresholdMs = 500;
461
+ setTimeout(() => {
462
+ this.refireIntersectionObservers();
463
+ }, fastScrollThresholdMs);
464
+ }
465
+ this.dom.style.top = `${domTop}px`;
466
+ updateTableMargin(table);
467
+ this.dom.scrollLeft = wrapper.scrollLeft;
468
+ this.emitOn(domTop, this.colControlsOffset);
457
469
  }
458
- destroy() {
459
- this.unsubscribe();
460
- if (this.tree) {
461
- this.makeRowHeaderNotSticky(this.tree.table, true);
470
+ makeRowHeaderNotSticky(table, isEditorDestroyed = false) {
471
+ if (!this.isSticky || !table || !this.dom) {
472
+ return;
462
473
  }
463
- this.emitOff(true);
474
+ this.dom.style.removeProperty('width');
475
+ this.dom.classList.remove('sticky');
476
+ table.classList.remove(ClassName.TABLE_STICKY);
477
+ this.isSticky = false;
478
+ this.dom.style.top = '';
479
+ table.style.removeProperty('margin-top');
480
+ this.emitOff(isEditorDestroyed);
464
481
  }
465
- ignoreMutation(mutationRecord) {
466
- /* tableRows are not directly editable by the user
467
- * so it should be safe to ignore mutations that we cause
468
- * by updating styles and classnames on this DOM element
469
- *
470
- * Update: should not ignore mutations for row selection to avoid known issue with table selection highlight in firefox
471
- * Related bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=1289673
472
- * */
473
- const isTableSelection = mutationRecord.type === 'selection' && mutationRecord.target.nodeName === 'TR';
474
- /**
475
- * Update: should not ignore mutations when an node is added, as this interferes with
476
- * prosemirrors handling of some language inputs in Safari (ie. Pinyin, Hiragana).
477
- *
478
- * In paticular, when a composition occurs at the start of the first node inside a table cell, if the resulting mutation
479
- * from the composition end is ignored than prosemirror will end up with; invalid table markup nesting and a misplaced
480
- * selection and insertion.
481
- */
482
- const isNodeInsertion = mutationRecord.type === 'childList' && mutationRecord.target.nodeName === 'TR' && mutationRecord.addedNodes.length;
483
- if (isTableSelection || isNodeInsertion) {
484
- return false;
482
+ getWrapperoffset(inverse = false) {
483
+ const focusValue = inverse ? !this.focused : this.focused;
484
+ return focusValue ? 0 : tableControlsSpacing;
485
+ }
486
+ getWrapperRefTop(wrapper) {
487
+ return Math.round(getTop(wrapper)) + this.getWrapperoffset();
488
+ }
489
+
490
+ // TODO: rename!
491
+ getScrolledTableTop(wrapper) {
492
+ return this.getWrapperRefTop(wrapper) - this.topPosEditorElement;
493
+ }
494
+ getCurrentTableTop(tree) {
495
+ return this.getScrolledTableTop(tree.wrapper) + tree.table.clientHeight;
496
+ }
497
+
498
+ /* emit external events */
499
+
500
+ emitOn(top, padding) {
501
+ if (top === this.top && padding === this.padding) {
502
+ return;
503
+ }
504
+ this.top = top;
505
+ this.padding = padding;
506
+ const pos = this.getPos();
507
+ if (Number.isFinite(pos)) {
508
+ updateStickyState({
509
+ pos,
510
+ top,
511
+ sticky: true,
512
+ padding
513
+ })(this.view.state, this.view.dispatch, this.view);
514
+ }
515
+ }
516
+ emitOff(isEditorDestroyed) {
517
+ if (this.top === 0 && this.padding === 0) {
518
+ return;
519
+ }
520
+ this.top = 0;
521
+ this.padding = 0;
522
+ const pos = this.getPos();
523
+ if (!isEditorDestroyed && Number.isFinite(pos)) {
524
+ updateStickyState({
525
+ pos,
526
+ sticky: false,
527
+ top: this.top,
528
+ padding: this.padding
529
+ })(this.view.state, this.view.dispatch, this.view);
485
530
  }
486
- return true;
487
531
  }
488
532
  }