176 lines
8.2 KiB
Diff
176 lines
8.2 KiB
Diff
diff --git a/src/StkTable/features/useAreaSelection.ts b/src/StkTable/features/useAreaSelection.ts
|
|
index edbdafc2..63eea823 100644
|
|
--- a/src/StkTable/features/useAreaSelection.ts
|
|
+++ b/src/StkTable/features/useAreaSelection.ts
|
|
@@ -158,6 +158,51 @@ export function useAreaSelection<DT extends Record<string, any>>(
|
|
|
|
/** 根据 selectionRanges 计算所有选区内 cellKey 的并集(非响应式,用于 DOM 操作) */
|
|
let selectedCellKeys: Set<string> = new Set();
|
|
+ let observedThead: HTMLTableSectionElement | null = null;
|
|
+ let observedTfoot: HTMLTableSectionElement | null = null;
|
|
+ let cachedHeaderHeight = 0;
|
|
+ let cachedFooterHeight = 0;
|
|
+ const sectionResizeObserver =
|
|
+ typeof ResizeObserver === 'undefined'
|
|
+ ? null
|
|
+ : new ResizeObserver(entries => {
|
|
+ for (const entry of entries) {
|
|
+ const height = entry.target.getBoundingClientRect().height;
|
|
+ if (entry.target === observedThead) cachedHeaderHeight = height;
|
|
+ if (entry.target === observedTfoot) cachedFooterHeight = height;
|
|
+ }
|
|
+ });
|
|
+
|
|
+ function observeTableSection(
|
|
+ next: HTMLTableSectionElement | null,
|
|
+ current: HTMLTableSectionElement | null,
|
|
+ setCurrent: (section: HTMLTableSectionElement | null) => void,
|
|
+ setHeight: (height: number) => void,
|
|
+ ) {
|
|
+ if (next === current) return;
|
|
+ if (current) sectionResizeObserver?.unobserve(current);
|
|
+ setCurrent(next);
|
|
+ setHeight(next?.getBoundingClientRect().height ?? 0);
|
|
+ if (next) sectionResizeObserver?.observe(next);
|
|
+ }
|
|
+
|
|
+ function getTableSectionHeights(container: HTMLElement) {
|
|
+ const thead = container.querySelector<HTMLTableSectionElement>('thead');
|
|
+ const tfoot = container.querySelector<HTMLTableSectionElement>('tfoot');
|
|
+ observeTableSection(
|
|
+ thead,
|
|
+ observedThead,
|
|
+ section => (observedThead = section),
|
|
+ height => (cachedHeaderHeight = height),
|
|
+ );
|
|
+ observeTableSection(
|
|
+ tfoot,
|
|
+ observedTfoot,
|
|
+ section => (observedTfoot = section),
|
|
+ height => (cachedFooterHeight = height),
|
|
+ );
|
|
+ return [cachedHeaderHeight, cachedFooterHeight] as const;
|
|
+ }
|
|
|
|
/** 重新计算 selectedCellKeys */
|
|
function recomputeSelectedCellKeys() {
|
|
@@ -235,6 +280,22 @@ export function useAreaSelection<DT extends Record<string, any>>(
|
|
const lastRange = ranges[ranges.length - 1];
|
|
const { minRow: lrMinRow, maxRow: lrMaxRow, minCol: lrMinCol, maxCol: lrMaxCol } = normalizeRange(lastRange);
|
|
|
|
+ // 键盘导航的常见路径只有一个单元格,直接定位目标行和列,
|
|
+ // 避免每次按键都扫描全部可见行。
|
|
+ if (ranges.length === 1 && lrMinRow === lrMaxRow && lrMinCol === lrMaxCol) {
|
|
+ const tr = tbody.querySelector(`tr[data-row-i="${lrMinRow}"]`);
|
|
+ const col = tableHeaderLast.value[lrMinCol];
|
|
+ const targetColKey = col ? String(colKeyGen.value(col)) : '';
|
|
+ const cells = tr?.querySelectorAll('td[data-col-key]') ?? [];
|
|
+ for (let i = 0; i < cells.length; i++) {
|
|
+ const td = cells[i] as HTMLElement;
|
|
+ if (td.getAttribute('data-col-key') !== targetColKey) continue;
|
|
+ td.classList.add(CELL_RANGE_SELECTED, CELL_RANGE_TOP, CELL_RANGE_BOTTOM, CELL_RANGE_LEFT, CELL_RANGE_RIGHT);
|
|
+ break;
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
+
|
|
// 遍历所有可见行
|
|
const trs = tbody.querySelectorAll('tr[data-row-i]');
|
|
for (let t = 0; t < trs.length; t++) {
|
|
@@ -317,6 +378,7 @@ export function useAreaSelection<DT extends Record<string, any>>(
|
|
});
|
|
onBeforeUnmount(() => {
|
|
removeListener();
|
|
+ sectionResizeObserver?.disconnect();
|
|
});
|
|
|
|
/**
|
|
@@ -860,10 +922,7 @@ export function useAreaSelection<DT extends Record<string, any>>(
|
|
const col = tableHeaderLast.value[colIndex];
|
|
if (!row || !col) return;
|
|
|
|
- const thead = container.querySelector('thead');
|
|
- const headerHeight = thead ? thead.offsetHeight : 0;
|
|
- const tfoot = container.querySelector('tfoot');
|
|
- const footerHeight = tfoot ? tfoot.offsetHeight : 0;
|
|
+ const [headerHeight, footerHeight] = getTableSectionHeights(container);
|
|
|
|
const vs = virtualScroll.value;
|
|
const vsx = virtualScrollX.value;
|
|
diff --git a/lib/StkTable-QUd4TJCF.js b/lib/StkTable-QUd4TJCF.js
|
|
index 512545c2..80fc32bd 100644
|
|
--- a/lib/StkTable-QUd4TJCF.js
|
|
+++ b/lib/StkTable-QUd4TJCF.js
|
|
@@ -444,6 +444,31 @@ function useAreaSelection(props, emits, tableContainerRef, dataSourceCopy, table
|
|
});
|
|
/** 根据 selectionRanges 计算所有选区内 cellKey 的并集(非响应式,用于 DOM 操作) */
|
|
let selectedCellKeys = /* @__PURE__ */ new Set();
|
|
+ let observedThead = null;
|
|
+ let observedTfoot = null;
|
|
+ let cachedHeaderHeight = 0;
|
|
+ let cachedFooterHeight = 0;
|
|
+ const sectionResizeObserver = typeof ResizeObserver === "undefined" ? null : new ResizeObserver((entries) => {
|
|
+ for (const entry of entries) {
|
|
+ const height = entry.target.getBoundingClientRect().height;
|
|
+ if (entry.target === observedThead) cachedHeaderHeight = height;
|
|
+ if (entry.target === observedTfoot) cachedFooterHeight = height;
|
|
+ }
|
|
+ });
|
|
+ function observeTableSection(next, current, setCurrent, setHeight) {
|
|
+ if (next === current) return;
|
|
+ if (current) sectionResizeObserver?.unobserve(current);
|
|
+ setCurrent(next);
|
|
+ setHeight(next?.getBoundingClientRect().height ?? 0);
|
|
+ if (next) sectionResizeObserver?.observe(next);
|
|
+ }
|
|
+ function getTableSectionHeights(container) {
|
|
+ const thead = container.querySelector("thead");
|
|
+ const tfoot = container.querySelector("tfoot");
|
|
+ observeTableSection(thead, observedThead, (section) => observedThead = section, (height) => cachedHeaderHeight = height);
|
|
+ observeTableSection(tfoot, observedTfoot, (section) => observedTfoot = section, (height) => cachedFooterHeight = height);
|
|
+ return [cachedHeaderHeight, cachedFooterHeight];
|
|
+ }
|
|
/** 重新计算 selectedCellKeys */
|
|
function recomputeSelectedCellKeys() {
|
|
const ranges = selectionRanges.value;
|
|
@@ -497,6 +522,19 @@ function useAreaSelection(props, emits, tableContainerRef, dataSourceCopy, table
|
|
if (cellHighlight) {
|
|
const lastRange = ranges[ranges.length - 1];
|
|
const { minRow: lrMinRow, maxRow: lrMaxRow, minCol: lrMinCol, maxCol: lrMaxCol } = normalizeRange(lastRange);
|
|
+ if (ranges.length === 1 && lrMinRow === lrMaxRow && lrMinCol === lrMaxCol) {
|
|
+ const tr = tbody.querySelector(`tr[data-row-i="${lrMinRow}"]`);
|
|
+ const col = tableHeaderLast.value[lrMinCol];
|
|
+ const targetColKey = col ? String(colKeyGen.value(col)) : "";
|
|
+ const cells = tr?.querySelectorAll("td[data-col-key]") ?? [];
|
|
+ for (let i = 0; i < cells.length; i++) {
|
|
+ const td = cells[i];
|
|
+ if (td.getAttribute("data-col-key") !== targetColKey) continue;
|
|
+ td.classList.add(CELL_RANGE_SELECTED, CELL_RANGE_TOP, CELL_RANGE_BOTTOM, CELL_RANGE_LEFT, CELL_RANGE_RIGHT);
|
|
+ break;
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
const trs = tbody.querySelectorAll("tr[data-row-i]");
|
|
for (let t = 0; t < trs.length; t++) {
|
|
const tr = trs[t];
|
|
@@ -559,6 +597,7 @@ function useAreaSelection(props, emits, tableContainerRef, dataSourceCopy, table
|
|
});
|
|
onBeforeUnmount(() => {
|
|
removeListener();
|
|
+ sectionResizeObserver?.disconnect();
|
|
});
|
|
/**
|
|
* 监听数据行数/列数变化,当行列变少时钳制选区与锚点,避免越界
|
|
@@ -966,10 +1005,7 @@ function useAreaSelection(props, emits, tableContainerRef, dataSourceCopy, table
|
|
const row = dataSourceCopy.value[rowIndex];
|
|
const col = tableHeaderLast.value[colIndex];
|
|
if (!row || !col) return;
|
|
- const thead = container.querySelector("thead");
|
|
- const headerHeight = thead ? thead.offsetHeight : 0;
|
|
- const tfoot = container.querySelector("tfoot");
|
|
- const footerHeight = tfoot ? tfoot.offsetHeight : 0;
|
|
+ const [headerHeight, footerHeight] = getTableSectionHeights(container);
|
|
const vs = virtualScroll.value;
|
|
const vsx = virtualScrollX.value;
|
|
const isScrollRowByRow = props.scrollRowByRow;
|