diff --git a/packages/s2-core/__tests__/bugs/issue-2501-spec.ts b/packages/s2-core/__tests__/bugs/issue-2501-spec.ts index 143c76f1ad..ac2670fce1 100644 --- a/packages/s2-core/__tests__/bugs/issue-2501-spec.ts +++ b/packages/s2-core/__tests__/bugs/issue-2501-spec.ts @@ -29,10 +29,10 @@ const s2Options: S2Options = { describe('Table Sheet Row Offsets Tests', () => { let s2: SpreadSheet; - beforeEach(() => { + beforeEach(async () => { s2 = new TableSheet(getContainer(), s2DataConfig, s2Options); - s2.render(); + await s2.render(); }); test('should get correctly row offset data', () => { diff --git a/packages/s2-core/__tests__/spreadsheet/hidden-columns-spec.ts b/packages/s2-core/__tests__/spreadsheet/hidden-columns-spec.ts index 19cc8de525..c3d8272819 100644 --- a/packages/s2-core/__tests__/spreadsheet/hidden-columns-spec.ts +++ b/packages/s2-core/__tests__/spreadsheet/hidden-columns-spec.ts @@ -31,7 +31,7 @@ describe('SpreadSheet Hidden Columns Tests', () => { }); afterEach(() => { - tableSheet.destroy(); + // tableSheet.destroy(); }); test('should get init column node', () => { @@ -205,6 +205,60 @@ describe('SpreadSheet Hidden Columns Tests', () => { expect(detail.displaySiblingNode.next?.field).toEqual('city'); expect(detail.hideColumnNodes).toHaveLength(1); }); + + // https://github.com/antvis/S2/issues/2495 + test('should reset latest hidden columns detail when hiddenColumnFields changed', async () => { + const hiddenColumns = ['province', 'city']; + const sheet = new TableSheet(getContainer(), mockTableDataConfig, { + ...s2Options, + interaction: { + hiddenColumnFields: hiddenColumns, + }, + }); + + await sheet.render(); + + sheet.setOptions({ + interaction: { + hiddenColumnFields: ['city'], + }, + }); + + await sheet.render(false); + + const hiddenColumnsDetail = sheet.store.get('hiddenColumnsDetail', []); + const [cityDetail] = hiddenColumnsDetail; + + expect(sheet.options.interaction!.hiddenColumnFields).toEqual(['city']); + expect(hiddenColumnsDetail).toHaveLength(1); + expect(cityDetail.hideColumnNodes).toHaveLength(1); + expect(cityDetail.hideColumnNodes[0].field).toEqual('city'); + }); + + test('should clear hidden columns detail if hiddenColumnFields is empty', async () => { + const hiddenColumns = ['province', 'city']; + const sheet = new TableSheet(getContainer(), mockTableDataConfig, { + ...s2Options, + interaction: { + hiddenColumnFields: hiddenColumns, + }, + }); + + await sheet.render(); + + sheet.setOptions({ + interaction: { + hiddenColumnFields: [], + }, + }); + + await sheet.render(false); + + const hiddenColumnsDetail = sheet.store.get('hiddenColumnsDetail', []); + + expect(sheet.options.interaction!.hiddenColumnFields).toBeEmpty(); + expect(hiddenColumnsDetail).toBeEmpty(); + }); }); describe('PivotSheet', () => { @@ -229,7 +283,7 @@ describe('SpreadSheet Hidden Columns Tests', () => { }); afterEach(() => { - // pivotSheet.destroy(); + pivotSheet.destroy(); }); test('should get init column node', () => { diff --git a/packages/s2-core/__tests__/unit/common/icons/gui-icon-spec.ts b/packages/s2-core/__tests__/unit/common/icons/gui-icon-spec.ts index 8f32dcde86..6c33d32faf 100644 --- a/packages/s2-core/__tests__/unit/common/icons/gui-icon-spec.ts +++ b/packages/s2-core/__tests__/unit/common/icons/gui-icon-spec.ts @@ -1,6 +1,6 @@ import { Group } from '@antv/g'; import { registerIcon } from '../../../../src/common/icons'; -import { sleep } from '../../../util/helpers'; +import { sleep, createPivotSheet } from '../../../util/helpers'; import { CustomImage } from '@/engine/CustomImage'; import { GuiIcon } from '@/common/icons/gui-icon'; import { ArrowDown } from '@/common/icons/svg/svgs'; @@ -102,4 +102,42 @@ describe('GuiIcon Tests', () => { icon.setImageAttrs({ fill: 'red' }); expect(spy).toHaveBeenCalled(); }); + + // https://github.com/antvis/S2/issues/2513 + test('should support cross origin for online url', () => { + const s2 = createPivotSheet({ + width: 200, + height: 200, + customSVGIcons: [ + { + name: 'Filter', + svg: 'https://gw.alipayobjects.com/zos/antfincdn/gu1Fsz3fw0/filter%26sort_filter.svg', + }, + ], + headerActionIcons: [ + { + icons: ['Filter'], + belongsCell: 'colCell', + defaultHide: false, + }, + { + icons: ['Filter'], + belongsCell: 'rowCell', + defaultHide: false, + }, + { + icons: ['Filter'], + belongsCell: 'cornerCell', + defaultHide: false, + }, + ], + }); + + async function render() { + await s2.render(); + s2.getCanvasElement().toDataURL(); + } + + expect(render).not.toThrow(); + }); }); diff --git a/packages/s2-core/src/common/icons/gui-icon.ts b/packages/s2-core/src/common/icons/gui-icon.ts index 6812659e49..fbadf71bf6 100644 --- a/packages/s2-core/src/common/icons/gui-icon.ts +++ b/packages/s2-core/src/common/icons/gui-icon.ts @@ -75,6 +75,8 @@ export class GuiIcon extends Group { * 或者 online 链接 */ img.src = svg; + // https://github.com/antvis/S2/issues/2513 + img.crossOrigin = 'anonymous'; } else { // 传入 svg 字符串(支持颜色fill) if (fill) { diff --git a/packages/s2-core/src/facet/base-facet.ts b/packages/s2-core/src/facet/base-facet.ts index 56c366ffa7..2c8d8c9aed 100644 --- a/packages/s2-core/src/facet/base-facet.ts +++ b/packages/s2-core/src/facet/base-facet.ts @@ -181,6 +181,10 @@ export abstract class BaseFacet { scrollY: number, ): PanelIndexes; + public abstract getViewCellHeights( + layoutResult?: LayoutResult, + ): ViewCellHeights; + public abstract getViewCellHeights(): ViewCellHeights; public abstract addDataCell(cell: DataCell): void; diff --git a/packages/s2-core/src/facet/pivot-facet.ts b/packages/s2-core/src/facet/pivot-facet.ts index 6c9a2f7a32..780bbbb08e 100644 --- a/packages/s2-core/src/facet/pivot-facet.ts +++ b/packages/s2-core/src/facet/pivot-facet.ts @@ -723,10 +723,10 @@ export class PivotFacet extends FrozenFacet { const { rows = [] } = this.spreadsheet.dataSet.fields; // 1. 用户拖拽或手动指定的行头宽度优先级最高 - const customRowWidth = this.getCellCustomSize(null, rowCell?.width!); + const customRowCellWidth = this.getCellCustomSize(null, rowCell?.width!); - if (isNumber(customRowWidth)) { - return customRowWidth; + if (isNumber(customRowCellWidth)) { + return customRowCellWidth; } // 2. 然后是计算 (+ icon province/city/level) @@ -737,16 +737,25 @@ export class PivotFacet extends FrozenFacet { const { bolderText: cornerCellTextStyle, icon: cornerIconStyle } = this.spreadsheet.theme.cornerCell!; - // 初始化角头时,保证其在树形模式下不换行,给与两个icon的宽度空余(tree icon 和 action icon),减少复杂的 action icon 判断 + /** + * 初始化角头时,保证其在树形模式下不换行, 给与两个icon的宽度空余(tree icon 和 action icon),减少复杂的 action icon 判断 + * 额外增加 1,当内容和容器宽度恰好相等时会出现换行 + */ const maxLabelWidth = this.spreadsheet.measureTextWidth(treeHeaderLabel, cornerCellTextStyle) + - cornerIconStyle!.size! * 2 + - cornerIconStyle!.margin!.left! + - cornerIconStyle!.margin!.right! + - this.rowCellTheme?.padding?.left! + - this.rowCellTheme?.padding?.right!; + cornerIconStyle.size * 2 + + cornerIconStyle.margin?.left + + cornerIconStyle.margin?.right + + this.rowCellTheme.padding?.left + + this.rowCellTheme.padding?.right + + 1; + + const width = Math.max( + customRowCellWidth ?? DEFAULT_TREE_ROW_CELL_WIDTH, + maxLabelWidth, + ); - return Math.max(DEFAULT_TREE_ROW_CELL_WIDTH, maxLabelWidth); + return Number.isNaN(width) ? DEFAULT_TREE_ROW_CELL_WIDTH : width; } /** diff --git a/packages/s2-core/src/sheet-type/pivot-sheet.ts b/packages/s2-core/src/sheet-type/pivot-sheet.ts index 2cce6f7bd2..7ae5725d23 100644 --- a/packages/s2-core/src/sheet-type/pivot-sheet.ts +++ b/packages/s2-core/src/sheet-type/pivot-sheet.ts @@ -83,11 +83,9 @@ export class PivotSheet extends SpreadSheet { } protected buildFacet() { - const defaultCell = (facet: ViewMeta) => new DataCell(facet, this); + const defaultCell = (viewMeta: ViewMeta) => new DataCell(viewMeta, this); - this.setOptions({ - dataCell: this.options.dataCell ?? defaultCell, - }); + this.options.dataCell ??= defaultCell; this.facet?.destroy(); this.facet = this.options.facet?.(this) ?? new PivotFacet(this); this.facet.render(); diff --git a/packages/s2-core/src/sheet-type/spread-sheet.ts b/packages/s2-core/src/sheet-type/spread-sheet.ts index 1585c1e20d..1d3929e21d 100644 --- a/packages/s2-core/src/sheet-type/spread-sheet.ts +++ b/packages/s2-core/src/sheet-type/spread-sheet.ts @@ -391,9 +391,16 @@ export abstract class SpreadSheet extends EE { this.initTooltip(); } + this.resetHiddenColumnsDetailInfoIfNeeded(); this.registerIcons(); } + private resetHiddenColumnsDetailInfoIfNeeded() { + if (!isEmpty(this.options.interaction?.hiddenColumnFields)) { + this.store.set('hiddenColumnsDetail', []); + } + } + private async doRender(reloadData = true, options: S2RenderOptions = {}) { // 防止表格卸载后, 再次调用 render 函数的报错 if ( diff --git a/packages/s2-core/src/sheet-type/table-sheet.ts b/packages/s2-core/src/sheet-type/table-sheet.ts index f6ecd71276..980d21712f 100644 --- a/packages/s2-core/src/sheet-type/table-sheet.ts +++ b/packages/s2-core/src/sheet-type/table-sheet.ts @@ -77,17 +77,15 @@ export class TableSheet extends SpreadSheet { protected bindEvents() {} protected buildFacet() { - const defaultCell = (facet: ViewMeta) => { - if (this.options.showSeriesNumber && facet.colIndex === 0) { - return new TableSeriesNumberCell(facet, this); + const defaultCell = (viewMeta: ViewMeta) => { + if (this.options.showSeriesNumber && viewMeta.colIndex === 0) { + return new TableSeriesNumberCell(viewMeta, this); } - return new TableDataCell(facet, this); + return new TableDataCell(viewMeta, this); }; - this.setOptions({ - dataCell: this.options.dataCell ?? defaultCell, - }); + this.options.dataCell ??= defaultCell; this.facet?.destroy(); this.facet = this.options.facet?.(this) ?? new TableFacet(this); this.facet.render(); diff --git a/packages/s2-vue/src/hooks/useSheetUpdate.ts b/packages/s2-vue/src/hooks/useSheetUpdate.ts index 9313b8ccdf..34c12bcd17 100644 --- a/packages/s2-vue/src/hooks/useSheetUpdate.ts +++ b/packages/s2-vue/src/hooks/useSheetUpdate.ts @@ -2,10 +2,10 @@ import type { S2Options, SpreadSheet } from '@antv/s2'; import { reactive, watch, type ShallowRef, isProxy } from 'vue'; import type { BaseSheetProps } from '../utils/initPropAndEmits'; -/* - * props会将所有属性用shallowReactive包裹起来: - * 1. 如果是对dataCfg或者options直接替换, 那么只需要简单的通过 ()=>props.dataCfg 这种getter就能监听到 - * 2. 如果 dataCfg任然是 reactive 数据,那么就存在直接修改了其中某一个属性的情况,这个时候就需要对所有的属性遍历来关联watch属性 +/** + * props 会将所有属性用 shallowReactive 包裹起来: + * 1. 如果是对 dataCfg 或者 options 直接替换, 那么只需要简单的通过 ()=> props.dataCfg 这种 getter 就能监听到 + * 2. 如果 dataCfg 任然是 reactive 数据,那么就存在直接修改了其中某一个属性的情况,这个时候就需要对所有的属性遍历来关联 watch 属性 * ? 如果数量特别多时,遍历可能存在性能问题,先暂时观察一下 */ export const useSheetUpdate = ( diff --git a/s2-site/docs/manual/basic/sheet-type/table-mode.zh.md b/s2-site/docs/manual/basic/sheet-type/table-mode.zh.md index 3a965af573..616f09d6a1 100644 --- a/s2-site/docs/manual/basic/sheet-type/table-mode.zh.md +++ b/s2-site/docs/manual/basic/sheet-type/table-mode.zh.md @@ -155,7 +155,7 @@ const App = () => { ```ts const s2Options = { showSeriesNumber: true, - seriesNumberText: '自定义序号标题' // 默认 "序号" + seriesNumberText: '序号', } ``` diff --git a/s2-site/examples/analysis/sort/demo/table-sort.tsx b/s2-site/examples/analysis/sort/demo/table-sort.tsx index 43590ccc97..71069e9d0f 100644 --- a/s2-site/examples/analysis/sort/demo/table-sort.tsx +++ b/s2-site/examples/analysis/sort/demo/table-sort.tsx @@ -111,7 +111,7 @@ fetch('https://assets.antv.antgroup.com/s2/basic-table-mode.json') sortFieldId: 'price', sortMethod: 'DESC', query: { - city: '浙江', + province: '浙江', }, }, ], diff --git a/s2-site/examples/basic/table/demo/table.ts b/s2-site/examples/basic/table/demo/table.ts index 2d30585aad..11a387b7d5 100644 --- a/s2-site/examples/basic/table/demo/table.ts +++ b/s2-site/examples/basic/table/demo/table.ts @@ -37,9 +37,9 @@ fetch('https://assets.antv.antgroup.com/s2/basic-table-mode.json') width: 600, height: 480, // 显示序号 - // showSeriesNumber: true, + showSeriesNumber: true, // 自定义序号列文本, 默认 "序号" - // seriesNumberText: '自定义序号标题', + seriesNumberText: '序号', }; const s2 = new TableSheet(container, s2DataConfig, s2Options); diff --git a/s2-site/examples/interaction/advanced/demo/pivot-hide-columns.ts b/s2-site/examples/interaction/advanced/demo/pivot-hide-columns.ts index b3bd8d9947..315689229e 100644 --- a/s2-site/examples/interaction/advanced/demo/pivot-hide-columns.ts +++ b/s2-site/examples/interaction/advanced/demo/pivot-hide-columns.ts @@ -1,5 +1,33 @@ import { PivotSheet, S2Event, S2Options } from '@antv/s2'; +function hideSelectedColumns(s2) { + // 兼容多选 + const selectedColumnNodes = s2.interaction + .getActiveCells() + .map((cell) => cell.getMeta()); + + const selectedColumnFields = selectedColumnNodes.map((node) => node.id); + s2.interaction.hideColumns(selectedColumnFields, true); +} + +function getTooltipContent(cell, options) { + const { spreadsheet, isLeaf } = cell.getMeta(); + + if (!isLeaf || !spreadsheet.options.tooltip.operation.hiddenColumns) { + return null; + } + + const button = document.createElement('button'); + button.type = 'button'; + button.innerHTML = '隐藏'; + button.className = 'ant-btn'; + button.addEventListener('click', () => { + hideSelectedColumns(spreadsheet); + }); + + return button; +} + fetch( 'https://gw.alipayobjects.com/os/bmw-prod/2a5dbbc8-d0a7-4d02-b7c9-34f6ca63cff6.json', ) @@ -21,6 +49,7 @@ fetch( // 开启手动隐藏, 叶子节点有效 hiddenColumns: true, }, + content: getTooltipContent, }, }; diff --git a/s2-site/examples/interaction/advanced/demo/table-hide-columns.ts b/s2-site/examples/interaction/advanced/demo/table-hide-columns.ts index de069134ff..b84f7ec330 100644 --- a/s2-site/examples/interaction/advanced/demo/table-hide-columns.ts +++ b/s2-site/examples/interaction/advanced/demo/table-hide-columns.ts @@ -1,11 +1,44 @@ +/* eslint-disable no-console */ import { TableSheet, S2Event, - S2DataConfig, - S2Options, S2Options, + S2DataConfig, + SpreadSheet, + S2CellType, + TooltipShowOptions, } from '@antv/s2'; +function hideSelectedColumns(s2: SpreadSheet) { + // 兼容多选 + const selectedColumnNodes = s2.interaction + .getActiveCells() + .map((cell) => cell.getMeta()); + + const selectedColumnFields = selectedColumnNodes.map((node) => node.field); + + s2.interaction.hideColumns(selectedColumnFields, true); +} + +function getTooltipContent(cell: S2CellType, options: TooltipShowOptions) { + const { spreadsheet, isLeaf } = cell.getMeta(); + + if (!isLeaf || !spreadsheet.options.tooltip?.operation?.hiddenColumns) { + return null; + } + + const button = document.createElement('button'); + + button.type = 'button'; + button.innerHTML = '隐藏'; + button.className = 'ant-btn'; + button.addEventListener('click', () => { + hideSelectedColumns(spreadsheet); + }); + + return button; +} + fetch('https://assets.antv.antgroup.com/s2/basic-table-mode.json') .then((res) => res.json()) .then(async (data) => { @@ -52,6 +85,7 @@ fetch('https://assets.antv.antgroup.com/s2/basic-table-mode.json') // 开启手动隐藏 hiddenColumns: true, }, + content: getTooltipContent, }, }; @@ -60,6 +94,7 @@ fetch('https://assets.antv.antgroup.com/s2/basic-table-mode.json') s2.on(S2Event.COL_CELL_EXPANDED, (cell) => { console.log('列头展开', cell); }); + s2.on( S2Event.COL_CELL_HIDDEN, (currentHiddenColumnsInfo, hiddenColumnsDetail) => { diff --git a/s2-site/examples/react-component/switcher/demo/pure-switcher.tsx b/s2-site/examples/react-component/switcher/demo/pure-switcher.tsx index 15eb42521b..1a8b0e2355 100644 --- a/s2-site/examples/react-component/switcher/demo/pure-switcher.tsx +++ b/s2-site/examples/react-component/switcher/demo/pure-switcher.tsx @@ -7,11 +7,14 @@ const switcherFields = { allowEmpty: false, }, columns: { - items: [{ id: 'type' }], + items: [{ id: 'type', displayName: '类型 (type)' }], }, values: { selectable: true, - items: [{ id: 'price' }, { id: 'cost' }], + items: [ + { id: 'price', checked: true }, + { id: 'cost', checked: false }, + ], }, }; diff --git a/s2-site/examples/react-component/switcher/demo/table.tsx b/s2-site/examples/react-component/switcher/demo/table.tsx index ed3281f374..58b0246d31 100644 --- a/s2-site/examples/react-component/switcher/demo/table.tsx +++ b/s2-site/examples/react-component/switcher/demo/table.tsx @@ -26,11 +26,11 @@ fetch( columns: { selectable: true, items: [ - { id: 'province' }, - { id: 'city' }, - { id: 'type' }, - { id: 'sub_type' }, - { id: 'number' }, + { id: 'province', displayName: '省份 (province)' }, + { id: 'city', displayName: '城市 (city)' }, + { id: 'type', displayName: '类别 (type)' }, + { id: 'sub_type', displayName: '子类别 (sub_type)' }, + { id: 'number', displayName: '数量 (number)' }, ], }, };