@antv/s2
Version:
effective spreadsheet render core lib
351 lines • 14.3 kB
JavaScript
import { find, get, merge } from 'lodash';
import { CellType, FrozenGroupArea, KEY_GROUP_ROW_RESIZE_AREA, ResizeAreaEffect, ResizeDirectionType, S2Event, } from '../common/constant';
import { CellBorderPosition, CellClipBox, } from '../common/interface';
import { CustomRect } from '../engine';
import { getHorizontalTextIconPosition, getVerticalIconPosition, } from '../utils/cell/cell';
import { renderCircle, renderTreeIcon } from '../utils/g-renders';
import { getAllChildrenNodeHeight } from '../utils/get-all-children-node-height';
import { getOrCreateResizeAreaGroupById, getResizeAreaAttrs, shouldAddResizeArea, } from '../utils/interaction/resize';
import { isMobile } from '../utils/is-mobile';
import { adjustTextIconPositionWhileScrolling } from './../utils/cell/text-scrolling';
import { normalizeTextAlign } from './../utils/normalize';
import { HeaderCell } from './header-cell';
export class RowCell extends HeaderCell {
get cellType() {
return CellType.ROW_CELL;
}
getBorderPositions() {
return [CellBorderPosition.BOTTOM, CellBorderPosition.LEFT];
}
initCell() {
super.initCell();
// 绘制单元格背景
this.drawBackgroundShape();
// 绘制交互背景
this.drawInteractiveBgShape();
// 绘制交互边框
this.drawInteractiveBorderShape();
// 绘制单元格文本 or 图片
this.drawTextOrCustomRenderer();
}
afterDrawText() {
// 绘制字段和 action标记 -- icon 和 action
this.drawActionAndConditionIcons();
// 绘制树状模式收起展开的 icon
this.drawTreeIcon();
// 绘制树状模式下子节点层级占位圆点
this.drawTreeLeafNodeAlignDot();
// 绘制单元格边框
this.drawBorders();
// 绘制 resize 热区
this.drawResizeAreaInLeaf();
this.update();
}
getBackgroundColor() {
const { backgroundColor, backgroundColorOpacity } = this.getCrossBackgroundColor(this.meta.rowIndex);
return merge({ backgroundColor, backgroundColorOpacity }, this.getBackgroundConditionFill());
}
showTreeIcon() {
return this.spreadsheet.isHierarchyTreeType() && !this.meta.isLeaf;
}
showTreeLeafNodeAlignDot() {
var _a, _b;
return (((_b = (_a = this.spreadsheet.options.style) === null || _a === void 0 ? void 0 : _a.rowCell) === null || _b === void 0 ? void 0 : _b.showTreeLeafNodeAlignDot) &&
this.spreadsheet.isHierarchyTreeType());
}
// 获取树状模式下叶子节点的父节点收起展开 icon 图形属性
getParentTreeIconCfg() {
var _a, _b;
if (!this.showTreeLeafNodeAlignDot() ||
!this.spreadsheet.isHierarchyTreeType() ||
!this.meta.isLeaf) {
return;
}
const treeIcon = (_b = (_a = this.meta.parent) === null || _a === void 0 ? void 0 : _a.belongsCell) === null || _b === void 0 ? void 0 : _b.getTreeIcon();
return treeIcon === null || treeIcon === void 0 ? void 0 : treeIcon.style;
}
onTreeIconClick() {
const { isCollapsed, hierarchy } = this.meta;
if (isMobile()) {
return;
}
// 折叠行头时因 scrollY 没变,导致底层出现空白
if (!isCollapsed) {
const { scrollY: oldScrollY } = this.spreadsheet.facet.getScrollOffset();
// 可视窗口高度
const viewportHeight = this.spreadsheet.facet.panelBBox.viewportHeight || 0;
// 被折叠项的高度
const deleteHeight = getAllChildrenNodeHeight(this.meta);
// 折叠后真实高度
const realHeight = hierarchy.height - deleteHeight;
if (oldScrollY > 0 && oldScrollY + viewportHeight > realHeight) {
const currentScrollY = realHeight - viewportHeight;
this.spreadsheet.facet.setScrollOffset({
scrollY: currentScrollY > 0 ? currentScrollY : 0,
});
}
}
this.emitCollapseEvent();
}
emitCollapseEvent() {
this.spreadsheet.emit(S2Event.ROW_CELL_COLLAPSED__PRIVATE, {
isCollapsed: !this.meta.isCollapsed,
node: this.meta,
});
}
drawTreeIcon() {
if (!this.showTreeIcon()) {
return;
}
const { isCollapsed } = this.meta;
const { x } = this.getBBoxByType(CellClipBox.CONTENT_BOX);
const { fill } = this.getTextStyle();
const { size } = this.getStyle().icon;
const contentIndent = this.getContentIndent();
const iconX = x + contentIndent;
const iconY = this.getIconPosition().y;
this.treeIcon = renderTreeIcon({
group: this,
iconCfg: {
x: iconX,
y: iconY,
width: size,
height: size,
fill,
},
isCollapsed,
onClick: () => {
this.onTreeIconClick();
},
});
// 移动端, 点击热区为整个单元格
if (isMobile()) {
this.addEventListener('touchend', () => {
this.emitCollapseEvent();
});
}
}
drawTreeLeafNodeAlignDot() {
const parentTreeIconCfg = this.getParentTreeIconCfg();
if (!parentTreeIconCfg) {
return;
}
const { size, margin } = this.getStyle().icon;
const x = parentTreeIconCfg.x + size + margin.right;
const textY = this.getTextPosition().y;
const { fill, fontSize } = this.getTextStyle();
// 半径,暂时先写死,后面看是否有这个点点的定制需求
const r = size / 5;
this.treeLeafNodeAlignDot = renderCircle(this, {
// 和收起展开 icon 保持居中对齐
cx: x + size / 2,
cy: textY + (fontSize - r) / 2,
r,
fill,
// 暂时先写死,后面看是否有这个点点的定制需求
fillOpacity: 0.3,
});
}
isBolderText() {
// 非叶子节点、小计总计,均为粗体
const { isLeaf, isTotals, level } = this.meta;
return (!isLeaf && level === 0) || isTotals;
}
getResizesArea() {
return getOrCreateResizeAreaGroupById(this.spreadsheet, KEY_GROUP_ROW_RESIZE_AREA);
}
drawResizeAreaInLeaf() {
if (!this.meta.isLeaf ||
this.meta.hideRowCellVerticalResize ||
!this.shouldDrawResizeAreaByType('rowCellVertical', this)) {
return;
}
const { x, y, width, height } = this.getBBoxByType();
const resizeStyle = this.getResizeAreaStyle();
const resizeArea = this.getResizesArea();
if (!resizeArea) {
return;
}
const { position, width: headerWidth, viewportHeight: headerHeight, scrollX = 0, scrollY = 0, } = this.getHeaderConfig();
const resizeAreaBBox = {
x,
y: y + height - resizeStyle.size,
width,
height: resizeStyle.size,
};
const isFrozen = this.meta.isFrozen;
const frozenGroupAreas = this.spreadsheet.facet
.frozenGroupAreas;
const frozenRowGroup = frozenGroupAreas[FrozenGroupArea.Row];
const frozenTrailingRowGroup = frozenGroupAreas[FrozenGroupArea.TrailingRow];
const resizeClipAreaBBox = {
x: 0,
y: isFrozen ? 0 : frozenRowGroup.height,
width: headerWidth,
height: isFrozen
? Number.POSITIVE_INFINITY
: headerHeight - frozenRowGroup.height - frozenTrailingRowGroup.height,
};
if (!shouldAddResizeArea(resizeAreaBBox, resizeClipAreaBBox, {
scrollX,
scrollY: isFrozen ? 0 : scrollY,
})) {
return;
}
const { offsetX, offsetY } = this.getHorizontalResizeAreaOffset();
const resizeAreaWidth = this.spreadsheet.isFrozenRowHeader()
? headerWidth - position.x - (x - scrollX)
: width;
const attrs = getResizeAreaAttrs({
theme: resizeStyle,
type: ResizeDirectionType.Vertical,
effect: ResizeAreaEffect.Cell,
offsetX,
offsetY,
width,
height,
meta: this.meta,
cell: this,
});
resizeArea.appendChild(new CustomRect({
style: Object.assign(Object.assign({}, attrs.style), { x: offsetX, y: offsetY + height - resizeStyle.size, width: resizeAreaWidth }),
}, attrs.appendInfo));
}
getHorizontalResizeAreaOffset() {
const { position, viewportHeight: headerHeight, scrollX = 0, scrollY = 0, } = this.getHeaderConfig();
const { x, y } = this.getBBoxByType();
const frozenGroupAreas = this.spreadsheet.facet
.frozenGroupAreas;
const frozenRowGroup = frozenGroupAreas[FrozenGroupArea.Row];
const frozenTrailingRowGroup = frozenGroupAreas[FrozenGroupArea.TrailingRow];
const offsetX = position.x + x - scrollX;
let offsetY = position.y;
if (this.meta.isFrozenHead) {
offsetY += y - frozenRowGroup.y;
}
else if (this.meta.isFrozenTrailing) {
offsetY +=
headerHeight -
frozenTrailingRowGroup.height +
y -
frozenTrailingRowGroup.y;
}
else {
offsetY += y - scrollY;
}
return {
offsetX,
offsetY,
};
}
getContentIndent() {
if (!this.spreadsheet.isHierarchyTreeType()) {
return 0;
}
const { icon, cell } = this.getStyle();
const iconWidth = icon.size + icon.margin.right;
let parent = this.meta.parent;
let sum = 0;
while (parent) {
if (parent.height !== 0) {
sum += iconWidth;
}
parent = parent.parent;
}
if (this.showTreeLeafNodeAlignDot()) {
sum += this.isTreeLevel()
? 0
: cell.padding.right + icon.margin.right;
}
return sum;
}
getTextIndent() {
const { size, margin } = this.getStyle().icon;
const contentIndent = this.getContentIndent();
const treeIconWidth = this.showTreeIcon() ||
(this.isTreeLevel() && this.showTreeLeafNodeAlignDot())
? size + margin.right
: 0;
return contentIndent + treeIconWidth;
}
// 判断当前节点的兄弟节点是否叶子节点
isTreeLevel() {
return find(get(this.meta, 'parent.children'), (cell) => !cell.isLeaf);
}
getMaxTextWidth() {
const { width } = this.getBBoxByType(CellClipBox.CONTENT_BOX);
return width - this.getTextIndent() - this.getActionAndConditionIconWidth();
}
getTextArea() {
const content = this.getBBoxByType(CellClipBox.CONTENT_BOX);
const textIndent = this.getTextIndent();
return Object.assign(Object.assign({}, content), { x: content.x + textIndent, width: content.width - textIndent });
}
handleViewport() {
if (this.meta.isFrozen) {
return {
start: 0,
size: Number.POSITIVE_INFINITY,
};
}
const { scrollY, viewportHeight } = this.getHeaderConfig();
const frozenGroupAreas = this.spreadsheet.facet
.frozenGroupAreas;
const frozenRowGroupHeight = frozenGroupAreas[FrozenGroupArea.Row].height;
const frozenTrailingRowGroupHeight = frozenGroupAreas[FrozenGroupArea.TrailingRow].height;
const viewport = {
start: scrollY + frozenRowGroupHeight,
size: viewportHeight - frozenRowGroupHeight - frozenTrailingRowGroupHeight,
};
return viewport;
}
getContentPosition({ contentWidth = this.getActualTextWidth(), } = {}) {
const textArea = this.getTextArea();
const textStyle = this.getTextStyle();
const { cell, icon: iconStyle } = this.getStyle();
const viewport = this.handleViewport();
const textHeight = this.getActualTextHeight();
const { textStart } = adjustTextIconPositionWhileScrolling(viewport, {
start: textArea.y,
size: textArea.height,
}, {
align: normalizeTextAlign(textStyle.textBaseline),
size: {
textSize: textHeight,
},
padding: {
start: cell.padding.top,
end: cell.padding.bottom,
},
});
const { textX, leftIconX, rightIconX } = getHorizontalTextIconPosition({
bbox: textArea,
textWidth: contentWidth,
textAlign: textStyle.textAlign,
groupedIcons: this.groupedIcons,
iconStyle,
isCustomRenderer: !!this.getRenderer(),
});
const iconY = getVerticalIconPosition(iconStyle === null || iconStyle === void 0 ? void 0 : iconStyle.size, textStart, textHeight, textStyle.textBaseline);
this.leftIconPosition = {
x: leftIconX,
y: iconY,
};
this.rightIconPosition = {
x: rightIconX,
y: iconY,
};
return { x: textX, y: textStart };
}
getTextPosition() {
return this.getContentPosition();
}
getResizedTextMaxLines() {
var _a, _b, _c, _d, _e;
const { rowCell } = this.spreadsheet.options.style;
return ((_d = (_b = (_a = rowCell === null || rowCell === void 0 ? void 0 : rowCell.maxLinesByField) === null || _a === void 0 ? void 0 : _a[this.meta.id]) !== null && _b !== void 0 ? _b : (_c = rowCell === null || rowCell === void 0 ? void 0 : rowCell.maxLinesByField) === null || _c === void 0 ? void 0 : _c[this.meta.field]) !== null && _d !== void 0 ? _d : this.getMaxLinesByCustomHeight({
isCustomHeight: (_e = this.meta.extra) === null || _e === void 0 ? void 0 : _e.isCustomHeight,
}));
}
}
//# sourceMappingURL=row-cell.js.map