@atlaskit/editor-plugin-table 15.3.15 → 15.3.17

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