TanStack Angular Table 单元格选择(Cell Selection)完全指南:从矩形框选到多选区状态管理
2026/9/20 17:08:16 网站建设 项目流程

TanStack Angular Table 单元格选择(Cell Selection)完全指南:从矩形框选到多选区状态管理

【免费下载链接】table🤖 Headless UI for building powerful tables & datagrids for TS/JS - React-Table, Vue-Table, Solid-Table, Svelte-Table项目地址: https://gitcode.com/gh_mirrors/ta/table

单元格选择(Cell Selection)是 TanStack Table 体系中面向表格数据网格(datagrid)场景的核心能力,它为表格引入电子表格风格的矩形区域选择:用户可以单击选中一个单元格、拖拽划过一片区域、Shift 点击扩展选区、Ctrl/Cmd 拖拽增删矩形块。本文以 TanStack Angular Table 的官方指南 docs/framework/angular/guide/cell-selection.md 为骨架,结合仓库中 packages/table-core 的源码实现、examples/angular/cell-selection 的可运行示例以及配套测试,完整讲解单元格选择状态的读写、鼠标交互、键盘导航、范围操作的底层原理与性能特征。读完本文,你将掌握如何在 Angular 应用中启用单元格选择、如何用外部原子或受控状态接管选择状态、如何渲染选区高亮与描边、如何把选区序列化为剪贴板文本,以及选区在排序、过滤、列固定、分页等表格变化下如何保持正确。

启用 Cell Selection:功能组合与最小配置

单元格选择是一个按需注册的表格功能(feature)。在 TanStack Angular Table 中,所有功能通过tableFeatures组合器聚合,再把组合结果传给injectTable。下面的最小配置展示了如何开启单元格选择并接入响应式数据:

import { signal } from '@angular/core' import { injectTable, tableFeatures, cellSelectionFeature, } from '@tanstack/angular-table' const features = tableFeatures({ cellSelectionFeature }) export class App { readonly data = signal(defaultData) readonly table = injectTable(() => ({ features, columns, data: this.data(), })) }

一旦注册了cellSelectionFeature,与单元格选择相关的一组 API 就会同步挂载到 table 实例、cell 实例与 column def 上。这一点可以从 table-core 的实现中得到印证:在 packages/table-core/src/features/cell-selection/cellSelectionFeature.ts 中,cellSelectionFeature通过assignCellPrototype为 cell 原型挂载getCanSelectgetIsSelectedgetIsFocusedgetTabIndexgetSelectionEdgesgetSelectionStartHandlergetSelectionExtendHandler等方法,通过constructTableAPIs为 table 挂载selectCellRangegetCellSelectionBoundsgetSelectedCellCountgetSelectedCellIdsgetSelectedCellRangesDatamoveCellSelectionextendCellSelectionsetFocusedCellselectAllCellsresetCellSelection等状态与操作 API。

仓库中的官方示例 examples/angular/cell-selection/src/app/table.ts 展示了更完整的组合方式——单元格选择往往与列排序、列固定、列可见性等配套功能一起使用:

export const features = tableFeatures({ cellSelectionFeature, columnOrderingFeature, columnPinningFeature, columnVisibilityFeature, rowSortingFeature, sortedRowModel: createSortedRowModel(), sortFns: { alphanumeric: sortFn_alphanumeric, datetime: sortFn_datetime, text: sortFn_text, }, })

随后通过createTableHook({ features })生成应用专用的injectTable工厂(见 examples/angular/cell-selection/src/app/table.ts),并在组件内注入表格(见 examples/angular/cell-selection/src/app/app.ts):

