UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

146 lines (143 loc) 7.23 kB
import React, { useEffect, useMemo, useRef } from 'react'; import rafSchedule from 'raf-schd'; import { createPortal } from 'react-dom'; import { akEditorTableCellOnStickyHeaderZIndex } from '@atlaskit/editor-shared-styles'; import { TableCssClassName as ClassName } from '../../types'; import { insertColumnButtonOffset } from '../common-styles'; var BUTTON_WIDTH = 20; export var calcLeftPos = function calcLeftPos(_ref) { var buttonWidth = _ref.buttonWidth, cellRectLeft = _ref.cellRectLeft, cellRefWidth = _ref.cellRefWidth, offset = _ref.offset; return cellRectLeft + cellRefWidth - buttonWidth - offset; }; export var calcObserverTargetMargin = function calcObserverTargetMargin(tableWrapper, fixedButtonRefCurrent) { var tableWrapperRect = tableWrapper.getBoundingClientRect(); var fixedButtonRect = fixedButtonRefCurrent.getBoundingClientRect(); var scrollLeft = tableWrapper.scrollLeft; return fixedButtonRect.left - tableWrapperRect.left + scrollLeft; }; export var FixedButton = function FixedButton(_ref2) { var children = _ref2.children, isContextualMenuOpen = _ref2.isContextualMenuOpen, mountTo = _ref2.mountTo, offset = _ref2.offset, stickyHeader = _ref2.stickyHeader, tableWrapper = _ref2.tableWrapper, targetCellPosition = _ref2.targetCellPosition, targetCellRef = _ref2.targetCellRef; var fixedButtonRef = useRef(null); var observerTargetRef = useRef(null); // Using refs here rather than state to prevent heaps of renders on scroll var scrollDataRef = useRef(0); var leftPosDataRef = useRef(0); useEffect(function () { var observerTargetRefCurrent = observerTargetRef.current; var fixedButtonRefCurrent = fixedButtonRef.current; if (fixedButtonRefCurrent && observerTargetRefCurrent) { scrollDataRef.current = tableWrapper.scrollLeft; leftPosDataRef.current = 0; // Hide the button initially in case there's a flash of the button being // outside the table before the Intersection Observer fires fixedButtonRefCurrent.style.visibility = 'hidden'; var margin = calcObserverTargetMargin(tableWrapper, fixedButtonRefCurrent); // Much more simple and predictable to add this margin to the observer target // rather than using it to calculate the rootMargin values observerTargetRefCurrent.style.marginLeft = "".concat(margin, "px"); var observer = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { fixedButtonRefCurrent.style.visibility = 'visible'; } else { fixedButtonRefCurrent.style.visibility = 'hidden'; } }); }, { root: tableWrapper, rootMargin: "0px ".concat(insertColumnButtonOffset, "px 0px 0px"), threshold: 1 }); var handleScroll = rafSchedule(function (event) { if (fixedButtonRef.current) { var delta = event.target.scrollLeft - scrollDataRef.current; var style = "translateX(".concat(leftPosDataRef.current - delta, "px)"); fixedButtonRef.current.style.transform = style; scrollDataRef.current = event.target.scrollLeft; leftPosDataRef.current = leftPosDataRef.current - delta; } }); observer.observe(observerTargetRefCurrent); // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners tableWrapper.addEventListener('scroll', handleScroll); return function () { // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners tableWrapper.removeEventListener('scroll', handleScroll); fixedButtonRefCurrent.style.transform = ''; observer.unobserve(observerTargetRefCurrent); }; } }, [fixedButtonRef, observerTargetRef, tableWrapper, targetCellPosition, targetCellRef, isContextualMenuOpen]); var fixedButtonTop = 0; var containerLeft = useMemo(function () { var container = targetCellRef.closest('[data-editor-container="true"]'); return (container === null || container === void 0 ? void 0 : container.getBoundingClientRect().left) || 0; }, [targetCellRef]); var left = useMemo(function () { var targetCellRect = targetCellRef.getBoundingClientRect(); var baseLeft = calcLeftPos({ buttonWidth: BUTTON_WIDTH, cellRectLeft: targetCellRect.left, cellRefWidth: targetCellRef.clientWidth, offset: offset }); return baseLeft - containerLeft; }, [containerLeft, targetCellRef, offset]); // Using a portal here to ensure wrapperRef has the tableWrapper as an // ancestor. This is required to make the Intersection Observer work. return /*#__PURE__*/createPortal( /*#__PURE__*/ // Using observerTargetRef here for our Intersection Observer. There is issues // getting the observer to work just using the fixedButtonRef, possible due // to using position fixed on this Element, or possibly due to its position // being changed on scroll. React.createElement("div", { ref: observerTargetRef, style: { // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 position: 'absolute', // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 top: "var(--ds-space-0, 0px)", // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 left: "var(--ds-space-0, 0px)", // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 width: "var(--ds-space-250, 20px)", // BUTTON_WIDTH // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 height: "var(--ds-space-250, 20px)" // BUTTON_WIDTH } }, /*#__PURE__*/React.createElement("div", { ref: fixedButtonRef, style: { // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 position: 'fixed', // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage/preview top: fixedButtonTop + stickyHeader.padding + offset * 2, // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766 zIndex: akEditorTableCellOnStickyHeaderZIndex, // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage/preview left: left, // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 width: "var(--ds-space-250, 20px)", // BUTTON_WIDTH // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 height: "var(--ds-space-250, 20px)" // BUTTON_WIDTH } // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 , className: ClassName.CONTEXTUAL_MENU_BUTTON_FIXED }, children)), mountTo); }; export default FixedButton;