Skip to content

feat(table): expand column filter operators and pill-editor UX#9597

Merged
kirangadhave merged 13 commits into
mainfrom
kg/filter-ops-expansion
May 19, 2026
Merged

feat(table): expand column filter operators and pill-editor UX#9597
kirangadhave merged 13 commits into
mainfrom
kg/filter-ops-expansion

Conversation

@kirangadhave

@kirangadhave kirangadhave commented May 19, 2026

Copy link
Copy Markdown
Member

Reviewer note: please review commit by commit.

Summary

Expands the table's column-filter capabilities and overhauls the filter-pill editor.

  • Filter models: typed text/number filter models, shared operators module, native between for numbers, value-drop for unary text ops.
  • Column header: full operator pickers for text and number columns. FilterByValuesList gains creatable mode and fixes the chosen-outside-top-K case.
  • Pill editor: full per-dtype operator support, operator-aware label/value formatting, plus UX polish (Apply gated by form submit + tooltip, draft preserved across compatible operator changes, keyboard focus flow, values picker as popover, right-sized value slots).
  • Regex filter: slash-bracketed input for the new regex text operator.
  • Bug fix: column-header text filter no longer eats special characters via key-propagation.
  • Creatable values picker: the in / not_in value 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

  • Filter a text column with every operator (contains, starts/ends with, equals, regex, is_empty, is_not_empty, ...)
  • Filter a number column with every operator (between, eq, lt, gt, ...)
  • Open the pill editor, switch operators: draft value preserved where compatible, cleared otherwise
  • Tab through column, operator, value, Apply
  • Apply button disabled with tooltip until value is valid; form submit also applies
  • FilterByValuesList: type to create a new value, confirm values outside top-K still display
  • Regex input accepts /pattern/flags
  • Special chars (/, [, ], etc.) typed into the column-header text filter reach the input

@vercel

vercel Bot commented May 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment May 19, 2026 3:03am

Request Review

@kirangadhave

Copy link
Copy Markdown
Member Author

@cubic-dev-ai

@cubic-dev-ai

cubic-dev-ai Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai

@kirangadhave I have started the AI code review. It will take a few minutes to complete.

kirangadhave and others added 9 commits May 18, 2026 19:13
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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread frontend/src/components/data-table/filter-by-values-picker.tsx
@kirangadhave kirangadhave force-pushed the kg/filter-ops-expansion branch from 30a332c to 9a25278 Compare May 19, 2026 02:14
@kirangadhave kirangadhave added the enhancement New feature or request label May 19, 2026
@kirangadhave kirangadhave marked this pull request as ready for review May 19, 2026 02:15
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).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 })}
        />

Comment thread frontend/src/components/ui/combobox.tsx Outdated
Comment thread frontend/src/components/data-table/regex-input.tsx
Comment thread frontend/src/components/data-table/filter-pill-editor.tsx
- 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.
@Light2Dark

Copy link
Copy Markdown
Member

This looks good, nice!

}
if (value.type === "date") {
return formatMinMax(value.min?.toISOString(), value.max?.toISOString());
return formatMinMaxLegacy(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why is this called legacy?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Comment on lines +201 to +210
useEffect(() => {
const firstInput = valueSlotRef.current?.querySelector<HTMLElement>(
'input, [role="combobox"], button',
);
if (firstInput) {
firstInput.focus();
} else {
operatorTriggerRef.current?.focus();
}
}, [draftType, draftOperator]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

there's also a FocusScope from "react-aria" component, which sounds like what this is doing. But non-blocking

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

@kirangadhave kirangadhave merged commit 6612f6d into main May 19, 2026
32 checks passed
@kirangadhave kirangadhave deleted the kg/filter-ops-expansion branch May 19, 2026 07:51
@github-actions

Copy link
Copy Markdown

🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.7-dev48

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants