Skip to content

Commit

Permalink
fix: 修复编辑表的输入框未回填格式化后的数据 close #2528 (#2549)
Browse files Browse the repository at this point in the history
* fix: 修复编辑表的输入框未回填格式化后的数据 close #2528

* fix: 修复循环引用

* fix: 编辑后不应该再次格式化

* fix: 移除定义

* test: 修复单测

* test: 增加单测
  • Loading branch information
lijinke666 authored Feb 23, 2024
1 parent 31cc240 commit 8d9c5b2
Show file tree
Hide file tree
Showing 19 changed files with 168 additions and 83 deletions.
57 changes: 57 additions & 0 deletions packages/s2-core/__tests__/bugs/issue-2528-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* @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-@']);
});
});
1 change: 1 addition & 0 deletions packages/s2-core/__tests__/unit/facet/pivot-facet-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ jest.mock('@/data-set/pivot-data-set', () => {
getDimensionValues: actualPivotDataSet.prototype.getDimensionValues,
getFieldsAndPivotMetaByField:
actualPivotDataSet.prototype.getFieldsAndPivotMetaByField,
displayFormattedValueMap: new Map(),
};
}),
};
Expand Down
1 change: 1 addition & 0 deletions packages/s2-core/__tests__/unit/facet/table-facet-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ jest.mock('@/sheet-type', () => {
},
dataSet: {
isEmpty: jest.fn(),
displayFormattedValueMap: new Map(),
},
};
}),
Expand Down
1 change: 1 addition & 0 deletions packages/s2-core/__tests__/util/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const createFakeSpreadSheet = () => {
getMultiData() {
return [];
},
displayFormattedValueMap: new Map(),
} as unknown as any;
s2.facet = {
layoutResult: {
Expand Down
3 changes: 2 additions & 1 deletion packages/s2-core/src/cell/base-cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import type { GuiIcon } from '../common/icons/gui-icon';
import { checkIsLinkField } from '../utils/interaction/link-field';
import type { Node } from '../facet/layout/node';
import type { ViewMeta } from '../common/interface/basic';
import { customMerge } from '../utils/merge';

export abstract class BaseCell<T extends SimpleBBox> extends Group {
// cell's data meta info
Expand Down Expand Up @@ -113,7 +114,7 @@ export abstract class BaseCell<T extends SimpleBBox> extends Group {
}

public setMeta(viewMeta: T) {
this.meta = viewMeta;
this.meta = customMerge(this.meta, viewMeta);
}

public getIconStyle() {
Expand Down
12 changes: 8 additions & 4 deletions packages/s2-core/src/cell/data-cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ export class DataCell extends BaseCell<ViewMeta> {
}
}

public setMeta(viewMeta: ViewMeta) {
super.setMeta(viewMeta);
public setMeta(viewMeta: Partial<ViewMeta>) {
super.setMeta(viewMeta as ViewMeta);
this.initCell();
}

Expand Down Expand Up @@ -293,12 +293,16 @@ export class DataCell extends BaseCell<ViewMeta> {
formattedValue: EMPTY_PLACEHOLDER,
};
}
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,
Expand Down
1 change: 0 additions & 1 deletion packages/s2-core/src/cell/table-data-cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
getOrCreateResizeAreaGroupById,
getResizeAreaAttrs,
} from '../utils/interaction/resize';
import { checkIsLinkField } from '../utils/interaction/link-field';

export class TableDataCell extends DataCell {
protected getLinkFieldStyle() {
Expand Down
3 changes: 3 additions & 0 deletions packages/s2-core/src/data-set/base-data-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export abstract class BaseDataSet {
// 透视表入口对象实例
public spreadsheet: SpreadSheet;

// 单元格所对应格式化后的值(用于编辑表)
public displayFormattedValueMap = new Map<string, string>();

public constructor(spreadsheet: SpreadSheet) {
this.spreadsheet = spreadsheet;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/s2-react/playground/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const tableSheetMultipleColumns: Columns = [
export const tableSheetDataCfg: S2DataConfig = {
data,
totalData,
meta,
meta: [{ field: 'number', formatter: (v) => `${v}-@` }, ...meta],
fields: {
columns: tableSheetSingleColumns,
},
Expand Down Expand Up @@ -77,7 +77,7 @@ export const pivotSheetDataCfgForCompactMode = customMerge(pivotSheetDataCfg, {
});

export const s2Options: SheetComponentOptions = {
debug: false,
debug: true,
width: 600,
height: 400,
frozenFirstRow: false,
Expand Down
1 change: 1 addition & 0 deletions packages/s2-react/playground/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,7 @@ function MainLayout() {
ref={s2Ref}
themeCfg={themeCfg}
onMounted={onSheetMounted}
onDataCellEditStart={logHandler('onDataCellEditStart')}
onDataCellEditEnd={logHandler('onDataCellEditEnd')}
/>
</TabPane>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
import type { Event as CanvasEvent } from '@antv/g-canvas';
import {
BaseCell,
S2Event,
SpreadSheet,
customMerge,
type DataType,
type S2CellType,
type TableDataCell,
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 { pick } from 'lodash';
import React from 'react';
import { useS2Event } from '../../../../hooks';
import { useSpreadSheetRef } from '../../../../utils/SpreadSheetContext';
import {
Expand All @@ -34,9 +28,15 @@ export interface CustomProps {
cell: S2CellType;
}

type DateCellEdit = (meta: ViewMeta, cell: TableDataCell) => void;

type EditCellProps = {
/**
* @deprecated use `onDataCellEditEnd` instead.
*/
onChange?: (data: DataType[]) => void;
onDataCellEditEnd?: (meta: ViewMeta) => void;
onDataCellEditStart?: DateCellEdit;
onDataCellEditEnd?: DateCellEdit;
trigger?: number;
CustomComponent?: React.FunctionComponent<CustomProps>;
};
Expand All @@ -46,30 +46,32 @@ function EditCellComponent(
) {
const { params, resolver } = props;
const spreadsheet = useSpreadSheetRef();
const { event, onChange, onDataCellEditEnd, CustomComponent } = params;
const cell: BaseCell<ViewMeta> = event.target.cfg.parent;
const {
event,
onChange,
onDataCellEditStart,
onDataCellEditEnd,
CustomComponent,
} = params;

const { left, top, width, height } = useMemo(() => {
const rect = (
spreadsheet?.container.cfg.container as HTMLElement
).getBoundingClientRect();
const cell = spreadsheet.getCell<TableDataCell>(event.target);
const { left, top, width, height } = React.useMemo<Partial<DOMRect>>(() => {
const rect = spreadsheet.getCanvasElement()?.getBoundingClientRect();

const modified = {
return {
left: window.scrollX + rect.left,
top: window.scrollY + rect.top,
width: rect.width,
height: rect.height,
};

return modified;
}, [spreadsheet?.container.cfg.container]);
}, [spreadsheet]);

const {
x: cellLeft,
y: cellTop,
width: cellWidth,
height: cellHeight,
} = useMemo(() => {
} = React.useMemo(() => {
const scroll = spreadsheet.facet.getScrollOffset();
const cellMeta = pick(cell.getMeta(), ['x', 'y', 'width', 'height']);

Expand All @@ -81,45 +83,44 @@ function EditCellComponent(
return cellMeta;
}, [cell, spreadsheet]);

const [inputVal, setInputVal] = useState(cell.getMeta().fieldValue);
const inputRef = useRef<HTMLInputElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [inputVal, setInputVal] = React.useState(() => cell.getFieldValue());

useEffect(() => {
setTimeout(() => {
// 防止触发表格全选
containerRef.current?.click();
// 开启 preventScroll, 防止页面有滚动条时触发滚动
inputRef.current?.focus({ preventScroll: true });
});
}, []);
const inputRef = React.useRef<HTMLInputElement>(null);
const containerRef = React.useRef<HTMLDivElement>(null);

const onSave = () => {
const { rowIndex, valueField } = cell.getMeta();
const { rowIndex, valueField, id } = cell.getMeta();
const displayData = spreadsheet.dataSet.getDisplayDataSet();
displayData[rowIndex][valueField] = inputVal;
spreadsheet.render(true);

onDataCellEditEnd?.(
merge(cell.getMeta(), {
fieldValue: inputVal,
data: {
[valueField]: inputVal,
},
}),
);
// 编辑后的值作为格式化后的结果, formatter 不再触发, 避免二次格式化
spreadsheet.dataSet.displayFormattedValueMap?.set(id, inputVal);
spreadsheet.render();

const editedMeta = customMerge(cell.getMeta(), {
fieldValue: inputVal,
data: {
[valueField]: inputVal,
},
});

onDataCellEditEnd?.(editedMeta, cell);
onChange?.(displayData);
resolver(true);
};

const onKeyDown: React.KeyboardEventHandler<HTMLTextAreaElement> = (e) => {
if (e.keyCode === 13) {
if (e.key === 'Enter') {
e.preventDefault();
onSave();
}
};

// 让输入框聚焦时光标在文字的末尾
const onFocus: React.FocusEventHandler<HTMLTextAreaElement> = (e) => {
e.target.selectionStart = e.target.value.length;
e.target.selectionEnd = e.target.value.length;
};

const styleProps = React.useMemo<React.CSSProperties>(() => {
return {
left: cellLeft,
Expand All @@ -134,6 +135,16 @@ function EditCellComponent(
setInputVal(val);
};

React.useEffect(() => {
onDataCellEditStart?.(cell.getMeta(), cell);
setTimeout(() => {
// 防止触发表格全选
containerRef.current?.click();
// 开启 preventScroll, 防止页面有滚动条时触发滚动
inputRef.current?.focus({ preventScroll: true });
});
}, []);

return (
<div
ref={containerRef}
Expand Down Expand Up @@ -168,31 +179,26 @@ function EditCellComponent(
}}
onBlur={onSave}
onKeyDown={onKeyDown}
onFocus={onFocus}
/>
)}
</div>
);
}

export const EditCell: React.FC<EditCellProps> = memo(
({ onChange, onDataCellEditEnd, CustomComponent }) => {
const spreadsheet = useSpreadSheetRef();

const cb = useCallback(
(event: CanvasEvent) => {
invokeComponent(
EditCellComponent,
{ event, onChange, onDataCellEditEnd, CustomComponent },
spreadsheet,
);
},
[spreadsheet],
);
export const EditCell: React.FC<EditCellProps> = React.memo((props) => {
const spreadsheet = useSpreadSheetRef();

const cb = React.useCallback(
(event: CanvasEvent) => {
invokeComponent(EditCellComponent, { ...props, event }, spreadsheet);
},
[spreadsheet],
);

useS2Event(S2Event.DATA_CELL_DOUBLE_CLICK, cb, spreadsheet);
useS2Event(S2Event.DATA_CELL_DOUBLE_CLICK, cb, spreadsheet);

return null;
},
);
return null;
});

EditCell.displayName = 'EditCell';
Loading

0 comments on commit 8d9c5b2

Please sign in to comment.