Skip to content

feat: add rich filter component with modular field engine and primitive components#1213

Open
robmanganelly wants to merge 5 commits intospartan-ng:mainfrom
robmanganelly:feature-rich-filter-squashed-commits
Open

feat: add rich filter component with modular field engine and primitive components#1213
robmanganelly wants to merge 5 commits intospartan-ng:mainfrom
robmanganelly:feature-rich-filter-squashed-commits

Conversation

@robmanganelly
Copy link

Introduces a comprehensive rich filter system for building dynamic, type-safe query filters. Includes a field engine with builders, handlers, operators, parser, and state management, along with new hlm-range-slider and hlm-time-input primitive components.

Field types supported: text, date, date range, time, boolean, combo (sync and async), range, and select. Async combobox fields include debounced resource request handling and a configurable itemToString default.

Focus management and keyboard navigation improvements are applied across all field types. ARIA attributes and accessibility enhancements are included for range-slider and time-input components.

Unit tests cover engine utilities, builders, handlers, parser, field specs, and the new primitive components.

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

Which package are you modifying?

Primitives

  • accordion
  • alert
  • alert-dialog
  • aspect-ratio
  • autocomplete
  • avatar
  • badge
  • breadcrumb
  • button
  • button-group
  • calendar
  • card
  • carousel
  • checkbox
  • collapsible
  • combobox
  • command
  • context-menu
  • data-table
  • date-picker
  • dialog
  • empty
  • dropdown-menu
  • field
  • form-field
  • hover-card
  • icon
  • input
  • input-group
  • input-otp
  • item
  • kbd
  • label
  • menubar
  • navigation-menu
  • pagination
  • popover
  • progress
  • radio-group
  • resizable
  • scroll-area
  • select
  • separator
  • sheet
  • sidebar
  • skeleton
  • slider
  • sonner
  • spinner
  • switch
  • table
  • tabs
  • textarea
  • toggle
  • toggle-group
  • tooltip
  • typography

Others

  • trpc
  • nx
  • repo
  • cli

What is the current behavior?

Nothing modified, existing flows remain unchanged

Closes #

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Screen Recording of the expected functionality

compressed-Screen.Recording.mp4

Architectural decisions

Engine is kept apart from the component to make it easier for users to implement their own version of the engine.
Each field comes with its own handler, Every new field should also have its own handler,
Feature uses function composition to extend functionalities, instead of inheritance

This feature can benefit from signal forms, once spartan supports them
I tried to not add any new dependency while creating this feature

The 2 new primitives that were created are not documented, pending approval for the spartan team, I did not want to waste time documenting them before knowing if they are going to make the cut, but this block really benefits from them

I decided to not extend the slider primitive and instead add one slider that have both edges movable, to prevent any breaking change in the project,

Feedback is highly appreciated, I hope you see this as useful as I do

Copilot AI review requested due to automatic review settings February 19, 2026 00:56
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a comprehensive rich filter system for building dynamic, type-safe query filters with a modular field engine. It includes two new primitive components (hlm-range-slider and hlm-time-input) and supports multiple field types: text, date, date range, time, boolean, combo (sync/async), range, and select. The implementation features focus management, keyboard navigation, ARIA attributes, and extensive unit test coverage.

Changes:

  • Adds new brain/helm primitives: range-slider and time-input components with full accessibility support
  • Implements modular filter engine with builders, handlers, operators, parser, and state management
  • Creates field components for all supported types with proper focus management and keyboard navigation
  • Includes comprehensive unit tests for engine utilities, handlers, parser, field specs, and primitives

Reviewed changes

