feat(grid): resizable column filter panel

This commit is contained in:
二丫讲梵 authored and GitHub committed 2026-09-05 01:03:38 +08:00
1 parent 61aa00bf8f
commit ce36a7f40d
3 files changed
+103 -1

No files matched your search

+91 -1
View File
@@ -1071,6 +1071,33 @@ const headerSortMenuOpenColumn = ref<number | null>(null);
const headerPanelDismissGuardUntil = ref(0);
const localFilterSearch = ref("");
const localFilterDraft = ref<LocalColumnFilterDraft | null>(null);
// Default sized to fully render a uuidv4 value (36 chars in text-xs mono) next to the
// checkbox and count columns; persisted per-editor as settings.localFilterPopoverWidth.
const LOCAL_FILTER_POPOVER_DEFAULT_WIDTH = 360;
const LOCAL_FILTER_POPOVER_MIN_WIDTH = 240;
const LOCAL_FILTER_POPOVER_VIEWPORT_PADDING = 16;
function localFilterPopoverMaxWidth() {
if (typeof window === "undefined") return LOCAL_FILTER_POPOVER_DEFAULT_WIDTH;
return Math.max(0, window.innerWidth - 32);
}
function clampLocalFilterPopoverWidth(width: number, maximumWidth = localFilterPopoverMaxWidth()) {
const normalizedWidth = Number.isFinite(width) ? width : LOCAL_FILTER_POPOVER_DEFAULT_WIDTH;
const maxWidth = Math.max(0, Math.min(localFilterPopoverMaxWidth(), maximumWidth));
const minWidth = Math.min(LOCAL_FILTER_POPOVER_MIN_WIDTH, maxWidth);
return Math.round(Math.max(minWidth, Math.min(maxWidth, normalizedWidth)));
}
const localFilterPopoverWidth = ref(clampLocalFilterPopoverWidth(settingsStore.editorSettings.localFilterPopoverWidth));
const localFilterPopoverOffsetX = ref(0);
const isResizingLocalFilter = ref(false);
let localFilterResizeDirection: "left" | "right" = "right";
let localFilterResizeStartX = 0;
let localFilterResizeStartWidth = LOCAL_FILTER_POPOVER_DEFAULT_WIDTH;
let localFilterResizeStartOffsetX = 0;
let localFilterResizeStartLeft = 0;
let localFilterResizeStartRight = 0;
const SERVER_COLUMN_FILTER_LIMIT = 1000;
const SERVER_COLUMN_FILTER_DEBOUNCE_MS = 300;
const serverFilterLoading = ref(false);
@@ -1395,6 +1422,7 @@ const canApplyTypedLocalFilterValue = computed(() => {
function openLocalFilter(colIdx: number, requestedMode: LocalFilterMode = "local") {
localFilterSearch.value = "";
localFilterPopoverOffsetX.value = 0;
const mode: LocalFilterMode = requestedMode === "server" && canUseServerColumnFilter.value ? "server" : "local";
const allKeys = mode === "server" ? [] : buildLocalFilterOptions(colIdx).map((option) => option.key);
localFilterDraft.value = {
@@ -1573,12 +1601,64 @@ function handleLocalFilterOpenChange(value: boolean, columnIndex: number) {
}
function closeLocalFilter() {
onLocalFilterResizeEnd();
localFilterOpenColumn.value = null;
localFilterDraft.value = null;
localFilterSearch.value = "";
localFilterPopoverOffsetX.value = 0;
resetServerFilterState();
}
function onLocalFilterResizeStart(event: MouseEvent, direction: "left" | "right") {
event.preventDefault();
isResizingLocalFilter.value = true;
localFilterResizeDirection = direction;
localFilterResizeStartX = event.clientX;
const handle = event.currentTarget as HTMLElement | null;
const popoverRect = handle?.parentElement?.getBoundingClientRect();
const renderedWidth = popoverRect?.width;
localFilterResizeStartWidth = clampLocalFilterPopoverWidth(renderedWidth ?? localFilterPopoverWidth.value);
localFilterResizeStartOffsetX = localFilterPopoverOffsetX.value;
localFilterResizeStartLeft = popoverRect?.left ?? LOCAL_FILTER_POPOVER_VIEWPORT_PADDING;
localFilterResizeStartRight = popoverRect?.right ?? localFilterResizeStartWidth;
localFilterPopoverWidth.value = localFilterResizeStartWidth;
document.body.classList.add("select-none", "cursor-col-resize");
window.addEventListener("mousemove", onLocalFilterResizeMove);
window.addEventListener("mouseup", onLocalFilterResizeEnd);
}
function onLocalFilterResizeMove(event: MouseEvent) {
if (!isResizingLocalFilter.value) return;
const deltaX = event.clientX - localFilterResizeStartX;
if (localFilterResizeDirection === "right") {
const maxWidth = typeof window === "undefined" ? LOCAL_FILTER_POPOVER_DEFAULT_WIDTH : window.innerWidth - LOCAL_FILTER_POPOVER_VIEWPORT_PADDING - localFilterResizeStartLeft;
localFilterPopoverWidth.value = clampLocalFilterPopoverWidth(localFilterResizeStartWidth + deltaX, maxWidth);
return;
}
const maxWidth = localFilterResizeStartRight - LOCAL_FILTER_POPOVER_VIEWPORT_PADDING;
const nextWidth = clampLocalFilterPopoverWidth(localFilterResizeStartWidth - deltaX, maxWidth);
localFilterPopoverWidth.value = nextWidth;
localFilterPopoverOffsetX.value = localFilterResizeStartOffsetX + localFilterResizeStartWidth - nextWidth;
}
function onLocalFilterResizeEnd() {
if (!isResizingLocalFilter.value) return;
isResizingLocalFilter.value = false;
settingsStore.updateEditorSettings({
localFilterPopoverWidth: localFilterPopoverWidth.value,
});
document.body.classList.remove("select-none", "cursor-col-resize");
window.removeEventListener("mousemove", onLocalFilterResizeMove);
window.removeEventListener("mouseup", onLocalFilterResizeEnd);
}
watch(
() => settingsStore.editorSettings.localFilterPopoverWidth,
(width) => {
if (!isResizingLocalFilter.value) localFilterPopoverWidth.value = clampLocalFilterPopoverWidth(width);
},
);
function formatterKeysForColumn(columnIndex: number): string[] {
const resultColumn = props.result.columns[columnIndex];
if (!props.connectionId || !resultColumn) return [];
@@ -8153,6 +8233,7 @@ onMounted(() => {
});
onDeactivated(pauseCanvasGridWork);
onUnmounted(() => {
onLocalFilterResizeEnd();
dataGridRuntimeScope.dispose();
foreignKeyDisplayRequests.dispose();
clearCellFormatCache();
@@ -13636,7 +13717,16 @@ function openGridSnapshot() {
<Filter class="h-3.5 w-3.5" />
</button>
</PopoverTrigger>
<PopoverContent align="start" side="bottom" class="w-[300px] max-w-[calc(100vw-2rem)] gap-0 overflow-hidden rounded-md border bg-popover p-0 text-popover-foreground shadow-xl" @click.stop @keydown.stop>
<PopoverContent
align="start"
side="bottom"
class="relative max-w-[calc(100vw-2rem)] gap-0 overflow-hidden rounded-md border bg-popover p-0 text-popover-foreground shadow-xl"
:style="{ width: `${localFilterPopoverWidth}px`, marginLeft: `${localFilterPopoverOffsetX}px` }"
@click.stop
@keydown.stop
>
<div role="separator" aria-orientation="vertical" aria-label="Resize filter panel" class="absolute left-0 top-0 z-10 h-full w-1.5 cursor-col-resize hover:bg-primary/30" @mousedown.stop="onLocalFilterResizeStart($event, 'left')" />
<div role="separator" aria-orientation="vertical" aria-label="Resize filter panel" class="absolute right-0 top-0 z-10 h-full w-1.5 cursor-col-resize hover:bg-primary/30" @mousedown.stop="onLocalFilterResizeStart($event, 'right')" />
<div class="border-b bg-muted/40 px-2 py-1.5 text-center text-xs font-semibold">
{{ columnFilterPanelTitle(col.name) }}
</div>