UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

1,133 lines 52.8 kB
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
import _inherits from "@babel/runtime/helpers/inherits";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
import { ACTION_SUBJECT_ID, ACTION_SUBJECT, EVENT_TYPE, TABLE_ACTION } from '@atlaskit/editor-common/analytics';
import { getParentOfTypeCount } from '@atlaskit/editor-common/nesting';
import { nodeVisibilityManager } from '@atlaskit/editor-common/node-visibility';
import { tableMarginTop } from '@atlaskit/editor-common/styles';
import { findOverflowScrollParent } from '@atlaskit/editor-common/ui';
import { findParentNodeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
import { fg } from '@atlaskit/platform-feature-flags/fg';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { INITIAL_STATIC_VIEWPORT_HEIGHT } from '../pm-plugins/editor-content-area-height';
import { getPluginState } from '../pm-plugins/plugin-factory';
import { pluginKey as tablePluginKey } from '../pm-plugins/plugin-key';
import { updateStickyState } from '../pm-plugins/sticky-headers/commands';
import { clearStickyCornerMaskPositions, syncStickyRowToTable, updateStickyMargins as updateTableMargin } from '../pm-plugins/table-resizing/utils/dom';
import { areAllRectsZero, getTop, getTree } from '../pm-plugins/utils/dom';
import { getTableRowIndex, supportedHeaderRow } from '../pm-plugins/utils/nodes';
import { TableCssClassName as ClassName, TableCssClassName } from '../types';
import { stickyHeaderBorderBottomWidth, stickyRowOffsetTop, tableControlsSpacing, tableScrollbarOffset } from '../ui/consts';
import TableNodeView from './TableNodeViewBase';
// limit scroll event calls
var HEADER_ROW_SCROLL_THROTTLE_TIMEOUT = 200;

// timeout for resetting the scroll class - if it's too long then users won't be able to click on the header cells,
// if too short it would trigger too many dom updates.
var HEADER_ROW_SCROLL_RESET_DEBOUNCE_TIMEOUT = 400;
var TableRowNativeStickyWithFallback = /*#__PURE__*/function (_TableNodeView) {
  function TableRowNativeStickyWithFallback(node, view, getPos, eventDispatcher, api) {
    var _this;
    _classCallCheck(this, TableRowNativeStickyWithFallback);
    _this = _callSuper(this, TableRowNativeStickyWithFallback, [node, view, getPos, eventDispatcher]);
    _defineProperty(_this, "cleanup", function () {
      var _this$onEditorContent, _this2;
      if (_this.isStickyHeaderEnabled) {
        _this.unsubscribe();
        _this.nodeVisibilityObserverCleanupFn && _this.nodeVisibilityObserverCleanupFn();
        var tree = getTree(_this.dom);
        if (tree) {
          _this.makeRowHeaderNotLegacySticky(tree.table, true);
        }
        _this.emitOff(false);
      }
      if (_this.tableContainerObserver) {
        _this.tableContainerObserver.disconnect();
      }
      (_this$onEditorContent = (_this2 = _this).onEditorContentAreaHeightChange) === null || _this$onEditorContent === void 0 || _this$onEditorContent.call(_this2);
    });
    _defineProperty(_this, "colControlsOffset", 0);
    _defineProperty(_this, "focused", false);
    _defineProperty(_this, "topPosEditorElement", 0);
    _defineProperty(_this, "sentinels", {});
    _defineProperty(_this, "sentinelData", {
      top: {
        isIntersecting: false,
        boundingClientRect: null,
        rootBounds: null
      },
      bottom: {
        isIntersecting: false,
        boundingClientRect: null,
        rootBounds: null
      }
    });
    _defineProperty(_this, "listening", false);
    _defineProperty(_this, "padding", 0);
    _defineProperty(_this, "top", 0);
    _defineProperty(_this, "hasScrolledSinceLoad", false);
    _defineProperty(_this, "disableNativeSticky", false);
    _defineProperty(_this, "isSingleRowTable", false);
    /**
     * Methods
     */
    _defineProperty(_this, "headerRowMouseScrollEnd", debounce(function () {
      _this.dom.classList.remove('no-pointer-events');
    }, HEADER_ROW_SCROLL_RESET_DEBOUNCE_TIMEOUT));
    // When the header is sticky, the header row is set to position: fixed
    // This prevents mouse wheel scrolling on the scroll-parent div when user's mouse is hovering the header row.
    // This fix sets pointer-events: none on the header row briefly to avoid this behaviour
    _defineProperty(_this, "headerRowMouseScroll", throttle(function () {
      if (_this.isLegacySticky) {
        _this.dom.classList.add('no-pointer-events');
        _this.headerRowMouseScrollEnd();
      }
    }, HEADER_ROW_SCROLL_THROTTLE_TIMEOUT));
    _defineProperty(_this, "toggleDisableNativeSticky", function (headerHeight, viewportHeight) {
      if (!_this.disableNativeSticky && headerHeight > viewportHeight * 0.5) {
        _this.disableNativeSticky = true;
        if (_this.isNativeSticky === undefined) {
          _this.dom.classList.remove(ClassName.NATIVE_STICKY);
        }
      }
      if (_this.disableNativeSticky && headerHeight <= viewportHeight * 0.5) {
        _this.disableNativeSticky = false;
      }
    });
    var rowIndex = getTableRowIndex(view, getPos);
    _this.isHeaderRow = supportedHeaderRow(node, rowIndex);
    if (_this.isHeaderRow) {
      _this.isSingleRowTable = _this.checkIsSingleRowTable();
    }
    _this.isLegacySticky = false;
    var _getPluginState = getPluginState(view.state),
      pluginConfig = _getPluginState.pluginConfig;
    _this.isStickyHeaderEnabled = !!pluginConfig.stickyHeaders;
    _this.api = api;

    // Sticky headers are the only thing limited mode turns off here, so there is nothing to read
    // or subscribe to when they were never enabled for this editor.
    if (isExperimentEnabled('platform_editor_dynamic_limited_mode')) {
      var _api$limitedMode, _limitedMode$currentS;
      var limitedMode = _this.isStickyHeaderEnabled ? api === null || api === void 0 || (_api$limitedMode = api.limitedMode) === null || _api$limitedMode === void 0 ? void 0 : _api$limitedMode.sharedState : undefined;
      if (limitedMode !== null && limitedMode !== void 0 && (_limitedMode$currentS = limitedMode.currentState()) !== null && _limitedMode$currentS !== void 0 && _limitedMode$currentS.enabled) {
        _this.isStickyHeaderEnabled = false;
      } else {
        _this.limitedModeUnsubscribe = limitedMode === null || limitedMode === void 0 ? void 0 : limitedMode.onChange(function (_ref) {
          var _this$limitedModeUnsu, _this3;
          var nextSharedState = _ref.nextSharedState;
          if (!(nextSharedState !== null && nextSharedState !== void 0 && nextSharedState.enabled)) {
            return;
          }

          // Order matters: cleanup() short-circuits unless sticky headers are still marked enabled.
          _this.cleanup();
          _this.isStickyHeaderEnabled = false;

          // Limited mode never turns back off, so this listener has done its job.
          (_this$limitedModeUnsu = (_this3 = _this).limitedModeUnsubscribe) === null || _this$limitedModeUnsu === void 0 || _this$limitedModeUnsu.call(_this3);
          _this.limitedModeUnsubscribe = undefined;
        });
      }
    } else {
      var _api$limitedMode2;
      if (api !== null && api !== void 0 && (_api$limitedMode2 = api.limitedMode) !== null && _api$limitedMode2 !== void 0 && (_api$limitedMode2 = _api$limitedMode2.sharedState.currentState()) !== null && _api$limitedMode2 !== void 0 && (_api$limitedMode2 = _api$limitedMode2.limitedModePluginKey.getState(view.state)) !== null && _api$limitedMode2 !== void 0 && _api$limitedMode2.documentSizeBreachesThreshold) {
        _this.isStickyHeaderEnabled = false;
        // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
        document.addEventListener('limited-mode-activated', _this.cleanup);
      }
    }
    var pos = _this.getPos();
    _this.isInNestedTable = false;
    if (fg('platform_editor_ai_table_ai_streaming_pos_fix')) {
      try {
        // We cannot trust that the value from getPos will be defined
        // https://discuss.prosemirror.net/t/getpos-is-undefined-in-nodeview-constructor/1246/4
        // There are also scenarios where the value it brings back does not tally with the current doc
        // E.g. when AI streaming brings in new content, this position brings back incorrect values that cannot be resolved
        if (pos) {
          _this.isInNestedTable = getParentOfTypeCount(view.state.schema.nodes.table)(view.state.doc.resolve(pos)) > 1;
        }
      } catch (_unused) {
        // Intentionally swallowed — getPos can return stale positions during AI streaming
      }
    } else {
      if (pos) {
        _this.isInNestedTable = getParentOfTypeCount(view.state.schema.nodes.table)(view.state.doc.resolve(pos)) > 1;
      }
    }
    if (_this.isHeaderRow) {
      _this.dom.setAttribute('data-vc-nvs', 'true');
      var _nodeVisibilityManage = nodeVisibilityManager(view.dom),
        observe = _nodeVisibilityManage.observe;
      _this.nodeVisibilityObserverCleanupFn = observe({
        element: _this.contentDOM,
        onFirstVisible: function onFirstVisible() {
          _this.subscribeWhenRowVisible();
        }
      });
    }
    if (_this.isHeaderRow && _this.isStickyHeaderEnabled) {
      var _api$table;
      _this.onEditorContentAreaHeightChange = api === null || api === void 0 || (_api$table = api.table) === null || _api$table === void 0 ? void 0 : _api$table.sharedState.onChange(function (_ref2) {
        var nextSharedState = _ref2.nextSharedState;
        if (nextSharedState !== null && nextSharedState !== void 0 && nextSharedState.editorContentAreaHeight && (nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.editorContentAreaHeight) !== _this.editorContentAreaHeight) {
          var _this$stickyRowHeight;
          _this.editorContentAreaHeight = nextSharedState.editorContentAreaHeight;
          _this.toggleDisableNativeSticky((_this$stickyRowHeight = _this.stickyRowHeight) !== null && _this$stickyRowHeight !== void 0 ? _this$stickyRowHeight : 0, nextSharedState.editorContentAreaHeight);
        }
      });
    }
    return _this;
  }
  _inherits(TableRowNativeStickyWithFallback, _TableNodeView);
  return _createClass(TableRowNativeStickyWithFallback, [{
    key: "subscribeWhenRowVisible",
    value: function subscribeWhenRowVisible() {
      if (this.listening) {
        return;
      }
      this.dom.setAttribute('data-header-row', 'true');
      if (this.isStickyHeaderEnabled) {
        this.subscribe();
      }
    }

    /**
     * Variables
     */
  }, {
    key: "update",
    value:
    /**
     * Methods: Nodeview Lifecycle
     */
    // Ignored via go/ees005
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    function update(node) {
      if (this.isHeaderRow) {
        this.updateSingleRowTableStickyHeaderState();
      }

      // do nothing if nodes were identical
      if (node === this.node) {
        return true;
      }

      // see if we're changing into a header row or
      // changing away from one
      var rowIndex = getTableRowIndex(this.view, this.getPos);
      var newNodeIsHeaderRow = supportedHeaderRow(node, rowIndex);
      if (this.isHeaderRow !== newNodeIsHeaderRow) {
        if (!newNodeIsHeaderRow && this.isHeaderRow) {
          var _this$dom$closest;
          (_this$dom$closest = this.dom.closest(".".concat(ClassName.TABLE_NODE_WRAPPER))) === null || _this$dom$closest === void 0 || _this$dom$closest.classList.remove(ClassName.TABLE_NODE_WRAPPER_NO_OVERFLOW);
        }
        return false; // re-create nodeview
      }

      // node is different but no need to re-create nodeview
      this.node = node;

      // don't do anything if we're just a regular tr
      if (!this.isHeaderRow) {
        return true;
      }

      // something changed, sync widths
      if (this.isStickyHeaderEnabled) {
        var tbody = this.dom.parentElement;
        var table = tbody && tbody.parentElement;
        syncStickyRowToTable(table);
      }
      return true;
    }
  }, {
    key: "destroy",
    value: function destroy() {
      if (this.isStickyHeaderEnabled) {
        var _this$nodeVisibilityO;
        this.unsubscribe();
        this.overflowObserver && this.overflowObserver.disconnect();
        this.overflowObserverEntries = undefined;
        this.stickyStateObserver && this.stickyStateObserver.disconnect();
        (_this$nodeVisibilityO = this.nodeVisibilityObserver) === null || _this$nodeVisibilityO === void 0 || _this$nodeVisibilityO.disconnect();
        this.nodeVisibilityObserverCleanupFn && this.nodeVisibilityObserverCleanupFn();
        var tree = getTree(this.dom);
        if (tree) {
          this.makeRowHeaderNotLegacySticky(tree.table, true);
        }
        this.emitOff(true);
      }
      if (isExperimentEnabled('platform_editor_dynamic_limited_mode')) {
        var _this$limitedModeUnsu2;
        (_this$limitedModeUnsu2 = this.limitedModeUnsubscribe) === null || _this$limitedModeUnsu2 === void 0 || _this$limitedModeUnsu2.call(this);
        this.limitedModeUnsubscribe = undefined;
      } else {
        // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
        document.removeEventListener('limited-mode-activated', this.cleanup);
      }
      if (this.tableContainerObserver) {
        this.tableContainerObserver.disconnect();
      }
    }
  }, {
    key: "ignoreMutation",
    value: function ignoreMutation(mutationRecord) {
      /* tableRows are not directly editable by the user
       * so it should be safe to ignore mutations that we cause
       * by updating styles and classnames on this DOM element
       *
       * Update: should not ignore mutations for row selection to avoid known issue with table selection highlight in firefox
       * Related bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=1289673
       * */
      var isTableSelection = mutationRecord.type === 'selection' && mutationRecord.target.nodeName === 'TR';
      /**
       * Update: should not ignore mutations when an node is added, as this interferes with
       * prosemirrors handling of some language inputs in Safari (ie. Pinyin, Hiragana).
       *
       * In paticular, when a composition occurs at the start of the first node inside a table cell, if the resulting mutation
       * from the composition end is ignored than prosemirror will end up with; invalid table markup nesting and a misplaced
       * selection and insertion.
       */
      var isNodeInsertion = mutationRecord.type === 'childList' && mutationRecord.target.nodeName === 'TR' && mutationRecord.addedNodes.length;
      if (isTableSelection || isNodeInsertion) {
        return false;
      }
      return true;
    }
  }, {
    key: "subscribe",
    value: function subscribe() {
      var _this4 = this;
      // Ignored via go/ees005
      // eslint-disable-next-line @atlaskit/editor/no-as-casting
      this.editorScrollableElement = findOverflowScrollParent(this.view.dom) || window;
      if (this.editorScrollableElement) {
        this.initObservers();
        this.topPosEditorElement = getTop(this.editorScrollableElement);
        this.scrollListener = function () {
          var _this4$overflowObserv;
          if (_this4.hasScrolledSinceLoad) {
            return;
          }
          _this4.hasScrolledSinceLoad = true;
          if (!_this4.overflowObserver) {
            return;
          }

          // Re-check intersection state now that scrolling has occurred
          var entries = (_this4$overflowObserv = _this4.overflowObserverEntries) !== null && _this4$overflowObserv !== void 0 ? _this4$overflowObserv : _this4.overflowObserver.takeRecords();
          _this4.overflowObserverEntries = undefined;

          /** NOTE: This logic is duplicated in the overflowObserver callback
           *  to avoid conflicting with a follow up refactor where this will
           *  be cleaned up.
           */
          entries.forEach(function (entry) {
            if (_this4.updateSingleRowTableStickyHeaderState()) {
              return;
            }
            var tableWrapper = _this4.dom.closest(".".concat(ClassName.TABLE_NODE_WRAPPER));
            if (tableWrapper && tableWrapper instanceof HTMLElement && !areAllRectsZero(entry)) {
              if (entry.isIntersecting) {
                tableWrapper.classList.add(ClassName.TABLE_NODE_WRAPPER_NO_OVERFLOW);
                _this4.dom.classList.add(ClassName.NATIVE_STICKY);
                _this4.isNativeSticky = true;
              } else {
                tableWrapper.classList.remove(ClassName.TABLE_NODE_WRAPPER_NO_OVERFLOW);
                _this4.dom.classList.remove(ClassName.NATIVE_STICKY);
                _this4.isNativeSticky = false;
              }
              _this4.refreshLegacyStickyState();
            }
          });
        };
        // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
        this.editorScrollableElement.addEventListener('scroll', this.scrollListener, {
          passive: true,
          once: true
        });
      }
      this.eventDispatcher.on('widthPlugin', this.updateStickyHeaderWidth.bind(this));

      // Ignored via go/ees005
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      this.eventDispatcher.on(tablePluginKey.key, this.onTablePluginState.bind(this));
      this.listening = true;

      // Ignored via go/ees005
      // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
      this.dom.addEventListener('wheel', this.headerRowMouseScroll.bind(this), {
        passive: true
      });
      // Ignored via go/ees005
      // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
      this.dom.addEventListener('touchmove', this.headerRowMouseScroll.bind(this), {
        passive: true
      });
    }
  }, {
    key: "unsubscribe",
    value: function unsubscribe() {
      if (!this.listening) {
        return;
      }
      if (this.intersectionObserver) {
        this.intersectionObserver.disconnect();
        // ED-16211 Once intersection observer is disconnected, we need to remove the isObserved from the sentinels
        // Otherwise when newer intersection observer is created it will not observe because it thinks its already being observed
        [this.sentinels.top, this.sentinels.bottom].forEach(function (el) {
          if (el) {
            delete el.dataset.isObserved;
          }
        });
      }
      if (this.resizeObserver) {
        this.resizeObserver.disconnect();
      }
      if (this.scrollListener && this.editorScrollableElement) {
        // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
        this.editorScrollableElement.removeEventListener('scroll', this.scrollListener);
        this.scrollListener = undefined;
      }
      this.eventDispatcher.off('widthPlugin', this.updateStickyHeaderWidth);
      // Ignored via go/ees005
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      this.eventDispatcher.off(tablePluginKey.key, this.onTablePluginState);
      this.listening = false;

      // Ignored via go/ees005
      // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
      this.dom.removeEventListener('wheel', this.headerRowMouseScroll);
      // Ignored via go/ees005
      // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
      this.dom.removeEventListener('touchmove', this.headerRowMouseScroll);
    }
  }, {
    key: "initOverflowObserver",
    value: function initOverflowObserver() {
      var _this5 = this;
      var tableWrapper = this.dom.closest(".".concat(ClassName.TABLE_NODE_WRAPPER));
      if (!tableWrapper) {
        return;
      }
      var options = {
        root: tableWrapper,
        threshold: 1
      };
      this.overflowObserver = new IntersectionObserver(function (entries, observer) {
        entries.forEach(function (entry) {
          var _this5$api;
          if (!(observer.root instanceof HTMLElement)) {
            return;
          }
          if (_this5.updateSingleRowTableStickyHeaderState()) {
            return;
          }
          // Only apply classes if page has scrolled since load
          if (!_this5.hasScrolledSinceLoad) {
            _this5.overflowObserverEntries = entries;
            return;
          }
          if (entry.isIntersecting) {
            observer.root.classList.add(ClassName.TABLE_NODE_WRAPPER_NO_OVERFLOW);
            if (!_this5.disableNativeSticky) {
              _this5.dom.classList.add(ClassName.NATIVE_STICKY);
            }
            _this5.isNativeSticky = true;
          } else {
            observer.root.classList.remove(ClassName.TABLE_NODE_WRAPPER_NO_OVERFLOW);
            _this5.dom.classList.remove(ClassName.NATIVE_STICKY);
            _this5.isNativeSticky = false;
          }
          _this5.refreshLegacyStickyState();
          (_this5$api = _this5.api) === null || _this5$api === void 0 || (_this5$api = _this5$api.analytics) === null || _this5$api === void 0 || (_this5$api = _this5$api.actions) === null || _this5$api === void 0 || _this5$api.fireAnalyticsEvent({
            action: TABLE_ACTION.STICKY_HEADER_METHOD_TOGGLED,
            actionSubject: ACTION_SUBJECT.TABLE,
            actionSubjectId: ACTION_SUBJECT_ID.TABLE_STICKY_HEADER,
            eventType: EVENT_TYPE.UI,
            attributes: {
              nativeStickyHeaderEnabled: entry.isIntersecting
            }
          });
        });
      }, options);
    }

    /**
     * This observer is used to track the 'stuck' state of the header row.
     * This roughly mimics `(at)container scroll-state(stuck: top)` in CSS,
     * but with full browser support.
     */
  }, {
    key: "initStickyStateObserver",
    value: function initStickyStateObserver() {
      var _this6 = this;
      if (!this.editorScrollableElement || !(this.editorScrollableElement instanceof Element)) {
        return;
      }
      var options = {
        root: this.editorScrollableElement,
        rootMargin: "-".concat(tableMarginTop + 1, "px 0px 0px 0px"),
        threshold: 1
      };
      this.stickyStateObserver = new IntersectionObserver(function (entries) {
        entries.forEach(function (entry) {
          var _entry$rootBounds;
          if (_this6.updateSingleRowTableStickyHeaderState()) {
            return;
          }
          var tableContainer = _this6.dom.closest(".".concat(ClassName.TABLE_CONTAINER));
          if (entry.intersectionRect.top === ((_entry$rootBounds = entry.rootBounds) === null || _entry$rootBounds === void 0 ? void 0 : _entry$rootBounds.top) && !_this6.disableNativeSticky) {
            _this6.dom.classList.add(ClassName.NATIVE_STICKY_ACTIVE);
            if (tableContainer && tableContainer instanceof HTMLElement) {
              tableContainer.dataset.tableHeaderIsStuck = 'true';
            }
          } else {
            _this6.dom.classList.remove(ClassName.NATIVE_STICKY_ACTIVE);
            if (tableContainer && tableContainer instanceof HTMLElement) {
              delete tableContainer.dataset.tableHeaderIsStuck;
            }
          }
        });
      }, options);
    }

    // initialize intersection observer to track if table is within scroll area
  }, {
    key: "initObservers",
    value: function initObservers() {
      var _this7 = this;
      if (!this.dom || this.dom.dataset.isObserved) {
        return;
      }
      this.dom.dataset.isObserved = 'true';
      this.createIntersectionObserver();
      this.createResizeObserver();
      if (!this.intersectionObserver || !this.resizeObserver) {
        return;
      }
      if (this.isHeaderRow && !this.isInNestedTable) {
        var _this$stickyStateObse;
        if (expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true)) {
          var _this$dom$getAttribut;
          this.dom.style.setProperty('anchor-name', (_this$dom$getAttribut = this.dom.getAttribute('data-node-anchor')) !== null && _this$dom$getAttribut !== void 0 ? _this$dom$getAttribut : '');
        }
        this.initOverflowObserver();
        this.initNodeVisibilityObserver();
        var closestTable = this.dom.closest('table');
        if (closestTable) {
          var _this$overflowObserve, _this$nodeVisibilityO2;
          (_this$overflowObserve = this.overflowObserver) === null || _this$overflowObserve === void 0 || _this$overflowObserve.observe(closestTable);
          (_this$nodeVisibilityO2 = this.nodeVisibilityObserver) === null || _this$nodeVisibilityO2 === void 0 || _this$nodeVisibilityO2.observe(closestTable);
        }
        this.initStickyStateObserver();
        (_this$stickyStateObse = this.stickyStateObserver) === null || _this$stickyStateObse === void 0 || _this$stickyStateObse.observe(this.dom);
      }
      this.resizeObserver.observe(this.dom);
      if (this.editorScrollableElement) {
        // Ignored via go/ees005
        // eslint-disable-next-line @atlaskit/editor/no-as-casting
        this.resizeObserver.observe(this.editorScrollableElement);
      }
      window.requestAnimationFrame(function () {
        var getTableContainer = function getTableContainer() {
          var _getTree;
          return (_getTree = getTree(_this7.dom)) === null || _getTree === void 0 ? void 0 : _getTree.wrapper.closest(".".concat(TableCssClassName.NODEVIEW_WRAPPER));
        };

        // we expect tree to be defined after animation frame
        var tableContainer = getTableContainer();
        if (tableContainer) {
          var getSentinelTop = function getSentinelTop() {
            return tableContainer &&
            // Ignored via go/ees005
            // eslint-disable-next-line @atlaskit/editor/no-as-casting
            tableContainer.getElementsByClassName(ClassName.TABLE_STICKY_SENTINEL_TOP).item(0);
          };
          var getSentinelBottom = function getSentinelBottom() {
            // Multiple bottom sentinels may be found if there are nested tables.
            // We need to make sure we get the last one which will belong to the parent table.

            var bottomSentinels = tableContainer && tableContainer.getElementsByClassName(ClassName.TABLE_STICKY_SENTINEL_BOTTOM);
            return (
              // eslint-disable-next-line @atlaskit/editor/no-as-casting
              bottomSentinels && bottomSentinels.item(bottomSentinels.length - 1)
            );
          };
          var sentinelsInDom = function sentinelsInDom() {
            return getSentinelTop() !== null && getSentinelBottom() !== null;
          };
          var observeStickySentinels = function observeStickySentinels() {
            _this7.sentinels.top = getSentinelTop();
            _this7.sentinels.bottom = getSentinelBottom();
            [_this7.sentinels.top, _this7.sentinels.bottom].forEach(function (el) {
              // skip if already observed for another row on this table
              if (el && !el.dataset.isObserved) {
                el.dataset.isObserved = 'true';

                // Ignored via go/ees005
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                _this7.intersectionObserver.observe(el);
              }
            });
          };
          var isInitialProsemirrorToDomRender = tableContainer.hasAttribute('data-prosemirror-initial-toDOM-render');

          // Sentinels may be in the DOM but they're part of the prosemirror placeholder structure which is replaced with the fully rendered React node.
          if (sentinelsInDom() && !isInitialProsemirrorToDomRender) {
            // great - DOM ready, observe as normal
            observeStickySentinels();
          } else {
            // concurrent loading issue - here TableRow is too eager trying to
            // observe sentinels before they are in the DOM, use MutationObserver
            // to wait for sentinels to be added to the parent Table node DOM
            // then attach the IntersectionObserver
            _this7.tableContainerObserver = new MutationObserver(function () {
              // Check if the tableContainer is still connected to the DOM. It can become disconnected when the placholder
              // prosemirror node is replaced with the fully rendered React node (see _handleTableRef).

              if (!tableContainer || !tableContainer.isConnected) {
                tableContainer = getTableContainer();
              }
              if (sentinelsInDom()) {
                var _this7$tableContainer;
                observeStickySentinels();
                (_this7$tableContainer = _this7.tableContainerObserver) === null || _this7$tableContainer === void 0 || _this7$tableContainer.disconnect();
              }
            });
            var mutatingNode = tableContainer;
            if (mutatingNode && _this7.tableContainerObserver) {
              _this7.tableContainerObserver.observe(mutatingNode, {
                subtree: true,
                childList: true
              });
            }
          }
        }
      });
    }

    // initialise intersection observer to track whether table is in scroll area
  }, {
    key: "initNodeVisibilityObserver",
    value: function initNodeVisibilityObserver() {
      var _this8 = this;
      this.nodeVisibilityObserver = new IntersectionObserver(function (entries) {
        entries.forEach(function (entry) {
          if (_this8.updateSingleRowTableStickyHeaderState()) {
            return;
          }
          if (!_this8.isNativeSticky) {
            return;
          }
          if (entry.intersectionRatio !== 0 && entry.intersectionRatio !== 1) {
            return;
          }
          if (_this8.disableNativeSticky === true) {
            _this8.dom.classList.remove(ClassName.NATIVE_STICKY);
          }
          if (_this8.disableNativeSticky === false) {
            _this8.dom.classList.add(ClassName.NATIVE_STICKY);
          }
        });
      }, {
        threshold: [0, 0.05, 0.95, 1]
      });
    }

    // updating bottom sentinel position if sticky header height changes
    // to allocate for new header height
  }, {
    key: "createResizeObserver",
    value: function createResizeObserver() {
      var _this9 = this;
      this.resizeObserver = new ResizeObserver(function (entries) {
        var tree = getTree(_this9.dom);
        if (!tree) {
          return;
        }
        var table = tree.table;
        entries.forEach(function (entry) {
          var _this9$editorScrollab;
          // On resize of the parent scroll element we need to adjust the width
          // of the sticky header
          // Ignored via go/ees005
          // eslint-disable-next-line @atlaskit/editor/no-as-casting
          if (entry.target.className === ((_this9$editorScrollab = _this9.editorScrollableElement) === null || _this9$editorScrollab === void 0 ? void 0 : _this9$editorScrollab.className)) {
            _this9.updateStickyHeaderWidth();
          } else {
            var _this9$editorContentA;
            var newHeight = entry.contentRect ? entry.contentRect.height :
            // Ignored via go/ees005
            // eslint-disable-next-line @atlaskit/editor/no-as-casting
            entry.target.offsetHeight;
            if (_this9.sentinels.bottom &&
            // When the table header is sticky, it would be taller by a 1px (border-bottom),
            // So we adding this check to allow a 1px difference.
            Math.abs(newHeight - (_this9.stickyRowHeight || 0)) > stickyHeaderBorderBottomWidth) {
              _this9.stickyRowHeight = newHeight;
              _this9.sentinels.bottom.style.bottom = "".concat(tableScrollbarOffset + stickyRowOffsetTop + newHeight, "px");
              updateTableMargin(table);
            }
            var viewportHeight = (_this9$editorContentA = _this9.editorContentAreaHeight) !== null && _this9$editorContentA !== void 0 ? _this9$editorContentA : INITIAL_STATIC_VIEWPORT_HEIGHT;
            _this9.toggleDisableNativeSticky(newHeight, viewportHeight);
          }
        });
      });
    }
  }, {
    key: "createIntersectionObserver",
    value: function createIntersectionObserver() {
      var _this0 = this;
      this.intersectionObserver = new IntersectionObserver(function (entries, _) {
        var _this0$editorScrollab, _this0$editorScrollab2;
        // IMPORTANT: please try and avoid using entry.rootBounds it's terribly inconsistent. For example; sometimes it may return
        // 0 height. In safari it will multiply all values by the window scale factor, however chrome & firfox won't.
        // This is why i just get the scroll view bounding rect here and use it, and fallback to the entry.rootBounds if needed.
        var rootBounds = (_this0$editorScrollab = _this0.editorScrollableElement) === null || _this0$editorScrollab === void 0 || (_this0$editorScrollab2 = _this0$editorScrollab.getBoundingClientRect) === null || _this0$editorScrollab2 === void 0 ? void 0 : _this0$editorScrollab2.call(_this0$editorScrollab);
        entries.forEach(function (entry) {
          var target = entry.target,
            isIntersecting = entry.isIntersecting,
            boundingClientRect = entry.boundingClientRect;
          // This observer only every looks at the top/bottom sentinels, we can assume if it's not one then it's the other.
          var targetKey = target.classList.contains(ClassName.TABLE_STICKY_SENTINEL_TOP) ? 'top' : 'bottom';
          // Cache the latest sentinel information
          _this0.sentinelData[targetKey] = {
            isIntersecting: isIntersecting,
            boundingClientRect: boundingClientRect,
            rootBounds: rootBounds !== null && rootBounds !== void 0 ? rootBounds : entry.rootBounds
          };
          // Keep the other sentinels rootBounds in sync with this latest one
          _this0.sentinelData[targetKey === 'top' ? 'bottom' : targetKey].rootBounds = rootBounds !== null && rootBounds !== void 0 ? rootBounds : entry.rootBounds;
        });
        _this0.refreshLegacyStickyState();
      }, {
        threshold: 0,
        root: this.editorScrollableElement
      });
    }
  }, {
    key: "refreshLegacyStickyState",
    value: function refreshLegacyStickyState() {
      if (this.updateSingleRowTableStickyHeaderState()) {
        return;
      }
      var tree = getTree(this.dom);
      if (!tree) {
        return;
      }
      var table = tree.table;
      if (this.isNativeSticky) {
        this.makeRowHeaderNotLegacySticky(table);
        return;
      }
      var shouldStick = this.shouldSticky();
      if (this.isLegacySticky !== shouldStick) {
        if (shouldStick) {
          var _this$sentinelData$to;
          // The rootRect is kept in sync across sentinels so it doesn't matter which one we use.
          var rootRect = (_this$sentinelData$to = this.sentinelData.top.rootBounds) !== null && _this$sentinelData$to !== void 0 ? _this$sentinelData$to : this.sentinelData.bottom.rootBounds;
          this.makeHeaderRowLegacySticky(tree, rootRect === null || rootRect === void 0 ? void 0 : rootRect.top);
        } else {
          this.makeRowHeaderNotLegacySticky(table);
        }
      }
    }
  }, {
    key: "shouldSticky",
    value: function shouldSticky() {
      if (
      // is Safari
      navigator.userAgent.includes('AppleWebKit') && !navigator.userAgent.includes('Chrome')) {
        var pos = this.getPos();
        if (typeof pos === 'number') {
          if (fg('platform_editor_ai_table_ai_streaming_pos_fix')) {
            try {
              // getPos can return stale positions during AI streaming which cannot be resolved
              var $tableRowPos = this.view.state.doc.resolve(pos);

              // layout -> layout column -> table -> table row
              if ($tableRowPos.depth >= 3) {
                var _findParentNodeCloses;
                var isInsideLayout = (_findParentNodeCloses = findParentNodeClosestToPos($tableRowPos, function (node) {
                  return node.type.name === 'layoutColumn';
                })) === null || _findParentNodeCloses === void 0 ? void 0 : _findParentNodeCloses.node;
                if (isInsideLayout) {
                  return false;
                }
              }
            } catch (_unused2) {
              // getPos can return stale positions during AI streaming — fall back to non-sticky
              return false;
            }
          } else {
            var _$tableRowPos = this.view.state.doc.resolve(pos);

            // layout -> layout column -> table -> table row
            if (_$tableRowPos.depth >= 3) {
              var _findParentNodeCloses2;
              var _isInsideLayout = (_findParentNodeCloses2 = findParentNodeClosestToPos(_$tableRowPos, function (node) {
                return node.type.name === 'layoutColumn';
              })) === null || _findParentNodeCloses2 === void 0 ? void 0 : _findParentNodeCloses2.node;
              if (_isInsideLayout) {
                return false;
              }
            }
          }
        }
      }
      return this.isHeaderSticky();
    }
  }, {
    key: "isHeaderSticky",
    value: function isHeaderSticky() {
      var _sentinelTop$rootBoun;
      /*
      # Overview
       I'm going to list all the view states associated with the sentinels and when they should trigger sticky headers.
       The format of the states are;    {top|bottom}:{in|above|below}
       ie sentinel:view-position -- both "above" and "below" are equal to out of the viewport
        For example; "top:in" means top sentinel is within the viewport. "bottom:above" means the bottom sentinel is
       above and out of the viewport
        This will hopefully simplify things and make it easier to determine when sticky should/shouldn't be triggered.
      # States
       top:in / bottom:in - NOT sticky
       top:in / bottom:above - NOT sticky - NOTE: This is an inversion clause
       top:in / bottom:below - NOT sticky
       top:above / bottom:in - STICKY
       top:above / bottom:above - NOT sticky
       top:above / bottom:below - STICKY
       top:below / bottom:in - NOT sticky - NOTE: This is an inversion clause
       top:below / bottom:above - NOT sticky - NOTE: This is an inversion clause
       top:below / bottom:below - NOT sticky
      # Summary
       The only time the header should be sticky is when the top sentinel is above the view and the bottom sentinel
       is in or below it.
      */

      var _this$sentinelData = this.sentinelData,
        sentinelTop = _this$sentinelData.top,
        sentinelBottom = _this$sentinelData.bottom;
      // The rootRect is kept in sync across sentinels so it doesn't matter which one we use.
      var rootBounds = (_sentinelTop$rootBoun = sentinelTop.rootBounds) !== null && _sentinelTop$rootBoun !== void 0 ? _sentinelTop$rootBoun : sentinelBottom.rootBounds;
      if (!rootBounds || !sentinelTop.boundingClientRect || !sentinelBottom.boundingClientRect) {
        return false;
      }

      // Normalize the sentinels to y points
      // Since the sentinels are actually rects 1px high we want make sure we normalise the inner most values closest to the table.
      var sentinelTopY = sentinelTop.boundingClientRect.bottom;
      var sentinelBottomY = sentinelBottom.boundingClientRect.top;

      // If header row height is more than 50% of viewport height don't do this
      var isRowHeaderTooLarge = this.stickyRowHeight && this.stickyRowHeight > window.innerHeight * 0.5;
      var isTopSentinelAboveScrollArea = !sentinelTop.isIntersecting && sentinelTopY <= rootBounds.top;
      var isBottomSentinelInOrBelowScrollArea = sentinelBottom.isIntersecting || sentinelBottomY > rootBounds.bottom;

      // This makes sure that the top sentinel is actually above the bottom sentinel, and that they havn't inverted.
      var isTopSentinelAboveBottomSentinel = sentinelTopY < sentinelBottomY;
      return isTopSentinelAboveScrollArea && isBottomSentinelInOrBelowScrollArea && isTopSentinelAboveBottomSentinel && !isRowHeaderTooLarge;
    }

    /* receive external events */
  }, {
    key: "onTablePluginState",
    value: function onTablePluginState(state) {
      var tableRef = state.tableRef;
      var tree = getTree(this.dom);
      if (!tree) {
        return;
      }

      // when header rows are toggled off - mark sentinels as unobserved
      if (!state.isHeaderRowEnabled) {
        [this.sentinels.top, this.sentinels.bottom].forEach(function (el) {
          if (el) {
            delete el.dataset.isObserved;
          }
        });
      }
      var isCurrentTableSelected = tableRef === tree.table;

      // If current table selected and header row is toggled off, turn off sticky header
      if (isCurrentTableSelected && !state.isHeaderRowEnabled && tree) {
        this.makeRowHeaderNotLegacySticky(tree.table);
      }
      this.focused = isCurrentTableSelected;
      var wrapper = tree.wrapper;
      var tableContainer = wrapper.parentElement;
      var tableContentWrapper = tableContainer === null || tableContainer === void 0 ? void 0 : tableContainer.parentElement;
      var parentContainer = tableContentWrapper && tableContentWrapper.parentElement;
      var isTableInsideLayout = parentContainer && parentContainer.getAttribute('data-layout-content');
      if (tableContentWrapper) {
        if (isCurrentTableSelected) {
          this.colControlsOffset = tableControlsSpacing;

          /**
           * Adding padding left causes flicker when table is inside layout column
           * 	and selection moves in/out of table.
           */
          if (!fg('platform_editor_table_flicker_issue')) {
            // move table a little out of the way
            // to provide spacing for table controls
            if (isTableInsideLayout) {
              tableContentWrapper.style.paddingLeft = '11px';
            }
          }
        } else {
          this.colControlsOffset = 0;
          /**
           * Adding padding left causes flicker when table is inside layout column
           * 	and selection moves in/out of table.
           */
          if (!fg('platform_editor_table_flicker_issue')) {
            if (isTableInsideLayout) {
              tableContentWrapper.style.removeProperty('padding-left');
            }
          }
        }
      }

      // run after table style changes have been committed
      setTimeout(function () {
        syncStickyRowToTable(tree.table);
      }, 0);
    }
  }, {
    key: "updateStickyHeaderWidth",
    value: function updateStickyHeaderWidth() {
      // table width might have changed, sync that back to sticky row
      var tree = getTree(this.dom);
      if (!tree) {
        return;
      }
      syncStickyRowToTable(tree.table);
    }

    /**
     * Manually refire the intersection observers.
     * Useful when the header may have detached from the table.
     */
  }, {
    key: "refireIntersectionObservers",
    value: function refireIntersectionObservers() {
      var _this1 = this;
      if (this.isLegacySticky) {
        [this.sentinels.top, this.sentinels.bottom].forEach(function (el) {
          if (el && _this1.intersectionObserver) {
            _this1.intersectionObserver.unobserve(el);
            _this1.intersectionObserver.observe(el);
          }
        });
      }
    }
  }, {
    key: "checkIsSingleRowTable",
    value: function checkIsSingleRowTable() {
      var _this10 = this;
      var pos = this.getPos();
      if (typeof pos !== 'number') {
        return false;
      }
      try {
        var _findParentNodeCloses3;
        var $tableRowPos = this.view.state.doc.resolve(pos);
        var tableNode = (_findParentNodeCloses3 = findParentNodeClosestToPos($tableRowPos, function (node) {
          return node.type === _this10.view.state.schema.nodes.table;
        })) === null || _findParentNodeCloses3 === void 0 ? void 0 : _findParentNodeCloses3.node;
        return (tableNode === null || tableNode === void 0 ? void 0 : tableNode.childCount) === 1;
      } catch (_unused3) {
        // getPos can return stale positions during AI streaming. Preserve the original sticky
        // header behaviour when the containing table cannot be determined.
        return false;
      }
    }
  }, {
    key: "updateSingleRowTableStickyHeaderState",
    value: function updateSingleRowTableStickyHeaderState() {
      var wasSingleRowTable = this.isSingleRowTable;
      this.isSingleRowTable = this.checkIsSingleRowTable();
      if (this.isSingleRowTable) {
        this.removeSingleRowTableStickyHeaderState();
      } else if (wasSingleRowTable) {
        this.refireNativeStickyObservers();
      }
      return this.isSingleRowTable;
    }
  }, {
    key: "removeSingleRowTableStickyHeaderState",
    value: function removeSingleRowTableStickyHeaderState() {
      this.dom.classList.remove(ClassName.NATIVE_STICKY, ClassName.NATIVE_STICKY_ACTIVE);
      this.isNativeSticky = false;
      var tableWrapper = this.dom.closest(".".concat(ClassName.TABLE_NODE_WRAPPER));
      tableWrapper === null || tableWrapper === void 0 || tableWrapper.classList.remove(ClassName.TABLE_NODE_WRAPPER_NO_OVERFLOW);
      var tableContainer = this.dom.closest(".".concat(ClassName.TABLE_CONTAINER));
      if (tableContainer instanceof HTMLElement) {
        delete tableContainer.dataset.tableHeaderIsStuck;
      }
      var tree = getTree(this.dom);
      if (tree) {
        this.makeRowHeaderNotLegacySticky(tree.table);
      }
    }
  }, {
    key: "refireNativeStickyObservers",
    value: function refireNativeStickyObservers() {
      var _this$stickyStateObse2, _this$stickyStateObse3;
      var table = this.dom.closest('table');
      if (table) {
        var _this$overflowObserve2, _this$overflowObserve3, _this$nodeVisibilityO3, _this$nodeVisibilityO4;
        (_this$overflowObserve2 = this.overflowObserver) === null || _this$overflowObserve2 === void 0 || _this$overflowObserve2.unobserve(table);
        (_this$overflowObserve3 = this.overflowObserver) === null || _this$overflowObserve3 === void 0 || _this$overflowObserve3.observe(table);
        (_this$nodeVisibilityO3 = this.nodeVisibilityObserver) === null || _this$nodeVisibilityO3 === void 0 || _this$nodeVisibilityO3.unobserve(table);
        (_this$nodeVisibilityO4 = this.nodeVisibilityObserver) === null || _this$nodeVisibilityO4 === void 0 || _this$nodeVisibilityO4.observe(table);
      }
      (_this$stickyStateObse2 = this.stickyStateObserver) === null || _this$stickyStateObse2 === void 0 || _this$stickyStateObse2.unobserve(this.dom);
      (_this$stickyStateObse3 = this.stickyStateObserver) === null || _this$stickyStateObse3 === void 0 || _this$stickyStateObse3.observe(this.dom);
    }
  }, {
    key: "makeHeaderRowLegacySticky",
    value: function makeHeaderRowLegacySticky(tree, scrollTop) {
      var _tbody$firstChild,
        _this11 = this;
      // If header row height is more than 50% of viewport height don't do this
      if (this.isLegacySticky || this.stickyRowHeight && this.stickyRowHeight > window.innerHeight / 2 || this.isInNestedTable) {
        return;
      }
      var table = tree.table,
        wrapper = tree.wrapper;

      // TODO: ED-16035 - Make sure sticky header is only applied to first row
      var tbody = this.dom.parentElement;
      var isFirstHeader = tbody === null || tbody === void 0 || (_tbody$firstChild = tbody.firstChild) === null || _tbody$firstChild === void 0 ? void 0 : _tbody$firstChild.isEqualNode(this.dom);
      if (!isFirstHeader) {
        return;
      }
      var currentTableTop = this.getCurrentTableTop(tree);
      if (!scrollTop) {
        scrollTop = getTop(this.editorScrollableElement);
      }
      var domTop = currentTableTop > 0 ? scrollTop : scrollTop + currentTableTop;
      if (!this.isLegacySticky) {
        var _this$editorScrollabl;
        syncStickyRowToTable(table);
        this.dom.classList.add('sticky');
        table.classList.add(ClassName.TABLE_STICKY);
        this.isLegacySticky = true;

        /**
         * The logic below is not desirable, but acts as a fail safe for scenarios where the sticky header
         * detaches from the table. This typically happens during a fast scroll by the user which causes
         * the intersection observer logic to not fire as expected.
         */
        // Ignored via go/ees005
        // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
        (_this$editorScrollabl = this.editorScrollableElement) === null || _this$editorScrollabl === void 0 || _this$editorScrollabl.addEventListener('scrollend', this.refireIntersectionObservers, {
          passive: true,
          once: true
        });
        var fastScrollThresholdMs = 500;
        setTimeout(function () {
          _this11.refireIntersectionObservers();
        }, fastScrollThresholdMs);
      }
      this.dom.style.top = "0px";
      updateTableMargin(table);
      this.dom.scrollLeft = wrapper.scrollLeft;
      this.emitOn(domTop, this.colControlsOffset);
    }
  }, {
    key: "makeRowHeaderNotLegacySticky",
    value: function makeRowHeaderNotLegacySticky(table) {
      var isEditorDestroyed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
      if (!this.isLegacySticky || !table || !this.dom) {
        return;
      }
      this.dom.style.removeProperty('width');
      if (expValEquals('platform_editor_table_q4_loveability', 'isEnabled', true)) {
        clearStickyCornerMaskPositions(table);
      }
      this.dom.classList.remove('sticky');
      table.classList.remove(ClassName.TABLE_STICKY);
      this.isLegacySticky = false;
      this.dom.style.top = '';
      table.style.removeProperty('margin-top');
      this.emitOff(isEditorDestroyed);
    }
  }, {
    key: "getWrapperoffset",
    value: function getWrapperoffset() {
      var inverse = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
      var focusValue = inverse ? !this.focused : this.focused;
      return focusValue ? 0 : tableControlsSpacing;
    }
  }, {
    key: "getWrapperRefTop",
    value: function getWrapperRefTop(wrapper) {
      return Math.round(getTop(wrapper)) + this.getWrapperoffset();
    }
  }, {
    key: "getScrolledTableTop",
    value: function getScrolledTableTop(wrapper) {
      return this.getWrapperRefTop(wrapper) - this.topPosEditorElement;
    }
  }, {
    key: "getCurrentTableTop",
    value: function getCurrentTableTop(tree) {
      return this.getScrolledTableTop(tree.wrapper) + tree.table.clientHeight;
    }

    /* emit external events */
  }, {
    key: "emitOn",
    value: function emitOn(top, padding) {
      if (top === this.top && padding === this.padding) {
        return;
      }
      this.top = top;
      this.padding = padding;
      // Ignored via go/ees005
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      var pos = this.getPos();
      if (Number.isFinite(pos)) {
        updateStickyState({
          pos: pos,
          top: top,
          sticky: true,
          padding: padding
        })(this.view.state, this.view.dispatch, this.view);
      }
    }
  }, {
    key: "emitOff",
    value: function emitOff(isEditorDestroyed) {
      if (this.top === 0 && this.padding === 0) {
        return;
      }
      this.top = 0;
      this.padding = 0;
      // Ignored via go/ees005
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      var pos = this.getPos();
      if (!isEditorDestroyed && Number.isFinite(pos)) {
        updateStickyState({
          pos: pos,
          sticky: false,
          top: this.top,
          padding: this.padding
        })(this.view.state, this.view.dispatch, this.view);
      }
    }
  }]);
}(TableNodeView);
export { TableRowNativeStickyWithFallback as default };