feat(table): expand column filter operators and pill-editor UX#9597
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@kirangadhave I have started the AI code review. It will take a few minutes to complete. |
Lifts filter operators into a shared module and adds typed models for text and number columns, including native between for numbers and value-drop handling for unary text operators.
…formatting Adds full per-dtype operator support in the pill editor and an operator-aware pill formatter for label/value rendering.
Combined polish for the pill editor: - gate Apply with form submit + tooltip - preserve draft value across compatible operator changes - keyboard focus through fields - show values picker as a popover - right-size value slot widths per operator
…al chars reach the input
for more information, see https://pre-commit.ci
There was a problem hiding this comment.
1 issue found across 16 files
Architecture diagram
sequenceDiagram
participant User as User
participant Header as Column Header
participant FilterMenu as Filter Menu (Number/Text)
participant PillEditor as Filter Pill Editor
participant FilterPills as Filter Pills (Display)
participant ValuesPicker as FilterByValuesList/CreatablePicker
participant RegexInput as Regex Input
participant FilterModel as Shared Filter Models (filters.ts)
participant OperatorLabels as operator-labels.ts
participant Backend as Backend API
Note over User,Backend: NEW: Expanded column filter operators and pill-editor UX
User->>Header: Click column header to open filter options
Header->>FilterMenu: Render NumberFilterMenu or TextFilterMenu based on column data type
alt Number column
FilterMenu->>OperatorLabels: Retrieve labeled operator options
OperatorLabels-->>FilterMenu: Full operator list (between, ==, !=, >, >=, <, <=, is_null, is_not_null)
FilterMenu->>FilterMenu: Operator selection triggers immediate commit for nullish ops
FilterMenu->>User: Display value/min-max inputs based on selected operator
User->>FilterMenu: Fill value fields
alt Apply disabled until valid
FilterMenu->>FilterMenu: Check required fields (min+max for between, value for comparison)
FilterMenu-->>User: Apply button disabled + tooltip
end
User->>FilterMenu: Click Apply or press Enter
FilterMenu->>FilterModel: Build filter object via Filter.number()
FilterModel-->>Header: Apply filter to column
Header->>Backend: Send filter conditions (single operator+value pair)
else Text column
FilterMenu->>OperatorLabels: Retrieve labeled operator options
OperatorLabels-->>FilterMenu: Full operator list (contains, equals, regex, starts/ends with, in, not_in, is_empty, is_null, is_not_null)
alt In/not_in operator selected
FilterMenu->>ValuesPicker: Render creatable FilterByValuesList
User->>ValuesPicker: Type to search
alt Query matches no existing values
ValuesPicker-->>User: Show "+ Add 'query'" option
User->>ValuesPicker: Select or press Enter to create
ValuesPicker->>FilterModel: Commit new value
end
ValuesPicker-->>FilterMenu: Return selected values
else Regex operator selected
FilterMenu->>RegexInput: Render slash-bracketed input (/pattern/flags)
User->>RegexInput: Type regex pattern between slashes
RegexInput-->>FilterMenu: Return pattern string
else Single-string operators (contains, equals, etc.)
FilterMenu->>User: Show text input seeded from current filter
User->>FilterMenu: Edit text value
end
User->>FilterMenu: Click Apply or press Enter
FilterMenu->>FilterModel: Build filter object via Filter.text()
FilterModel-->>Header: Apply filter to column
Header->>Backend: Send text condition (single operator+string)
end
Note over Header,PillEditor: NEW: Filter pill editor with full operator awareness
User->>PillEditor: Open pill editor from existing filter pill
PillEditor->>FilterModel: Extract operator and draft value from snapshot
alt Operator switch incompatible
PillEditor->>PillEditor: Clear draft value for new operator type
User->>PillEditor: Enter new value
else Operator switch compatible
PillEditor->>PillEditor: Preserve draft value
end
User->>PillEditor: Edit column, operator, or value
PillEditor->>PillEditor: Rehydrate snapshot if matches original column+operator
User->>PillEditor: Submit form (Enter or Apply button)
alt Apply disabled
PillEditor-->>User: Show tooltip with missing value message
else Apply valid
PillEditor->>FilterModel: Build filter value from draft
FilterModel->>Backend: Commit filter via setColumnFilters
Backend-->>PillEditor: Filter applied
PillEditor-->>User: Close editor
end
Note over FilterPills,Backend: CHANGED: Pill display per operator type
FilterPills->>FilterModel: Read current filter value
FilterPills->>OperatorLabels: Map operator to display label
alt Number filter
FilterPills->>FilterPills: Format operator+value (e.g., "> 18", "between 1 - 9")
else Text filter
FilterPills->>FilterPills: Format per operator (e.g., "contains 'hello'", "is in [a, b]")
else Nullish/is_empty
FilterPills->>FilterPills: Show operator label only (e.g., "is null", "is empty")
end
FilterPills-->>User: Display formatted filter pill
Note over FilterModel,Backend: CHANGED: Number between emits single condition
FilterModel->>Backend: Convert filter to condition(s)
alt Number between
FilterModel->>Backend: Single "between" condition with {min, max} value
else Number comparison
FilterModel->>Backend: Single condition with operator+value
else Text operators
FilterModel->>Backend: Single condition per operator type
else Nullish/is_empty booleans
FilterModel->>Backend: Condition with no value field
end
Backend-->>FilterModel: Return filtered data
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
30a332c to
9a25278
Compare
CommandInput was uncontrolled, so clearing query after creating a value did
not clear the visible input. Bind value={query} and drop the per-keystroke
trim (trimming happens at use sites).
There was a problem hiding this comment.
Pull request overview
Expands data-table filtering with richer text/number operators and updates the filter-pill editing experience, including operator-aware value inputs, creatable value selection, regex UI, and updated tests.
Changes:
- Adds shared operator labels and typed filter models for number/text operators, including native
between, regex, membership, empty/nullish states. - Updates column-header and filter-pill editors to expose operator-specific controls and validation.
- Adds tests for filter serialization, pill editor behavior, column-header menus, and creatable value lists.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
frontend/src/plugins/impl/data-frames/schema.ts |
Allows condition values to be omitted for unary operators. |
frontend/src/components/ui/number-field.tsx |
Removes spinner controls from tab order. |
frontend/src/components/ui/combobox.tsx |
Makes combobox trigger focusable/role-based. |
frontend/src/components/data-table/regex-input.tsx |
Adds regex-specific input presentation. |
frontend/src/components/data-table/operator-labels.ts |
Adds shared labels for filter operators. |
frontend/src/components/data-table/header-items.tsx |
Adds disabled state support for Apply buttons. |
frontend/src/components/data-table/filters.ts |
Adds typed operator sets and serializes new filter shapes. |
frontend/src/components/data-table/filter-pills.tsx |
Formats pill labels for new operators. |
frontend/src/components/data-table/filter-pill-editor.tsx |
Reworks pill editor around operator-aware drafts and form submit. |
frontend/src/components/data-table/filter-by-values-picker.tsx |
Adds creatable values and preserves selected values outside top-K. |
frontend/src/components/data-table/column-header.tsx |
Adds full text/number operator menus. |
frontend/src/components/data-table/__tests__/filters.test.ts |
Updates and expands filter serialization tests. |
frontend/src/components/data-table/__tests__/filter-pill-editor.test.tsx |
Adds pill editor behavior tests. |
frontend/src/components/data-table/__tests__/filter-by-values-picker.test.tsx |
Adds creatable/top-K picker tests. |
frontend/src/components/data-table/__tests__/column-header.test.tsx |
Adds column-header menu tests. |
frontend/src/components/data-table/__tests__/column-header.test.ts |
Updates seed tests for new number filter shape. |
Comments suppressed due to low confidence (1)
frontend/src/components/data-table/filter-pill-editor.tsx:438
- This picker also uses a trigger button without
type="button", so inside the new form it behaves as a submit button. For select filters that already have values, clicking the picker can submit/apply and close the editor before the popover opens; ensure the trigger is non-submit.
return (
<div className="flex w-48">
<FilterByValuesPicker
column={column}
calculateTopKRows={calculateTopKRows}
chosenValues={v.options ?? []}
onChange={(values) => onChange({ kind: "options", options: values })}
/>
05614ba to
bdc0c52
Compare
bdc0c52 to
914c240
Compare
- Combobox column-picker trigger: use native button with type=button so Enter/Space activates and the form does not submit. - FilterByValuesPicker trigger: add type=button for the same reason. - Auto-focus selector now also matches button, so the values-picker gets focus when opening the pill with an in/not_in operator. - Update test to match popover trigger label after the picker moved inside a popover.
914c240 to
8df5041
Compare
|
This looks good, nice! |
| } | ||
| if (value.type === "date") { | ||
| return formatMinMax(value.min?.toISOString(), value.max?.toISOString()); | ||
| return formatMinMaxLegacy( |
There was a problem hiding this comment.
Previously we overloaded the between operator to support all comparison operators. The UI now enforces separate operators for everything, but we still support previous behaivor (for e.g. backend can send min: null && max: 7 which becomes val <= 7).
The formatter handles the legacy behavior.
| useEffect(() => { | ||
| const firstInput = valueSlotRef.current?.querySelector<HTMLElement>( | ||
| 'input, [role="combobox"], button', | ||
| ); | ||
| if (firstInput) { | ||
| firstInput.focus(); | ||
| } else { | ||
| operatorTriggerRef.current?.focus(); | ||
| } | ||
| }, [draftType, draftOperator]); |
There was a problem hiding this comment.
there's also a FocusScope from "react-aria" component, which sounds like what this is doing. But non-blocking
There was a problem hiding this comment.
FocusScope doesn't actually replicate this completely. it focuses on first focusable element on mount (we want to focus on first input OR operator). Also the useEffect runs on operator change, and adjusts focus accordingly, which FocusScope won't do.
|
🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.7-dev48 |
Summary
Expands the table's column-filter capabilities and overhauls the filter-pill editor.
FilterByValuesListgains creatable mode and fixes the chosen-outside-top-K case.regextext operator.in/not_invalue picker now supports typing values that aren't in the top-K — they appear as a "+ Add" item and, once selected, render at the bottom of the picker with no count so they stay visible and removable.Screenshots/Videos
Column Header Filtering
Screen.Recording.2026-05-18.at.7.54.06.PM.mov
Values picker
Screen.Recording.2026-05-18.at.7.57.32.PM.mov
Test plan
/pattern/flags/,[,], etc.) typed into the column-header text filter reach the input