Copilot reviewed 64 out of 64 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tsconfig.base.json Adds path mappings for new range-slider and time-input packages
libs/brain/range-slider/* Implements headless range slider with dual thumbs, keyboard control, and CVA support
libs/brain/time-input/* Implements 12-hour time input with segments, keyboard navigation, and picker popover
libs/helm/range-slider/* Styled range slider component wrapping brain implementation
libs/helm/time-input/* Styled time input with popover picker and clock icon trigger
apps/app/.../rich-filter/engine/* Core filter engine with types, operators, builders, handlers, and parser
apps/app/.../rich-filter/fields/* Field component implementations for all supported types
apps/app/.../rich-filter/rich-filter.ts Main filter component with dynamic field rendering
*.spec.ts Comprehensive test suites for all new functionality

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@greptile-apps
Copy link

greptile-apps bot commented Feb 19, 2026

Greptile Summary

This PR introduces a comprehensive rich filter system with modular architecture and two new primitives (hlm-range-slider and hlm-time-input).

Architecture:

  • Engine layer: Field builders, handlers, parser, and operators provide type-safe state management using Angular signals
  • Field components: 10 field types (text, number, date, daterange, time, boolean, range, select, combobox, async combobox) with consistent UX patterns
  • Primitives: Range slider supports dual-thumb interaction with crossover handling; time input provides segment-based editing with keyboard navigation

Key strengths:

  • Excellent test coverage with 10 spec files covering engine logic, handlers, and primitives
  • Clean separation between engine logic and UI components allows custom implementations
  • Proper accessibility with ARIA attributes throughout
  • Type-safe design with discriminated unions and type guards
  • No new dependencies added
  • Function composition over inheritance for extensibility

Implementation notes:

  • Async combobox uses httpResource with 450ms debounce and query token replacement
  • Focus management and keyboard navigation implemented across all field types
  • Injector caching prevents memory leaks with proper cleanup on destroy
  • Parser strips private fields (prefixed with __) for clean output

Confidence Score: 4/5

  • Safe to merge with minor style improvements noted
  • Well-architected feature with comprehensive tests and careful attention to accessibility. Code quality is high with proper type safety and state management. The TODO comments indicate areas for future improvement (combobox API, signal forms) but don't represent blocking issues. No breaking changes or security concerns identified.
  • No files require special attention - the implementation is solid across the board

Important Files Changed

Filename Overview
apps/app/src/app/pages/(blocks-preview)/blocks-preview/rich-filter/engine/builders.ts Defines field builders and filter model with proper type safety and state management
apps/app/src/app/pages/(blocks-preview)/blocks-preview/rich-filter/engine/handlers.ts Implements field handlers with computed signals and type guards for each field type
apps/app/src/app/pages/(blocks-preview)/blocks-preview/rich-filter/rich-filter.ts Main component with dynamic field rendering using NgComponentOutlet and injector caching
apps/app/src/app/pages/(blocks-preview)/blocks-preview/rich-filter/fields/combo-async.ts Async combobox field with debounced HTTP resource requests and query token replacement
libs/brain/range-slider/src/lib/brn-range-slider.ts Range slider primitive with two-thumb support, crossover handling, and CVA implementation
libs/brain/time-input/src/lib/brn-time-input.ts Time input component with segment-based editing and CVA implementation

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User Interaction] --> B{Add Filter Field}
    B --> C[buildFilterModel creates state]
    C --> D[Field added to model]
    D --> E[SpartanRichFilter renders field]
    E --> F[NgComponentOutlet with cached injector]
    F --> G[Field-specific handler provides state access]
    G --> H{Field Type}
    H -->|Text/Number| I[Basic input fields]
    H -->|Date/Time| J[Calendar/Time picker]
    H -->|Combo/Select| K[Dropdown/Combobox]
    H -->|Async Combo| L[HTTP Resource with debounce]
    H -->|Range| M[Dual-thumb Range Slider]
    H -->|Boolean| N[Checkbox field]
    I --> O[User edits value]
    J --> O
    K --> O
    L --> O
    M --> O
    N --> O
    O --> P[Handler updates state via WritableSignal]
    P --> Q[filterParser strips private fields]
    Q --> R[Output: Clean query object]
Loading

Last reviewed commit: 1eddd32

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

64 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +131 to +134
// TODO fix combobox api to expose a reference to the input element
// to make it possible to focus without querying the DOM
const el = this.monitoredInput().nativeElement.querySelector('input[brnComboboxInput]');
this.service.isFocused() && this.focusMonitor.focusVia(el, FAKE_FOCUS_ORIGIN);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOM query to find input element - consider exposing input ref from combobox API to avoid direct DOM manipulation

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Author

@robmanganelly robmanganelly Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of scope for this task, if this is desired must be resolved via issue, not in this PR

Promise.resolve().then(() => {
this.popoverBtn()?.nativeElement.click();
});
// TODO: Does BrainPopoverTrigger expose a method to close the popover programmatically?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if BrnPopoverTrigger exposes a close method to avoid this workaround

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of scope for this task, if this is desired must be resolved via issue, not in this PR

@marcjulian
Copy link
Collaborator

This looks awesome! I noticed that you added a new component for range-slider, however, the slider has recently received some updates and includes now a range as well. Can you have a look if you could use the already existing slider range https://spartan.ng/components/slider#examples__range?

@robmanganelly robmanganelly force-pushed the feature-rich-filter-squashed-commits branch from ad55841 to b24ba73 Compare February 19, 2026 21:46
…ve components

Introduces a comprehensive rich filter system for building dynamic, type-safe
query filters. Includes a field engine with builders, handlers, operators,
parser, and state management, along with new hlm-range-slider and
hlm-time-input primitive components.

Field types supported: text, date, date range, time, boolean, combo (sync and
async), range, and select. Async combobox fields include debounced resource
request handling and a configurable itemToString default.

Focus management and keyboard navigation improvements are applied across all
field types. ARIA attributes and accessibility enhancements are included for
range-slider and time-input components.

Unit tests cover engine utilities, builders, handlers, parser, field specs,
and the new primitive components.
Add explicit `public` modifiers to satisfy
@typescript-eslint/explicit-member-accessibility.
Add eslint-disable comments for @typescript-eslint/naming-convention
on `_`-prefixed bindings that must retain their names.
Remove unused imports (ResourceRef, WritableSignal, computed, input,
RangeValue, FilterModelRef, screen).
Rename FieldClose output from `onCloseField` to `fieldclosed` to
satisfy @angular-eslint/no-output-on-prefix.
Prefix test host selectors with `spartan-` per
@angular-eslint/component-selector.
Remove redundant `standalone: true` (default since Angular 19).
Reorder @component metadata keys per
@nx/workspace-component-directive-key-order.
Use `type` imports where values are only used as types.
Run format:write on the codebase to fix failing CI task
Add CLI generator infrastructure for the range-slider and time-input
helm primitives, enabling scaffolding via `nx g @spartan-ng/cli:ui`.

- Add generator entry points and EJS templates for both primitives
- Register range-slider and time-input in primitives type, dependency
  map, and supported UI libraries config
- Update generate-ui-docs snapshot to reflect time-input API changes
@robmanganelly robmanganelly force-pushed the feature-rich-filter-squashed-commits branch from b24ba73 to cd114e2 Compare February 19, 2026 21:48
…filter

- Remove custom range slider cmp and related files
- Update rich filter field cmp to use helm slider
- Adjust imports and templates in rich filter fields
- Remove all references to the old range slider from the codebase
@robmanganelly robmanganelly force-pushed the feature-rich-filter-squashed-commits branch from cd114e2 to 3bf0039 Compare February 19, 2026 21:53
@robmanganelly
Copy link
Author

robmanganelly commented Feb 19, 2026

This looks awesome! I noticed that you added a new component for range-slider, however, the slider has recently received some updates and includes now a range as well. Can you have a look if you could use the already existing slider range https://spartan.ng/components/slider#examples__range?

commit 3bf0039 implements this feedback

@robmanganelly
Copy link
Author

any update here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants