mirror of
https://github.com/dream-num/univer.git
synced 2026-09-24 15:32:18 +08:00
fix(scroll): gap config (#6828)
This commit is contained in:
@@ -25,7 +25,7 @@ import { ThemeService } from '../../services/theme/theme.service';
|
||||
import { ColorKit } from '../../shared';
|
||||
import { BooleanNumber, HorizontalAlign } from '../../types/enum';
|
||||
import { LocaleType } from '../../types/enum/locale-type';
|
||||
import { SheetSkeleton } from '../sheet-skeleton';
|
||||
import { createSheetGapTestConfig, SheetSkeleton } from '../sheet-skeleton';
|
||||
import { RANGE_TYPE } from '../typedef';
|
||||
import { createCoreTestBed } from './create-core-test-bed';
|
||||
|
||||
@@ -350,6 +350,50 @@ describe('SheetSkeleton integration', () => {
|
||||
expect(skeleton.getColGapAtPosition(100)).toBe(-1);
|
||||
});
|
||||
|
||||
it('should preserve viewport positions when decomposing offsets that land inside gaps', () => {
|
||||
const testBed = createCoreTestBed(workbookDataFactory());
|
||||
const injector = testBed.univer.__getInjector();
|
||||
const worksheet = testBed.sheet.getActiveSheet()!;
|
||||
const skeleton = new SheetSkeleton(
|
||||
worksheet,
|
||||
testBed.sheet.getStyles(),
|
||||
injector.get(LocaleService),
|
||||
injector.get(IContextService),
|
||||
injector.get(IConfigService),
|
||||
injector as Injector
|
||||
).calculate()!;
|
||||
|
||||
disposables.push(() => testBed.univer.dispose());
|
||||
|
||||
skeleton.setGapConfig({
|
||||
rowGaps: {
|
||||
1: { size: 5 },
|
||||
},
|
||||
colGaps: {
|
||||
2: { size: 7 },
|
||||
},
|
||||
});
|
||||
|
||||
const viewportScrollX = 195;
|
||||
const viewportScrollY = 30;
|
||||
const { row, column, rowOffset, columnOffset } = skeleton.getOffsetRelativeToRowCol(viewportScrollX, viewportScrollY);
|
||||
const { startX, startY } = skeleton.getCellWithCoordByIndex(row, column, false);
|
||||
|
||||
expect({ row, column, rowOffset, columnOffset }).toEqual({
|
||||
row: 1,
|
||||
column: 2,
|
||||
rowOffset: -3,
|
||||
columnOffset: -4,
|
||||
});
|
||||
expect({
|
||||
viewportScrollX: startX + columnOffset,
|
||||
viewportScrollY: startY + rowOffset,
|
||||
}).toEqual({
|
||||
viewportScrollX,
|
||||
viewportScrollY,
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep provided gap default colors and return no-gap helpers when row/col gaps are absent', () => {
|
||||
const testBed = createCoreTestBed(workbookDataFactory());
|
||||
const injector = testBed.univer.__getInjector();
|
||||
@@ -377,6 +421,52 @@ describe('SheetSkeleton integration', () => {
|
||||
expect(skeleton.getColGapAtPosition(20)).toBe(-1);
|
||||
});
|
||||
|
||||
it('should expose a reusable mixed gap test fixture', () => {
|
||||
const testBed = createCoreTestBed(workbookDataFactory());
|
||||
const injector = testBed.univer.__getInjector();
|
||||
const worksheet = testBed.sheet.getActiveSheet()!;
|
||||
const skeleton = new SheetSkeleton(
|
||||
worksheet,
|
||||
testBed.sheet.getStyles(),
|
||||
injector.get(LocaleService),
|
||||
injector.get(IContextService),
|
||||
injector.get(IConfigService),
|
||||
injector as Injector
|
||||
).calculate()!;
|
||||
|
||||
disposables.push(() => testBed.univer.dispose());
|
||||
|
||||
skeleton.setGapConfig(createSheetGapTestConfig());
|
||||
|
||||
expect(skeleton.gapConfig.defaultBackgroundColor).toBe('rgba(24, 119, 242, 0.08)');
|
||||
expect(skeleton.gapConfig.defaultStripeColor).toBe('rgba(24, 119, 242, 0.25)');
|
||||
expect(skeleton.gapConfig.rowGaps?.[3]).toEqual({
|
||||
size: 10,
|
||||
color: 'rgba(245, 158, 11, 0.14)',
|
||||
});
|
||||
expect(skeleton.gapConfig.colGaps?.[4]).toEqual({
|
||||
size: 12,
|
||||
color: 'rgba(244, 63, 94, 0.12)',
|
||||
stripeColor: 'rgba(225, 29, 72, 0.30)',
|
||||
});
|
||||
|
||||
expect(skeleton.rowHeightAccumulation).toEqual([28, 58, 82, 116, 116, 140, 178, 202]);
|
||||
expect(skeleton.columnWidthAccumulation).toEqual([72, 197, 277, 349, 361, 433]);
|
||||
|
||||
expect(skeleton.getRowGapAtPosition(30)).toBe(1);
|
||||
expect(skeleton.getRowGapAtPosition(86)).toBe(3);
|
||||
expect(skeleton.getRowGapAtPosition(145)).toBe(6);
|
||||
expect(skeleton.getColGapAtPosition(74)).toBe(1);
|
||||
expect(skeleton.getColGapAtPosition(200)).toBe(2);
|
||||
expect(skeleton.getColGapAtPosition(355)).toBe(4);
|
||||
expect(skeleton.getNoMergeCellWithCoordByIndex(3, 2, false)).toEqual({
|
||||
startY: 92,
|
||||
endY: 116,
|
||||
startX: 205,
|
||||
endX: 277,
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve provided one-sided gap color and fill the missing side from theme', () => {
|
||||
const testBed = createCoreTestBed(workbookDataFactory());
|
||||
const injector = testBed.univer.__getInjector();
|
||||
|
||||
@@ -76,6 +76,39 @@ export interface ISheetGapConfig {
|
||||
defaultBackgroundColor?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable gap fixture for visual and integration testing.
|
||||
*/
|
||||
export function createSheetGapTestConfig(overrides: Partial<ISheetGapConfig> = {}): ISheetGapConfig {
|
||||
const baseConfig: ISheetGapConfig = {
|
||||
defaultBackgroundColor: 'rgba(24, 119, 242, 0.08)',
|
||||
defaultStripeColor: 'rgba(24, 119, 242, 0.25)',
|
||||
rowGaps: {
|
||||
1: { size: 6 },
|
||||
3: { size: 10, color: 'rgba(245, 158, 11, 0.14)' },
|
||||
6: { size: 14, color: 'rgba(16, 185, 129, 0.12)', stripeColor: 'rgba(5, 150, 105, 0.35)' },
|
||||
},
|
||||
colGaps: {
|
||||
1: { size: 5 },
|
||||
2: { size: 8, stripeColor: 'rgba(59, 130, 246, 0.35)' },
|
||||
4: { size: 12, color: 'rgba(244, 63, 94, 0.12)', stripeColor: 'rgba(225, 29, 72, 0.30)' },
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
...baseConfig,
|
||||
...overrides,
|
||||
rowGaps: {
|
||||
...baseConfig.rowGaps,
|
||||
...overrides.rowGaps,
|
||||
},
|
||||
colGaps: {
|
||||
...baseConfig.colGaps,
|
||||
...overrides.colGaps,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional gap size getter for coordinate calculation functions.
|
||||
*/
|
||||
@@ -1068,20 +1101,12 @@ export class SheetSkeleton extends Skeleton {
|
||||
rowOffset: number;
|
||||
} {
|
||||
const column = searchArray(this.columnWidthAccumulation, offsetX);
|
||||
let columnOffset = 0;
|
||||
if (column === 0) {
|
||||
columnOffset = offsetX;
|
||||
} else {
|
||||
columnOffset = offsetX - this._columnWidthAccumulation[column - 1];
|
||||
}
|
||||
const columnStart = (this._columnWidthAccumulation[column - 1] || 0) + this.getColGapSize(column);
|
||||
const columnOffset = offsetX - columnStart;
|
||||
|
||||
const row = searchArray(this.rowHeightAccumulation, offsetY);
|
||||
let rowOffset = 0;
|
||||
if (row === 0) {
|
||||
rowOffset = offsetY;
|
||||
} else {
|
||||
rowOffset = offsetY - this._rowHeightAccumulation[row - 1];
|
||||
}
|
||||
const rowStart = (this._rowHeightAccumulation[row - 1] || 0) + this.getRowGapSize(row);
|
||||
const rowOffset = offsetY - rowStart;
|
||||
return {
|
||||
row,
|
||||
column,
|
||||
|
||||
+51
@@ -19,6 +19,7 @@ import type { IBoundRectNoAngle, IViewportInfo } from '../../../basics/vector2';
|
||||
import {
|
||||
BooleanNumber,
|
||||
BorderStyleTypes,
|
||||
createSheetGapTestConfig,
|
||||
ILogService,
|
||||
IUniverInstanceService,
|
||||
LocaleType,
|
||||
@@ -489,4 +490,54 @@ describe('spreadsheet integration', () => {
|
||||
);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('draws the reusable gap fixture with mixed default and item colors', () => {
|
||||
const { spreadsheet, skeleton, mainCanvas } = fixture;
|
||||
const context = mainCanvas.getContext() as any;
|
||||
|
||||
skeleton.setGapConfig(createSheetGapTestConfig());
|
||||
|
||||
const drawSingleGapRectSpy = vi.spyOn(spreadsheet as any, '_drawSingleGapRect');
|
||||
|
||||
(spreadsheet as any)._drawGapAreas(
|
||||
context,
|
||||
skeleton,
|
||||
0,
|
||||
7,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
500,
|
||||
0,
|
||||
320
|
||||
);
|
||||
|
||||
expect(drawSingleGapRectSpy).toHaveBeenCalledTimes(6);
|
||||
expect(drawSingleGapRectSpy).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
size: 10,
|
||||
color: 'rgba(245, 158, 11, 0.14)',
|
||||
}),
|
||||
expect.any(Number),
|
||||
expect.any(Number),
|
||||
expect.any(Number),
|
||||
10,
|
||||
'rgba(24, 119, 242, 0.08)',
|
||||
'rgba(24, 119, 242, 0.25)'
|
||||
);
|
||||
expect(drawSingleGapRectSpy).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
size: 8,
|
||||
stripeColor: 'rgba(59, 130, 246, 0.35)',
|
||||
}),
|
||||
expect.any(Number),
|
||||
expect.any(Number),
|
||||
8,
|
||||
expect.any(Number),
|
||||
'rgba(24, 119, 242, 0.08)',
|
||||
'rgba(24, 119, 242, 0.25)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -131,4 +131,44 @@ describe('SheetScrollManagerService', () => {
|
||||
viewportScrollX: 337,
|
||||
});
|
||||
});
|
||||
|
||||
it('calculates viewport scroll from the actual cell start when gaps shift the origin', () => {
|
||||
const injector = new Injector();
|
||||
injector.add([
|
||||
SheetSkeletonManagerService,
|
||||
{
|
||||
useValue: {
|
||||
getCurrentSkeleton: () => ({
|
||||
rowHeightAccumulation: [28, 57, 81],
|
||||
columnWidthAccumulation: [72, 192, 271],
|
||||
getCellWithCoordByIndex: (row: number, column: number, header?: boolean) => {
|
||||
expect(header).toBe(false);
|
||||
expect(row).toBe(1);
|
||||
expect(column).toBe(2);
|
||||
|
||||
return {
|
||||
startY: 33,
|
||||
startX: 199,
|
||||
};
|
||||
},
|
||||
}),
|
||||
} as unknown as SheetSkeletonManagerService,
|
||||
},
|
||||
]);
|
||||
const service = injector.createInstance(SheetScrollManagerService, createRenderContext('u-1'));
|
||||
|
||||
expect(service.calcViewportScrollFromRowColOffset({
|
||||
sheetViewStartRow: 1,
|
||||
sheetViewStartColumn: 2,
|
||||
offsetX: -4,
|
||||
offsetY: -3,
|
||||
scrollX: 0,
|
||||
scrollY: 0,
|
||||
viewportScrollX: 0,
|
||||
viewportScrollY: 0,
|
||||
})).toEqual({
|
||||
viewportScrollX: 195,
|
||||
viewportScrollY: 30,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -169,10 +169,10 @@ export class SheetScrollManagerService implements IRenderModule {
|
||||
offsetY = offsetY || 0;
|
||||
|
||||
const skeleton = this._sheetSkeletonManagerService.getCurrentSkeleton();
|
||||
const rowAcc = skeleton?.rowHeightAccumulation[sheetViewStartRow - 1] || 0;
|
||||
const colAcc = skeleton?.columnWidthAccumulation[sheetViewStartColumn - 1] || 0;
|
||||
const viewportScrollX = colAcc + offsetX;
|
||||
const viewportScrollY = rowAcc + offsetY;
|
||||
const { startX: colStart = skeleton?.columnWidthAccumulation[sheetViewStartColumn - 1] || 0, startY: rowStart = skeleton?.rowHeightAccumulation[sheetViewStartRow - 1] || 0 } =
|
||||
skeleton?.getCellWithCoordByIndex?.(sheetViewStartRow, sheetViewStartColumn, false) ?? {};
|
||||
const viewportScrollX = colStart + offsetX;
|
||||
const viewportScrollY = rowStart + offsetY;
|
||||
|
||||
return {
|
||||
viewportScrollX,
|
||||
|
||||
Reference in New Issue
Block a user