@atlaskit/editor-plugin-table 5.1.0 → 5.2.0

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 (77) hide show
  1. package/CHANGELOG.md +6 -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} +313 -307
  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/utils/dom.js +31 -1
  10. package/dist/cjs/plugins/table/utils/index.js +12 -0
  11. package/dist/cjs/plugins/table/utils/nodes.js +31 -7
  12. package/dist/es2019/plugins/table/index.js +3 -2
  13. package/dist/es2019/plugins/table/nodeviews/{tableCell.js → TableCell.js} +28 -24
  14. package/dist/es2019/plugins/table/nodeviews/TableNodeViewBase.js +22 -0
  15. package/dist/es2019/plugins/table/{pm-plugins/sticky-headers/nodeviews/tableRow.js → nodeviews/TableRow.js} +273 -284
  16. package/dist/es2019/plugins/table/pm-plugins/main.js +8 -8
  17. package/dist/es2019/plugins/table/pm-plugins/sticky-headers/index.js +1 -2
  18. package/dist/es2019/plugins/table/pm-plugins/sticky-headers/plugin.js +1 -9
  19. package/dist/es2019/plugins/table/utils/dom.js +30 -0
  20. package/dist/es2019/plugins/table/utils/index.js +1 -1
  21. package/dist/es2019/plugins/table/utils/nodes.js +16 -0
  22. package/dist/esm/plugins/table/index.js +3 -2
  23. package/dist/esm/plugins/table/nodeviews/TableCell.js +105 -0
  24. package/dist/esm/plugins/table/nodeviews/TableNodeViewBase.js +24 -0
  25. package/dist/esm/plugins/table/{pm-plugins/sticky-headers/nodeviews/tableRow.js → nodeviews/TableRow.js} +314 -307
  26. package/dist/esm/plugins/table/pm-plugins/main.js +16 -13
  27. package/dist/esm/plugins/table/pm-plugins/sticky-headers/index.js +1 -2
  28. package/dist/esm/plugins/table/pm-plugins/sticky-headers/plugin.js +1 -9
  29. package/dist/esm/plugins/table/utils/dom.js +30 -0
  30. package/dist/esm/plugins/table/utils/index.js +1 -1
  31. package/dist/esm/plugins/table/utils/nodes.js +24 -0
  32. package/dist/types/plugins/table/nodeviews/TableCell.d.ts +13 -0
  33. package/dist/types/plugins/table/nodeviews/TableNodeViewBase.d.ts +18 -0
  34. package/dist/types/plugins/table/nodeviews/TableRow.d.ts +60 -0
  35. package/dist/types/plugins/table/pm-plugins/main.d.ts +1 -1
  36. package/dist/types/plugins/table/pm-plugins/sticky-headers/index.d.ts +0 -1
  37. package/dist/types/plugins/table/pm-plugins/sticky-headers/plugin.d.ts +1 -1
  38. package/dist/types/plugins/table/types.d.ts +1 -0
  39. package/dist/types/plugins/table/utils/dom.d.ts +6 -0
  40. package/dist/types/plugins/table/utils/index.d.ts +1 -1
  41. package/dist/types/plugins/table/utils/nodes.d.ts +12 -2
  42. package/dist/types-ts4.5/plugins/table/nodeviews/TableCell.d.ts +13 -0
  43. package/dist/types-ts4.5/plugins/table/nodeviews/TableNodeViewBase.d.ts +18 -0
  44. package/dist/types-ts4.5/plugins/table/nodeviews/TableRow.d.ts +60 -0
  45. package/dist/types-ts4.5/plugins/table/pm-plugins/main.d.ts +1 -1
  46. package/dist/types-ts4.5/plugins/table/pm-plugins/sticky-headers/index.d.ts +0 -1
  47. package/dist/types-ts4.5/plugins/table/pm-plugins/sticky-headers/plugin.d.ts +1 -1
  48. package/dist/types-ts4.5/plugins/table/types.d.ts +1 -0
  49. package/dist/types-ts4.5/plugins/table/utils/dom.d.ts +6 -0
  50. package/dist/types-ts4.5/plugins/table/utils/index.d.ts +1 -1
  51. package/dist/types-ts4.5/plugins/table/utils/nodes.d.ts +12 -2
  52. package/package.json +1 -1
  53. package/src/__tests__/unit/nodeviews/cell.ts +2 -2
  54. package/src/__tests__/unit/pm-plugins/sticky-headers/tableRow.tsx +25 -148
  55. package/src/plugins/table/index.tsx +2 -0
  56. package/src/plugins/table/nodeviews/{tableCell.tsx → TableCell.ts} +41 -46
  57. package/src/plugins/table/nodeviews/TableNodeViewBase.ts +32 -0
  58. package/src/plugins/table/{pm-plugins/sticky-headers/nodeviews/tableRow.ts → nodeviews/TableRow.ts} +193 -246
  59. package/src/plugins/table/pm-plugins/main.ts +10 -19
  60. package/src/plugins/table/pm-plugins/sticky-headers/index.ts +0 -1
  61. package/src/plugins/table/pm-plugins/sticky-headers/plugin.ts +1 -9
  62. package/src/plugins/table/types.ts +2 -0
  63. package/src/plugins/table/utils/dom.ts +38 -0
  64. package/src/plugins/table/utils/index.ts +2 -0
  65. package/src/plugins/table/utils/nodes.ts +30 -2
  66. package/dist/cjs/plugins/table/nodeviews/tableCell.js +0 -99
  67. package/dist/cjs/plugins/table/pm-plugins/sticky-headers/nodeviews/dom.js +0 -35
  68. package/dist/es2019/plugins/table/pm-plugins/sticky-headers/nodeviews/dom.js +0 -29
  69. package/dist/esm/plugins/table/nodeviews/tableCell.js +0 -93
  70. package/dist/esm/plugins/table/pm-plugins/sticky-headers/nodeviews/dom.js +0 -29
  71. package/dist/types/plugins/table/nodeviews/tableCell.d.ts +0 -17
  72. package/dist/types/plugins/table/pm-plugins/sticky-headers/nodeviews/dom.d.ts +0 -6
  73. package/dist/types/plugins/table/pm-plugins/sticky-headers/nodeviews/tableRow.d.ts +0 -73
  74. package/dist/types-ts4.5/plugins/table/nodeviews/tableCell.d.ts +0 -17
  75. package/dist/types-ts4.5/plugins/table/pm-plugins/sticky-headers/nodeviews/dom.d.ts +0 -6
  76. package/dist/types-ts4.5/plugins/table/pm-plugins/sticky-headers/nodeviews/tableRow.d.ts +0 -73
  77. package/src/plugins/table/pm-plugins/sticky-headers/nodeviews/dom.ts +0 -37
