Skip to content

feat(datagrid): add per-column value filter (#1454)#1756

Merged
datlechin merged 1 commit into
mainfrom
emdash/feat-sql-column-table-header-icon-pvm93
Jun 22, 2026
Merged

feat(datagrid): add per-column value filter (#1454)#1756
datlechin merged 1 commit into
mainfrom
emdash/feat-sql-column-table-header-icon-pvm93

Conversation

@datlechin

Copy link
Copy Markdown
Member

Closes #1454.

Adds an Excel/DataGrip-style per-column value filter that narrows the loaded rows without re-querying the server.

What it does

  • Hover a column header to reveal a funnel icon; click it (or right-click the header → Filter Values…) to open a popover.
  • The popover lists the distinct values in that column from the loaded rows, each with an occurrence count, plus (NULL) and (Empty) entries. Search the list, check the values to keep, click Apply (Return applies, Esc cancels).
  • Filter several columns at once; the grid shows only rows matching every filter (intersection). Columns with an active filter keep a filled funnel icon.
  • Clear a single column or all columns from the header right-click menu, or reopen the popover and Select All.

How it works

  • A displayIDs accessor (valueFilteredIDs ?? sortedIDs) slots above the existing sortedIDs layer, exactly where the issue suggested. Every read site (displayRow, numberOfRows, cachedRowCount, prewarm, copy/select, RowVisualIndex) goes through it, so the filter composes with SQL sort, pagination, and change tracking. displayIDs == sortedIDs when no filter is active.
  • The funnel is drawn in SortableHeaderCell (matching the existing sort chevron) rather than overlaid NSButtons, to avoid hundreds of buttons on wide grids. Hover-reveal via the header tracking area; line.3.horizontal.decrease.circle.fill when active. The funnel is a distinct hit target from the sort-on-title click.
  • Distinct values run through the same CellDisplayFormatter the cells use, so popover labels match what's shown in the grid.
  • Popover is a transient NSPopover hosting SwiftUI via PopoverPresenter, anchored to the funnel rect.
  • Selections persist and re-apply to whatever rows load next (page change / re-query); they clear only when the column set changes or you clear them.

Scope boundary

The filter state lives in the grid coordinator and is ephemeral, like sortedIDs: not persisted to disk, not shown in the status-bar row count, and not counted as "active work" by the tab-replacement guard. The funnel fill is the active-filter indicator the issue asked for. These integrations can be added later if wanted.

Tests

  • GridValueFilterStateTests — set/clear/clearAll/prune, active tracking.
  • TableViewCoordinatorValueFilterTests — distinct extraction with NULL bucket and counts, single/multi-column intersection, null-only selection, clear, inserted-rows-stay-visible, column-set pruning, and displayRow/tableRowsIndex/cachedRowCount via displayIDs.

Docs

  • New "Filtering Loaded Values" section in docs/features/data-grid.mdx, with a note that it operates on the current page and points to the Filter Panel for server-side filtering.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 35c8470f14

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

var sortedIDs: [RowID]?
var valueFilteredIDs: [RowID]?
var valueFilterState = GridValueFilterState()
var displayIDs: [RowID]? { valueFilteredIDs ?? sortedIDs }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Map filtered display rows before mutating rows

Once displayIDs can come from valueFilteredIDs, NSTableView row indexes are positions in the filtered list, but mutation delegates still pass selectedRowIndexes/grid selection rows to RowEditingCoordinator.deleteSelectedRows, which indexes tableRows.rows[rowIndex] directly. With a filter where display row 0 is actually RowID.existing(2), pressing Delete marks/removes storage row 0 instead of the visible row, so the wrong record can be deleted; convert display indexes through tableRowsIndex(forDisplayRow:) or pass row IDs for row mutations.

Useful? React with 👍 / 👎.

Comment on lines 21 to +22
invalidateDisplayCache()
visualIndex.updateRow(row, from: changeManager, sortedIDs: sortedIDs)
visualIndex.updateRow(row, from: changeManager, sortedIDs: displayIDs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recompute value filters after edited cells change

When a value filter is active, this commit path only updates the visual index and reloads the edited row; it never recomputes valueFilteredIDs. For example, with a status = active value filter, editing a visible row's status to inactive leaves that row visible and included in copy/selection until a full reload or filter change occurs, even though it no longer matches the active filter.

Useful? React with 👍 / 👎.

@mintlify

mintlify Bot commented Jun 22, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Jun 22, 2026, 5:36 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f634a3ca2c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


invalidateDisplayCache()
visualIndex.updateRow(row, from: changeManager, sortedIDs: sortedIDs)
visualIndex.updateRow(row, from: changeManager, sortedIDs: displayIDs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Record filtered edits with storage row indices

When a value filter makes display row 0 correspond to a different storage row, recordCellEdit mutates the correct storage row via tableRowsIndex(forDisplayRow:), but the change manager record is still keyed by the display row; this subsequent visual update reinforces that display index as the edited row. For a filtered view where display row 0 is storage row 2, editing the cell records an undo/update for row 0, so Undo or later merged edits can modify the wrong stored row even though the visible cell was changed. Record the change against the storage row/row ID and translate only when painting display state.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f622ffcbc9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 531 to +533
guard row >= 0, row < tableView.numberOfRows else { return }
invalidateDisplayCache(forDisplayRow: row, column: column)
visualIndex.updateRow(row, from: changeManager, sortedIDs: sortedIDs)
visualIndex.updateRow(row, from: changeManager, sortedIDs: displayIDs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Map storage delta rows before repainting filtered views

When a value filter is active, Delta.cellChanged row values still come from TableRows/RowOperationsManager as storage indices, but this branch validates and repaints them as display indices. For example, if storage row 2 is the only visible row after filtering, an undo/discard delta for row 2 fails the row < tableView.numberOfRows check and the visible row is not reloaded; in larger filtered sets it can repaint the wrong display row. Map the row ID to its current filtered display index, or fall back to recomputing/reloading when filters are active.

Useful? React with 👍 / 👎.

@datlechin datlechin force-pushed the emdash/feat-sql-column-table-header-icon-pvm93 branch from f622ffc to 9a897db Compare June 22, 2026 18:51
@datlechin datlechin merged commit 46eefc9 into main Jun 22, 2026
2 of 3 checks passed
@datlechin datlechin deleted the emdash/feat-sql-column-table-header-icon-pvm93 branch June 22, 2026 18:52

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9a897db33a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

recomputeValueFilteredIDs()
updateCache()
visualIndex.rebuild(from: changeManager, sortedIDs: displayIDs)
selectionController.clear()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear row selection when value filters change

When applying or clearing a value filter while an NSTableView row is selected, this only clears the grid cell-selection overlay; selectedRowIndices and the table view's selected row indexes are left as display indexes across a reordered/shortened displayIDs set. For example, selecting display row 0 for row A and then filtering so row C becomes display row 0 leaves row 0 selected, so subsequent copy/delete/context-menu actions operate as if C was intentionally selected. Deselect rows here or remap the selection by RowID before reloading.

Useful? React with 👍 / 👎.

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.

feat(datagrid): per-column local value filter (Excel-style)

1 participant