Skip to content

Commit 4e832f3

Browse files
authored
feat: 表头吸顶 (Sticky Header) 功能及配套 (#3323)
* feat: 表头吸顶 init * fix: 修复已知问题,增加官网案例 * chore: 修复案例异常 * feat: 表头吸顶支持部分交互 * chore: 修复 CR 意见 * fix: 修复 CR 问题
1 parent 0fba0c3 commit 4e832f3

File tree

9 files changed

+807
-21
lines changed

9 files changed

+807
-21
lines changed

packages/s2-core/src/common/interface/interaction.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,31 @@ export interface InteractionOptions {
330330
* @see https://s2.antv.antgroup.com/manual/advanced/interaction/custom
331331
*/
332332
customInteractions?: CustomInteraction[];
333+
334+
/**
335+
* 开启 window 级别表头吸顶, 当表格高度超出页面可视区域时, 表头会自动吸附在视口顶部
336+
* @default false
337+
*/
338+
stickyHeader?: boolean | StickyHeaderOptions;
339+
}
340+
341+
export interface StickyHeaderOptions {
342+
/**
343+
* 吸顶时的 top 偏移量 (应对外部业务带有固定 Header 的情况, 如导航栏)
344+
* @default 0
345+
*/
346+
offsetTop?: number | (() => number);
347+
348+
/**
349+
* 绑定的外部滚动容器 (默认 window)
350+
*/
351+
scrollContainer?: HTMLElement | Window;
352+
353+
/**
354+
* 是否启用吸顶表头的交互能力 (排序/resize/展开折叠等)
355+
* @default false
356+
*/
357+
enableInteraction?: boolean;
333358
}
334359

335360
export interface InteractionCellHighlightOptions {

packages/s2-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ export * from './sheet-type';
1313
export * from './styles';
1414
export * from './theme';
1515
export * from './ui/scrollbar';
16+
export * from './ui/sticky-header';
1617
export * from './ui/tooltip';
1718
export * from './utils';

packages/s2-core/src/sheet-type/spread-sheet.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import type { Node } from '../facet/layout/node';
6464
import { RootInteraction } from '../interaction/root';
6565
import { getTheme } from '../theme';
6666
import { HdAdapter } from '../ui/hd-adapter';
67+
import { StickyHeaderController } from '../ui/sticky-header';
6768
import { BaseTooltip } from '../ui/tooltip';
6869
import { getOffscreenCanvas, removeOffscreenCanvas } from '../utils/canvas';
6970
import { clearValueRangeState } from '../utils/condition/state-controller';
@@ -98,6 +99,8 @@ export abstract class SpreadSheet extends EE {
9899

99100
public hdAdapter: HdAdapter;
100101

102+
public stickyHeaderController: StickyHeaderController | null = null;
103+
101104
/**
102105
* 表格是否已销毁
103106
*/
@@ -152,6 +155,7 @@ export abstract class SpreadSheet extends EE {
152155
this.registerIcons();
153156
this.setOverscrollBehavior();
154157
this.mountSheetInstance();
158+
this.initStickyHeader();
155159
}
156160

157161
protected setupDataConfig(dataCfg: S2DataConfig) {
@@ -244,6 +248,24 @@ export abstract class SpreadSheet extends EE {
244248
}
245249
}
246250

251+
/**
252+
* 初始化表头吸顶控制器 (延迟到首次渲染完成后)
253+
*/
254+
private initStickyHeader() {
255+
if (!this.options.interaction?.stickyHeader) {
256+
return;
257+
}
258+
259+
// 吸顶控制器依赖 facet.cornerBBox, 需要在首次渲染完成后才能初始化
260+
const onFirstRender = () => {
261+
this.off(S2Event.LAYOUT_AFTER_RENDER, onFirstRender);
262+
this.stickyHeaderController?.destroy();
263+
this.stickyHeaderController = new StickyHeaderController(this);
264+
};
265+
266+
this.on(S2Event.LAYOUT_AFTER_RENDER, onFirstRender);
267+
}
268+
247269
protected initInteraction() {
248270
this.interaction?.destroy?.();
249271
this.interaction = new RootInteraction(this);
@@ -410,6 +432,24 @@ export abstract class SpreadSheet extends EE {
410432

411433
this.resetHiddenColumnsDetailInfoIfNeeded();
412434
this.registerIcons();
435+
this.syncStickyHeader();
436+
}
437+
438+
/**
439+
* 同步吸顶控制器状态:options 更新后如果 stickyHeader 配置变更则销毁/重建
440+
*/
441+
private syncStickyHeader() {
442+
const enabled = !!this.options.interaction?.stickyHeader;
443+
444+
// 先销毁旧控制器(无论是关闭还是选项内容变更都需要重建)
445+
if (this.stickyHeaderController) {
446+
this.stickyHeaderController.destroy();
447+
this.stickyHeaderController = null;
448+
}
449+
450+
if (enabled) {
451+
this.initStickyHeader();
452+
}
413453
}
414454

415455
/**
@@ -528,6 +568,8 @@ export abstract class SpreadSheet extends EE {
528568
this.destroyed = true;
529569
this.restoreOverscrollBehavior();
530570
this.emit(S2Event.LAYOUT_DESTROY);
571+
this.stickyHeaderController?.destroy();
572+
this.stickyHeaderController = null;
531573
this.facet?.destroy();
532574
this.hdAdapter?.destroy();
533575
this.interaction?.destroy();

0 commit comments

Comments
 (0)