UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

415 lines (407 loc) 24.4 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.TableContainer = exports.ResizableTableContainer = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _react = _interopRequireWildcard(require("react")); var _classnames = _interopRequireDefault(require("classnames")); var _analytics = require("@atlaskit/editor-common/analytics"); var _hooks = require("@atlaskit/editor-common/hooks"); var _nodeWidth = require("@atlaskit/editor-common/node-width"); var _editorSharedStyles = require("@atlaskit/editor-shared-styles"); var _platformFeatureFlags = require("@atlaskit/platform-feature-flags"); var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals"); var _experiments = require("@atlaskit/tmp-editor-statsig/experiments"); var _commandsWithAnalytics = require("../pm-plugins/commands/commands-with-analytics"); var _pluginFactory = require("../pm-plugins/plugin-factory"); var _consts = require("../pm-plugins/table-resizing/utils/consts"); var _misc = require("../pm-plugins/table-resizing/utils/misc"); var _alignment = require("../pm-plugins/utils/alignment"); var _types = require("../types"); var _tableContainerStyles = require("./table-container-styles"); var _TableResizer = require("./TableResizer"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } var InnerContainer = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref) { var className = _ref.className, style = _ref.style, node = _ref.node, children = _ref.children; return /*#__PURE__*/_react.default.createElement("div", { ref: ref // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 , style: style // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 , className: className, "data-number-column": node.attrs.isNumberColumnEnabled, "data-layout": node.attrs.layout, "data-testid": "table-container" }, children); }); var AlignmentTableContainer = function AlignmentTableContainer(_ref2) { var node = _ref2.node, children = _ref2.children, pluginInjectionApi = _ref2.pluginInjectionApi, getPos = _ref2.getPos, editorView = _ref2.editorView; var alignment = node.attrs.layout !== _alignment.ALIGN_START ? _alignment.ALIGN_CENTER : _alignment.ALIGN_START; var _useSharedPluginState = (0, _hooks.useSharedPluginStateWithSelector)(pluginInjectionApi, ['table'], function (states) { var _states$tableState, _states$tableState2; return { isFullWidthModeEnabled: (_states$tableState = states.tableState) === null || _states$tableState === void 0 ? void 0 : _states$tableState.isFullWidthModeEnabled, wasFullWidthModeEnabled: (_states$tableState2 = states.tableState) === null || _states$tableState2 === void 0 ? void 0 : _states$tableState2.wasFullWidthModeEnabled }; }), isFullWidthModeEnabled = _useSharedPluginState.isFullWidthModeEnabled, wasFullWidthModeEnabled = _useSharedPluginState.wasFullWidthModeEnabled; (0, _react.useEffect)(function () { if (editorView && getPos) { var state = editorView.state, dispatch = editorView.dispatch; if (wasFullWidthModeEnabled && isFullWidthModeEnabled !== undefined && !isFullWidthModeEnabled && alignment !== _alignment.ALIGN_CENTER && node.attrs.width > _editorSharedStyles.akEditorDefaultLayoutWidth) { var _pluginInjectionApi$a; var pos = getPos && getPos(); if (typeof pos !== 'number') { return; } (0, _commandsWithAnalytics.setTableAlignmentWithTableContentWithPosWithAnalytics)(pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions)(_alignment.ALIGN_CENTER, alignment, { pos: pos, node: node }, _analytics.INPUT_METHOD.AUTO, _analytics.CHANGE_ALIGNMENT_REASON.EDITOR_APPEARANCE_CHANGED)(state, dispatch); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [editorView, isFullWidthModeEnabled, wasFullWidthModeEnabled, node]); var style = (0, _react.useMemo)(function () { return (0, _tableContainerStyles.getAlignmentStyle)(alignment); }, [alignment]); return /*#__PURE__*/_react.default.createElement("div", { "data-testid": "table-alignment-container", "data-ssr-placeholder": "table-".concat(node.attrs.localId), "data-ssr-placeholder-replace": "table-".concat(node.attrs.localId) // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 , style: style }, children); }; var AlignmentTableContainerWrapper = function AlignmentTableContainerWrapper(_ref3) { var isTableAlignmentEnabled = _ref3.isTableAlignmentEnabled, node = _ref3.node, children = _ref3.children, pluginInjectionApi = _ref3.pluginInjectionApi, getPos = _ref3.getPos, editorView = _ref3.editorView; if (!isTableAlignmentEnabled) { return /*#__PURE__*/_react.default.createElement("div", { "data-testid": "table-alignment-container", "data-ssr-placeholder": "table-".concat(node.attrs.localId), "data-ssr-placeholder-replace": "table-".concat(node.attrs.localId), style: { // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 display: 'flex', // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 justifyContent: 'center' } }, children); } return /*#__PURE__*/_react.default.createElement(AlignmentTableContainer, { node: node, pluginInjectionApi: pluginInjectionApi, getPos: getPos, editorView: editorView }, children); }; var selector = function selector(states) { return { tableState: states.tableState, editorViewModeState: states.editorViewModeState }; }; var getPadding = function getPadding(containerWidth) { return containerWidth <= _editorSharedStyles.akEditorFullPageNarrowBreakout && (0, _experiments.editorExperiment)('platform_editor_preview_panel_responsiveness', true, { exposure: true }) ? _editorSharedStyles.akEditorGutterPaddingReduced : (0, _editorSharedStyles.akEditorGutterPaddingDynamic)(); }; var ResizableTableContainer = exports.ResizableTableContainer = /*#__PURE__*/_react.default.memo(function (_ref4) { var children = _ref4.children, className = _ref4.className, node = _ref4.node, containerWidth = _ref4.containerWidth, lineLength = _ref4.lineLength, editorView = _ref4.editorView, getPos = _ref4.getPos, tableRef = _ref4.tableRef, isResizing = _ref4.isResizing, pluginInjectionApi = _ref4.pluginInjectionApi, tableWrapperHeight = _ref4.tableWrapperHeight, isWholeTableInDanger = _ref4.isWholeTableInDanger, isTableScalingEnabled = _ref4.isTableScalingEnabled, allowFixedColumnWidthOption = _ref4.allowFixedColumnWidthOption, isTableAlignmentEnabled = _ref4.isTableAlignmentEnabled, shouldUseIncreasedScalingPercent = _ref4.shouldUseIncreasedScalingPercent, isCommentEditor = _ref4.isCommentEditor, isChromelessEditor = _ref4.isChromelessEditor; var containerRef = (0, _react.useRef)(null); var tableWidthRef = (0, _react.useRef)(_editorSharedStyles.akEditorDefaultLayoutWidth); var _useState = (0, _react.useState)(false), _useState2 = (0, _slicedToArray2.default)(_useState, 2), resizing = _useState2[0], setIsResizing = _useState2[1]; var _useSharedPluginState2 = (0, _hooks.useSharedPluginStateWithSelector)(pluginInjectionApi, ['table', 'editorViewMode'], selector), tableState = _useSharedPluginState2.tableState, editorViewModeState = _useSharedPluginState2.editorViewModeState; var isFullWidthModeEnabled = tableState === null || tableState === void 0 ? void 0 : tableState.isFullWidthModeEnabled; var isMaxWidthModeEnabled = tableState === null || tableState === void 0 ? void 0 : tableState.isMaxWidthModeEnabled; var mode = editorViewModeState === null || editorViewModeState === void 0 ? void 0 : editorViewModeState.mode; // If the editor is in max width mode and the table has no width, use the max width value rather than the default table value var tableWidth = isMaxWidthModeEnabled && !node.attrs.width && (0, _platformFeatureFlags.fg)('platform_editor_max_width_default_width') ? _consts.TABLE_MAX_WIDTH : (0, _nodeWidth.getTableContainerWidth)(node); var updateContainerHeight = (0, _react.useCallback)(function (height) { var _containerRef$current; // current StickyHeader State is not stable to be fetch. // we need to update stickyHeader plugin to make sure state can be // consistently fetch and refactor below var stickyHeaders = (_containerRef$current = containerRef.current) === null || _containerRef$current === void 0 ? void 0 : _containerRef$current.getElementsByClassName('pm-table-sticky'); if (!stickyHeaders || stickyHeaders.length < 1) { // when starting to drag, we need to keep the original space, // -- When sticky header not appear, margin top(24px) and margin bottom(16px), should be 40px, // 1px is border width but collapse make it 0.5. // -- When sticky header appear, we should add first row height but reduce // collapsed border return typeof height === 'number' ? "".concat(height + 40.5, "px") : 'auto'; } else { var _containerRef$current2; var stickyHeaderHeight = ((_containerRef$current2 = containerRef.current) === null || _containerRef$current2 === void 0 ? void 0 : _containerRef$current2.getElementsByTagName('th')[0].getBoundingClientRect().height) || 0; return typeof height === 'number' ? "".concat(height + stickyHeaderHeight + 39.5, "px") : 'auto'; } }, []); var onResizeStart = (0, _react.useCallback)(function () { setIsResizing(true); }, []); var onResizeStop = (0, _react.useCallback)(function () { setIsResizing(false); }, []); var updateWidth = (0, _react.useCallback)(function (width) { if (!containerRef.current) { return; } // make sure during resizing // the pm-table-resizer-container width is the same as its child div resizer-item // otherwise when resize table from wider to narrower , pm-table-resizer-container stays wider // and cause the fabric-editor-popup-scroll-parent to overflow if (containerRef.current.style.width !== "".concat(width, "px")) { containerRef.current.style.width = "".concat(width, "px"); } }, []); var displayGuideline = (0, _react.useCallback)(function (guidelines) { var _pluginInjectionApi$g, _pluginInjectionApi$g2; return (_pluginInjectionApi$g = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$g2 = pluginInjectionApi.guideline) === null || _pluginInjectionApi$g2 === void 0 || (_pluginInjectionApi$g2 = _pluginInjectionApi$g2.actions) === null || _pluginInjectionApi$g2 === void 0 ? void 0 : _pluginInjectionApi$g2.displayGuideline(editorView)({ guidelines: guidelines })) !== null && _pluginInjectionApi$g !== void 0 ? _pluginInjectionApi$g : false; }, [pluginInjectionApi, editorView]); var attachAnalyticsEvent = (0, _react.useCallback)(function (payload) { var _pluginInjectionApi$a2; return pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$a2 = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a2 === void 0 ? void 0 : _pluginInjectionApi$a2.actions.attachAnalyticsEvent(payload); }, [pluginInjectionApi]); var displayGapCursor = (0, _react.useCallback)(function (toggle) { var _pluginInjectionApi$c, _pluginInjectionApi$c2, _pluginInjectionApi$s; return (_pluginInjectionApi$c = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c2 = pluginInjectionApi.core) === null || _pluginInjectionApi$c2 === void 0 ? void 0 : _pluginInjectionApi$c2.actions.execute(pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$s = pluginInjectionApi.selection) === null || _pluginInjectionApi$s === void 0 ? void 0 : _pluginInjectionApi$s.commands.displayGapCursor(toggle))) !== null && _pluginInjectionApi$c !== void 0 ? _pluginInjectionApi$c : false; }, [pluginInjectionApi]); var isFullPageAppearance = !isCommentEditor && !isChromelessEditor; var _useMemo = (0, _react.useMemo)(function () { var responsiveContainerWidth = 0; var resizeHandleSpacing = 12; var padding = getPadding(containerWidth); // When Full width or Max width editor enabled, a Mac OS user can change "ak-editor-content-area" width by // updating Settings -> Appearance -> Show scroll bars from "When scrolling" to "Always". It causes // issues when viwport width is less than full/max width Editor's width. To detect avoid them // we need to use lineLength to defined responsiveWidth instead of containerWidth // (which does not get updated when Mac setting changes) in Full-width/Max-width editor. if (isFullWidthModeEnabled || isMaxWidthModeEnabled && (0, _platformFeatureFlags.fg)('platform_editor_max_width_default_width')) { // When: Show scroll bars -> containerWidth = akEditorGutterPadding * 2 + lineLength; // When: Always -> containerWidth = akEditorGutterPadding * 2 + lineLength + scrollbarWidth; // scrollbarWidth can vary. Values can be 14, 15, 16 and up to 20px; // lineLength is undefined on first paint → width: NaN → wrapper expands to page // width. rAF col-sizing then runs before the number-column padding and // the final shrink, so column widths are locked in wrong. // If the value isn't finite we fall back to gutterWidth for that first frame. if (isTableScalingEnabled && Number.isFinite(lineLength) && lineLength !== undefined) { responsiveContainerWidth = lineLength; } else { responsiveContainerWidth = containerWidth - padding * 2 - resizeHandleSpacing; } } else if (isCommentEditor) { responsiveContainerWidth = containerWidth - _consts.TABLE_OFFSET_IN_COMMENT_EDITOR; } else { // 76 is currently an accepted padding value considering the spacing for resizer handle // containerWidth = width of a DIV with test id="ak-editor-fp-content-area". It is a parent of // a DIV with className="ak-editor-content-area". This DIV has padding left and padding right. // padding left = padding right = akEditorGutterPadding = 32 responsiveContainerWidth = isTableScalingEnabled ? containerWidth - padding * 2 : containerWidth - padding * 2 - resizeHandleSpacing; } // Fix for HOT-119925: Ensure table width is properly constrained and responsive // For wide tables, ensure they don't exceed container width and can be scrolled var calculatedWidth = !node.attrs.width && isCommentEditor ? responsiveContainerWidth : Math.min(tableWidth, responsiveContainerWidth); // Ensure minimum width for usability while respecting container constraints var width = Math.max(calculatedWidth, Math.min(responsiveContainerWidth * 0.5, 300)); var maxResizerWidth = isCommentEditor ? responsiveContainerWidth : Math.min(responsiveContainerWidth, (0, _expValEquals.expValEquals)('editor_tinymce_full_width_mode', 'isEnabled', true) || (0, _expValEquals.expValEquals)('confluence_max_width_content_appearance', 'isEnabled', true) ? _consts.TABLE_MAX_WIDTH : _consts.TABLE_FULL_WIDTH); return { width: width, maxResizerWidth: maxResizerWidth }; }, [containerWidth, isCommentEditor, isFullWidthModeEnabled, isMaxWidthModeEnabled, isTableScalingEnabled, lineLength, node.attrs.width, tableWidth]), width = _useMemo.width, maxResizerWidth = _useMemo.maxResizerWidth; (0, _react.useEffect)(function () { if (!isResizing) { tableWidthRef.current = width; } }, [width, isResizing]); // CSS Solution for table resizer container width var tableResizerContainerWidth = (0, _react.useMemo)(function () { return (0, _misc.getTableResizerContainerForFullPageWidthInCSS)(node, isTableScalingEnabled); }, [node, isTableScalingEnabled]); // CSS Solution for table resizer max width var tableResizerMaxWidth = _react.default.useMemo(function () { var isFullPageAppearance = !isCommentEditor && !isChromelessEditor; var nonResizingMaxWidth = isFullPageAppearance ? (0, _misc.getTableResizerContainerMaxWidthInCSS)(isCommentEditor, isChromelessEditor, isTableScalingEnabled) : maxResizerWidth; // isResizing is needed, otherwise we can't resize table. // when not resizing, maxWidth is calculated based on the container width via CSS return !isResizing ? nonResizingMaxWidth : maxResizerWidth; }, [isCommentEditor, isChromelessEditor, isTableScalingEnabled, isResizing, maxResizerWidth]); // TODO: EDITOR-1679 - Add support for lineLength being undefined at runtime. var tableResizerProps = { // The `width` is used for .resizer-item in <TableResizer>, and it has to be a number // So we can't use min(var(--ak-editor-table-width), ${tableWidth}px) here // We still have to use JS to calculate width width: width, maxWidth: tableResizerMaxWidth, containerWidth: containerWidth, // oxlint-disable-next-line @typescript-eslint/no-non-null-assertion -- To be fixed in EDITOR-1679 lineLength: lineLength, updateWidth: updateWidth, editorView: editorView, getPos: getPos, node: node, tableRef: tableRef, displayGuideline: displayGuideline, attachAnalyticsEvent: attachAnalyticsEvent, displayGapCursor: displayGapCursor, isTableAlignmentEnabled: isTableAlignmentEnabled, isFullWidthModeEnabled: isFullWidthModeEnabled, isTableScalingEnabled: isTableScalingEnabled, allowFixedColumnWidthOption: allowFixedColumnWidthOption, isWholeTableInDanger: isWholeTableInDanger, shouldUseIncreasedScalingPercent: shouldUseIncreasedScalingPercent, pluginInjectionApi: pluginInjectionApi, onResizeStart: onResizeStart, onResizeStop: onResizeStop, isCommentEditor: isCommentEditor }; var isLivePageViewMode = mode === 'view'; return /*#__PURE__*/_react.default.createElement(AlignmentTableContainerWrapper, { isTableAlignmentEnabled: isTableAlignmentEnabled, node: node, pluginInjectionApi: pluginInjectionApi, getPos: getPos, editorView: editorView }, /*#__PURE__*/_react.default.createElement("div", { style: { // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop '--ak-editor-table-gutter-padding': 'calc(var(--ak-editor--large-gutter-padding) * 2)', '--ak-editor-table-width': isFullPageAppearance ? tableResizerContainerWidth : "".concat(tableWidthRef.current, "px"), // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop width: 'var(--ak-editor-table-width)', height: resizing ? updateContainerHeight(tableWrapperHeight !== null && tableWrapperHeight !== void 0 ? tableWrapperHeight : 'auto') : 'auto', position: isLivePageViewMode ? 'relative' : 'unset' } // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 , className: _types.TableCssClassName.TABLE_RESIZER_CONTAINER, ref: containerRef }, /*#__PURE__*/_react.default.createElement(_TableResizer.TableResizer, (0, _extends2.default)({}, tableResizerProps, { disabled: isLivePageViewMode }), /*#__PURE__*/_react.default.createElement(InnerContainer // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 , { className: className, node: node }, children)))); }); var TableContainer = exports.TableContainer = function TableContainer(_ref5) { var children = _ref5.children, node = _ref5.node, className = _ref5.className, _ref5$containerWidth = _ref5.containerWidth, editorWidth = _ref5$containerWidth.width, lineLength = _ref5$containerWidth.lineLength, editorView = _ref5.editorView, getPos = _ref5.getPos, tableRef = _ref5.tableRef, isNested = _ref5.isNested, tableWrapperHeight = _ref5.tableWrapperHeight, isResizing = _ref5.isResizing, pluginInjectionApi = _ref5.pluginInjectionApi, isWholeTableInDanger = _ref5.isWholeTableInDanger, isTableResizingEnabled = _ref5.isTableResizingEnabled, isTableScalingEnabled = _ref5.isTableScalingEnabled, allowFixedColumnWidthOption = _ref5.allowFixedColumnWidthOption, isTableAlignmentEnabled = _ref5.isTableAlignmentEnabled, shouldUseIncreasedScalingPercent = _ref5.shouldUseIncreasedScalingPercent, isCommentEditor = _ref5.isCommentEditor, isChromelessEditor = _ref5.isChromelessEditor; if (isTableResizingEnabled && !isNested) { return /*#__PURE__*/_react.default.createElement(ResizableTableContainer // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 , { className: className, node: node // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion , containerWidth: editorWidth // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion , lineLength: lineLength, editorView: editorView, getPos: getPos, tableRef: tableRef, tableWrapperHeight: tableWrapperHeight, isResizing: isResizing, pluginInjectionApi: pluginInjectionApi, isTableScalingEnabled: isTableScalingEnabled, allowFixedColumnWidthOption: allowFixedColumnWidthOption, isWholeTableInDanger: isWholeTableInDanger, isTableAlignmentEnabled: isTableAlignmentEnabled, shouldUseIncreasedScalingPercent: shouldUseIncreasedScalingPercent, isCommentEditor: isCommentEditor, isChromelessEditor: isChromelessEditor }, children); } var _getPluginState = (0, _pluginFactory.getPluginState)(editorView.state), isDragAndDropEnabled = _getPluginState.isDragAndDropEnabled; var isFullPageAppearance = !isCommentEditor && !isChromelessEditor; var resizableTableWidth = isFullPageAppearance ? (0, _misc.getTableResizerContainerForFullPageWidthInCSS)(node, isTableScalingEnabled) : "calc(100cqw - calc(var(--ak-editor--large-gutter-padding) * 2))"; return /*#__PURE__*/_react.default.createElement(InnerContainer, { node: node // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766 , className: (0, _classnames.default)(className, { 'less-padding': editorWidth < _editorSharedStyles.akEditorMobileBreakoutPoint && !isNested && !(isChromelessEditor && isDragAndDropEnabled) }), style: // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) { // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 width: 'inherit', marginLeft: isChromelessEditor ? 18 : undefined, // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop '--ak-editor-table-width': resizableTableWidth } }, children); };