@@ -2,13 +2,16 @@ 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 { getPluginState } from '../pm-plugins/plugin-factory';
7
+ import { pluginKey as tablePluginKey } from '../pm-plugins/plugin-key';
8
+ import { updateStickyState } from '../pm-plugins/sticky-headers/commands';
9
+ import { syncStickyRowToTable, updateStickyMargins as updateTableMargin } from '../pm-plugins/table-resizing/utils/dom';
10
+ import { TableCssClassName as ClassName, TableCssClassName } from '../types';
11
+ import { STICKY_HEADER_TOGGLE_TOLERANCE_MS, stickyHeaderBorderBottomWidth, stickyRowOffsetTop, tableControlsSpacing, tableScrollbarOffset } from '../ui/consts';
12
+ import { getTop, getTree } from '../utils/dom';
13
+ import { supportedHeaderRow } from '../utils/nodes';
14
+ import TableNodeView from './TableNodeViewBase';
12
15
 
13
16
  // limit scroll event calls
14
17
  const HEADER_ROW_SCROLL_THROTTLE_TIMEOUT = 200;
@@ -16,33 +19,19 @@ const HEADER_ROW_SCROLL_THROTTLE_TIMEOUT = 200;
16
19
  // timeout for resetting the scroll class - if it’s too long then users won’t be able to click on the header cells,
