From 3043193510bd078abafdbd3d5913abe5222ec51a Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Wed, 6 May 2026 10:45:18 +0200 Subject: [PATCH] fix: custom filter not removed + use popover --- src/adapter/adapter/filter.ts | 6 +- .../FilterPopup/FilterPopup.module.css | 46 ++++++++++++- .../components/FilterPopup/FilterPopup.tsx | 14 +++- src/view/components/IconBtn.tsx | 2 + src/view/components/OutsideClick.tsx | 33 ---------- src/view/components/elements/TreeBar.tsx | 65 +++++++++++-------- .../components/TimelineBar/TimelineBar.tsx | 27 +++----- src/view/store/filter.ts | 32 +++++++-- test-e2e/tests/hoc-filter-disable.test.ts | 56 ++++++++++++++++ .../profiler/flamegraph/memo-sibling.test.ts | 2 +- 10 files changed, 195 insertions(+), 88 deletions(-) delete mode 100644 src/view/components/OutsideClick.tsx diff --git a/src/adapter/adapter/filter.ts b/src/adapter/adapter/filter.ts index 8da0b206..d2f56ba4 100644 --- a/src/adapter/adapter/filter.ts +++ b/src/adapter/adapter/filter.ts @@ -1,4 +1,8 @@ -import { RawFilter } from "../../view/store/filter"; +export interface RawFilter { + id?: number; + value: string; + enabled: boolean; +} export interface RawFilterState { regex: RawFilter[]; diff --git a/src/view/components/FilterPopup/FilterPopup.module.css b/src/view/components/FilterPopup/FilterPopup.module.css index b3be93b8..0c81b134 100644 --- a/src/view/components/FilterPopup/FilterPopup.module.css +++ b/src/view/components/FilterPopup/FilterPopup.module.css @@ -3,20 +3,60 @@ display: flex; height: 100%; align-items: center; + anchor-name: --filter-popover-anchor; } .filter { - position: absolute; - top: 90%; - right: -0.5rem; + position: fixed; + inset: 2rem 0.75rem auto auto; + margin: 0; border-top: none; border: 0.0625rem solid var(--color-border); background: var(--color-bg); + color: var(--color-text); + font-family: sans-serif; + font-size: 0.875rem; width: 12rem; z-index: 10; padding: 0.5rem 0.5rem 0.5rem 0.75rem; } +.filter:popover-open { + display: block; +} + +.filterBtnWrapper:has(.filter:popover-open) + > :global(.icon-btn) + :global(.icon-btn-inner) { + position: relative; + z-index: 2; + color: var(--color-selected-text); +} + +.filterBtnWrapper:has(.filter:popover-open) + > :global(.icon-btn) + :global(.icon-btn-bg) { + display: block; + content: ""; + position: absolute; + left: -0.25rem; + top: -0.25rem; + right: -0.25rem; + bottom: -0.25rem; + background: var(--color-selected-bg); + border-radius: 0.2rem; +} + +@supports (position-area: bottom) { + .filter { + position: fixed; + position-area: bottom span-left; + position-anchor: --filter-popover-anchor; + inset: auto; + margin-top: 0.25rem; + } +} + .filter form { margin: 0; } diff --git a/src/view/components/FilterPopup/FilterPopup.tsx b/src/view/components/FilterPopup/FilterPopup.tsx index 113bc20e..417a5c81 100644 --- a/src/view/components/FilterPopup/FilterPopup.tsx +++ b/src/view/components/FilterPopup/FilterPopup.tsx @@ -74,14 +74,26 @@ export function FilterPopup({ onFiltersSubmit, filterActions, className, + id, + onOpen, }: { children: ComponentChildren; filterActions?: ComponentChildren; onFiltersSubmit: () => void; className?: string; + id: string; + onOpen?: () => void; }) { return ( -
+
{ + if ((e as any).newState === "open") onOpen?.(); + }} + >
{ e.preventDefault(); diff --git a/src/view/components/IconBtn.tsx b/src/view/components/IconBtn.tsx index ccd33da2..50698735 100644 --- a/src/view/components/IconBtn.tsx +++ b/src/view/components/IconBtn.tsx @@ -7,6 +7,7 @@ export interface IconBtnProps { disabled?: boolean; color?: string; onClick?: () => void; + popoverTarget?: string; styling?: "secondary" | "primary"; children: ComponentChildren; testId?: string; @@ -22,6 +23,7 @@ export function IconBtn(props: IconBtnProps) { title={props.title} disabled={props.disabled} data-testid={props.testId} + popovertarget={props.popoverTarget} onClick={e => { e.stopPropagation(); if (props.onClick) props.onClick(); diff --git a/src/view/components/OutsideClick.tsx b/src/view/components/OutsideClick.tsx deleted file mode 100644 index f0990600..00000000 --- a/src/view/components/OutsideClick.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { h } from "preact"; -import { useEffect, useRef } from "preact/hooks"; - -export interface Props { - onClick: () => void; - children: any; - class?: string; - style?: string | Record; -} - -export function OutsideClick(props: Props) { - const ref = useRef(null); - - useEffect(() => { - if (!ref.current) return; - - const listener = (e: MouseEvent) => { - if (ref.current && !ref.current.contains(e.target as any)) { - props.onClick(); - } - }; - - const root = ref.current!.getRootNode() as HTMLElement; - root.addEventListener("click", listener); - return () => root.removeEventListener("click", listener); - }, [props.children, ref.current]); - - return ( -
- {props.children} -
- ); -} diff --git a/src/view/components/elements/TreeBar.tsx b/src/view/components/elements/TreeBar.tsx index 338c5101..32a8b0e0 100644 --- a/src/view/components/elements/TreeBar.tsx +++ b/src/view/components/elements/TreeBar.tsx @@ -6,7 +6,6 @@ import { Icon, Picker } from "../icons"; import { useStore } from "../../store/react-bindings"; import s from "./TreeBar.module.css"; import { useSearch } from "../../store/search"; -import { OutsideClick } from "../OutsideClick"; import { FilterCheck, FilterPopup } from "../FilterPopup/FilterPopup"; import filterBarStyles from "../FilterPopup/FilterPopup.module.css"; @@ -15,8 +14,6 @@ export function TreeBar() { const isPicking = store.isPicking.value; const { value, count, selected, goPrev, goNext } = useSearch(); - const [filterVisible, setFilterVisible] = useState(false); - const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); @@ -92,20 +89,16 @@ export function TreeBar() {
- setFilterVisible(false)} - class={filterBarStyles.filterBtnWrapper} - > +
setFilterVisible(!filterVisible)} + popoverTarget="tree-filter-popup" > - {filterVisible && } - + +
); @@ -123,9 +116,24 @@ export function TreeFilterPopup() { store.filter.filterTextSignal.value, ); const [filters, setFilters] = useState(store.filter.filters.value); + const resetFilters = () => { + setFilterDom(store.filter.filterDom.value); + setFilterFragment(store.filter.filterFragment.value); + setFilterHoc(store.filter.filterHoc.value); + setFilterRoot(store.filter.filterRoot.value); + setFilterTextSignal(store.filter.filterTextSignal.value); + setFilters(store.filter.filters.value); + }; + const removeFilter = (id: number) => { + const nextFilters = filters.filter(filter => filter.id !== id); + setFilters(nextFilters); + store.filter.filters.value = nextFilters; + }; return ( { store.filter.filterDom.value = filterDom; store.filter.filterFragment.value = filterFragment; @@ -143,7 +151,10 @@ export function TreeFilterPopup() { title="Add new filter" testId="add-filter" onClick={() => - setFilters([...filters, { enabled: false, value: "" }]) + setFilters([ + ...filters, + store.filter.createFilter({ enabled: false, value: "" }), + ]) } > @@ -182,17 +193,20 @@ export function TreeFilterPopup() { checked={filterTextSignal} /> {/* Custom user filters */} - {filters.map((x, i) => { + {filters.map(x => { return ( -
+