@atlaskit/editor-plugin-table 15.3.14 → 15.3.16

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,785 @@
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
+ }
304
+ });
305
+ }, options);
306
+ }
307
+ // initialize intersection observer to track if table is within scroll area
308
+ }, {
309
+ key: "initObservers",
310
+ value: function initObservers() {
311
+ var _this3 = this;
312
+ if (!this.dom || this.dom.dataset.isObserved) {
313
+ return;
314
+ }
315
+ this.dom.dataset.isObserved = 'true';
316
+ this.createIntersectionObserver();
317
+ this.createResizeObserver();
318
+ if (!this.intersectionObserver || !this.resizeObserver) {
319
+ return;
320
+ }
321
+ if (this.isHeaderRow && !this.isInNestedTable) {
322
+ var _this$overflowObserve;
323
+ this.initOverflowObserver();
324
+ // eslint-disable-next-line @atlaskit/editor/no-as-casting
325
+ (_this$overflowObserve = this.overflowObserver) === null || _this$overflowObserve === void 0 || _this$overflowObserve.observe(this.dom.closest('table'));
326
+ }
327
+ this.resizeObserver.observe(this.dom);
328
+ if (this.editorScrollableElement) {
329
+ // Ignored via go/ees005
330
+ // eslint-disable-next-line @atlaskit/editor/no-as-casting
331
+ this.resizeObserver.observe(this.editorScrollableElement);
332
+ }
333
+ window.requestAnimationFrame(function () {
334
+ var getTableContainer = function getTableContainer() {
335
+ var _getTree;
336
+ return (_getTree = (0, _dom2.getTree)(_this3.dom)) === null || _getTree === void 0 ? void 0 : _getTree.wrapper.closest(".".concat(_types.TableCssClassName.NODEVIEW_WRAPPER));
337
+ };
338
+
339
+ // we expect tree to be defined after animation frame
340
+ var tableContainer = getTableContainer();
341
+ if (tableContainer) {
342
+ var getSentinelTop = function getSentinelTop() {
343
+ return tableContainer &&
344
+ // Ignored via go/ees005
345
+ // eslint-disable-next-line @atlaskit/editor/no-as-casting
346
+ tableContainer.getElementsByClassName(_types.TableCssClassName.TABLE_STICKY_SENTINEL_TOP).item(0);
347
+ };
348
+ var getSentinelBottom = function getSentinelBottom() {
349
+ // Multiple bottom sentinels may be found if there are nested tables.
350
+ // We need to make sure we get the last one which will belong to the parent table.
351
+
352
+ var bottomSentinels = tableContainer && tableContainer.getElementsByClassName(_types.TableCssClassName.TABLE_STICKY_SENTINEL_BOTTOM);
353
+ return (
354
+ // eslint-disable-next-line @atlaskit/editor/no-as-casting
355
+ bottomSentinels && bottomSentinels.item(bottomSentinels.length - 1)
356
+ );
357
+ };
358
+ var sentinelsInDom = function sentinelsInDom() {
359
+ return getSentinelTop() !== null && getSentinelBottom() !== null;
360
+ };
361
+ var observeStickySentinels = function observeStickySentinels() {
362
+ _this3.sentinels.top = getSentinelTop();
363
+ _this3.sentinels.bottom = getSentinelBottom();
364
+ [_this3.sentinels.top, _this3.sentinels.bottom].forEach(function (el) {
365
+ // skip if already observed for another row on this table
366
+ if (el && !el.dataset.isObserved) {
367
+ el.dataset.isObserved = 'true';
368
+
369
+ // Ignored via go/ees005
370
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
371
+ _this3.intersectionObserver.observe(el);
372
+ }
373
+ });
374
+ };
375
+ var isInitialProsemirrorToDomRender = tableContainer.hasAttribute('data-prosemirror-initial-toDOM-render');
376
+
377
+ // Sentinels may be in the DOM but they're part of the prosemirror placeholder structure which is replaced with the fully rendered React node.
378
+ if (sentinelsInDom() && !isInitialProsemirrorToDomRender) {
379
+ // great - DOM ready, observe as normal
380
+ observeStickySentinels();
381
+ } else {
382
+ // concurrent loading issue - here TableRow is too eager trying to
383
+ // observe sentinels before they are in the DOM, use MutationObserver
384
+ // to wait for sentinels to be added to the parent Table node DOM
385
+ // then attach the IntersectionObserver
386
+ _this3.tableContainerObserver = new MutationObserver(function () {
387
+ // Check if the tableContainer is still connected to the DOM. It can become disconnected when the placholder
388
+ // prosemirror node is replaced with the fully rendered React node (see _handleTableRef).
389
+
390
+ if (!tableContainer || !tableContainer.isConnected) {
391
+ tableContainer = getTableContainer();
392
+ }
393
+ if (sentinelsInDom()) {
394
+ var _this3$tableContainer;
395
+ observeStickySentinels();
396
+ (_this3$tableContainer = _this3.tableContainerObserver) === null || _this3$tableContainer === void 0 || _this3$tableContainer.disconnect();
397
+ }
398
+ });
399
+ var mutatingNode = tableContainer;
400
+ if (mutatingNode && _this3.tableContainerObserver) {
401
+ _this3.tableContainerObserver.observe(mutatingNode, {
402
+ subtree: true,
403
+ childList: true
404
+ });
405
+ }
406
+ }
407
+ }
408
+ });
409
+ }
410
+
411
+ // updating bottom sentinel position if sticky header height changes
412
+ // to allocate for new header height
413
+ }, {
414
+ key: "createResizeObserver",
415
+ value: function createResizeObserver() {
416
+ var _this4 = this;
417
+ this.resizeObserver = new ResizeObserver(function (entries) {
418
+ var tree = (0, _dom2.getTree)(_this4.dom);
419
+ if (!tree) {
420
+ return;
421
+ }
422
+ var table = tree.table;
423
+ entries.forEach(function (entry) {
424
+ var _this4$editorScrollab;
425
+ // On resize of the parent scroll element we need to adjust the width
426
+ // of the sticky header
427
+ // Ignored via go/ees005
428
+ // eslint-disable-next-line @atlaskit/editor/no-as-casting
429
+ if (entry.target.className === ((_this4$editorScrollab = _this4.editorScrollableElement) === null || _this4$editorScrollab === void 0 ? void 0 : _this4$editorScrollab.className)) {
430
+ _this4.updateStickyHeaderWidth();
431
+ } else {
432
+ var newHeight = entry.contentRect ? entry.contentRect.height :
433
+ // Ignored via go/ees005
434
+ // eslint-disable-next-line @atlaskit/editor/no-as-casting
435
+ entry.target.offsetHeight;
436
+ if (_this4.sentinels.bottom &&
437
+ // When the table header is sticky, it would be taller by a 1px (border-bottom),
438
+ // So we adding this check to allow a 1px difference.
439
+ Math.abs(newHeight - (_this4.stickyRowHeight || 0)) > _consts.stickyHeaderBorderBottomWidth) {
440
+ _this4.stickyRowHeight = newHeight;
441
+ _this4.sentinels.bottom.style.bottom = "".concat(_consts.tableScrollbarOffset + _consts.stickyRowOffsetTop + newHeight, "px");
442
+ (0, _dom.updateStickyMargins)(table);
443
+ }
444
+ }
445
+ });
446
+ });
447
+ }
448
+ }, {
449
+ key: "createIntersectionObserver",
450
+ value: function createIntersectionObserver() {
451
+ var _this5 = this;
452
+ this.intersectionObserver = new IntersectionObserver(function (entries, _) {
453
+ var _this5$editorScrollab, _this5$editorScrollab2;
454
+ // IMPORTANT: please try and avoid using entry.rootBounds it's terribly inconsistent. For example; sometimes it may return
455
+ // 0 height. In safari it will multiply all values by the window scale factor, however chrome & firfox won't.
456
+ // This is why i just get the scroll view bounding rect here and use it, and fallback to the entry.rootBounds if needed.
457
+ 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);
458
+ entries.forEach(function (entry) {
459
+ var target = entry.target,
460
+ isIntersecting = entry.isIntersecting,
461
+ boundingClientRect = entry.boundingClientRect;
462
+ // This observer only every looks at the top/bottom sentinels, we can assume if it's not one then it's the other.
463
+ var targetKey = target.classList.contains(_types.TableCssClassName.TABLE_STICKY_SENTINEL_TOP) ? 'top' : 'bottom';
464
+ // Cache the latest sentinel information
465
+ _this5.sentinelData[targetKey] = {
466
+ isIntersecting: isIntersecting,
467
+ boundingClientRect: boundingClientRect,
468
+ rootBounds: rootBounds !== null && rootBounds !== void 0 ? rootBounds : entry.rootBounds
469
+ };
470
+ // Keep the other sentinels rootBounds in sync with this latest one
471
+ _this5.sentinelData[targetKey === 'top' ? 'bottom' : targetKey].rootBounds = rootBounds !== null && rootBounds !== void 0 ? rootBounds : entry.rootBounds;
472
+ });
473
+ _this5.refreshStickyState();
474
+ }, {
475
+ threshold: 0,
476
+ root: this.editorScrollableElement
477
+ });
478
+ }
479
+ }, {
480
+ key: "refreshStickyState",
481
+ value: function refreshStickyState() {
482
+ var tree = (0, _dom2.getTree)(this.dom);
483
+ if (!tree) {
484
+ return;
485
+ }
486
+ var table = tree.table;
487
+ var shouldStick = this.shouldSticky();
488
+ if (this.isSticky !== shouldStick) {
489
+ if (shouldStick && !this.isNativeSticky) {
490
+ var _this$sentinelData$to;
491
+ // The rootRect is kept in sync across sentinels so it doesn't matter which one we use.
492
+ var rootRect = (_this$sentinelData$to = this.sentinelData.top.rootBounds) !== null && _this$sentinelData$to !== void 0 ? _this$sentinelData$to : this.sentinelData.bottom.rootBounds;
493
+ this.makeHeaderRowSticky(tree, rootRect === null || rootRect === void 0 ? void 0 : rootRect.top);
494
+ } else {
495
+ this.makeRowHeaderNotSticky(table);
496
+ }
497
+ }
498
+ }
499
+ }, {
500
+ key: "shouldSticky",
501
+ value: function shouldSticky() {
502
+ if (
503
+ // is Safari
504
+ navigator.userAgent.includes('AppleWebKit') && !navigator.userAgent.includes('Chrome')) {
505
+ var pos = this.getPos();
506
+ if (typeof pos === 'number') {
507
+ var $tableRowPos = this.view.state.doc.resolve(pos);
508
+
509
+ // layout -> layout column -> table -> table row
510
+ if ($tableRowPos.depth >= 3) {
511
+ var _findParentNodeCloses;
512
+ var isInsideLayout = (_findParentNodeCloses = (0, _utils.findParentNodeClosestToPos)($tableRowPos, function (node) {
513
+ return node.type.name === 'layoutColumn';
514
+ })) === null || _findParentNodeCloses === void 0 ? void 0 : _findParentNodeCloses.node;
515
+ if (isInsideLayout) {
516
+ return false;
517
+ }
518
+ }
519
+ }
520
+ }
521
+ return this.isHeaderSticky();
522
+ }
523
+ }, {
524
+ key: "isHeaderSticky",
525
+ value: function isHeaderSticky() {
526
+ var _sentinelTop$rootBoun;
527
+ /*
528
+ # Overview
529
+ I'm going to list all the view states associated with the sentinels and when they should trigger sticky headers.
530
+ The format of the states are; {top|bottom}:{in|above|below}
531
+ ie sentinel:view-position -- both "above" and "below" are equal to out of the viewport
532
+ For example; "top:in" means top sentinel is within the viewport. "bottom:above" means the bottom sentinel is
533
+ above and out of the viewport
534
+ This will hopefully simplify things and make it easier to determine when sticky should/shouldn't be triggered.
535
+ # States
536
+ top:in / bottom:in - NOT sticky
537
+ top:in / bottom:above - NOT sticky - NOTE: This is an inversion clause
538
+ top:in / bottom:below - NOT sticky
539
+ top:above / bottom:in - STICKY
540
+ top:above / bottom:above - NOT sticky
541
+ top:above / bottom:below - STICKY
542
+ top:below / bottom:in - NOT sticky - NOTE: This is an inversion clause
543
+ top:below / bottom:above - NOT sticky - NOTE: This is an inversion clause
544
+ top:below / bottom:below - NOT sticky
545
+ # Summary
546
+ The only time the header should be sticky is when the top sentinel is above the view and the bottom sentinel
547
+ is in or below it.
548
+ */
549
+
550
+ var _this$sentinelData = this.sentinelData,
551
+ sentinelTop = _this$sentinelData.top,
552
+ sentinelBottom = _this$sentinelData.bottom;
553
+ // The rootRect is kept in sync across sentinels so it doesn't matter which one we use.
554
+ var rootBounds = (_sentinelTop$rootBoun = sentinelTop.rootBounds) !== null && _sentinelTop$rootBoun !== void 0 ? _sentinelTop$rootBoun : sentinelBottom.rootBounds;
555
+ if (!rootBounds || !sentinelTop.boundingClientRect || !sentinelBottom.boundingClientRect) {
556
+ return false;
557
+ }
558
+
559
+ // Normalize the sentinels to y points
560
+ // Since the sentinels are actually rects 1px high we want make sure we normalise the inner most values closest to the table.
561
+ var sentinelTopY = sentinelTop.boundingClientRect.bottom;
562
+ var sentinelBottomY = sentinelBottom.boundingClientRect.top;
563
+
564
+ // If header row height is more than 50% of viewport height don't do this
565
+ var isRowHeaderTooLarge = this.stickyRowHeight && this.stickyRowHeight > window.innerHeight * 0.5;
566
+ var isTopSentinelAboveScrollArea = !sentinelTop.isIntersecting && sentinelTopY <= rootBounds.top;
567
+ var isBottomSentinelInOrBelowScrollArea = sentinelBottom.isIntersecting || sentinelBottomY > rootBounds.bottom;
568
+
569
+ // This makes sure that the top sentinel is actually above the bottom sentinel, and that they havn't inverted.
570
+ var isTopSentinelAboveBottomSentinel = sentinelTopY < sentinelBottomY;
571
+ return isTopSentinelAboveScrollArea && isBottomSentinelInOrBelowScrollArea && isTopSentinelAboveBottomSentinel && !isRowHeaderTooLarge;
572
+ }
573
+
574
+ /* receive external events */
575
+ }, {
576
+ key: "onTablePluginState",
577
+ value: function onTablePluginState(state) {
578
+ var tableRef = state.tableRef;
579
+ var tree = (0, _dom2.getTree)(this.dom);
580
+ if (!tree) {
581
+ return;
582
+ }
583
+
584
+ // when header rows are toggled off - mark sentinels as unobserved
585
+ if (!state.isHeaderRowEnabled) {
586
+ [this.sentinels.top, this.sentinels.bottom].forEach(function (el) {
587
+ if (el) {
588
+ delete el.dataset.isObserved;
589
+ }
590
+ });
591
+ }
592
+ var isCurrentTableSelected = tableRef === tree.table;
593
+
594
+ // If current table selected and header row is toggled off, turn off sticky header
595
+ if (isCurrentTableSelected && !state.isHeaderRowEnabled && tree) {
596
+ this.makeRowHeaderNotSticky(tree.table);
597
+ }
598
+ this.focused = isCurrentTableSelected;
599
+ var wrapper = tree.wrapper;
600
+ var tableContainer = wrapper.parentElement;
601
+ var tableContentWrapper = tableContainer === null || tableContainer === void 0 ? void 0 : tableContainer.parentElement;
602
+ var parentContainer = tableContentWrapper && tableContentWrapper.parentElement;
603
+ var isTableInsideLayout = parentContainer && parentContainer.getAttribute('data-layout-content');
604
+ if (tableContentWrapper) {
605
+ if (isCurrentTableSelected) {
606
+ this.colControlsOffset = _consts.tableControlsSpacing;
607
+
608
+ // move table a little out of the way
609
+ // to provide spacing for table controls
610
+ if (isTableInsideLayout) {
611
+ tableContentWrapper.style.paddingLeft = '11px';
612
+ }
613
+ } else {
614
+ this.colControlsOffset = 0;
615
+ if (isTableInsideLayout) {
616
+ tableContentWrapper.style.removeProperty('padding-left');
617
+ }
618
+ }
619
+ }
620
+
621
+ // run after table style changes have been committed
622
+ setTimeout(function () {
623
+ (0, _dom.syncStickyRowToTable)(tree.table);
624
+ }, 0);
625
+ }
626
+ }, {
627
+ key: "updateStickyHeaderWidth",
628
+ value: function updateStickyHeaderWidth() {
629
+ // table width might have changed, sync that back to sticky row
630
+ var tree = (0, _dom2.getTree)(this.dom);
631
+ if (!tree) {
632
+ return;
633
+ }
634
+ (0, _dom.syncStickyRowToTable)(tree.table);
635
+ }
636
+
637
+ /**
638
+ * Manually refire the intersection observers.
639
+ * Useful when the header may have detached from the table.
640
+ */
641
+ }, {
642
+ key: "refireIntersectionObservers",
643
+ value: function refireIntersectionObservers() {
644
+ var _this6 = this;
645
+ if (this.isSticky) {
646
+ [this.sentinels.top, this.sentinels.bottom].forEach(function (el) {
647
+ if (el && _this6.intersectionObserver) {
648
+ _this6.intersectionObserver.unobserve(el);
649
+ _this6.intersectionObserver.observe(el);
650
+ }
651
+ });
652
+ }
653
+ }
654
+ }, {
655
+ key: "makeHeaderRowSticky",
656
+ value: function makeHeaderRowSticky(tree, scrollTop) {
657
+ var _tbody$firstChild,
658
+ _this7 = this;
659
+ // If header row height is more than 50% of viewport height don't do this
660
+ if (this.isSticky || this.stickyRowHeight && this.stickyRowHeight > window.innerHeight / 2 || this.isInNestedTable) {
661
+ return;
662
+ }
663
+ var table = tree.table,
664
+ wrapper = tree.wrapper;
665
+
666
+ // TODO: ED-16035 - Make sure sticky header is only applied to first row
667
+ var tbody = this.dom.parentElement;
668
+ var isFirstHeader = tbody === null || tbody === void 0 || (_tbody$firstChild = tbody.firstChild) === null || _tbody$firstChild === void 0 ? void 0 : _tbody$firstChild.isEqualNode(this.dom);
669
+ if (!isFirstHeader) {
670
+ return;
671
+ }
672
+ var currentTableTop = this.getCurrentTableTop(tree);
673
+ if (!scrollTop) {
674
+ scrollTop = (0, _dom2.getTop)(this.editorScrollableElement);
675
+ }
676
+ var domTop = currentTableTop > 0 ? scrollTop : scrollTop + currentTableTop;
677
+ if (!this.isSticky) {
678
+ var _this$editorScrollabl;
679
+ (0, _dom.syncStickyRowToTable)(table);
680
+ this.dom.classList.add('sticky');
681
+ table.classList.add(_types.TableCssClassName.TABLE_STICKY);
682
+ this.isSticky = true;
683
+
684
+ /**
685
+ * The logic below is not desirable, but acts as a fail safe for scenarios where the sticky header
686
+ * detaches from the table. This typically happens during a fast scroll by the user which causes
687
+ * the intersection observer logic to not fire as expected.
688
+ */
689
+ // Ignored via go/ees005
690
+ // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
691
+ (_this$editorScrollabl = this.editorScrollableElement) === null || _this$editorScrollabl === void 0 || _this$editorScrollabl.addEventListener('scrollend', this.refireIntersectionObservers, {
692
+ passive: true,
693
+ once: true
694
+ });
695
+ var fastScrollThresholdMs = 500;
696
+ setTimeout(function () {
697
+ _this7.refireIntersectionObservers();
698
+ }, fastScrollThresholdMs);
699
+ }
700
+ this.dom.style.top = "0px";
701
+ (0, _dom.updateStickyMargins)(table);
702
+ this.dom.scrollLeft = wrapper.scrollLeft;
703
+ this.emitOn(domTop, this.colControlsOffset);
704
+ }
705
+ }, {
706
+ key: "makeRowHeaderNotSticky",
707
+ value: function makeRowHeaderNotSticky(table) {
708
+ var isEditorDestroyed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
709
+ if (!this.isSticky || !table || !this.dom) {
710
+ return;
711
+ }
712
+ this.dom.style.removeProperty('width');
713
+ this.dom.classList.remove('sticky');
714
+ table.classList.remove(_types.TableCssClassName.TABLE_STICKY);
715
+ this.isSticky = false;
716
+ this.dom.style.top = '';
717
+ table.style.removeProperty('margin-top');
718
+ this.emitOff(isEditorDestroyed);
719
+ }
720
+ }, {
721
+ key: "getWrapperoffset",
722
+ value: function getWrapperoffset() {
723
+ var inverse = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
724
+ var focusValue = inverse ? !this.focused : this.focused;
725
+ return focusValue ? 0 : _consts.tableControlsSpacing;
726
+ }
727
+ }, {
728
+ key: "getWrapperRefTop",
729
+ value: function getWrapperRefTop(wrapper) {
730
+ return Math.round((0, _dom2.getTop)(wrapper)) + this.getWrapperoffset();
731
+ }
732
+ }, {
733
+ key: "getScrolledTableTop",
734
+ value: function getScrolledTableTop(wrapper) {
735
+ return this.getWrapperRefTop(wrapper) - this.topPosEditorElement;
736
+ }
737
+ }, {
738
+ key: "getCurrentTableTop",
739
+ value: function getCurrentTableTop(tree) {
740
+ return this.getScrolledTableTop(tree.wrapper) + tree.table.clientHeight;
741
+ }
742
+
743
+ /* emit external events */
744
+ }, {
745
+ key: "emitOn",
746
+ value: function emitOn(top, padding) {
747
+ if (top === this.top && padding === this.padding) {
748
+ return;
749
+ }
750
+ this.top = top;
751
+ this.padding = padding;
752
+ // Ignored via go/ees005
753
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
754
+ var pos = this.getPos();
755
+ if (Number.isFinite(pos)) {
756
+ (0, _commands.updateStickyState)({
757
+ pos: pos,
758
+ top: top,
759
+ sticky: true,
760
+ padding: padding
761
+ })(this.view.state, this.view.dispatch, this.view);
762
+ }
763
+ }
764
+ }, {
765
+ key: "emitOff",
766
+ value: function emitOff(isEditorDestroyed) {
767
+ if (this.top === 0 && this.padding === 0) {
768
+ return;
769
+ }
770
+ this.top = 0;
771
+ this.padding = 0;
772
+ // Ignored via go/ees005
773
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
774
+ var pos = this.getPos();
775
+ if (!isEditorDestroyed && Number.isFinite(pos)) {
776
+ (0, _commands.updateStickyState)({
777
+ pos: pos,
778
+ sticky: false,
779
+ top: this.top,
780
+ padding: this.padding
781
+ })(this.view.state, this.view.dispatch, this.view);
782
+ }
783
+ }
784
+ }]);
785
+ }(_TableNodeViewBase.default);