Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 修复编辑表的输入框未回填格式化后的数据 close #2528 #2549

Merged
merged 8 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
}
}

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 @@
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);

Check warning on line 305 in packages/s2-core/src/cell/data-cell.ts

View check run for this annotation

Codecov / codecov/patch

packages/s2-core/src/cell/data-cell.ts#L305

Added line #L305 was not covered by tests

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 @@
cell: S2CellType;
}

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

Check warning on line 32 in packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx

View check run for this annotation

Codecov / codecov/patch

packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx#L32

Added line #L32 was not covered by tests
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 @@
) {
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,

Check warning on line 53 in packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx

View check run for this annotation

Codecov / codecov/patch

packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx#L53

Added line #L53 was not covered by tests
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();

Check warning on line 59 in packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx

View check run for this annotation

Codecov / codecov/patch

packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx#L58-L59

Added lines #L58 - L59 were not covered by tests

const modified = {
return {

Check warning on line 61 in packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx

View check run for this annotation

Codecov / codecov/patch

packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx#L61

Added line #L61 was not covered by tests
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 @@
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());

Check warning on line 86 in packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx

View check run for this annotation

Codecov / codecov/patch

packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx#L86

Added line #L86 was not covered by tests

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

Check warning on line 88 in packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx

View check run for this annotation

Codecov / codecov/patch

packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx#L88

Added line #L88 was not covered by tests
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,

Check warning on line 100 in packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx

View check run for this annotation

Codecov / codecov/patch

packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx#L99-L100

Added lines #L99 - L100 were not covered by tests
data: {
[valueField]: inputVal,

Check warning on line 102 in packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx

View check run for this annotation

Codecov / codecov/patch

packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx#L102

Added line #L102 was not covered by tests
lijinke666 marked this conversation as resolved.
Show resolved Hide resolved
},
});

Check warning on line 104 in packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx

View check run for this annotation

Codecov / codecov/patch

packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx#L104

Added line #L104 was not covered by tests

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 @@
setInputVal(val);
};

React.useEffect(() => {
onDataCellEditStart?.(cell.getMeta(), cell);

Check warning on line 139 in packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx

View check run for this annotation

Codecov / codecov/patch

packages/s2-react/src/components/sheets/editable-sheet/edit-cell/index.tsx#L139

Added line #L139 was not covered by tests
setTimeout(() => {
// 防止触发表格全选
containerRef.current?.click();
// 开启 preventScroll, 防止页面有滚动条时触发滚动
inputRef.current?.focus({ preventScroll: true });
});
}, []);

return (
<div
ref={containerRef}
Expand Down Expand Up @@ -168,31 +179,26 @@
}}
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
Loading