readonly table = injectTable(() => ({ key: 'cell-selection', // 供 devtools 识别 data: this.data(), columns: this.columns, getRowId: (row: Person) => row.id, enableCellSelection: true, // 开启所有单元格的可选择能力 debugTable: true, }))

读取单元格选择状态

表格实例已经替你管理了选择状态,你可以通过以下 API 读取选区或其派生信息:

  • table.atoms.cellSelection.get()—— 返回当前的单元格选择状态。在 Angular 响应式上下文(模板、computed(...)effect(...))中读取会被自动追踪,数据变化时视图自动刷新;在响应式上下文之外调用则是一次性的普通快照读取。
  • getSelectedCellCount()—— 返回已选中的单元格数量。
  • getSelectedCellIds()—— 返回每个已选中单元格的 id。
  • getCellSelectionRowIds()/getCellSelectionColumnIds()—— 返回选区所触及的行与列。
  • getSelectedCellRangesData()—— 返回每个最终正选区(positive region)的值,按行优先(row-major)组织成二维网格。
console.log(table.atoms.cellSelection.get()) // 读取单元格选择状态 console.log(table.getSelectedCellCount()) // 3 console.log(table.getSelectedCellIds()) // ['0_firstName', '0_lastName', '1_firstName'] console.log(table.getSelectedCellRangesData()) // [[['Tanner', 'Linsley'], ['Kevin', 'Vandy']]]

从实现角度看,getSelectedCellIdsgetSelectedCellRangesData这类扩展 API 都是**记忆化(memoized)且按需拉取(pull-based)**的:只要你不调用它们,它们就不会产生任何计算开销。因此,一张仅做高亮展示的表格永远不会为枚举一个超大选区而付出代价。其底层实现位于 packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts 的table_getSelectedCellRangesData:它先通过getCellSelectionBounds把操作日志解析成互不相交的正矩形,再调用forEachSelectedCell按区域、按行偏移、按列偏移遍历单元格并收集cell.getValue(),最终输出Array<Array<Array<unknown>>>形式的行优先网格。

在 Angular 模板中,这些读取会借助信号的追踪机制保持新鲜。官方示例 examples/angular/cell-selection/src/app/app.html 直接在内联模板里展示统计信息:

<div> {{ table.getSelectedCellCount().toLocaleString() }} cells selected across {{ table.getCellSelectionRowIds().length.toLocaleString() }} rows and {{ table.getCellSelectionColumnIds().length }} columns </div>

选择状态的数据结构

CellSelectionState是一个有序的范围操作数组,每个操作以决定矩形的两个对角点存储:

type CellSelectionRange = { anchorRowId: string anchorColumnId: string focusRowId: string focusColumnId: string operation?: 'include' | 'exclude' } type CellSelectionState = Array<CellSelectionRange>

这里有两个设计要点值得深入理解:

锚点(anchor)与焦点(focus)的区分。anchor角是选区开始的位置,保持不变;focus角是在拖拽或 Shift 扩展时移动的一端。存储这两个角、而不是归一化后的 min/max 矩形,正是 Shift 扩展以及"折叠回活动单元格"这类交互能够实现的前提——归一化矩形会在扩展过程中丢失"哪一端是固定端"这一关键信息。

顺序应用的操作日志。所有 range 按数组顺序依次应用。operation省略时表示包含(include),这是为了向后兼容;exclude范围则从当前产生的选区中减去其矩形。这个紧凑的操作日志设计意味着"全选但排除这些单元格"这种交互不需要为每个选中的单元格建立一条映射记录。从 packages/table-core/tests/implementation/features/cell-selection/cellSelectionRange.test.ts 中的测试可以看到,排除操作确实会从正选区几何中挖洞:

// 选中 r0..r2 × a..c 的 3×3 矩形,再排除中心单元格 r1_b table.selectCellRange(rangeOf('r0', 'a', 'r2', 'c')) table.selectCellRange(rangeOf('r1', 'b', 'r1', 'b'), { mode: 'exclude' }) expect(table.getSelectedCellCount()).toBe(8) expect(table.getSelectedCellIds()).not.toContain('r1_b')

同一测试文件还验证了"排除之后可以再次包含回来"、"mode选项优先于已被废弃的additive选项"等语义(详见cellSelectionRange.test.ts中的bounds resolution用例组)。

管理单元格选择状态

外部原子(推荐)

如果你需要在应用的其他位置访问选择状态,可以自行持有这个状态切片。v9 推荐的方式是通过atoms表选项传入外部原子:

import { createAtom } from '@tanstack/angular-store' import { injectTable, tableFeatures, cellSelectionFeature, type CellSelectionState, } from '@tanstack/angular-table' const features = tableFeatures({ cellSelectionFeature }) export class App { readonly cellSelectionAtom = createAtom<CellSelectionState>([]) readonly table = injectTable(() => ({ features, columns, data: this.data(), atoms: { cellSelection: this.cellSelectionAtom }, })) }

受控状态(经典模式)

经典的受控状态模式同样可用:用state.cellSelection传入当前值,用onCellSelectionChange接收更新回调:

export class App { readonly cellSelection = signal<CellSelectionState>([]) readonly table = injectTable(() => ({ features, columns, data: this.data(), state: { cellSelection: this.cellSelection() }, onCellSelectionChange: (updater) => { this.cellSelection.update((old) => typeof updater === 'function' ? updater(old) : updater, ) }, })) }

[!NOTE] 拖拽过程中,指针每跨过一个单元格边界就会触发一次状态更新,因此onCellSelectionChange在拖拽期间会被反复调用。如果你要把选区同步到服务器或 URL,请务必做防抖处理,或者在mouseup时统一提交。

官方示例 examples/angular/cell-selection/src/app/app.ts 中把这两种模式以注释形式保留为备选方案(atoms: { cellSelection: cellSelectionAtom }state: { cellSelection }配合onCellSelectionChange),默认走的是表格内部状态。

使用有意义的行 id

单元格选择以行 id + 列 id为键,因此一个有意义、稳定的行 id 在这里与行选择(row selection)同等重要。请使用getRowId表选项,把选择键绑定到数据中稳定不变的字段上:

readonly table = injectTable(() => ({ features, //... getRowId: (row) => row.uuid, // 使用数据库中的 uuid 作为行 id }))

官方示例使用的正是这一模式:getRowId: (row: Person) => row.id(见 examples/angular/cell-selection/src/app/app.ts)。稳定的行 id 是"选区跨数据变化存续"的基石,也是下面"范围如何存活于表格变化"一节所讨论行为的根基。

条件启用单元格选择

单元格选择对每个单元格默认开启。你可以用enableCellSelection表选项整体关闭,或传入函数实现按单元格控制:

readonly table = injectTable(() => ({ features, //... enableCellSelection: (cell) => cell.row.original.age > 18, // 仅成年人的单元格可选 }))

列定义也可以单独退出选择,这是复选框列、操作列等场景的常见需求。列级false的优先级高于表选项

columnHelper.accessor('actions', { enableCellSelection: false, // 该列永远不可选 })

不可选的单元格即便被矩形直接划过也会被跳过;moveCellSelection会越过其所在的列而不会落在上面。在 UI 中请用cell.getCanSelect()决定是否为单元格挂接选择事件处理器。官方示例 examples/angular/cell-selection/src/app/app.html 正是这样做的——只有cell.getCanSelect()为真的单元格才渲染mousedown/mouseenter处理器与 tabindex:

@for (cell of row.getVisibleCells(); track cell.id) { @if (cell.getCanSelect()) { <td [class]="getCellClassName(cell)" [attr.tabindex]="cell.getTabIndex()" (mousedown)="cell.getSelectionStartHandler()($event)" (mouseenter)="cell.getSelectionExtendHandler()($event)" > <ng-container *flexRenderCell="cell; let renderCell"> {{ renderCell }} </ng-container> </td> } @else { <td> <ng-container *flexRenderCell="cell; let renderCell"> {{ renderCell }} </ng-container> </td> } }

示例中progress列上还保留着// enableCellSelection: false,的注释,方便读者取消注释观察"整列退出选择"的效果。

鼠标交互

两个 cell 处理器驱动了全部鼠标交互:

  • cell.getSelectionStartHandler()—— 绑定到onMouseDown
  • cell.getSelectionExtendHandler()—— 绑定到onMouseEnter
<td (mousedown)="cell.getSelectionStartHandler()($event)" (mouseenter)="cell.getSelectionExtendHandler()($event)" > <ng-container *flexRenderCell="cell; let renderCell"> {{ renderCell }} </ng-container> </td>

不需要自行处理mouseup。起始处理器(start handler)会自行挂载一个 document 级别的mouseup监听器,并在拖拽结束时移除它——因此即使指针在表格之外释放,拖拽也能正确收尾。如果你的表格渲染在另一个 document 中(例如 iframe 或弹出窗口),请把该 document 传进去:cell.getSelectionStartHandler(myDocument)

拖拽选择(Drag Selection)

在单元格上按下鼠标会开启一个新的单格范围,之后指针每进入一个单元格,该范围的焦点角就随之移动。设置enableCellSelectionDrag: false可以关闭拖拽扩展,改为要求显式点击。

Shift 范围选择(Shift Range Selection)

Shift 点击会把当前活动范围的焦点角移动到被点击的单元格,锚点保持不变。因此活动单元格始终停留在选区开始的位置,与电子表格行为一致。

处理器在事件的event.shiftKeyevent.nativeEvent.shiftKey任一暴露时即识别为 Shift。你可以禁用范围行为,或替换检测逻辑:

readonly table = injectTable(() => ({ features, //... enableCellRangeSelection: false, // 例如,改用平台修饰键而不是 Shift: // isCellRangeSelectionEvent: event => Boolean(event.metaKey), }))

默认的 Shift 检测与多选区修饰键检测都定义在 packages/table-core/src/features/cell-selection/cellSelectionFeature.ts 的getDefaultTableOptions中。源码同时暴露了event.ctrlKey/event.metaKeynativeEvent双通道的兼容读取,Angular 的合成事件与原生事件都能被正确识别。

多选区(Multiple Ranges)

Ctrl 点击或 Cmd 点击一个未选中的单元格会追加一个包含矩形;在已选中的单元格上发起同样的修饰键交互则会改为追加一个排除操作——于是点击会移除该单元格,拖拽会减去整个矩形。拖拽开始的那一刻,其包含/排除属性即被固定;缩小一个排除拖拽的范围时,离开其矩形的单元格会被恢复。设置enableMultiCellRangeSelection: false可同时禁用这两种行为,或通过覆盖isMultiCellRangeSelectionEvent更换修饰键。

编程式范围操作(Programmatic Range Operations)

table.selectCellRange(range)会替换当前选择。传入{ mode: 'include' }追加包含操作,{ mode: 'exclude' }追加排除操作。旧的{ additive: true }选项保留为 include 模式的废弃别名;若同时提供了mode,则以mode为准。table.getCellSelectionBounds()会把操作日志解析为确定性的、互不相交的正矩形集合。

测试文件 cellSelectionRange.test.ts 对这组语义给出了精确的断言,包括:

  • 角点归一化:selectCellRange(rangeOf('r3', 'c', 'r1', 'a'))无论角点顺序如何,都归一化为minRowIndex: 1, maxRowIndex: 3, minColumnIndex: 0, maxColumnIndex: 2的包含式索引矩形;
  • 多个不相交矩形分别解析;
  • 重叠矩形在行/列汇总(rollup)与单元格 id 中自动去重(例如getSelectedCellRowIds()返回['r0','r1','r2','r3'],计数只算 7 个不重复单元格);
  • getCellSelectionBounds()是记忆化的,两次连续读取返回同一个引用。

渲染选区 UI

TanStack Table 不规定你如何渲染选中的单元格,下面这些 cell API 提供了所需的一切:

  • cell.getIsSelected()—— 该单元格是否落在任意范围内
  • cell.getIsFocused()—— 是否为活动单元格(被排除的锚点可以处于聚焦状态而不被选中)
  • cell.getSelectionEdges()—— 哪些边位于选区边界上
  • cell.getTabIndex()—— 聚焦单元格返回0,其余返回-1,用于实现 roving tabindex

getSelectionEdges()返回{ top, right, bottom, left },其中某条边为true表示该方向上的相邻单元格本身未被选中。这正是你能围绕选区(包括多个分离矩形的并集)绘制一条连续外轮廓、而无需每个单元格自查邻居的原因。

function getCellClassName(cell) { // 绝大多数单元格未选中,先提前返回,避免无谓地请求边信息 if (!cell.getIsSelected()) { return cell.getIsFocused() ? 'cell cell-focused' : 'cell' } const edges = cell.getSelectionEdges() return [ 'cell', 'cell-selected', cell.getIsFocused() && 'cell-focused', edges.top && 'cell-edge-top', edges.right && 'cell-edge-right', edges.bottom && 'cell-edge-bottom', edges.left && 'cell-edge-left', ] .filter(Boolean) .join(' ') }

官方示例 examples/angular/cell-selection/src/app/app.ts 中的getCellClassName实现了同一逻辑,并特别强调:绝大多数未选中的单元格应尽早返回,避免为它们再次解析位置来求边信息——getSelectionEdges()只有在确认选中之后才值得调用。

[!TIP] 用box-shadow: inset ...而不是border绘制轮廓。在border-collapse表格中,更粗的边框会加宽共享的网格线,导致行高在单元格被选中时发生变化;而 box-shadow 永远不会影响布局。

键盘导航

单元格选择自身不携带任何键盘处理逻辑,而是暴露一组命令式 API,让专用的快捷键库(例如 TanStack Hotkeys)来驱动它:

  • table.moveCellSelection(direction)—— 把选区折叠为单格并朝指定方向移动一步
  • table.extendCellSelection(direction)—— 移动活动范围的焦点角,锚点保持不变
  • table.setFocusedCell(rowId, columnId)—— 把选区折叠到某个具体单元格
  • table.selectAllCells()—— 选中所有可选单元格
  • table.resetCellSelection(true)—— 清空选区

direction取值为'up''down''left''right'

import { injectHotkeys } from '@tanstack/angular-hotkeys' export class App { readonly grid = viewChild<ElementRef<HTMLDivElement>>('grid') constructor() { injectHotkeys( [ { hotkey: 'ArrowUp', callback: () => this.table.moveCellSelection('up'), }, { hotkey: 'ArrowDown', callback: () => this.table.moveCellSelection('down'), }, { hotkey: 'Shift+ArrowDown', callback: () => this.table.extendCellSelection('down'), }, { hotkey: 'Mod+A', callback: () => this.table.selectAllCells() }, { hotkey: 'Escape', callback: () => this.table.resetCellSelection(true), }, ], () => ({ target: this.grid()?.nativeElement ?? null }), ) } }

务必把快捷键作用域限定在网格元素上,而不是整个 document,否则方向键和 Escape 会劫持页面上其他输入框的按键。官方示例在 examples/angular/cell-selection/src/app/app.ts 中实现了完整的键位方案:四个方向键移动选区、Shift+方向键扩展、Mod+A全选、Mod+C复制选区(复用copySelection())、Escape 清空;对应的网格容器在模板中以<div #grid tabindex="0">标记(见 app.html)。

复制选区到剪贴板

getSelectedCellRangesData()返回原始值,索引结构为[regionIndex][rowIndex][columnIndex]。一个 region 是所有包含/排除操作应用完毕后的最终不相交正矩形之一,因此它不一定与存储的状态一一对应。把数据转换为剪贴板文本属于应用层职责——分隔符、null的表示、引号规则都只能由你来决定。

function escapeTsvValue(value: unknown) { const text = value == null ? '' : String(value) const safeText = typeof value === 'string' && /^[\t\r ]*[=+@-]/.test(value) ? `'${text}` : text // 电子表格期望:字段一旦包含分隔符、换行或引号就必须加引号,内部引号翻倍 return /["\t\n\r]/.test(safeText) ? `"${safeText.replace(/"/g, '""')}"` : safeText } function toTsv(ranges: Array<Array<Array<unknown>>>) { return ranges .map((grid) => grid.map((row) => row.map(escapeTsvValue).join('\t')).join('\n'), ) .join('\n\n') } navigator.clipboard.writeText(toTsv(table.getSelectedCellRangesData()))

这段 TSV 序列化代码直接取自官方示例 examples/angular/cell-selection/src/app/app.ts(escapeTsvValuetoTsv)。示例还专门在网格外部放置了一个textarea作为粘贴测试区,用于目测复制出来的制表符分隔形状;它位于网格元素之外,因此表格的快捷键不会拦截其中的输入与Mod+V(见 app.html)。

选区如何存活于表格变化

范围存储的是行 id 与列 id,而不是位置,因此它们跟随的是角落单元格,而不是屏幕坐标。

  • 排序、过滤与列重排:角落单元格保持钉住,表格会重算它们之间夹着什么。从"行 A 到行 B"的范围在排序后仍然是从 A 到 B,即使中间落入了不同的行。
  • 列固定(pinning):渲染顺序中已计入固定列的位移,因此列被固定后矩形在视觉上依然连续。这一正确性由 packages/table-core/src/features/cell-selection/cellSelectionFeature.utils.ts 中getDisplayOrderedColumns的实现保证——它刻意不使用getVisibleLeafColumns()(该函数只过滤可见性、不按固定列重排),而是按"start 固定列 → 中间列 → end 固定列"的渲染顺序建立列索引,使选区在索引空间与视觉空间同时保持连续。
  • 隐藏角落单元格所在的列:范围变为惰性(inert)。不会有任何内容渲染为选中态,但范围仍保留在状态中,列重新显示后选区随之恢复。
  • 分页:范围针对分页前的顺序解析,因此一个范围可以横跨多页,并在你当前浏览的页面上正确点亮。

由于一次列重排可能把选区拓宽到用户从未选过的列上,有些应用倾向在列布局变化时清空选区。这是用户层的决策,一个 Angulareffect即可实现:

let isFirstLayout = true effect(() => { // 读取原子,使该 effect 只追踪布局切片 this.table.atoms.columnOrder.get() this.table.atoms.columnPinning.get() this.table.atoms.columnVisibility.get() untracked(() => { if (isFirstLayout) { isFirstLayout = false return } this.table.resetCellSelection(true) }) })

官方示例在 examples/angular/cell-selection/src/app/app.ts 的构造函数中实现了一个更完整的版本——它额外追踪this.table.atoms.sorting.get(),并在首次执行后跳过,之后任何列布局或排序变化都会清空选区;示例页面还提供了 "Shuffle Columns"、"Reverse Column Order" 等按钮,方便你直观观察"选区钉住角落单元格"的行为。

重置单元格选择

table.resetCellSelection()恢复initialState.cellSelection。传入true则忽略初始状态、彻底清空选区。

选择还会在data变化时自动重置:因为新数据可能使范围指向的行 id 失效,或者在新数据恰好复用旧 id 时静默地重新选中单元格。用autoResetCellSelection: false关闭此行为——注意autoResetAll的优先级更高,会覆盖它:

readonly table = injectTable(() => ({ features, //... autoResetCellSelection: false, // 跨数据变化保留范围 }))

源码层面,自动重置的逻辑位于table_autoResetCellSelection(cellSelectionFeature.utils.ts):只有同时满足autoResetAllautoResetCellSelection(默认true)时才调度重置;重置回initialState.cellSelection而非空数组,使得首次行模型计算成为一次 no-op。同时,getDefaultTableOptions中列出的全部默认值——autoResetCellSelection: trueenableCellSelection: trueenableCellRangeSelection: trueenableMultiCellRangeSelection: trueenableCellSelectionDrag: true——都定义在 cellSelectionFeature.ts 中,可供查阅核对。

性能特征

Angular 的信号机制会追踪这些读取,因此一次选区变化只会标记实际读取了它的组件。这里不存在 React 中那种按行Subscribe的对应物,官方示例中的单元格就是朴素渲染的(见 examples/angular/cell-selection/src/app/app.html)。

在 1000 行 × 12 列的表格上实测,使用朴素读取与ChangeDetectionStrategy.OnPush时,一次拖拽更新的耗时约为15ms/次移动

每个单元格的读取在设计上都是廉价的。cell.getIsSelected()只需解析该单元格的行、列索引,然后与记忆化的选区边界缓存做若干次整数比较即可(见 cellSelectionFeature.utils.ts 中的cell_getIsSelectedisWithinBounds)。源码注释明确说明:这些 cell 级 API刻意不做记忆化——为每个单元格分配记忆化闭包和依赖数组的开销,远大于对表格级边界缓存的几次整数比较。如果超大表格确实成为瓶颈,请使用虚拟化(见 docs/framework/angular/guide/virtualization.md)让只有可见行存在于 DOM 中,而不是退回到订阅模式。

最后需要提醒:Angular 的变更检测是异步刷新的,因此在点击后同步读取 DOM 的测试可能抢先于更新执行。测试应轮询断言(如 Playwright 的expect.poll),而不是只读取一次。官方 e2e 测试 examples/angular/cell-selection/tests/e2e/smoke.spec.ts 正是如此——它验证了 Shift 点击后td.cell-selected恰好为 9 个、起始单元格保持cell-focused类、范围之外的单元格不带选中类,并对"Regenerate Data"后的数据刷新使用expect.poll等待。

小结与参考

单元格选择把电子表格式的矩形选区完整地带入了 Angular 表格:cellSelectionFeature组合进tableFeatures即可启用;atoms.cellSelection与一系列getSelectedCell*API 负责读取,外部原子与受控状态两种模式负责接管;getSelectionStartHandler/getSelectionExtendHandler驱动拖拽、Shift 扩展与多选区增删;moveCellSelection等命令式 API 交给 TanStack Hotkeys 等快捷键库驱动;选区以 anchor/focus 角点操作日志的形式存储,天然具备跨排序、过滤、列固定与分页的稳定性。

想深入实践,可以运行官方示例 examples/angular/cell-selection(含app.tsapp.htmltable.ts完整源码与 e2e 测试);想研究底层原理,可阅读 packages/table-core/src/features/cell-selection/cellSelectionFeature.ts 与 cellSelectionFeature.utils.ts,以及 cellSelectionRange.test.ts 中覆盖边界解析、去重、排除语义、记忆化等行为的单元测试。表格的整体状态管理方式可进一步参考 docs/framework/angular/guide/table-state.md。

【免费下载链接】table🤖 Headless UI for building powerful tables & datagrids for TS/JS - React-Table, Vue-Table, Solid-Table, Svelte-Table项目地址: https://gitcode.com/gh_mirrors/ta/table

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询