@atlaskit/renderer
Version:
Renderer component
399 lines (381 loc) • 22.3 kB
JavaScript
"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
})
);
}));
};