feat(content-drive): merge base + content type filters into one chip#35503
feat(content-drive): merge base + content type filters into one chip#35503
Conversation
Chip/pill filter trigger button with: - title + selections[] inputs; label computed from selection count (none → title, 1-2 → joined, 3+ → "first and N more..." via i18n) - active state derived from selections length (primary colors vs neutral) - clicked / removed outputs; close icon stops propagation so removed does not also fire clicked - Host-element styling via flex justify-between so chevron always sits at the right edge of the chip Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r-content-drive-filters
…r-content-drive-filters
…r-content-drive-filters
Chip filter updates: - Split title and values into separate spans so the title can be accented with font-semibold while values stay in regular weight - Shift active/hover colors from brand-specific blues to the primary palette tints (primary-100/200 + primary-400/500/700/800) so 4+ active chips no longer visually overwhelm the toolbar - Drop component SCSS: all state colors now live in a Tailwind class string toggled by the computed active() signal - Enforce min-w-[140px] on the host so chips never shrink below the design floor; line-height set to 150% (leading-normal) - i18n the "and N more" fallback via DotMessageService Toolbar updates: - Swap the filter row from a fixed grid (repeat(3, minmax(...))) to flex-wrap so chips wrap onto a second line instead of being clipped at narrow widths - Replace grid-rows-2 (1fr 1fr, which forced row 1 to match the wrapped filter row height on responsive) with grid-rows-[auto_auto] so each row hugs its content - Bump inter-chip gap to gap-2 (8px) and vertical row gap to gap-y-4 (16px) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Restore the 14rem (min-w-56) floor on the three filter fields that got lost when the toolbar filter row switched from a fixed grid to flex-wrap. Without it, flex items shrink freely and the multiselects collapsed below their PrimeNG theme-default min-width. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop the class-based assertions (active/inactive color classes) and replace with a presence test for the values span so coverage now reflects what the component does, not how it's styled. Tailwind class names are an implementation detail that should stay testable via visual review, not brittle string comparisons in unit tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r-content-drive-filters
- Make onRemove protected — it is template-only, not part of the public component API (review #1) - Replace per-token i18n string assembly with a single parameterized key 'content-drive.chip-filter.overflow-label={0} and {1} more' so word order is owned by the translation, not the JS template, and RTL/non-English locales get correct grammar (review #2) - Drop the 'border' static class + 'border-none' active class mix; border now lives only on the inactive variant (review #5) Skipping review #3 (host class assertions) per established preference for presence/behavior tests over CSS class assertions, and #4 (chip not wired into the toolbar) which is intentional staging. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r-content-drive-filters
…tions Address PR review feedback (#35465): - Close icon is now a real <button> with aria-label; keyboard users can actually remove a filter. - Chip host gets role=button plus Enter/Space keydown so the primary activator is keyboard-reachable. - tabIndex exposed as an input so a future roving-tabindex toolbar can manage focus across many chips. - Active and inactive states now share a border, removing the 1px state-switch layout jump; transition timing eased for smoother feel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rive-filters' of https://github.com/dotCMS/core into 33524-task-create-new-chip-like-component-for-content-drive-filters
…r-content-drive-filters
For #35490. Initial work-in-progress on the migration: - Language field migrated off p-multiSelect onto DotChipFilterComponent + p-popover + p-listbox + DotFilterListItemComponent. Filter input, reset-on-close, and debounced store updates preserved. - New DotFilterListItemComponent in @dotcms/portlets/content-drive/ui to standardize row markup (label + optional secondary, palette colors). - New shared CHIP_FILTER_POPOVER_PT and CHIP_FILTER_LISTBOX_PT constants so every filter chip uses the same chrome (rounded popover, no arrow, borderless listbox, palette-driven hover/selected colors, 16x16 checkboxes, py-3 rows). Still TODO on this ticket: merge Base Type + Content Type into a single filter with a master/detail panel as described in #35490. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… Clear all For #35490. Three related changes to filter behavior: - setGlobalSearch no longer wipes other filters. It strips/sets the search title in place, so applying a search keeps your active base/content-type/language filters, and clearing the input removes the title key without disturbing the rest. - setPath is now strictly navigation: it only updates path and resets pagination. Folder navigation no longer touches filters at all (previously it leaked title:undefined into the filter map, which made the filter-state computed look truthy on every folder click). - New clearFilters store action and a "Clear all" p-button in the toolbar (text variant, sits inline next to the last filter chip) that calls it. Visible only when at least one filter is active. The filter row is now items-center so chips and the button align. Adds the content-drive.filters.clear-all i18n key and tests covering: search-preserves-filters, search-empty-strips-title, clearFilters action, setPath leaves filters untouched, Clear all button visibility and click delegation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Fix bug where Enter on the close button emitted both `removed` and `clicked` (keydown bubbled to host even though the synthesized click was stopped). Single host keydown handler now ignores keydowns that bubbled from a descendant. - Drop unnecessary `computed()` wrapper around the static `removeLabel` string; resolve via the `dm` pipe in the template instead. - Narrow `clicked` output from `Event` to `void` to avoid leaking DOM internals to consumers. - Remove 32 lines of commented-out demo `<dot-chip-filter>` usages and the unused `DotChipFilterComponent` import from the toolbar. - Add regression tests for Enter/Space on the close button. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…chip Replaces the two separate Base Type and Content Type filters in the content drive toolbar with a single merged chip-style filter built on the new `dot-chip-filter` + `<p-popover>` + `<p-listbox>` pattern. Layout: - Two-column popover (Base Type / Content Type) - Left listbox uses single-select for the focused base type plus an inline checkbox per row for the actual filter selection. An "All Content" row sits at the top to browse every type without narrowing. - Right listbox is multi-select with checkbox + filter input + virtual scroll + lazy load. Filtered by the focused base type only — never by all selected ones. - Selected content types are pinned to the top of the right list so they remain visible across focus changes. - Panel content is mounted with @if while the popover is open so the listbox's virtual scroll measures real dimensions on each open (otherwise it computes 0 visible items behind the hidden overlay). Selection cascade rules: - Selecting a content type selects its parent base type. - Unselecting the last content type of a base type drops the base type. - Unselecting a base type drops all its content types. - Selecting a base type alone never auto-selects content types. Chip label rules (per ticket): - `Name (All)` for a base type selected without narrowing. - Specific content type names when narrowed. - Mixed states combine both. Other: - Reverts the chip-filter `clicked` output back to `output<Event>()` so consumers can pass it to `popover.toggle($event)` for positioning. - Removes the old base-type-selector and content-type-field components and updates the toolbar to render the merged filter. - Adds i18n keys for the new filter (title, columns, all-content, all). - Tests drive every interaction through events on rendered elements rather than calling protected methods. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @zJaaal's task in 5m 55s —— View job PR Review
Issues Found1. Panel-reopen data corruption after an active search (Bug)
protected onPanelHide(): void {
this.$popoverOpen.set(false);
patchState(this.$state, { contentTypeFilter: '' }); // data not cleared
}Fix: also reset 2. Spinner clears prematurely during concurrent search-debounce + focus-change (Bug)If the user types (setting
if (req.baseType !== currentType) {
patchState(this.$state, { loading: false }); // ← fires while focus-fetch is in flight
return EMPTY;
}The spinner disappears before results arrive. Fix: only clear loading in the stale guard if no other fetch is in flight (e.g., by tracking an 3.
|
Content-type filter: - Race conditions: a #cancelFetch$ subject is fired on focus change so any in-flight focus or lazy-load fetch is cancelled before the new one runs. Late responses from a stale focus can no longer overwrite the right list. - canLoadMore: use server-side pagination math (currentPage / totalEntries / perPage) instead of comparing the client-filtered count against totalEntries. The client-side FORM/system filter would otherwise leave canLoadMore stuck at true and trigger a wasted empty fetch every load. - Initial load: drop the misplaced tap(loading=true) from after the http call. The initial state already has loading: true and the tap was firing on the response (the opposite of intent). - Chip flash: $chipSelections returns [] until the base-type catalog has loaded, so the chip never renders raw enum names like 'CONTENT (All)' before falling back to 'Content (All)'. Language field: - Drop the of(...).pipe(debounceTime).subscribe() pattern from onChange. It wasn't actually debouncing (each call built a fresh pipeline), and the 500ms wait felt broken to users selecting a language. Now updates the store immediately on every change. Tests: - openPopover() helper asserts $popoverOpen is true after the click so a silently inert helper would fail loud rather than passing assertions against an empty DOM. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s' banner
Base-type checkbox is now tri-state:
- empty → click to select the base (no narrowing).
- pi-check → click to drop the base + its content types.
- pi-minus → some content types of this base are selected (partial).
Clicking it drops the narrowing and keeps the base — i.e.
switches to "all content types of this base type".
The pi-minus checkbox is styled identically to the checked state (white
icon on the primary background). PrimeNG v21 doesn't apply any host class
for the indeterminate state, so we tag the host with `.dot-checkbox-partial`
ourselves and redirect the base checkbox CSS tokens to their checked
equivalents from inside the component's styles.
A green banner reading "All content types selected." is now rendered
between the search input and the list whenever the focused base type is
selected with no narrowing content types — i.e. the right list is acting
as "all of this base type". This covers both:
- selecting a base type alone, and
- clicking a partial checkbox to drop its narrowing.
To make the banner sit between filter and items, the right column's
built-in `[filter]` is replaced with a custom `pInputText` search bar
above the listbox; everything still flows through the same fetch /
debounce / cancel pipeline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d of CSS Replaces the ::ng-deep CSS workaround with PrimeNG's native passthrough API. A `partialCheckboxPt` object pumps the checked-state design tokens straight into the checkbox's box and icon slots when the row is in the partial state — same visual outcome (white pi-minus on a primary background) but no global selectors and no scoped-style escapes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the absolute-positioned <i class="pi pi-search"> with PrimeNG's p-iconfield + p-inputicon wrapper for the content type search input, matching the standard pattern used elsewhere in the app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…re logic - Search input switched from manual icon positioning to PrimeNG's <p-iconfield> + <p-inputicon> so the magnifying glass aligns and the placeholder padding is handled by the design system. - Indeterminate checkbox styling moved from a CSS workaround to PrimeNG's [pt] passthrough — a `partialCheckboxPt` object pumps the checked-state tokens directly into the box and icon slots. - Popover height is now derived from the LEFT listbox's natural height (LISTBOX_SCROLL_HEIGHT + column header) instead of a hardcoded 30rem, so the popover always matches the base-type list with no leftover space at the bottom. - When the "All content types selected" banner appears, the right listbox's scroll height shrinks by exactly one item slot — banner takes that row's place — so the popover height stays constant. - Removed the "pin selected at top of right list" behavior; user testing showed it was confusing. The cache stays so selected items can still be resolved across focus changes. - `#ensureParam` now only includes selected content types whose baseType matches the focused base type (or all selections when focus is ALL_CONTENT). Avoids asking the API to ensure items the current focus would never legitimately return. - Renamed i18n key `all-content` → `all-content-types`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- #loadBaseTypes now has a catchError that falls back to []. A failed base-types fetch used to leave the left column showing only "All Content Types" with no user feedback. - Initial content-type fetch reads `ensure` straight from the store instead of from #ensureParam. The cache is empty at this point so #ensureParam was always returning undefined, which meant restoring contentType filters from the URL silently dropped any item that wasn't on page 1 (the chip would render empty). - Lazy-load empty-response branch now also sets loading: false. It was leaving the loading flag stuck at true, which the empty-state template reads. - Replaced the O(n²) Object.entries(MAP_NUMBERS_TO_BASE_TYPES).find reverse lookup in #syncStore with a precomputed MAP_BASE_TYPES_TO_NUMBERS constant. - Language field now declares `implements OnInit` so a typo in the hook name would actually fail compilation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Race: a buffered debounced search whose focus changed mid-window used to slip past `takeUntil(#cancelFetch$)` because the cancel had already fired before switchMap created its inner observable. The switchMap callback now re-reads `$focusedBaseType()` and returns EMPTY (plus resets `loading: false`) when the buffered request's `baseType` no longer matches the live focus, so the focus-change path stays the sole owner of the right list. - Removed `$fetchingItems` — never referenced in the template. - Removed the dead `pTemplate="emptyfilter"` block — the right listbox no longer has `[filter]="true"` (we render our own search input). - `$chipSelections` now falls back to the raw enum name (e.g. `CONTENT (All)`) when the base-type catalog isn't loaded yet, instead of returning [] and silently hiding active filters when the catalog API fails. - `$selectedBaseTypes`'s lookup uses `MAP_NUMBERS_TO_BASE_TYPES[Number(k)]` to match how the store does the same lookup — keeps the index types consistent. - Bound `isBaseTypePartial(item.name)` once via `@let` in the left listbox row template instead of calling it twice (once for `[indeterminate]`, once for `[pt]`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
dot-chip-filter+<p-popover>+<p-listbox>(per [TASK] Content Drive: Migrate existent filters to chip + popover + listbox pattern #35490).Behavior
Selection cascade:
Chip label rules:
Name (All)for a base type selected without narrowing.Content (All), Blog Author.Implementation notes
@if ($popoverOpen())so the listbox is recreated on each open. Without this, primeng's virtual scroll computes 0 visible items behind the hidden overlay and the right list never renders until clicked.$selectedContentTypesresolve full objects from the store even when those items are no longer in the visible page.clickedoutput tooutput<Event>()so consumers can pass the event intopopover.toggle($event)for positioning.ngModelChange,onChange,onLazyLoad,onFilter, popoveronHide, chipremoved/clicked) — no protected-method bracket access.Test plan
Name (All).baseTypeandcontentTypeare persisted to the store; the chip shows the content type name (no(All)).(All)rows on the right respect the focused base type.baseTypeandcontentTypeare removed from the store.🤖 Generated with Claude Code