From 9db0ff0a12601e6d47b56950c5cb65f6d30dbd5f Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Thu, 13 Aug 2026 12:30:18 +0900 Subject: [PATCH] refactor: remove redundant data table toolbar effects --- .../components/data-table/toolbar.test.tsx | 15 ++++++++ .../src/components/data-table/toolbar.tsx | 37 ++++++++++--------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/data-table/toolbar.test.tsx b/packages/core/src/components/data-table/toolbar.test.tsx index 6745a061..1ca588c7 100644 --- a/packages/core/src/components/data-table/toolbar.test.tsx +++ b/packages/core/src/components/data-table/toolbar.test.tsx @@ -445,6 +445,21 @@ describe("AddFilterPanel", () => { // --------------------------------------------------------------------------- describe("FilterChip", () => { + it("focuses the operator search input when the operator popover opens", async () => { + const user = userEvent.setup(); + const control = makeControl({ + filters: [{ field: "name", operator: "contains", value: "Alice" }], + }); + render(, { + wrapper, + }); + + await user.click(screen.getByRole("button", { name: "contains" })); + + const input = await screen.findByPlaceholderText("Search..."); + expect(document.activeElement).toBe(input); + }); + it("calls removeFilter when the remove button is clicked", async () => { const user = userEvent.setup(); const control = makeControl({ diff --git a/packages/core/src/components/data-table/toolbar.tsx b/packages/core/src/components/data-table/toolbar.tsx index 3b74d5de..06fc311c 100644 --- a/packages/core/src/components/data-table/toolbar.tsx +++ b/packages/core/src/components/data-table/toolbar.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; +import { useCallback, useMemo, useState, type ReactNode } from "react"; import { Popover } from "@base-ui/react/popover"; import { ChevronDown, Filter as FilterIcon, X, Check, Search } from "lucide-react"; import { cn } from "@/lib/utils"; @@ -287,17 +287,22 @@ function AddFilterPanel({ if (col) setOperator(seedPanelOperator(control, col)); }; - // Keep the selection in sync with the search: if the query filters out the - // currently-selected field, advance to the first still-visible field so the - // field list and the value editor don't desync (empty highlight on the left - // while the right still shows the old field's editor). - useEffect(() => { - if (!fq || visibleFieldColumns.length === 0) return; - if (visibleFieldColumns.some((c) => c.filter.field === fieldName)) return; - const first = visibleFieldColumns[0]; + const handleFieldQueryChange = (value: string) => { + setFieldQuery(value); + + const nextQuery = value.trim().toLowerCase(); + if (!nextQuery) return; + + const nextVisible = columns.filter((c) => + (c.label ?? c.filter.field).toLowerCase().includes(nextQuery), + ); + if (nextVisible.length === 0) return; + if (nextVisible.some((c) => c.filter.field === fieldName)) return; + + const first = nextVisible[0]; setFieldName(first.filter.field); setOperator(seedPanelOperator(control, first)); - }, [fq, visibleFieldColumns, fieldName, control]); + }; // Always reopen on the first field rather than wherever the user last was. const handleOpenChange = (next: boolean) => { @@ -358,7 +363,7 @@ function AddFilterPanel({ setFieldQuery(e.target.value)} + onChange={(e) => handleFieldQueryChange(e.target.value)} placeholder={t("searchFields")} aria-label={t("searchFields")} className="astw:h-8 astw:pl-8 astw:text-sm" @@ -1255,12 +1260,6 @@ function OperatorList({ }) { const t = useDataTableT(); const [query, setQuery] = useState(""); - const ref = useRef(null); - - useEffect(() => { - const id = requestAnimationFrame(() => ref.current?.focus()); - return () => cancelAnimationFrame(id); - }, []); const q = query.trim().toLowerCase(); const items = operators @@ -1271,8 +1270,10 @@ function OperatorList({
+ {/* Base UI's Popover.Popup moves initial focus to the first tabbable + element by default, which is this input. On touch it focuses the + popup instead so the virtual keyboard doesn't jump open. */} setQuery(e.target.value)} placeholder={t("filterOperatorSearchPlaceholder")}