fix(filters): value menu flash-on-click, empty state, sticky field filter (user report)
- The flash was a race: on localhost the suggestion fetch resolved between the focus (mousedown) and the click event, so the just-opened menu was closed by its own click-outside handler — the input sits outside the menu div. Clicks anywhere inside the value cell now never close the menu; outside clicks do. - The menu opens immediately in a loading state; an empty browse renders 'No matching values' instead of nothing. Plain free-input fields (page_count etc.) open no menu at all. - Field menu: the filter input moved outside the scroll region — it sticks while the grouped list scrolls beneath it.
This commit is contained in:
1 parent
c747f0a76c
commit
4c03116ec7
5 files changed
+65
-26
No files matched your search
@@ -441,6 +441,7 @@ export function filterBuilderData() {
|
||||
fieldFilter: "" as string, // the field menu's filter input
|
||||
suggestions: {} as Record<string, Suggestion[]>,
|
||||
suggestionsOpen: "" as string, // rowKey whose value menu is open
|
||||
suggestionsLoading: "" as string, // rowKey currently fetching
|
||||
idLabels: {} as Record<string, string>, // id → display, from suggestion fetches
|
||||
_suggestTimer: null as number | null,
|
||||
_countTimer: null as number | null,
|
||||
@@ -700,6 +701,10 @@ export function filterBuilderData() {
|
||||
if (typed.length === 1) return;
|
||||
const token = localStorage.getItem("token");
|
||||
if (!token) return;
|
||||
// Open the menu NOW (loading state) — never a blank flash.
|
||||
this.suggestions[rowKey] = [];
|
||||
this.suggestionsLoading = rowKey;
|
||||
this.suggestionsOpen = rowKey;
|
||||
// Symmetry: the tree sent EXCLUDES the rule being edited — the
|
||||
// menu is computed under every OTHER COMPLETED rule (incomplete
|
||||
// rules are not filters; sending them would be a 400).
|
||||
@@ -720,6 +725,7 @@ export function filterBuilderData() {
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.warn(`suggestion fetch failed (${response.status})`);
|
||||
this.suggestions[rowKey] = [];
|
||||
return;
|
||||
}
|
||||
const data = (await response.json()) as { results?: Suggestion[] };
|
||||
@@ -727,14 +733,17 @@ export function filterBuilderData() {
|
||||
if (s.display) this.idLabels[s.value] = s.display;
|
||||
}
|
||||
this.suggestions[rowKey] = data.results ?? [];
|
||||
this.suggestionsOpen = rowKey;
|
||||
} catch (error) {
|
||||
console.warn("suggestion fetch failed", error);
|
||||
this.suggestions[rowKey] = [];
|
||||
} finally {
|
||||
if (this.suggestionsLoading === rowKey) this.suggestionsLoading = "";
|
||||
}
|
||||
},
|
||||
openValueMenu(rowKey: string, path: number[], rule: FilterRuleState): void {
|
||||
const meta = FIELD_BY_KEY.get(rule.field);
|
||||
if (meta?.valueKind === "fixed") {
|
||||
if (!meta) return;
|
||||
if (meta.valueKind === "fixed") {
|
||||
// Static options: no fetch.
|
||||
this.suggestions[rowKey] = (meta.options ?? []).map((v) => ({
|
||||
value: v,
|
||||
@@ -743,14 +752,16 @@ export function filterBuilderData() {
|
||||
this.suggestionsOpen = rowKey;
|
||||
return;
|
||||
}
|
||||
if (meta?.suggest) {
|
||||
if (meta.suggest) {
|
||||
void this.fetchSuggestions(rowKey, path, rule);
|
||||
return;
|
||||
}
|
||||
this.suggestionsOpen = rowKey;
|
||||
// Plain free-input field: no value menu.
|
||||
},
|
||||
suggestionShown(rowKey: string): boolean {
|
||||
return this.suggestionsOpen === rowKey && (this.suggestions[rowKey]?.length ?? 0) > 0;
|
||||
// Open for the whole life of the interaction — an empty result
|
||||
// renders the empty state, it never flashes the menu closed.
|
||||
return this.suggestionsOpen === rowKey;
|
||||
},
|
||||
|
||||
// ── live count ──────────────────────────────────────────────────
|
||||
|
||||
Reference in new issue
Block a user