17
20
  // if too short it would trigger too many dom updates.
18
21
  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
- }
22
+ export default class TableRow extends TableNodeView {
38
23
  constructor(node, view, getPos, eventDispatcher) {
39
- // this is the sticky header table row
24
+ super(node, view, getPos, eventDispatcher);
40
25
  _defineProperty(this, "colControlsOffset", 0);
41
26
  _defineProperty(this, "focused", false);
42
27
  _defineProperty(this, "topPosEditorElement", 0);
43
28
  _defineProperty(this, "sentinels", {});
44
- /* external events */
45
29
  _defineProperty(this, "listening", false);
30
+ _defineProperty(this, "padding", 0);
31
+ _defineProperty(this, "top", 0);
32
+ /**
33
+ * Methods
34
+ */
46
35
  _defineProperty(this, "headerRowMouseScrollEnd", debounce(() => {
47
36
  this.dom.classList.remove('no-pointer-events');
48
37
  }, HEADER_ROW_SCROLL_RESET_DEBOUNCE_TIMEOUT));
@@ -55,215 +44,92 @@ export class TableRowNodeView {
55
44
  this.headerRowMouseScrollEnd();
56
45
  }
57
46
  }, 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;
47
+ this.isHeaderRow = supportedHeaderRow(node);
48
+ this.isSticky = false;
49
+ const {
50
+ pluginConfig,
51
+ isDragAndDropEnabled
52
+ } = getPluginState(view.state);
53
+ this.isStickyHeaderEnabled = !!pluginConfig.stickyHeaders;
54
+ this.isDragAndDropEnabled = !!isDragAndDropEnabled;
55
+ if (this.isHeaderRow) {
56
+ this.dom.setAttribute('data-header-row', 'true');
57
+ if (this.isStickyHeaderEnabled) {
58
+ this.subscribe();
64
59
  }
60
+ }
61
+ }
65
62
 
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;
63
+ /**
64
+ * Variables
65
+ */
75
66
 
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
- }
67
+ // @ts-ignore
100
68
 
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;
69
+ /**
70
+ * Methods: Nodeview Lifecycle
71
+ */
72
+ update(node, ..._args) {
73
+ // do nothing if nodes were identical
74
+ if (node === this.node) {
75
+ return true;
76
+ }
124
77
 
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
- }
78
+ // see if we're changing into a header row or
79
+ // changing away from one
80
+ const newNodeIsHeaderRow = supportedHeaderRow(node);
81
+ if (this.isHeaderRow !== newNodeIsHeaderRow) {
82
+ return false; // re-create nodeview
83
+ }
131
84
 
132
- // if the sticky header is below the editor area make it non-sticky
133
- if (stickyHeaderRect.top > editorAreaRect.top) {
134
- return false;
135
- }
85
+ // node is different but no need to re-create nodeview
86
+ this.node = node;
136
87
 
137
- // otherwise make it non-sticky
138
- return false;
139
- });
140
- /**
141
- * Manually refire the intersection observers.
142
- * Useful when the header may have detached from the table.
143
- */
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;
88
+ // don't do anything if we're just a regular tr
89
+ if (!this.isHeaderRow) {
90
+ return true;
91
+ }
164
92
 
165
- // ED-16035 Make sure sticky header is only applied to first row
93
+ // something changed, sync widths
94
+ if (this.isStickyHeaderEnabled) {
166
95
  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;
182
-
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
191
- });
192
- const fastScrollThresholdMs = 500;
193
- setTimeout(() => {
194
- this.refireIntersectionObservers();
195
- }, fastScrollThresholdMs);
196
- }
197
- this.dom.style.top = `${domTop}px`;
198
- updateTableMargin(table);
199
- this.dom.scrollLeft = wrapper.scrollLeft;
200
- this.emitOn(domTop, this.colControlsOffset);
201
- });
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);
96
+ const table = tbody && tbody.parentElement;
97
+ syncStickyRowToTable(table);
98
+ }
99
+ return true;
100
+ }
101
+ destroy() {
102
+ if (this.isStickyHeaderEnabled) {
103
+ this.unsubscribe();
104
+ const tree = getTree(this.dom);
105
+ if (tree) {
106
+ this.makeRowHeaderNotSticky(tree.table, true);
251
107
  }
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();
108
+ this.emitOff(true);
109
+ }
110
+ }
111
+ ignoreMutation(mutationRecord) {
112
+ /* tableRows are not directly editable by the user
113
+ * so it should be safe to ignore mutations that we cause
114
+ * by updating styles and classnames on this DOM element
115
+ *
116
+ * Update: should not ignore mutations for row selection to avoid known issue with table selection highlight in firefox
117
+ * Related bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=1289673
118
+ * */
119
+ const isTableSelection = mutationRecord.type === 'selection' && mutationRecord.target.nodeName === 'TR';
120
+ /**
121
+ * Update: should not ignore mutations when an node is added, as this interferes with
122
+ * prosemirrors handling of some language inputs in Safari (ie. Pinyin, Hiragana).
123
+ *
124
+ * In paticular, when a composition occurs at the start of the first node inside a table cell, if the resulting mutation
125
+ * from the composition end is ignored than prosemirror will end up with; invalid table markup nesting and a misplaced
126
+ * selection and insertion.
127
+ */
128
+ const isNodeInsertion = mutationRecord.type === 'childList' && mutationRecord.target.nodeName === 'TR' && mutationRecord.addedNodes.length;
129
+ if (isTableSelection || isNodeInsertion) {
130
+ return false;
266
131
  }
132
+ return true;
267
133
  }
268
134
  subscribe() {
269
135
  this.editorScrollableElement = findOverflowScrollParent(this.view.dom) || window;
@@ -271,8 +137,8 @@ export class TableRowNodeView {
271
137
  this.initObservers();
272
138
  this.topPosEditorElement = getTop(this.editorScrollableElement);
273
139
  }
274
- this.eventDispatcher.on('widthPlugin', this.updateStickyHeaderWidth);
275
- this.eventDispatcher.on(tablePluginKey.key, this.onTablePluginState);
140
+ this.eventDispatcher.on('widthPlugin', this.updateStickyHeaderWidth.bind(this));
141
+ this.eventDispatcher.on(tablePluginKey.key, this.onTablePluginState.bind(this));
276
142
  this.listening = true;
277
143
  this.dom.addEventListener('wheel', this.headerRowMouseScroll.bind(this), {
278
144
  passive: true
@@ -321,9 +187,9 @@ export class TableRowNodeView {
321
187
  this.resizeObserver.observe(this.editorScrollableElement);
322
188
  }
323
189
  window.requestAnimationFrame(() => {
324
- var _this$tree;
190
+ var _getTree;
325
191
  // 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}`);
192
+ const tableContainer = (_getTree = getTree(this.dom)) === null || _getTree === void 0 ? void 0 : _getTree.wrapper.closest(`.${TableCssClassName.NODEVIEW_WRAPPER}`);
327
193
  if (tableContainer) {
328
194
  this.sentinels.top = tableContainer.getElementsByClassName(ClassName.TABLE_STICKY_SENTINEL_TOP).item(0);
329
195
  this.sentinels.bottom = tableContainer.getElementsByClassName(ClassName.TABLE_STICKY_SENTINEL_BOTTOM).item(0);
@@ -342,17 +208,18 @@ export class TableRowNodeView {
342
208
  // to allocate for new header height
343
209
  createResizeObserver() {
344
210
  this.resizeObserver = new ResizeObserver(entries => {
345
- if (!this.tree) {
211
+ const tree = getTree(this.dom);
212
+ if (!tree) {
346
213
  return;
347
214
  }
348
215
  const {
349
216
  table
350
- } = this.tree;
217
+ } = tree;
351
218
  entries.forEach(entry => {
352
- var _this$editorScrollabl2;
219
+ var _this$editorScrollabl;
353
220
  // On resize of the parent scroll element we need to adjust the width
354
221
  // of the sticky header
355
- if (entry.target.className === ((_this$editorScrollabl2 = this.editorScrollableElement) === null || _this$editorScrollabl2 === void 0 ? void 0 : _this$editorScrollabl2.className)) {
222
+ if (entry.target.className === ((_this$editorScrollabl = this.editorScrollableElement) === null || _this$editorScrollabl === void 0 ? void 0 : _this$editorScrollabl.className)) {
356
223
  this.updateStickyHeaderWidth();
357
224
  } else {
358
225
  const newHeight = entry.contentRect ? entry.contentRect.height : entry.target.offsetHeight;
@@ -370,12 +237,13 @@ export class TableRowNodeView {
370
237
  }
371
238
  createIntersectionObserver() {
372
239
  this.intersectionObserver = new IntersectionObserver((entries, _) => {
373
- if (!this.tree) {
240
+ const tree = getTree(this.dom);
241
+ if (!tree) {
374
242
  return;
375
243
  }
376
244
  const {
377
245
  table
378
- } = this.tree;
246
+ } = tree;
379
247
  if (table.rows.length < 2) {
380
248
  // ED-19307 - When there's only one row in a table the top & bottom sentinels become inverted. This creates some nasty visiblity
381
249
  // toggling side-effects because the intersection observers gets confused.
@@ -394,7 +262,7 @@ export class TableRowNodeView {
394
262
  const sentinelIsBelowScrollArea = (((_entry$rootBounds2 = entry.rootBounds) === null || _entry$rootBounds2 === void 0 ? void 0 : _entry$rootBounds2.bottom) || 0) < entry.boundingClientRect.bottom;
395
263
  if (!entry.isIntersecting && !sentinelIsBelowScrollArea) {
396
264
  var _entry$rootBounds3;
397
- this.tree && this.makeHeaderRowSticky(this.tree, (_entry$rootBounds3 = entry.rootBounds) === null || _entry$rootBounds3 === void 0 ? void 0 : _entry$rootBounds3.top);
265
+ tree && this.makeHeaderRowSticky(tree, (_entry$rootBounds3 = entry.rootBounds) === null || _entry$rootBounds3 === void 0 ? void 0 : _entry$rootBounds3.top);
398
266
  this.lastStickyTimestamp = Date.now();
399
267
  } else {
400
268
  table && this.makeRowHeaderNotSticky(table);
@@ -414,7 +282,7 @@ export class TableRowNodeView {
414
282
  }
415
283
  } else if (entry.isIntersecting && sentinelIsAboveScrollArea) {
416
284
  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);
285
+ 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
286
  this.lastStickyTimestamp = Date.now();
419
287
  }
420
288
  }
@@ -424,65 +292,186 @@ export class TableRowNodeView {
424
292
  root: this.editorScrollableElement
425
293
  });
426
294
  }
295
+ /* receive external events */
427
296
 
428
- /* paint/update loop */
297
+ onTablePluginState(state) {
298
+ const tableRef = state.tableRef;
299
+ const tree = getTree(this.dom);
300
+ if (!tree) {
301
+ return;
302
+ }
429
303
 
430
- /* nodeview lifecycle */
431
- update(node, ..._args) {
432
- // do nothing if nodes were identical
433
- if (node === this.node) {
434
- return true;
304
+ // when header rows are toggled off - mark sentinels as unobserved
305
+ if (!state.isHeaderRowEnabled) {
306
+ [this.sentinels.top, this.sentinels.bottom].forEach(el => {
307
+ if (el) {
308
+ delete el.dataset.isObserved;
309
+ }
310
+ });
435
311
  }
312
+ const isCurrentTableSelected = tableRef === tree.table;
436
313
 
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
314
+ // If current table selected and header row is toggled off, turn off sticky header
315
+ if (isCurrentTableSelected && !state.isHeaderRowEnabled && tree) {
316
+ this.makeRowHeaderNotSticky(tree.table);
317
+ }
318
+ this.focused = isCurrentTableSelected;
319
+ const {
320
+ wrapper
321
+ } = tree;
322
+ const tableContainer = wrapper.parentElement;
323
+ const tableContentWrapper = tableContainer.parentElement;
324
+ const layoutContainer = tableContentWrapper && tableContentWrapper.parentElement;
325
+ if (isCurrentTableSelected) {
326
+ this.colControlsOffset = tableControlsSpacing;
327
+ if (layoutContainer && layoutContainer.getAttribute('data-layout-content')) {
328
+ // move table a little out of the way
329
+ // to provide spacing for table controls
330
+ tableContentWrapper.style.paddingLeft = '11px';
331
+ }
332
+ } else {
333
+ this.colControlsOffset = 0;
334
+ if (layoutContainer && layoutContainer.getAttribute('data-layout-content')) {
335
+ tableContentWrapper.style.removeProperty('padding-left');
336
+ }
442
337
  }
443
338
 
444
- // node is different but no need to re-create nodeview
445
- this.node = node;
339
+ // run after table style changes have been committed
340
+ setTimeout(() => {
341
+ syncStickyRowToTable(tree.table);
342
+ }, 0);
343
+ }
344
+ updateStickyHeaderWidth() {
345
+ // table width might have changed, sync that back to sticky row
346
+ const tree = getTree(this.dom);
347
+ if (!tree) {
348
+ return;
349
+ }
350
+ syncStickyRowToTable(tree.table);
351
+ }
446
352
 
447
- // don't do anything if we're just a regular tr
448
- if (!this.isHeaderRow) {
449
- return true;
353
+ /**
354
+ * Manually refire the intersection observers.
355
+ * Useful when the header may have detached from the table.
356
+ */
357
+ refireIntersectionObservers() {
358
+ if (this.isSticky) {
359
+ [this.sentinels.top, this.sentinels.bottom].forEach(el => {
360
+ if (el && this.intersectionObserver) {
361
+ this.intersectionObserver.unobserve(el);
362
+ this.intersectionObserver.observe(el);
363
+ }
364
+ });
450
365
  }
366
+ }
367
+ makeHeaderRowSticky(tree, scrollTop) {
368
+ var _tbody$firstChild;
369
+ // If header row height is more than 50% of viewport height don't do this
370
+ if (this.isSticky || this.stickyRowHeight && this.stickyRowHeight > window.innerHeight / 2) {
371
+ return;
372
+ }
373
+ const {
374
+ table,
375
+ wrapper
376
+ } = tree;
451
377
 
452
- // something changed, sync widths
378
+ // ED-16035 Make sure sticky header is only applied to first row
453
379
  const tbody = this.dom.parentElement;
454
- const table = tbody && tbody.parentElement;
455
- syncStickyRowToTable(table);
456
- return true;
380
+ 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);
381
+ if (!isFirstHeader) {
382
+ return;
383
+ }
384
+ const currentTableTop = this.getCurrentTableTop(tree);
385
+ if (!scrollTop) {
386
+ scrollTop = getTop(this.editorScrollableElement);
387
+ }
388
+ const domTop = currentTableTop > 0 ? scrollTop : scrollTop + currentTableTop;
389
+ if (!this.isSticky) {
390
+ var _this$editorScrollabl2;
391
+ syncStickyRowToTable(table);
392
+ this.dom.classList.add('sticky');
393
+ table.classList.add(ClassName.TABLE_STICKY);
394
+ this.isSticky = true;
395
+
396
+ /**
397
+ * The logic below is not desirable, but acts as a fail safe for scenarios where the sticky header
398
+ * detaches from the table. This typically happens during a fast scroll by the user which causes
399
+ * the intersection observer logic to not fire as expected.
400
+ */
401
+ (_this$editorScrollabl2 = this.editorScrollableElement) === null || _this$editorScrollabl2 === void 0 ? void 0 : _this$editorScrollabl2.addEventListener('scrollend', this.refireIntersectionObservers, {
402
+ passive: true,
403
+ once: true
404
+ });
405
+ const fastScrollThresholdMs = 500;
406
+ setTimeout(() => {
407
+ this.refireIntersectionObservers();
408
+ }, fastScrollThresholdMs);
409
+ }
410
+ this.dom.style.top = `${domTop}px`;
411
+ updateTableMargin(table);
412
+ this.dom.scrollLeft = wrapper.scrollLeft;
413
+ this.emitOn(domTop, this.colControlsOffset);
457
414
  }
458
- destroy() {
459
- this.unsubscribe();
460
- if (this.tree) {
461
- this.makeRowHeaderNotSticky(this.tree.table, true);
415
+ makeRowHeaderNotSticky(table, isEditorDestroyed = false) {
416
+ if (!this.isSticky || !table || !this.dom) {
417
+ return;
462
418
  }
463
- this.emitOff(true);
419
+ this.dom.style.removeProperty('width');
420
+ this.dom.classList.remove('sticky');
421
+ table.classList.remove(ClassName.TABLE_STICKY);
422
+ this.isSticky = false;
423
+ this.dom.style.top = '';
424
+ table.style.removeProperty('margin-top');
425
+ this.emitOff(isEditorDestroyed);
464
426
  }
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;
427
+ getWrapperoffset(inverse = false) {
428
+ const focusValue = inverse ? !this.focused : this.focused;
429
+ return focusValue ? 0 : tableControlsSpacing;
430
+ }
431
+ getWrapperRefTop(wrapper) {
432
+ return Math.round(getTop(wrapper)) + this.getWrapperoffset();
433
+ }
434
+
435
+ // TODO: rename!
436
+ getScrolledTableTop(wrapper) {
437
+ return this.getWrapperRefTop(wrapper) - this.topPosEditorElement;
438
+ }
439
+ getCurrentTableTop(tree) {
440
+ return this.getScrolledTableTop(tree.wrapper) + tree.table.clientHeight;
441
+ }
442
+
443
+ /* emit external events */
444
+
445
+ emitOn(top, padding) {
446
+ if (top === this.top && padding === this.padding) {
447
+ return;
448
+ }
449
+ this.top = top;
450
+ this.padding = padding;
451
+ const pos = this.getPos();
452
+ if (Number.isFinite(pos)) {
453
+ updateStickyState({
454
+ pos,
455
+ top,
456
+ sticky: true,
457
+ padding
458
+ })(this.view.state, this.view.dispatch, this.view);
459
+ }
460
+ }
461
+ emitOff(isEditorDestroyed) {
462
+ if (this.top === 0 && this.padding === 0) {
463
+ return;
464
+ }
465
+ this.top = 0;
466
+ this.padding = 0;
467
+ const pos = this.getPos();
468
+ if (!isEditorDestroyed && Number.isFinite(pos)) {
469
+ updateStickyState({
470
+ pos,
471
+ sticky: false,
472
+ top: this.top,
473
+ padding: this.padding
474
+ })(this.view.state, this.view.dispatch, this.view);
485
475
  }
486
- return true;
487
476
  }
488
477
  }