fix(map): preserve empty facet selection

This commit is contained in:
Codex
2026-08-05 18:29:19 +03:00
parent 07a42b8832
commit 2cd3b33141
2 changed files with 68 additions and 12 deletions
+24 -8
View File
@@ -106,8 +106,9 @@ export function mapPresentationBindingIsAll(bindingId: string, filters: MapPrese
/**
* Apply one interactive facet-chip transition without collapsing the storage
* contract. A missing field means unconstrained, while an explicitly persisted
* empty array remains available to represent an intentional match-nothing view.
* contract. A missing field means that facet is not part of the current union,
* while one explicitly empty field represents the intentional match-nothing
* view after the final selected chip is switched off.
*/
export function toggleMapPresentationFacetSelection(
facets: Record<string, string[]>,
@@ -116,14 +117,30 @@ export function toggleMapPresentationFacetSelection(
) {
const selected = facets[field];
if (!selected?.includes(value)) {
return { ...facets, [field]: [...(selected ?? []), value] };
// A saved empty view can contain one or more empty facet arrays. They are
// a global zero-match sentinel, not active constraints, so selecting the
// first chip must replace them instead of leaving a hidden empty array that
// would continue to suppress every fact.
const activeFacets = Object.fromEntries(
Object.entries(facets).filter(([, values]) => values.length > 0),
);
return { ...activeFacets, [field]: [...(activeFacets[field] ?? []), value] };
}
const nextSelected = selected.filter((item) => item !== value);
if (nextSelected.length > 0) return { ...facets, [field]: nextSelected };
const { [field]: _removed, ...unconstrained } = facets;
return unconstrained;
const { [field]: _removed, ...remaining } = facets;
const activeFacets = Object.fromEntries(
Object.entries(remaining).filter(([, values]) => values.length > 0),
);
if (Object.keys(activeFacets).length > 0) return activeFacets;
// Never collapse the last interactive deselection to `{}`: the renderer
// correctly interprets `{}` as unconstrained/all. Retain an explicit empty
// facet so the zero-selection state remains zero matches through any toggle
// cycle and through a later Application Save.
return { [field]: [] };
}
export function mapPresentationProfileForFact(
@@ -192,9 +209,8 @@ export function mapFactMatchesFilters(
return [{ facet, selected }];
});
// Persisted empty arrays are an explicit match-nothing state. Interactive
// deselection removes the field instead, so this branch is only reached for
// a deliberately saved empty view.
// Persisted empty arrays and the final interactive deselection are an
// explicit match-nothing state.
if (selectedFacets.some(({ selected }) => selected.length === 0)) return false;
if (selectedFacets.length === 0) return true;