mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 04:43:49 +00:00
feat(grid): copy selection aggregate values on click
This commit is contained in:
@@ -15475,6 +15475,7 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
|
||||
:selection-summary="selectionSummary"
|
||||
:selection-summary-sum-text="selectionSummarySumText"
|
||||
:selection-summary-average-text="selectionSummaryAverageText"
|
||||
:selection-summary-pending="isSelectingCells"
|
||||
:loading="gridPaginationBusy || infiniteScrollLoading"
|
||||
:infinite-scroll-enabled="infiniteScrollEnabled"
|
||||
:infinite-scroll-all-loaded="infiniteScrollAllLoaded"
|
||||
@@ -15500,6 +15501,7 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
|
||||
@last-page="lastPage"
|
||||
@load-all-rows="loadAllRowsAndGoToLast"
|
||||
@select-export="selectExportMenuItem"
|
||||
@copy-selection-summary="copyText"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ const props = withDefaults(
|
||||
selectionSummary: { cellCount: number; rowCount: number } | null;
|
||||
selectionSummarySumText: string;
|
||||
selectionSummaryAverageText: string;
|
||||
// 拖拽选区期间聚合值只是占位符,父组件把该状态传下来,值不可复制
|
||||
selectionSummaryPending?: boolean;
|
||||
loading: boolean;
|
||||
infiniteScrollEnabled: boolean;
|
||||
infiniteScrollAllLoaded: boolean;
|
||||
@@ -33,7 +35,7 @@ const props = withDefaults(
|
||||
canGoNextPage: boolean;
|
||||
canJumpLastPage: boolean;
|
||||
}>(),
|
||||
{ paginationEnabled: true, loadAllRowsActive: false, loadAllRowsEnabled: true, canLoadAllRows: false, canExport: true },
|
||||
{ paginationEnabled: true, loadAllRowsActive: false, loadAllRowsEnabled: true, canLoadAllRows: false, canExport: true, selectionSummaryPending: false },
|
||||
);
|
||||
|
||||
const customPageSizeInput = defineModel<string>("customPageSizeInput", { default: "" });
|
||||
@@ -56,8 +58,18 @@ const emit = defineEmits<{
|
||||
loadAllRows: [];
|
||||
selectExport: [value: string];
|
||||
applyCustomPageSizeAndSetDefault: [];
|
||||
// 载荷是聚合值当前显示出来的文本,保证复制到的就是用户看到的那个数
|
||||
copySelectionSummary: [text: string];
|
||||
}>();
|
||||
|
||||
// 拖拽选区中聚合值显示为占位符 "…",此时不给复制入口,避免复制出一个假值
|
||||
const selectionSummaryValueClass = computed(() => (props.selectionSummaryPending ? "" : "cursor-pointer transition-colors hover:text-foreground"));
|
||||
|
||||
function copySelectionSummaryValue(text: string) {
|
||||
if (props.selectionSummaryPending) return;
|
||||
emit("copySelectionSummary", text);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.currentPage,
|
||||
(page) => {
|
||||
@@ -93,8 +105,12 @@ function handlePageInputKeydown(event: KeyboardEvent) {
|
||||
<template>
|
||||
<div class="flex min-w-max items-center justify-end gap-1">
|
||||
<div v-if="selectionSummary" class="flex shrink-0 items-center gap-3 tabular-nums">
|
||||
<span class="shrink-0">{{ t("grid.selectionSum", { value: selectionSummarySumText }) }}</span>
|
||||
<span class="shrink-0">{{ t("grid.selectionAverage", { value: selectionSummaryAverageText }) }}</span>
|
||||
<button type="button" class="shrink-0 text-left" :class="selectionSummaryValueClass" :title="selectionSummaryPending ? undefined : t('grid.copyValue')" :disabled="selectionSummaryPending" data-selection-summary-sum @click="copySelectionSummaryValue(selectionSummarySumText)">
|
||||
<span v-if="!selectionSummaryPending" class="sr-only">{{ t("grid.copyValue") }}: </span>{{ t("grid.selectionSum", { value: selectionSummarySumText }) }}
|
||||
</button>
|
||||
<button type="button" class="shrink-0 text-left" :class="selectionSummaryValueClass" :title="selectionSummaryPending ? undefined : t('grid.copyValue')" :disabled="selectionSummaryPending" data-selection-summary-average @click="copySelectionSummaryValue(selectionSummaryAverageText)">
|
||||
<span v-if="!selectionSummaryPending" class="sr-only">{{ t("grid.copyValue") }}: </span>{{ t("grid.selectionAverage", { value: selectionSummaryAverageText }) }}
|
||||
</button>
|
||||
<div class="flex shrink-0 items-center gap-1">
|
||||
<span class="shrink-0">{{ t("grid.selectionCells", { count: selectionSummary.cellCount }) }}</span>
|
||||
<span class="shrink-0">{{ t("grid.rows", { count: selectionSummary.rowCount }) }}</span>
|
||||
|
||||
@@ -461,6 +461,78 @@ describe("DataGridPagination", () => {
|
||||
const exportMenus = findAll(mounted.root, (node) => node.props["data-stub"] === "LightDropdown" && node.props["aria-label"] === "grid.export");
|
||||
expect(exportMenus).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("copies each aggregate value on click, and only the values", () => {
|
||||
const copySelectionSummary = vi.fn();
|
||||
const mounted = mountComponent(DataGridPagination, {
|
||||
selectionSummary: { cellCount: 4, rowCount: 2 },
|
||||
selectionSummarySumText: "10",
|
||||
selectionSummaryAverageText: "2.5",
|
||||
loading: false,
|
||||
infiniteScrollEnabled: false,
|
||||
infiniteScrollAllLoaded: false,
|
||||
pageSize: 100,
|
||||
customPageSizeInput: "",
|
||||
pageSizeMenuItems: [],
|
||||
exportMenuItems: [],
|
||||
currentPage: 1,
|
||||
canGoNextPage: false,
|
||||
canJumpLastPage: false,
|
||||
onCopySelectionSummary: copySelectionSummary,
|
||||
});
|
||||
|
||||
const sum = findOne(mounted.root, (node) => node.props["data-selection-summary-sum"] === "");
|
||||
const average = findOne(mounted.root, (node) => node.props["data-selection-summary-average"] === "");
|
||||
// Native buttons provide tab focus plus Enter/Space activation without recreating
|
||||
// the complete button interaction model on a clickable text span.
|
||||
expect(sum.type).toBe("button");
|
||||
expect(average.type).toBe("button");
|
||||
expect(sum.props.title).toBe("grid.copyValue");
|
||||
expect(hostText(sum)).toContain("grid.copyValue");
|
||||
|
||||
dispatch(sum, "click");
|
||||
expect(copySelectionSummary).toHaveBeenCalledWith("10");
|
||||
|
||||
dispatch(average, "click");
|
||||
expect(copySelectionSummary).toHaveBeenLastCalledWith("2.5");
|
||||
expect(copySelectionSummary).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("keeps the aggregate values non-copyable while the selection drag is pending", () => {
|
||||
const copySelectionSummary = vi.fn();
|
||||
const mounted = mountComponent(DataGridPagination, {
|
||||
selectionSummary: { cellCount: 4, rowCount: 2 },
|
||||
selectionSummarySumText: "…",
|
||||
selectionSummaryAverageText: "…",
|
||||
selectionSummaryPending: true,
|
||||
loading: false,
|
||||
infiniteScrollEnabled: false,
|
||||
infiniteScrollAllLoaded: false,
|
||||
pageSize: 100,
|
||||
customPageSizeInput: "",
|
||||
pageSizeMenuItems: [],
|
||||
exportMenuItems: [],
|
||||
currentPage: 1,
|
||||
canGoNextPage: false,
|
||||
canJumpLastPage: false,
|
||||
onCopySelectionSummary: copySelectionSummary,
|
||||
});
|
||||
|
||||
const sum = findOne(mounted.root, (node) => node.props["data-selection-summary-sum"] === "");
|
||||
// Pending aggregate placeholders retain their layout but expose no copy action.
|
||||
expect(sum.props.title).toBeUndefined();
|
||||
expect(sum.props.disabled).toBe(true);
|
||||
|
||||
dispatch(sum, "click");
|
||||
expect(copySelectionSummary).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("DataGrid selection summary copy wiring", () => {
|
||||
it("routes the pagination aggregate copy through the grid clipboard helper", () => {
|
||||
expect(dataGridSource).toMatch(/@copy-selection-summary="copyText"/);
|
||||
expect(dataGridSource).toMatch(/:selection-summary-pending="isSelectingCells"/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DataGridColumnHeader", () => {
|
||||
|
||||
Reference in New Issue
Block a user