From 95d67ca02b774aed426a179a16aa27f0c172356e Mon Sep 17 00:00:00 2001 From: Jinke Li Date: Fri, 23 Feb 2024 16:10:04 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E8=A1=A8=E7=9A=84=E8=BE=93=E5=85=A5=E6=A1=86=E6=9C=AA=E5=9B=9E?= =?UTF-8?q?=E5=A1=AB=E6=A0=BC=E5=BC=8F=E5=8C=96=E5=90=8E=E7=9A=84=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=20close=20#2528=20(#2549)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 修复编辑表的输入框未回填格式化后的数据 close #2528 * fix: 修复循环引用 * fix: 编辑后不应该再次格式化 * fix: 移除定义 * test: 修复单测 * test: 增加单测 --- .../s2-core/__tests__/bugs/issue-2528-spec.ts | 58 +++++ .../__tests__/unit/facet/pivot-facet-spec.ts | 1 + .../__tests__/unit/facet/table-facet-spec.ts | 1 + packages/s2-core/__tests__/util/helpers.ts | 2 + packages/s2-core/src/cell/data-cell.ts | 11 +- .../s2-core/src/data-set/base-data-set.ts | 5 + packages/s2-react/playground/config.ts | 208 ++++++++++++++++++ .../custom-cell/edit-cell/index.tsx | 158 ++++++------- .../sheets/editable-sheet/edit-cell/index.tsx | 199 ----------------- .../sheets/editable-sheet/index.tsx | 6 +- packages/s2-shared/src/interface.ts | 4 +- s2-site/docs/api/basic-class/base-cell.zh.md | 2 +- .../docs/api/basic-class/base-data-set.zh.md | 1 + .../docs/api/components/sheet-component.zh.md | 3 +- .../manual/basic/analysis/editable-mode.zh.md | 7 +- .../react-component/sheet/demo/editable.tsx | 9 +- 16 files changed, 384 insertions(+), 291 deletions(-) create mode 100644 packages/s2-core/__tests__/bugs/issue-2528-spec.ts create mode 100644 packages/s2-react/playground/config.ts delete mode 100644 packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx diff --git a/packages/s2-core/__tests__/bugs/issue-2528-spec.ts b/packages/s2-core/__tests__/bugs/issue-2528-spec.ts new file mode 100644 index 0000000000..db5b8ec91a --- /dev/null +++ b/packages/s2-core/__tests__/bugs/issue-2528-spec.ts @@ -0,0 +1,58 @@ +/** + * @description spec for issue #2528 + * https://github.com/antvis/S2/issues/2528 + */ + +import * as mockDataConfig from '../data/simple-table-data.json'; +import { getContainer } from '../util/helpers'; +import type { SpreadSheet, S2DataConfig, S2Options } from '@/index'; +import { TableSheet } from '@/sheet-type'; + +const s2DataConfig: S2DataConfig = { + ...mockDataConfig, + meta: [{ field: 'cost', formatter: (v) => `${v}-@` }], +}; + +const s2Options: S2Options = { + width: 800, + height: 400, +}; + +describe('Table Sheet Editable Formatter Tests', () => { + let s2: SpreadSheet; + + beforeEach(() => { + s2 = new TableSheet(getContainer(), s2DataConfig, s2Options); + + s2.render(); + }); + + test('should get formatted data', () => { + const costValues = s2.interaction + .getPanelGroupAllDataCells() + .filter((cell) => cell.getMeta().valueField === 'cost') + .map((cell) => cell.getFieldValue()); + + expect(costValues).toEqual(['2-@', '2-@', '2-@']); + }); + + test('should only format data once after data edited', () => { + const id = '0-root[&]cost'; + const inputValue = 'test'; + + // 模拟一次编辑 (更新第一行的 cost) + const displayData = s2.dataSet.getDisplayDataSet(); + + displayData[0].cost = inputValue; + s2.dataSet.displayFormattedValueMap?.set(id, inputValue); + + s2.render(); + + const costValues = s2.interaction + .getPanelGroupAllDataCells() + .filter((cell) => cell.getMeta().valueField === 'cost') + .map((cell) => cell.getFieldValue()); + + expect(costValues).toEqual([inputValue, '2-@', '2-@']); + }); +}); diff --git a/packages/s2-core/__tests__/unit/facet/pivot-facet-spec.ts b/packages/s2-core/__tests__/unit/facet/pivot-facet-spec.ts index 7017d81773..e8c87f83da 100644 --- a/packages/s2-core/__tests__/unit/facet/pivot-facet-spec.ts +++ b/packages/s2-core/__tests__/unit/facet/pivot-facet-spec.ts @@ -131,6 +131,7 @@ jest.mock('@/data-set/pivot-data-set', () => { getDimensionValues: actualPivotDataSet.prototype.getDimensionValues, getFieldsAndPivotMetaByField: actualPivotDataSet.prototype.getFieldsAndPivotMetaByField, + displayFormattedValueMap: new Map(), }; }), }; diff --git a/packages/s2-core/__tests__/unit/facet/table-facet-spec.ts b/packages/s2-core/__tests__/unit/facet/table-facet-spec.ts index b0b7eeb74e..38208e3579 100644 --- a/packages/s2-core/__tests__/unit/facet/table-facet-spec.ts +++ b/packages/s2-core/__tests__/unit/facet/table-facet-spec.ts @@ -63,6 +63,7 @@ jest.mock('@/sheet-type', () => { }, dataSet: { isEmpty: jest.fn(), + displayFormattedValueMap: new Map(), }, isHierarchyTreeType: jest.fn(), getCanvasElement: () => diff --git a/packages/s2-core/__tests__/util/helpers.ts b/packages/s2-core/__tests__/util/helpers.ts index ba66984dd1..736ba8a3db 100644 --- a/packages/s2-core/__tests__/util/helpers.ts +++ b/packages/s2-core/__tests__/util/helpers.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-lines-per-function */ import fs from 'fs'; import path from 'path'; import { dsvFormat } from 'd3-dsv'; @@ -101,6 +102,7 @@ export const createFakeSpreadSheet = (config?: { return []; }, getField: jest.fn(), + displayFormattedValueMap: new Map(), } as unknown as any; const layoutResult: LayoutResult = { diff --git a/packages/s2-core/src/cell/data-cell.ts b/packages/s2-core/src/cell/data-cell.ts index f50a7d5469..1389747753 100644 --- a/packages/s2-core/src/cell/data-cell.ts +++ b/packages/s2-core/src/cell/data-cell.ts @@ -251,8 +251,8 @@ export class DataCell extends BaseCell { } } - public setMeta(viewMeta: ViewMeta) { - super.setMeta(viewMeta); + public setMeta(viewMeta: Partial) { + super.setMeta(viewMeta as ViewMeta); this.initCell(); } @@ -358,12 +358,15 @@ export class DataCell extends BaseCell { }; } - const { rowId, valueField, fieldValue, data } = this.meta; + const { rowId, valueField, fieldValue, data, id } = this.meta; + const displayFormattedValue = + this.spreadsheet.dataSet.displayFormattedValueMap?.get(id); const rowMeta = this.spreadsheet.dataSet.getFieldMeta(rowId!); const fieldId = rowMeta ? rowId : valueField; const formatter = this.spreadsheet.dataSet.getFieldFormatter(fieldId!); // TODO: 这里只用 formatter(fieldValue, this.meta) 即可, 为了保持兼容, 暂时在第三个参入传入 meta 信息 - const formattedValue = formatter(fieldValue, data, this.meta); + const formattedValue = + displayFormattedValue ?? formatter(fieldValue, data, this.meta); return { value: fieldValue, diff --git a/packages/s2-core/src/data-set/base-data-set.ts b/packages/s2-core/src/data-set/base-data-set.ts index aa099cf125..661f7542bd 100644 --- a/packages/s2-core/src/data-set/base-data-set.ts +++ b/packages/s2-core/src/data-set/base-data-set.ts @@ -78,6 +78,11 @@ export abstract class BaseDataSet { */ protected displayData: RawData[]; + /** + * 单元格所对应格式化后的值(用于编辑表) + */ + public displayFormattedValueMap = new Map(); + public constructor(spreadsheet: SpreadSheet) { this.spreadsheet = spreadsheet; } diff --git a/packages/s2-react/playground/config.ts b/packages/s2-react/playground/config.ts new file mode 100644 index 0000000000..40dba2668a --- /dev/null +++ b/packages/s2-react/playground/config.ts @@ -0,0 +1,208 @@ +import { isUpDataValue, type Columns, customMerge } from '@antv/s2'; +import type { S2DataConfig, ThemeCfg } from '@antv/s2'; +import { getBaseSheetComponentOptions } from '@antv/s2-shared'; +import type { SliderSingleProps } from 'antd'; +import { + data, + totalData, + meta, + fields, +} from '../__tests__/data/mock-dataset.json'; +import type { SheetComponentOptions } from '../src/components'; + +export const tableSheetSingleColumns: Columns = [ + 'province', + 'city', + 'type', + 'sub_type', + 'number', +]; + +export const tableSheetMultipleColumns: Columns = [ + { + key: 'area', + children: ['province', 'city'], + }, + 'type', + { + key: 'money', + children: [{ key: 'price' }, 'number'], + }, +]; + +export const tableSheetDataCfg: S2DataConfig = { + data, + totalData, + meta: [{ field: 'number', formatter: (v) => `${v}-@` }, ...meta], + fields: { + columns: tableSheetSingleColumns, + }, +}; + +export const pivotSheetDataCfg: S2DataConfig = { + data, + totalData, + meta, + fields, +}; + +export const pivotSheetDataCfgForCompactMode = customMerge(pivotSheetDataCfg, { + data: [ + ...pivotSheetDataCfg.data, + { + province: '浙江', + city: '杭州', + type: '笔', + price: '11111111', + }, + { + province: '浙江', + city: '杭州', + type: '纸张', + price: '2', + }, + { + province: '浙江', + city: '舟山', + type: '笔', + price: '2', + }, + { + province: '浙江', + city: '舟山', + type: '纸张', + price: '133.333', + }, + ], +}); + +export const s2Options: SheetComponentOptions = { + debug: true, + width: 600, + height: 400, + frozenFirstRow: false, + showSeriesNumber: false, + interaction: { + enableCopy: true, + copyWithHeader: true, + copyWithFormat: true, + // 防止 mac 触摸板横向滚动触发浏览器返回, 和移动端下拉刷新 + overscrollBehavior: 'none', + brushSelection: { + data: true, + col: true, + row: true, + }, + }, + tooltip: { + showTooltip: true, + operation: { + trend: true, + }, + }, + conditions: { + text: [], + interval: [ + { + field: 'number', + mapping() { + return { + fill: '#80BFFF', + // 自定义柱状图范围 + isCompare: true, + maxValue: 8000, + minValue: 300, + }; + }, + }, + ], + }, + headerActionIcons: [ + { + iconNames: ['SortDown'], + belongsCell: 'colCell', + defaultHide: true, + }, + { + iconNames: ['SortDown'], + belongsCell: 'rowCell', + defaultHide: true, + }, + { + iconNames: ['SortDown'], + belongsCell: 'cornerCell', + defaultHide: true, + }, + ], + hierarchyType: 'grid', + style: { + colCfg: { + hideMeasureColumn: false, + }, + rowCfg: { + width: 100, + }, + cellCfg: { + height: 50, + width: 100, + }, + }, +}; + +export const s2ThemeConfig: ThemeCfg = { + name: 'default', + theme: {}, +}; + +export const sliderOptions: SliderSingleProps = { + min: 0, + max: 10, + step: 0.1, + marks: { + 0.2: '0.2', + 1: '1 (默认)', + 2: '2', + 10: '10', + }, +}; + +export const mockGridAnalysisOptions: SheetComponentOptions = { + width: 1600, + height: 600, + style: { + layoutWidthType: 'colAdaptive', + cellCfg: { + width: 400, + height: 100, + valuesCfg: { + widthPercent: [40, 0.2, 0.2, 0.2], + }, + }, + }, + tooltip: { showTooltip: false }, + interaction: { + selectedCellsSpotlight: true, + }, + conditions: { + text: [ + { + mapping: (value, cellInfo) => { + const { colIndex } = cellInfo; + + if (colIndex <= 1) { + return { + fill: '#000', + }; + } + + return { + fill: isUpDataValue(value) ? '#FF4D4F' : '#29A294', + }; + }, + }, + ], + }, +}; + +export const defaultOptions = + getBaseSheetComponentOptions(s2Options); diff --git a/packages/s2-react/src/components/sheets/editable-sheet/custom-cell/edit-cell/index.tsx b/packages/s2-react/src/components/sheets/editable-sheet/custom-cell/edit-cell/index.tsx index d2b2af977a..bbbeb56ed8 100644 --- a/packages/s2-react/src/components/sheets/editable-sheet/custom-cell/edit-cell/index.tsx +++ b/packages/s2-react/src/components/sheets/editable-sheet/custom-cell/edit-cell/index.tsx @@ -1,23 +1,16 @@ +import type { Event as CanvasEvent } from '@antv/g-canvas'; import { - GEvent, S2Event, - S2_PREFIX_CLS, SpreadSheet, - type DataItem, + customMerge, + type RawData, type S2CellType, + type TableDataCell, type ViewMeta, - type RawData, } from '@antv/s2'; import { Input } from 'antd'; -import { isNil, merge, pick } from 'lodash'; -import React, { - memo, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import { isNil, pick } from 'lodash'; +import React, { useMemo } from 'react'; import { useSpreadSheetInstance } from '../../../../../context/SpreadSheetContext'; import { useS2Event } from '../../../../../hooks'; import { @@ -30,38 +23,47 @@ export interface CustomProps { style: React.CSSProperties; onChange: (value: string) => void; onSave: () => void; - value: DataItem; + value: string; spreadsheet: SpreadSheet; - cell: S2CellType | null; + cell: S2CellType; } +type DateCellEdit = (meta: ViewMeta, cell: TableDataCell) => void; + type EditCellProps = { - onChange?: (val: RawData[]) => void; - onDataCellEditEnd?: (meta: ViewMeta) => void; + /** + * @deprecated use `onDataCellEditEnd` instead. + */ + onChange?: (data: RawData[]) => void; + onDataCellEditStart?: DateCellEdit; + onDataCellEditEnd?: DateCellEdit; trigger?: number; CustomComponent?: React.FunctionComponent; }; -const EDIT_CELL_CLASS = `${S2_PREFIX_CLS}-edit-cell`; - function EditCellComponent( - props: InvokeComponentProps<{ cell: S2CellType } & EditCellProps>, + props: InvokeComponentProps<{ event: CanvasEvent } & EditCellProps>, ) { const { params, resolver } = props; const s2 = useSpreadSheetInstance(); - const { cell, onChange, onDataCellEditEnd, CustomComponent } = params; - - const { left, top, width, height } = useMemo(() => { - const rect = s2?.getCanvasElement().getBoundingClientRect(); - - const modified = { + const { + event, + onChange, + onDataCellEditStart, + onDataCellEditEnd, + CustomComponent, + } = params; + + const cell = s2.getCell(event.target); + const { left, top, width, height } = React.useMemo>(() => { + const rect = s2.getCanvasElement()?.getBoundingClientRect(); + + return { left: window.scrollX + rect.left, top: window.scrollY + rect.top, width: rect.width, height: rect.height, }; - - return modified; }, [s2]); const { @@ -91,69 +93,69 @@ function EditCellComponent( return cellMeta; }, [cell, s2]); - const [inputVal, setInputVal] = useState( - cell?.getMeta()?.fieldValue, - ); + const [inputVal, setInputVal] = React.useState(() => cell!.getFieldValue()); - const inputRef = useRef(null); - const containerRef = useRef(null); - - /** - * 在 Enter 确定输入后,会执行 onSave 逻辑,然后组件被销毁后,又会通过 blur 再执行一遍,这个时候 displayData 顺序已经变了,再执行获取的 rowIndex 是错误的 - * 本质上,还是和 invokeComponent 中在移除 dom 节点前,没有提前 unmount 组件有关(1.x 中进行了处理,2.x 中现在只对其中一个分支处理了) - * */ - const hasSaved = useRef(false); - - useEffect(() => { - setTimeout(() => { - // 防止触发表格全选 - containerRef.current?.click(); - // 开启 preventScroll, 防止页面有滚动条时触发滚动 - inputRef.current?.focus({ preventScroll: true }); - }); - }, []); + const inputRef = React.useRef(null); + const containerRef = React.useRef(null); const onSave = () => { - if (!cell || hasSaved.current) { - return; - } - - const { rowIndex, valueField } = cell.getMeta(); + const { rowIndex, valueField, id } = cell!.getMeta(); const displayData = s2.dataSet.getDisplayDataSet(); displayData[rowIndex][valueField] = inputVal; - s2.render(true); + // 编辑后的值作为格式化后的结果, formatter 不再触发, 避免二次格式化 + s2.dataSet.displayFormattedValueMap?.set(id, inputVal); + s2.render(); - const meta = merge(cell.getMeta(), { + const editedMeta = customMerge(cell!.getMeta(), { fieldValue: inputVal, - valueField, data: { [valueField]: inputVal, }, - }) as ViewMeta; + }); - onDataCellEditEnd?.(meta); + onDataCellEditEnd?.(editedMeta, cell!); + onChange?.(displayData); + resolver(true); + }; - if (onChange) { - onChange(displayData); + const onKeyDown: React.KeyboardEventHandler = (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + onSave(); } - - hasSaved.current = true; - resolver(true); }; - const styleProps: React.CSSProperties = { - left: cellLeft, - top: cellTop, - width: cellWidth, - height: cellHeight, - zIndex: 1000, + // 让输入框聚焦时光标在文字的末尾 + const onFocus: React.FocusEventHandler = (e) => { + e.target.selectionStart = e.target.value.length; + e.target.selectionEnd = e.target.value.length; }; + const styleProps = React.useMemo(() => { + return { + left: cellLeft, + top: cellTop, + width: cellWidth, + height: cellHeight, + zIndex: 1000, + }; + }, []); + const onChangeValue = (val: string) => { setInputVal(val); }; + React.useEffect(() => { + onDataCellEditStart?.(cell!.getMeta(), cell!); + setTimeout(() => { + // 防止触发表格全选 + containerRef.current?.click(); + // 开启 preventScroll, 防止页面有滚动条时触发滚动 + inputRef.current?.focus({ preventScroll: true }); + }); + }, []); + return (
{CustomComponent ? ( { setInputVal(e.target.value); }} onBlur={onSave} - onPressEnter={onSave} + onKeyDown={onKeyDown} + onFocus={onFocus} /> )}
); } -export const EditCell = memo(({ onChange, CustomComponent }: EditCellProps) => { +export const EditCell: React.FC = React.memo((props) => { + const { onChange, CustomComponent } = props; const s2 = useSpreadSheetInstance(); - const onEditCell = useCallback( - (event: GEvent) => { + const onEditCell = React.useCallback( + (event: CanvasEvent) => { invokeComponent({ component: EditCellComponent, params: { @@ -212,7 +216,7 @@ export const EditCell = memo(({ onChange, CustomComponent }: EditCellProps) => { [CustomComponent, onChange, s2], ); - useS2Event(S2Event.DATA_CELL_CLICK, onEditCell, s2); + useS2Event(S2Event.DATA_CELL_DOUBLE_CLICK, onEditCell, s2); return null; }); diff --git a/packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx b/packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx deleted file mode 100644 index aaf73c6286..0000000000 --- a/packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import type { Event as CanvasEvent } from '@antv/g-canvas'; -import { - BaseCell, - S2Event, - SpreadSheet, - type DataType, - type S2CellType, - type ViewMeta, -} from '@antv/s2'; -import { Input } from 'antd'; -import { merge, pick } from 'lodash'; -import React, { - memo, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { useS2Event } from '../../../../hooks'; -import { useSpreadSheetRef } from '../../../../utils/SpreadSheetContext'; -import { - invokeComponent, - type InvokeComponentProps, -} from '../../../../utils/invokeComponent'; -import './index.less'; - -export interface CustomProps { - style: React.CSSProperties; - onChange: (value: string) => void; - onSave: () => void; - value: string; - spreadsheet: SpreadSheet; - cell: S2CellType; -} - -type EditCellProps = { - onChange?: (data: DataType[]) => void; - onDataCellEditEnd?: (meta: ViewMeta) => void; - trigger?: number; - CustomComponent?: React.FunctionComponent; -}; - -function EditCellComponent( - props: InvokeComponentProps<{ event: CanvasEvent } & EditCellProps>, -) { - const { params, resolver } = props; - const spreadsheet = useSpreadSheetRef(); - const { event, onChange, onDataCellEditEnd, CustomComponent } = params; - const cell: BaseCell = event.target.cfg.parent; - - const { left, top, width, height } = useMemo(() => { - const rect = ( - spreadsheet?.container.cfg.container as HTMLElement - ).getBoundingClientRect(); - - const modified = { - left: window.scrollX + rect.left, - top: window.scrollY + rect.top, - width: rect.width, - height: rect.height, - }; - - return modified; - }, [spreadsheet?.container.cfg.container]); - - const { - x: cellLeft, - y: cellTop, - width: cellWidth, - height: cellHeight, - } = useMemo(() => { - const scroll = spreadsheet.facet.getScrollOffset(); - const cellMeta = pick(cell.getMeta(), ['x', 'y', 'width', 'height']); - - cellMeta.x -= scroll.scrollX || 0; - cellMeta.y -= - (scroll.scrollY || 0) - - (spreadsheet.getColumnNodes()[0] || { height: 0 }).height; - - return cellMeta; - }, [cell, spreadsheet]); - - const [inputVal, setInputVal] = useState(cell.getMeta().fieldValue); - const inputRef = useRef(null); - const containerRef = useRef(null); - - useEffect(() => { - setTimeout(() => { - // 防止触发表格全选 - containerRef.current?.click(); - // 开启 preventScroll, 防止页面有滚动条时触发滚动 - inputRef.current?.focus({ preventScroll: true }); - }); - }, []); - - const onSave = () => { - const { rowIndex, valueField } = cell.getMeta(); - const displayData = spreadsheet.dataSet.getDisplayDataSet(); - - displayData[rowIndex][valueField] = inputVal; - spreadsheet.render(true); - - onDataCellEditEnd?.( - merge(cell.getMeta(), { - fieldValue: inputVal, - data: { - [valueField]: inputVal, - }, - }), - ); - - onChange?.(displayData); - resolver(true); - }; - - const onKeyDown: React.KeyboardEventHandler = (e) => { - if (e.keyCode === 13) { - e.preventDefault(); - onSave(); - } - }; - - const styleProps = React.useMemo(() => { - return { - left: cellLeft, - top: cellTop, - width: cellWidth, - height: cellHeight, - zIndex: 1000, - }; - }, []); - - const onChangeValue = (val: string) => { - setInputVal(val); - }; - - return ( -
- {CustomComponent ? ( - - ) : ( - { - setInputVal(e.target.value); - }} - onBlur={onSave} - onKeyDown={onKeyDown} - /> - )} -
- ); -} - -export const EditCell: React.FC = memo( - ({ onChange, onDataCellEditEnd, CustomComponent }) => { - const spreadsheet = useSpreadSheetRef(); - - const cb = useCallback( - (event: CanvasEvent) => { - invokeComponent( - EditCellComponent, - { event, onChange, onDataCellEditEnd, CustomComponent }, - spreadsheet, - ); - }, - [spreadsheet], - ); - - useS2Event(S2Event.DATA_CELL_DOUBLE_CLICK, cb, spreadsheet); - - return null; - }, -); - -EditCell.displayName = 'EditCell'; diff --git a/packages/s2-react/src/components/sheets/editable-sheet/index.tsx b/packages/s2-react/src/components/sheets/editable-sheet/index.tsx index dd79d7c357..1fa621b7dd 100644 --- a/packages/s2-react/src/components/sheets/editable-sheet/index.tsx +++ b/packages/s2-react/src/components/sheets/editable-sheet/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React from 'react'; import { BaseSheet } from '../base-sheet'; import type { SheetComponentsProps } from '../interface'; import { DragCopyPoint } from './drag-copy'; @@ -6,12 +6,10 @@ import { EditCell } from './custom-cell'; export const EditableSheet: React.FC = React.memo( (props) => { - const onChange = useCallback(() => {}, []); - return ( diff --git a/packages/s2-shared/src/interface.ts b/packages/s2-shared/src/interface.ts index 3704945285..33257692b5 100644 --- a/packages/s2-shared/src/interface.ts +++ b/packages/s2-shared/src/interface.ts @@ -24,6 +24,7 @@ import type { SeriesNumberCell, SortParams, SpreadSheet, + TableDataCell, TargetCellInfo, ThemeCfg, TooltipContentType, @@ -137,7 +138,8 @@ export interface BaseSheetComponentProps< onDataCellBrushSelection?: (brushRangeDataCells: DataCell[]) => void; onDataCellSelectMove?: (metaList: ViewMetaData[]) => void; onDataCellRender?: (cell: DataCell) => void; - onDataCellEditEnd?: (meta: ViewMeta) => void; + onDataCellEditStart?: (meta: ViewMeta, cell: TableDataCell) => void; + onDataCellEditEnd?: (meta: ViewMeta, cell: TableDataCell) => void; // ============== Corner Cell ==================== onCornerCellHover?: (data: TargetCellInfo) => void; diff --git a/s2-site/docs/api/basic-class/base-cell.zh.md b/s2-site/docs/api/basic-class/base-cell.zh.md index 20ca60b7a1..b6394ce1bb 100644 --- a/s2-site/docs/api/basic-class/base-cell.zh.md +++ b/s2-site/docs/api/basic-class/base-cell.zh.md @@ -13,7 +13,7 @@ cell.getActualText() | 参数 | 说明 | 类型 | | --- | --- | --- | | getMeta | 获取单元格元数据 | () => [ViewMeta](#viewmeta) | -| setMeta | 设置单元格元数据 | (vieMeta: [ViewMeta](#viewmeta)) => void | +| setMeta | 设置单元格元数据 | (vieMeta: [Partial](#viewmeta)) => void | | getIconStyle | 获取单元格图标样式 | () => [IconTheme](/docs/api/general/S2Theme#icontheme) | | getStyle | 获取单元格样式 | () => [DefaultCellTheme](/docs/api/general/S2Theme#defaultcelltheme) | | getTextAndIconPosition | 获取单元格文本和图标的位置 | (iconCount: `number`) => [TextAndIconPosition](#textandiconposition) | diff --git a/s2-site/docs/api/basic-class/base-data-set.zh.md b/s2-site/docs/api/basic-class/base-data-set.zh.md index f4eccae074..66a97be76d 100644 --- a/s2-site/docs/api/basic-class/base-data-set.zh.md +++ b/s2-site/docs/api/basic-class/base-data-set.zh.md @@ -34,6 +34,7 @@ s2.dataSet.getFieldName('type') | getCellMultiData | 获取批量的单元格数据 | (params?: [GetCellMultiDataParams](#getcellmultidataparams)) => [DataType[]](#datatype) | | | moreThanOneValue | 是否超过 1 个数值 | () => [ViewMeta](#viewmeta) | | | isEmpty | 是否为空数据集 | () => `boolean` | `@antv/s2^1.51.1` | +| displayFormattedValueMap | 单元格所对应格式化后的值(用于编辑表) | `Map` | `@antv/s2-v1.54.5` | ### SimpleData diff --git a/s2-site/docs/api/components/sheet-component.zh.md b/s2-site/docs/api/components/sheet-component.zh.md index bc53d783c4..cd7d33eaed 100644 --- a/s2-site/docs/api/components/sheet-component.zh.md +++ b/s2-site/docs/api/components/sheet-component.zh.md @@ -50,7 +50,8 @@ tag: Updated | onDataCellMouseMove | 数值单元格鼠标移动事件 | (data: [TargetCellInfo](#targetcellinfo)) => void | | | | onDataCellBrushSelection | 数值单元格刷选事件 | ( dataCells: [DataCell](/docs/api/basic-class/base-cell)[] ) => void | | | | onDataCellSelectMove | 数值单元格键盘方向键移动事件 | (metas: CellMeta[]) => void | | | -| onDataCellEditEnd | 数值单元格编辑完成(暂只支持编辑表) | (meta: [ViewMeta](/docs/api/basic-class/node)) => void | | | +| onDataCellEditStart | 数值单元格编辑开始(暂只支持编辑表) | (meta: [ViewMeta](/docs/api/basic-class/node), cell: [S2CellType](/docs/api/basic-class/base-cell)) => void | | | +| onDataCellEditEnd | 数值单元格编辑完成(暂只支持编辑表) | (meta: [ViewMeta](/docs/api/basic-class/node), cell: [S2CellType](/docs/api/basic-class/base-cell)) => void | | | | onCornerCellHover | 角头鼠标悬停事件 | (data: [TargetCellInfo](#targetcellinfo)) => void | | | | onCornerCellClick | 角头鼠标单击事件 | (data: [TargetCellInfo](#targetcellinfo)) => void | | | | onCornerCellDoubleClick | 角头鼠标双击事件 | (data: [TargetCellInfo](#targetcellinfo)) => void | | | diff --git a/s2-site/docs/manual/basic/analysis/editable-mode.zh.md b/s2-site/docs/manual/basic/analysis/editable-mode.zh.md index 813be1d588..165468e54b 100644 --- a/s2-site/docs/manual/basic/analysis/editable-mode.zh.md +++ b/s2-site/docs/manual/basic/analysis/editable-mode.zh.md @@ -167,8 +167,11 @@ ReactDOM.render( sheetType="editable" // 此处指定 sheetType 为 editable dataCfg={s2DataCfg} options={s2Options} - onDataCellEditEnd={(meta) => { - console.log('onDataCellEditEnd', meta); + onDataCellEditStart={(meta, cell) => { + console.log('onDataCellEditStart:', meta, cell); + }} + onDataCellEditEnd={(meta, cell) => { + console.log('onDataCellEditEnd:', meta, cell); }} />, document.getElementById('container') diff --git a/s2-site/examples/react-component/sheet/demo/editable.tsx b/s2-site/examples/react-component/sheet/demo/editable.tsx index ce76fd1100..51d01bff76 100644 --- a/s2-site/examples/react-component/sheet/demo/editable.tsx +++ b/s2-site/examples/react-component/sheet/demo/editable.tsx @@ -52,8 +52,12 @@ fetch('https://assets.antv.antgroup.com/s2/basic-table-mode.json') }, }; - const onDataCellEditEnd = (meta) => { - console.log('onDataCellEditEnd', meta); + const onDataCellEditStart = (meta, cell) => { + console.log('onDataCellEditStart:', meta, cell); + }; + + const onDataCellEditEnd = (meta, cell) => { + console.log('onDataCellEditEnd:', meta, cell); }; reactDOMClient @@ -63,6 +67,7 @@ fetch('https://assets.antv.antgroup.com/s2/basic-table-mode.json') dataCfg={s2DataConfig} options={s2Options} sheetType="editable" + onDataCellEditStart={onDataCellEditStart} onDataCellEditEnd={onDataCellEditEnd} />, );