UNPKG

@atlaskit/renderer

Version:
399 lines (381 loc) 22.3 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.colWidthSum = exports.Colgroup = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireWildcard(require("react")); var _styles = require("@atlaskit/editor-common/styles"); var _ui = require("@atlaskit/editor-common/ui"); var _editorSharedStyles = require("@atlaskit/editor-shared-styles"); var _nodeWidth = require("@atlaskit/editor-common/node-width"); var _useFeatureFlags = require("../../../use-feature-flags"); var _rendererContext = require("../../../renderer-context"); var _platformFeatureFlags = require("@atlaskit/platform-feature-flags"); var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals"); 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); } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } // we allow scaling down column widths by no more than 30% // this intends to reduce unwanted scrolling in the Renderer in these scenarios: // User A creates a table with column widths → User B views it on a smaller screen // User A creates a table with column widths → User A views it with reduced viewport space (eg. Confluence sidebar is open) var MAX_SCALING_PERCENT = 0.3; var MAX_SCALING_PERCENT_TABLES_WITH_FIXED_COLUMN_WIDTHS_OPTION = 0.4; var isTableColumnResized = function isTableColumnResized(columnWidths) { var filteredWidths = columnWidths.filter(function (width) { return width !== 0; }); return !!filteredWidths.length; }; var fixColumnWidth = function fixColumnWidth(_ref) { var columnWidth = _ref.columnWidth, zeroWidthColumnsCount = _ref.zeroWidthColumnsCount, scaleDownPercent = _ref.scaleDownPercent, skipMinWidth = _ref.skipMinWidth; if (columnWidth === 0) { return columnWidth; } // If the tables total width (including no zero widths col or cols without width) is less than the current layout // We scale up the columns to meet the minimum of the table layout. if (zeroWidthColumnsCount === 0 && scaleDownPercent) { var scaled = Math.floor((1 - scaleDownPercent) * columnWidth); return skipMinWidth ? scaled : Math.max(scaled, _styles.tableCellMinWidth); } var adjusted = columnWidth - _styles.tableCellBorderWidth; if (skipMinWidth) { return adjusted; } return Math.max(adjusted, zeroWidthColumnsCount ? _editorSharedStyles.akEditorTableLegacyCellMinWidth : _styles.tableCellMinWidth); }; var calcScalePercent = function calcScalePercent(_ref2) { var renderWidth = _ref2.renderWidth, tableWidth = _ref2.tableWidth, maxScale = _ref2.maxScale, isNumberColumnEnabled = _ref2.isNumberColumnEnabled; var noNumColumnScalePercent = renderWidth / tableWidth; // when numbered column is enabled, we need to calculate the scale percent without the akEditorTableNumberColumnWidth // As numbered column width is not scaled down var numColumnScalePercent = (renderWidth - _editorSharedStyles.akEditorTableNumberColumnWidth) / (tableWidth - _editorSharedStyles.akEditorTableNumberColumnWidth); var diffPercent = 1 - noNumColumnScalePercent; return diffPercent < maxScale ? isNumberColumnEnabled ? 1 - numColumnScalePercent : diffPercent : maxScale; }; var colWidthSum = exports.colWidthSum = function colWidthSum(columnWidths) { return columnWidths.reduce(function (prev, curr) { return curr + prev; }, 0); }; /** * Returns the data-column available width: total width minus the fixed number column if enabled. */ var getDataColumnWidth = function getDataColumnWidth(totalWidth, isNumberColumnEnabled) { return isNumberColumnEnabled ? totalWidth - _editorSharedStyles.akEditorTableNumberColumnWidth : totalWidth; }; /** * Scales column widths proportionally to fit within availableWidth, matching the editor's * scaleTableTo(): floors each column to the nearest pixel, then redistributes any rounding * remainder to the first column that can absorb it without going below tableCellMinWidth. */ var scaleColumnsToWidth = function scaleColumnsToWidth(columnWidths, availableWidth) { var rawTotalWidth = columnWidths.reduce(function (sum, w) { return sum + w; }, 0); var scaleFactor = availableWidth / rawTotalWidth; var scaledWidths = columnWidths.map(function (colWidth) { return Math.max(Math.floor(colWidth * scaleFactor), _styles.tableCellMinWidth); }); var totalScaled = scaledWidths.reduce(function (sum, w) { return sum + w; }, 0); var diff = availableWidth - totalScaled; if (diff !== 0 && Math.abs(diff) < _styles.tableCellMinWidth) { for (var i = 0; i < scaledWidths.length; i++) { if (scaledWidths[i] + diff > _styles.tableCellMinWidth) { scaledWidths[i] += diff; break; } } } return scaledWidths.map(function (width) { return { width: "".concat(width, "px") }; }); }; /** * Computes column widths for tables inside sync blocks, matching the editor's scaleTableTo() exactly. * Returns null if not inside a sync block. * * For nested tables (isInsideOfTable=true), we use getTableContainerWidth(tableNode) as the * reference — the width the editor saved, which already accounts for the parent cell's available * space (colwidth minus tableCellPadding * 2). This matches bodiedSyncBlock where isRendererNested=false, * so renderScaleDownColgroup uses getTableContainerWidth(tableNode). For syncBlock the nested * renderer has isRendererNested=true, which incorrectly overrides tableContainerWidth with renderWidth * (the full container), causing overflow by 2 * tableCellPadding (16px). */ var renderSyncBlockColgroup = function renderSyncBlockColgroup(_ref3) { var isInsideOfSyncBlock = _ref3.isInsideOfSyncBlock, isInsideOfTable = _ref3.isInsideOfTable, tableNode = _ref3.tableNode, columnWidths = _ref3.columnWidths, isNumberColumnEnabled = _ref3.isNumberColumnEnabled, renderWidthProp = _ref3.renderWidth, contextWidth = _ref3.contextWidth; if (!isInsideOfSyncBlock) { return null; } var rawTotalWidth = columnWidths.reduce(function (sum, w) { return sum + w; }, 0); if (isInsideOfTable) { return null; } // SSR / first client render before WidthObserver measures. Output % of original ADF // proportions so columns are stable — the CSS container query (100cqw) handles actual // scaling width. We key off renderWidthProp alone because WidthProvider initialises with // document.body.offsetWidth (non-zero on the client), so contextWidth > 0 even before // WidthObserver has measured the real container, which would skip this branch and produce // column widths scaled to the full viewport. if (renderWidthProp <= 0) { var fullTableWidth = isNumberColumnEnabled ? rawTotalWidth + _editorSharedStyles.akEditorTableNumberColumnWidth : rawTotalWidth; return columnWidths.map(function (colWidth) { return { width: "".concat(colWidth / fullTableWidth * 100, "%") }; }); } // contextWidth measures the sync block content area. Subtract 2 to match the editor's // getParentNodeWidth() border offset. Fall back to renderWidthProp for the non-CSS path. var effectiveRenderWidth = contextWidth > 0 ? contextWidth - 2 : renderWidthProp; var availableWidth = getDataColumnWidth(effectiveRenderWidth, isNumberColumnEnabled); // Only scale down if the table is actually wider than the available space. // If the table fits, return original widths with the same tableCellBorderWidth (2px) adjustment // that renderScaleDownColgroup applies via fixColumnWidth's no-scale path. if (availableWidth >= rawTotalWidth) { return columnWidths.map(function (colWidth) { return { width: "".concat(Math.max(colWidth - _styles.tableCellBorderWidth, _styles.tableCellMinWidth), "px") }; }); } // Cap scaling at MAX_SCALING_PERCENT (30%), matching renderScaleDownColgroup's calcScalePercent. // // Both `availableWidth` and `rawTotalWidth` are already in data-column space (number column // excluded via getDataColumnWidth), so `diffPercent = 1 - availableWidth / rawTotalWidth` is // equivalent to calcScalePercent's `numColumnScalePercent` path when isNumberColumnEnabled, // and to the `noNumColumnScalePercent` path otherwise. We express the cap as a target width // (clampedAvailableWidth) rather than a scale-down percentage, but the result is the same: // columns are scaled by at most MAX_SCALING_PERCENT (30%). var diffPercent = 1 - availableWidth / rawTotalWidth; var clampedAvailableWidth = diffPercent <= MAX_SCALING_PERCENT ? availableWidth : Math.floor(rawTotalWidth * (1 - MAX_SCALING_PERCENT)); return scaleColumnsToWidth(columnWidths, clampedAvailableWidth); }; var renderScaleDownColgroup = function renderScaleDownColgroup(props) { var _props$tableNode; var columnWidths = props.columnWidths, isNumberColumnEnabled = props.isNumberColumnEnabled, renderWidth = props.renderWidth, tableNode = props.tableNode, rendererAppearance = props.rendererAppearance, isInsideOfBlockNode = props.isInsideOfBlockNode, isInsideOfTable = props.isInsideOfTable, isinsideMultiBodiedExtension = props.isinsideMultiBodiedExtension, isTableScalingEnabled = props.isTableScalingEnabled, isTableFixedColumnWidthsOptionEnabled = props.isTableFixedColumnWidthsOptionEnabled, allowTableResizing = props.allowTableResizing, isTopLevelRenderer = props.isTopLevelRenderer, isInsideOfSyncBlock = props.isInsideOfSyncBlock; var skipMinWidth = !!(isInsideOfTable && isInsideOfSyncBlock); if (!columnWidths || columnWidths.every(function (width) { return width === 0; }) && (0, _platformFeatureFlags.fg)('platform_editor_numbered_column_in_include')) { return []; } var tableColumnResized = isTableColumnResized(columnWidths); var noOfColumns = columnWidths.length; var targetWidths; // This is a fix for ED-23259 // Some extensions (for ex: Page Properties or Excerpt) do not renderer tables directly inside themselves. They use ReactRenderer. // So if we add a check like isInsideExtension (similar to exising isInsideBlockNode), it will fail, and to the only way to learn // if the table is rendered inside another node, is to check if the Renderer itself is nested. var isRendererNested = isTopLevelRenderer === false; // appearance == comment && allowTableResizing && !tableNode?.attrs.width, means it is a comment // appearance == comment && !allowTableResizing && !tableNode?.attrs.width, means it is a inline comment // When comment and inline comment table width inherits from the parent container, we want tableContainerWidth === renderWidth // Tables with numbered columns inside of extensions cannot use the renderWidth when it is 0. In this case, it should // explicitly use the width coming from the table node as the final table container width. var tableContainerWidth = rendererAppearance === 'comment' && !(tableNode !== null && tableNode !== void 0 && tableNode.attrs.width) || isRendererNested && (!(0, _platformFeatureFlags.fg)('platform_fix_nested_num_column_scaling') || !isNumberColumnEnabled) ? renderWidth : (0, _nodeWidth.getTableContainerWidth)(tableNode); if (allowTableResizing && !isInsideOfBlockNode && !isInsideOfTable && !isinsideMultiBodiedExtension && !tableColumnResized) { // when no columns are resized, each column should have equal width, equals to tableWidth / noOfColumns var _tableWidth = (isNumberColumnEnabled ? tableContainerWidth - _editorSharedStyles.akEditorTableNumberColumnWidth : tableContainerWidth) - 1; var defaultColumnWidth = _tableWidth / noOfColumns; targetWidths = new Array(noOfColumns).fill(defaultColumnWidth); } else if (!tableColumnResized) { return null; } var sumOfColumns = colWidthSum(columnWidths); // tables in the wild may be smaller than table container width (col resizing bugs, created before custom widths etc.) // this causes issues with num column scaling as we add a new table column in renderer var isTableSmallerThanContainer = sumOfColumns < tableContainerWidth - 1; var forceScaleForNumColumn = isTableScalingEnabled && isNumberColumnEnabled && tableColumnResized; // when table resized and number column is enabled, we need to scale down the table in render if (forceScaleForNumColumn) { var calculatedTableContainerWidth = rendererAppearance === 'comment' ? sumOfColumns : tableContainerWidth; var scalePercentage = +((calculatedTableContainerWidth - _editorSharedStyles.akEditorTableNumberColumnWidth) / calculatedTableContainerWidth); var targetMaxWidth = calculatedTableContainerWidth - _editorSharedStyles.akEditorTableNumberColumnWidth; var totalWidthAfterScale = 0; var newScaledTargetWidths = columnWidths.map(function (width) { // we need to scale each column UP, to ensure total width of table matches table container var patchedWidth = isTableSmallerThanContainer ? width / sumOfColumns * (calculatedTableContainerWidth - 1) : width; var newWidth = Math.floor(patchedWidth * scalePercentage); totalWidthAfterScale += newWidth; return newWidth; }); var diff = targetMaxWidth - totalWidthAfterScale; targetWidths = newScaledTargetWidths; if (diff > 0 || diff < 0 && Math.abs(diff) < _styles.tableCellMinWidth) { var updated = false; targetWidths = targetWidths.map(function (width) { if (!updated && width + diff > _styles.tableCellMinWidth) { updated = true; width += diff; } return width; }); } } targetWidths = targetWidths || columnWidths; // @see ED-6056 var maxTableWidth = renderWidth < tableContainerWidth ? renderWidth : tableContainerWidth; var tableWidth = isNumberColumnEnabled ? _editorSharedStyles.akEditorTableNumberColumnWidth : 0; var minTableWidth = tableWidth; var zeroWidthColumnsCount = 0; targetWidths.forEach(function (width) { if (width) { tableWidth += Math.ceil(width); } else { zeroWidthColumnsCount += 1; } minTableWidth += Math.ceil(width) || _editorSharedStyles.akEditorTableLegacyCellMinWidth; }); var cellMinWidth = 0; var scaleDownPercent = 0; var isTableScalingWithFixedColumnWidthsOptionEnabled = isTableScalingEnabled && isTableFixedColumnWidthsOptionEnabled; var isTableWidthFixed = isTableScalingWithFixedColumnWidthsOptionEnabled && ((_props$tableNode = props.tableNode) === null || _props$tableNode === void 0 ? void 0 : _props$tableNode.attrs.displayMode) === 'fixed'; var maxScalingPercent = isTableScalingWithFixedColumnWidthsOptionEnabled || isTableScalingEnabled && rendererAppearance === 'comment' ? MAX_SCALING_PERCENT_TABLES_WITH_FIXED_COLUMN_WIDTHS_OPTION : MAX_SCALING_PERCENT; // fixes migration tables with zero-width columns if (zeroWidthColumnsCount > 0) { if (minTableWidth > maxTableWidth) { var minWidth = Math.ceil((maxTableWidth - tableWidth) / zeroWidthColumnsCount); cellMinWidth = minWidth < _editorSharedStyles.akEditorTableLegacyCellMinWidth ? _editorSharedStyles.akEditorTableLegacyCellMinWidth : minWidth; } } // scaling down else if (renderWidth < tableWidth && !isTableWidthFixed) { var shouldTable100ScaleDown = rendererAppearance === 'comment' && allowTableResizing && !(tableNode !== null && tableNode !== void 0 && tableNode.attrs.width); scaleDownPercent = calcScalePercent({ renderWidth: renderWidth, tableWidth: tableWidth, maxScale: (0, _platformFeatureFlags.fg)('platform-ssr-table-resize') ? maxScalingPercent : shouldTable100ScaleDown ? 1 : maxScalingPercent, isNumberColumnEnabled: isNumberColumnEnabled }); } if (isNumberColumnEnabled && (tableWidth < maxTableWidth || maxTableWidth === 0)) { var fixedColWidths = targetWidths.map(function (width) { return fixColumnWidth({ columnWidth: width, zeroWidthColumnsCount: zeroWidthColumnsCount, scaleDownPercent: scaleDownPercent, skipMinWidth: skipMinWidth }) || cellMinWidth; }); var sumFixedColumnWidths = colWidthSum(fixedColWidths); return fixedColWidths.map(function (colWidth) { var width = Math.max(colWidth, cellMinWidth); if (colWidth > _editorSharedStyles.akEditorTableCellMinWidth) { // To make sure the numbered column isn't scaled, use // percentages for the other columns. // Calculate the percentage based on the sum of the fixed column widths return { width: "".concat(width / sumFixedColumnWidths * 100, "%") }; } else { // If the column is equal to or less than the minimum cell width, we need to return a fixed pixel width. // This is to prevent columns being scaled below the minimum cell width. var style = width ? { width: "".concat(width, "px") } : {}; return style; } }); } return targetWidths.map(function (colWidth) { var width = fixColumnWidth({ columnWidth: colWidth, zeroWidthColumnsCount: zeroWidthColumnsCount, scaleDownPercent: scaleDownPercent, skipMinWidth: skipMinWidth }) || cellMinWidth; var style = width ? { width: "".concat(width, "px") } : {}; return style; }); }; var Colgroup = exports.Colgroup = function Colgroup(props) { var _ref4, _renderSyncBlockColgr; var _useRendererContext = (0, _rendererContext.useRendererContext)(), isTopLevelRenderer = _useRendererContext.isTopLevelRenderer, nestedRendererType = _useRendererContext.nestedRendererType; var columnWidths = props.columnWidths, isNumberColumnEnabled = props.isNumberColumnEnabled; var _useContext = (0, _react.useContext)(_ui.WidthContext), contextWidth = _useContext.width; var flags = (0, _useFeatureFlags.useFeatureFlags)(); if (!columnWidths) { return null; } var isTableFixedColumnWidthsOptionEnabled = (_ref4 = (0, _platformFeatureFlags.fg)('platform_editor_table_fixed_column_width_prop') ? props.allowFixedColumnWidthOption : flags && 'tableWithFixedColumnWidthsOption' in flags && flags.tableWithFixedColumnWidthsOption) !== null && _ref4 !== void 0 ? _ref4 : false; // For referenced sync blocks, nestedRendererType='syncedBlock' is set via RendererContextProvider // in AKRendererWrapper. ReactSerializer is a class and cannot read React context, so we detect // it here in the Colgroup component via useRendererContext() instead of prop-drilling. var isInsideOfSyncBlock = nestedRendererType === 'syncedBlock'; // renderSyncBlockColgroup returns null when not applicable (flag off, SSR, not a sync block), // in which case ?? falls back to the standard renderScaleDownColgroup path. var colStyles = (_renderSyncBlockColgr = renderSyncBlockColgroup({ isInsideOfSyncBlock: isInsideOfSyncBlock, isInsideOfTable: !!props.isInsideOfTable, tableNode: props.tableNode, columnWidths: columnWidths, isNumberColumnEnabled: !!isNumberColumnEnabled, renderWidth: props.renderWidth, contextWidth: contextWidth })) !== null && _renderSyncBlockColgr !== void 0 ? _renderSyncBlockColgr : renderScaleDownColgroup(_objectSpread(_objectSpread({}, props), {}, { isTopLevelRenderer: isTopLevelRenderer, isInsideOfSyncBlock: isInsideOfSyncBlock, isTableScalingEnabled: props.rendererAppearance === 'full-page' || props.rendererAppearance === 'full-width' || props.rendererAppearance === 'max' && ((0, _expValEquals.expValEquals)('editor_tinymce_full_width_mode', 'isEnabled', true) || (0, _expValEquals.expValEquals)('confluence_max_width_content_appearance', 'isEnabled', true)) || props.rendererAppearance === 'comment', isTableFixedColumnWidthsOptionEnabled: isTableFixedColumnWidthsOptionEnabled && (props.rendererAppearance === 'full-page' || props.rendererAppearance === 'full-width' || props.rendererAppearance === 'max' && ((0, _expValEquals.expValEquals)('editor_tinymce_full_width_mode', 'isEnabled', true) || (0, _expValEquals.expValEquals)('confluence_max_width_content_appearance', 'isEnabled', true))) })); if (!colStyles) { return null; } return /*#__PURE__*/_react.default.createElement("colgroup", null, isNumberColumnEnabled && /*#__PURE__*/_react.default.createElement("col", { // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766 style: { width: _editorSharedStyles.akEditorTableNumberColumnWidth }, "data-test-id": 'num' }), colStyles.map(function (style, idx) { return ( /*#__PURE__*/ // Ignored via go/ees005 // eslint-disable-next-line react/no-array-index-key, @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766 _react.default.createElement("col", { key: idx, style: style }) ); })); };