Skip to content

[DataGridPro] Add new multiSelect column type#21157

Merged
siriwatknp merged 156 commits into
mui:masterfrom
siriwatknp:feat/multiselect
Jun 8, 2026
Merged

[DataGridPro] Add new multiSelect column type#21157
siriwatknp merged 156 commits into
mui:masterfrom
siriwatknp:feat/multiselect

Conversation

@siriwatknp

@siriwatknp siriwatknp commented Jan 29, 2026

Copy link
Copy Markdown
Member

closes #4410

Summary

Adds a new multiSelect column type to DataGridPro that stores array values and displays them as chips. This is a Pro feature.

Usage

const columns = [
  {
    field: 'tags',
    headerName: 'Tags',
    type: 'multiSelect',
    editable: true,
    valueOptions: ['React', 'TypeScript', 'Node.js'],
    // or object options:
    // valueOptions: [{ value: 'fe', label: 'Frontend' }, ...]
  },
];
// rows: [{ id: 1, tags: ['React', 'TypeScript'] }]

Features

  • Cell display: Values render as Material UI Chips with smart overflow handling — a "+N" chip appears when chips don't fit, clicking it opens a popup showing all values
  • Editing: Autocomplete-based multi-select dropdown; Ctrl/Cmd+Enter to commit, Escape to cancel
  • Filtering: contains / doesNotContain operators with multi-value baseSelect (multiple) input. contains [A, B] matches rows containing A or B (OR semantics); doesNotContain [A, B] matches rows containing neither. Plus isEmpty / isNotEmpty
  • Header filtering: Uses a multiple Select (not Autocomplete) to avoid layout overflow issues with chips in the header filter row
  • Quick filter: Regex-based matching against formatted values with diacritic support
  • Sorting: Alphabetical sort by joining all cell values into a string and comparing with localeCompare. Empty arrays sort to the start (ascending)
  • Copy/paste: Joins by separator on copy, splits and validates against valueOptions on paste
  • Auto row height: Chips wrap with flexWrap when row has dynamic height
  • Row spanning: Includes rowSpanValueGetter that serializes arrays to sorted comma-joined strings for correct value comparison (arrays compare by reference, not value)
  • Column autosizing: Hidden chips are revealed during autosize measurement for accurate width calculation
  • Custom chips: slotProps.chip accepts a function (option, index) => ChipProps for per-value customization (colors, avatars, variants)
  • Keyboard navigation: Space toggles overflow popup, Escape closes it, overflow chip auto-focuses when cell is focused
  • Row grouping (Premium): default groupingValueGetter serializes the array to a sorted comma-joined key so rows sharing the same set of values cluster into one group regardless of order. Override with an explicit groupingValueGetter to customize (e.g. order-sensitive grouping). Empty arrays return null and remain ungrouped at root
  • Aggregation (Premium): only size is exposed (counts rows with non-empty array). Numeric aggregations are filtered out because they have no unambiguous meaning over arrays. Footer aggregation cell renders via GridFooterCell to match other column types; grouped-row aggregation renders formattedValue
  • Live chip overflow during resize: the +N chip recomputes in real time while the user drags the column separator (throttled, with asymmetric hysteresis to prevent flicker at chip-fit boundaries) — not only on drag-stop. Drag-tick width is read from the columnResize event (params.width), so cells make no DOM reads per tick; sub-pixel getBoundingClientRect runs on commit only

Configuration

Prop Type Description
valueOptions Array<string | number | { value, label }> or (params) => Array Available options
getOptionLabel (option) => string Custom label extraction
getOptionValue (option) => string | number Custom value extraction
separator string (default: ',') Display/export join separator

Customization

Chips can be customized per-value via slotProps.chip, which supports a callback signature (option, index) => ChipProps. This lets you assign different colors, avatars, or variants based on the option value:

const columns = [
  {
    field: 'tags',
    type: 'multiSelect',
    valueOptions: ['React', 'TypeScript', 'Node.js'],
    slotProps: {
      chip: (option, index) => ({
        color: option === 'React' ? 'primary' : 'default',
        variant: 'outlined',
      }),
    },
  },
];

See the Custom multi-select chips recipe demo for a full example.

For Reviewers

Architecture: Pro tier via preprocessor

The column type is registered in x-data-grid-pro via a useGridMultiSelectPreProcessors hook that plugs into the hydrateColumns pipe processor. This applies multiSelect defaults (renderers, operators, sort comparator) to any column with type: 'multiSelect'. The preprocessor uses the user's original column props (not the already-merged state) to avoid string-type defaults leaking in. It also respects hasBeenResized to preserve manual column widths after resize.

Types (GridMultiSelectColDef) remain in x-data-grid for type safety — consumers define columns against the community types even when using Pro features.

Cell display: measure-once + live resize

GridMultiSelectCell renders all chips in the DOM but hides overflow chips via CSS class (multiSelectCellChip--hidden). On mount, useLayoutEffect measures each chip width via getBoundingClientRect().width (sub-pixel precision) and caches them in a ref. Visible count is derived via calculateVisibleCount — a pure function that sums chip widths against container width with strict (no-tolerance) fit. The row gap and +N overflow chip widths (per digit count) are not hardcoded — they are measured per column by GridMultiSelectMeasurer (see the 2026-05-04 follow-up below) and read from apiRef.caches.multiSelect; calibrated defaults (gap = 4 px, 32/38/44 px for 1/2/3-digit overflow) act as fallbacks until the first measurement reports.

Live resize. The cell subscribes to the columnResize event and commits containerWidth via a 32 ms throttle. During drag, container width is derived from params.width minus a cached column − container offset (cell padding + borders), so the cell makes no DOM reads per tick. After drag, the columnWidth prop effect re-measures the container with getBoundingClientRect for sub-pixel accuracy. Chip widths are still measured sub-pixel via getBoundingClientRect on layout.

Hysteresis. Natural pointer jitter produces ±1-3 px micro-reversals around a chip-fit boundary, which without smoothing makes visibleCount flap between N and N+1. The cell applies asymmetric hysteresis — upward transitions (revealing a chip) require 4 px of headroom past the real boundary; downward transitions (hiding a chip) apply immediately so chips never visually overflow the cell edge. State kept in prevVisibleCountRef, committed in a useLayoutEffect.

For auto row height, the component detects rowHasAutoHeight and skips the overflow logic entirely, using CSS flexWrap instead.

For autosizing, hidden chips are temporarily shown (their CSS class is toggled) so the autosize algorithm measures the full content width.

Group rows. renderMultiSelectCell short-circuits to formattedValue when rowNode.type === 'group' so grouped rows display the joined key as plain text instead of trying to render chips over a string value.

Edit cell

GridEditMultiSelectCell uses MUI Autocomplete in multiple mode with disableCloseOnSelect. The Popper is portaled to the row element and positioned with offset. Enter key handling differs by edit mode:

  • Cell edit mode — bare Enter exits edit mode; Ctrl/Cmd+Enter commits the value.
  • Row edit mode — bare Enter dismisses the popup but keeps the cell focused (a second Enter re-opens it); Ctrl/Cmd+Enter commits.

The popup height is density-aware via CSS variable var(--_rowHeight) derived from grid state, scaling the listbox maxHeight to calc(var(--_rowHeight) * 3.5).

Header filter

Uses a multiple Select (via baseSelect with multiple) instead of Autocomplete to avoid layout overflow issues with chips in the constrained header filter row. SelectProps was extended with multiple and renderValue to support this.

Filter operators

gridMultiSelectOperators provides contains and doesNotContain with a multi-value baseSelect (multiple) input (GridFilterInputMultipleMultiSelect) — the same component reused by the header filter.

The two operators are deliberately asymmetric:

  • contains [A, B] uses OR — matches rows whose array contains A or B (any of the filter values).
  • doesNotContain [A, B] matches rows whose array contains none of the filter values.

This pair maps cleanly to user intent for tag-style data ("contains any" / "contains none"). is/isNot operators were considered but removed to keep the UX simple. Filter values are pre-parsed into a Set so the apply function is one pass over the cell array.

Row grouping

Grouping works out of the box because GRID_MULTI_SELECT_COL_DEF ships a default groupingValueGetter that serializes the cell array to a sorted comma-joined string. Both groupingValueGetter and rowSpanValueGetter share a multiSelectKey helper. Reference-equal arrays with the same members (any order) collapse into a single group; users who want order-sensitivity override the getter explicitly.

Two upstream tweaks were needed for the grouping column to render multiSelect keys correctly:

  • createGroupingColDef skips the original column's valueFormatter when it's a multiSelect (the default formatter expects an array; the group key is a string). Same treatment singleSelect already had.
  • renderMultiSelectCell returns formattedValue instead of <GridMultiSelectCell> when the row is a group row, avoiding the array-shaped chip rendering on a string key.

Pro / Premium integration

Gating is applied at runtime by useGridMultiSelectPreProcessors on GRID_MULTI_SELECT_COL_DEF. Charts and aggregation panels read these flags post-hydrate from the column lookup; the pivot panel skips type === 'multiSelect' explicitly (see the 2026-05-04 type-system cleanup follow-up below). Each flag prevents Tags-style columns from appearing where their array values would produce nonsense:

Flag Default for multiSelect Effect
groupable true (inherited) Default groupingValueGetter serializes arrays to a sorted comma-joined key. Rows with the same set of values cluster regardless of order; override the getter to customize
pivotable false Set on the column def for consistency, but the pivot panel filters multiSelect by type — arrays would explode into a combinatorial number of pivot columns
chartable true
availableAggregationFunctions ['size'] Only size appears in the column menu; numeric aggs (sum/avg/min/max) are filtered out

Users can opt in or out of any of these by setting them explicitly on their column; defaults apply only when unset. Aggregated cells render via GridFooterCell (footer position) or params.formattedValue (grouped rows) so they visually match other column types.

A dev-only warnOnce fires when type: 'multiSelect' is used in the community-tier <DataGrid /> (the multiSelect preprocessor is Pro-only, so the column would otherwise silently render as a string column).

CSS classes

New classes added to gridClasses: multiSelectCell, multiSelectCellChip, multiSelectCellChip--hidden, multiSelectCellOverflow, multiSelectCellPopup, multiSelectCellPopperContent.


Review follow-ups (2026-04-15, @MBilalShafi)

  • Gate pivoting / chartsmultiSelect columns now opt out of pivoting and charts; size is the only exposed aggregation function. (Grouping was initially gated too but has since been re-enabled — see the @cherniavskii follow-up below.)
  • Default filter logic (#r3086247827) — contains [A, B] uses OR semantics: matches rows containing A or B. doesNotContain [A, B] matches rows containing none of the filter values. The asymmetry is intentional — "contains any" / "contains none" map cleanly to user intent for tag-style data.
  • Autosize doesn't expand hidden chips (#r3086299133) — during autosize the multiSelect cell root gets width: max-content + overflow: visible, letting the intrinsic sum of all (now-revealed) chips drive the column width instead of the flex container's width: 100% capping it.
  • Spanned cells: chip vertical alignment (#r3086684899) — cells with aria-rowspan > 1 now top-align (alignItems + alignContent: flex-start), wrap (flexWrap: wrap), and drop the +N overflow so all chips stay visible regardless of how the tall spanned cell is scrolled.
  • Clipboard paste of multiple values (#r3086705136) — pastedValueParser now splits on any of , ; \t \n | (runs collapsed) and matches option value / label case-insensitively. Default separator also changed from ', '',' for cleaner CSV / copy-paste round-trips.
  • Firefox overflow +N chip disappears at narrow widths (#r3087186067) — fixed by the cell-display rewrite: sub-pixel chip widths via getBoundingClientRect, sub-pixel container width on commit, strict (no-tolerance) fit in calculateVisibleCount, and 4 px asymmetric hysteresis to absorb both pointer jitter and the integer column width used during active drag.

Review follow-ups (2026-04-22, @cherniavskii)

  • Enable row grouping on multiSelect (#r3119881896) — arrays now serialize to a sorted comma-joined string via a default groupingValueGetter, so rows sharing the same set of values (regardless of order) cluster into one group. RowGroupingFullExample grouping model updated to ['commodity', 'tags'] so the multiSelect grouping path is visible in the docs. Users can override with their own groupingValueGetter to customize (e.g. order-sensitive grouping). createGroupingColDef was updated to bypass the multiSelect valueFormatter on grouping keys (mirrors the existing singleSelect bypass); renderMultiSelectCell renders formattedValue for group rows instead of trying to render chips over a string key.

Review follow-ups (2026-04-29, edit-cell UX refactor)

  • baseModal slot added to the grid's slot system. The multiSelect edit cell now wraps its popper in a <rootProps.slots.baseModal> (default Material implementation forwards to @mui/material/Modal). Provides scroll-lock + backdrop the same way singleSelect (Material Select) does, and lets consumers swap it out alongside other base slots.
  • Internal open state in the edit cell, decoupled from hasFocus. Previously the popup was tied to grid focus, which meant Escape couldn't dismiss it without losing the cell. Now the cell has its own open and:
    • Cell edit mode — popup opens on edit start; Escape / backdrop click / bare Enter publish cellEditStop (existing behavior).
    • Row edit mode — popup opens on edit start. Escape / backdrop click only setOpen(false) (the cell stays focused, value preserved). Click on chips area or pressing Enter reopens. A second Escape on the focused cell bubbles to the grid's cellKeyDown listener and exits the row — matching singleSelect's two-step Escape.
  • Tab/Shift+Tab to an offscreen multiSelect cell now scrolls it into view. The Modal locks body scroll and the autocomplete input lives in a body-portaled popper, so the browser-default scroll-into-view that text-input cells rely on doesn't fire here. The edit cell calls apiRef.scrollToIndexes on the focus-gain edge (tracked via a ref) to bring the cell into view via the grid's own scroll API.
  • Re-entry auto-opens the popup. When the cell loses focus, open resets to true so Shift+Tabbing back opens the autocomplete by default — matches the "every focus opens the autocomplete" expectation.
  • Capture-phase Escape handling. Autocomplete swallows Escape with stopPropagation to "avoid the Modal handling the event". An onKeyDownCapture on the popper content runs first (top-down) so we can route Escape through closePopup regardless of Autocomplete's own bubble-phase handling.

Review follow-ups (2026-04-30, code review pass)

  • Fixed dead reorder branch in GridMultiSelectCell. The "show filtered value first" reorder did rawArrayValue.indexOf(filterValue) against an array filter value, which always returned -1, so the reorder never fired. Now finds the first cell value matching any of the active contains filter values and hoists it to the front.
  • contains / doesNotContain performance. Filter values are pre-parsed into a Set; the apply function is one pass over the cell array (O(n) instead of O(n × m)).
  • Drag-time resize no longer reads container width per tick. During columnResize, the cell uses params.width minus a cached column − container offset; sub-pixel getBoundingClientRect only runs on commit. Eliminates per-cell DOM reads on every drag tick.
  • Single grid-level resize subscription (fixes EventEmitter listener leak). Previously each visible multiSelect cell registered its own columnResize + columnResizeStop listeners, tripping the grid's "Possible EventEmitter memory leak detected. 21 columnResizeStop listeners added" warning on any tall grid. useGridMultiSelectPreProcessors now hosts a single per-grid drag broadcaster on apiRef.current.caches.multiSelect (subscribeDrag(field, cb)), throttled per field. Cells consume via one useEffect and unsubscribe on unmount. Listener count is now O(1) regardless of how many multiSelect cells are visible; per-tick setTimeouts collapse to one timer per actively-resized field. Tests verify listener count stays bounded with 25 visible rows and that subscribeDrag broadcasts / cleans up correctly.
  • Edit-cell scroll effect tightened. apiRef.scrollToIndexes now fires only on the focus-gain edge (tracked via prevHasFocusRef); a separate effect handles popup-closed-while-focused chip refocus. Avoids redundant scroll calls when open toggles while focus is held.
  • Memoized optionByValue in GridMultiSelectCell to stop rebuilding the Map (and invalidating downstream memos) on every render.
  • Extracted multiSelectKey helper shared by groupingValueGetter and rowSpanValueGetter — previously identical duplicated code.
  • Removed as any aggregation casts in renderMultiSelectCell by typing the params with GridAggregationCellMeta.
  • Dev-only warning when type: 'multiSelect' is used in the community-tier <DataGrid />. The multiSelect preprocessor is Pro-only, so without the warning the column would silently render as a string column with broken array handling.
  • Tests: explicit OR-semantics assertion on multi-value contains; new test for the chip-reorder behavior under an active contains filter.
  • Cell-display split + drag-only hysteresis — chip rendering, measurement, and overflow logic moved into a GridMultiSelectChips subcomponent (delegated from GridMultiSelectCell); hysteresis now gated to active drag (snaps to exact fit on autosize / mount / prop-width changes); subscription is via the per-grid broadcaster on apiRef.caches.multiSelect.subscribeDrag, not columnResize directly. Supersedes the "Cell display: measure-once + live resize" architecture text above.

Review follow-ups (2026-05-04, font-independent overflow chip widths)

  • Hardcoded overflow chip widths depend on font/style (#r3180068015) — the +N chip widths (32/38/44 px for 1/2/3 digits) and row gap (4 px) were calibrated for the default Material small outlined chip. Under custom fonts, theme slot overrides on MuiDataGrid-MultiSelectChips, or chip variant changes, the algorithm could mis-compute visibleCount and clip chips at the cell edge.

    Replaced with a per-column measurer (GridMultiSelectMeasurer) that renders three hidden sample chips (+9 / +99 / +999) inside MultiSelectChipsRoot (the same styled slot the cells use), measures their widths via getBoundingClientRect, and reads the row's actual gap from getComputedStyle. Metrics are published per field to apiRef.caches.multiSelect (setOverflowMetrics(field, ...)); cells subscribe via subscribeOverflowMetrics(field, cb) and fall back to the calibrated defaults until the first measurement reports.

    Lifecycle is header-driven. The measurer is mounted via the column's renderHeader, composed by useGridMultiSelectPreProcessors so the user's renderHeader (if any) is wrapped — the measurer is preserved regardless of customization. When the column is hidden via the visibility model, the header unmounts → measurer unmounts → field's metrics are cleared from the cache. No grid-root mount, no global gating; pay-for-what-you-use per column.

    Implementation reuses useResizeObserver from @mui/x-internals (handles the dev-mode RAF wrap that prevents the React "update during render" warning) and fastArrayCompare for the cache's no-op dedupe so subscribed cells don't re-render on equal updates.

Review follow-ups (2026-05-04, type-system cleanups, @arminmeh)

  • Don't narrow singleSelect.getOptionValue return type (#r3180059816) — reverted (value: ValueOptions) => string | number back to => any on GridSingleSelectColDef. Narrowing was a breaking change for consumers returning objects/booleans and transforming them through valueGetter / valueFormatter / renderCell. JSDoc reverted to @returns {string} to match master.
  • Make multiSelect non-existent in Community GridColumnTypesRecord (#r3180438412) — converted GridColumnTypesRecord from a type alias to an interface that omits multiSelect; Pro augments it via the existing declare module '@mui/x-data-grid' block in typeOverloads/modules.ts to add multiSelect?: GridColTypeDef (same pattern as GridEventLookup / GridColDefPro / etc., no new mechanism). Dropped the runtime stub from getGridDefaultColumnTypes: charts and aggregation read their gates post-hydrate from the column lookup (set by useGridMultiSelectPreProcessors on GRID_MULTI_SELECT_COL_DEF); the pivot panel now skips type === 'multiSelect' directly since pivot pre-reads col-type defaults from props.columns before any preprocessor runs and pivot doesn't support array values anyway.

- Add GridMultiSelectColDef interface with valueOptions, getOptionLabel, getOptionValue, separator
- Register multiSelect in column types with default sortComparator (by array length), valueFormatter (join with separator), and pastedValueParser
- Add multiSelectCell* CSS classes
- Add isMultiSelectColDef helper
- Add placeholder for header filter input
- Add GridMultiSelectCell with chip rendering and +N overflow
- Wire renderCell in GRID_MULTI_SELECT_COL_DEF
- Add cell display tests
- Add GridEditMultiSelectCell for editing with Autocomplete
- Add GridFilterInputMultiSelect for filter panel
- Add demo and documentation
- Add tests for filtering, editing, quick filter
- Update API docs and exports
- Add tests for overflow chip auto-focus on cell focus
- Add tests for popup open/close via Space and Escape
- Add openMultiSelectPopup helper in test/utils/helperFn.ts
…iSelect cell

Performance improvement: removed per-cell ResizeObserver.
- Measure chips and container width once on mount
- React to colDef.computedWidth changes via delta calculation
- Use useMemo for visible count calculation from cached measurements
- "+9" (1 digit) = 32px
- "+99" (2 digits) = 38px
- "+999" (3+ digits) = 44px

Also update test comment to reflect actual skip reason (DOM layout, not ResizeObserver)
Skip useEffect reset on initial mount to prevent race condition
with useLayoutEffect measurement
Custom popper slot makes disablePortal unnecessary. Updated docs to reflect custom popper approach.
- Add optional container param to getColumnValues helper
- Use setProps instead of user.type to avoid debounce race conditions
- Scope assertions to test container for parallel test isolation
- Render all chips in DOM, use CSS class to hide overflow chips
- Show hidden chips during autosizing for proper width calculation
- Extract calculateVisibleCount to utils with tests
- Fix subpixel tolerance bug (1px per chip)
@siriwatknp siriwatknp added scope: data grid Changes related to the data grid. type: new feature Expand the scope of the product to solve a new problem. plan: Pro Impact at least one Pro user. labels Jan 29, 2026
@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jan 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@mui-bot

mui-bot commented Jan 29, 2026

Copy link
Copy Markdown

Deploy preview: https://deploy-preview-21157--material-ui-x.netlify.app/

Updated pages:

Bundle size report

Bundle Parsed size Gzip size
@mui/x-data-grid 🔺+543B(+0.13%) 🔺+130B(+0.11%)
@mui/x-data-grid-pro 🔺+13.2KB(+2.61%) 🔺+4.16KB(+2.82%)
@mui/x-data-grid-premium 🔺+13.1KB(+1.97%) 🔺+3.91KB(+2.03%)
@mui/x-charts 0B(0.00%) 0B(0.00%)
@mui/x-charts-pro 0B(0.00%) 0B(0.00%)
@mui/x-charts-premium 0B(0.00%) 0B(0.00%)
@mui/x-date-pickers 0B(0.00%) 0B(0.00%)
@mui/x-date-pickers-pro 0B(0.00%) 0B(0.00%)
@mui/x-tree-view 0B(0.00%) 0B(0.00%)
@mui/x-tree-view-pro 0B(0.00%) 0B(0.00%)

Details of bundle changes

Generated by 🚫 dangerJS against df1c9eb

@github-actions github-actions Bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jan 30, 2026
Comment thread packages/x-data-grid-pro/src/colDef/gridMultiSelectColDef.ts Outdated
Comment thread packages/x-data-grid-pro/src/colDef/gridMultiSelectColDef.ts Outdated
Base hydrated colDef on processed column (keeps dimensions, hasBeenResized,
computed props), overlay multiSelect defaults only where user didn't set,
skip dimension keys. Fixes resized width lost after repeated hydration.
Comment thread packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx Outdated
Comment thread packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx Outdated
Comment thread packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx Outdated
Comment thread packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx Outdated
Comment thread packages/x-data-grid-pro/src/colDef/gridMultiSelectColDef.ts Outdated
Measure +N overflow chip widths/gap once per grid via a single
GridMultiSelectMeasurer in <GridRoot>, instead of per-column via a
renderHeader override. Metrics are grid-global (identical across columns).
Removes the renderHeader override, fixing the default header rendering.
Recompute the +N overflow on committed column-width changes (resize stop),
matching how other cells reflow, instead of subscribing each cell to a
throttled drag broadcaster. Removes the per-grid columnResize listener,
the cache drag pub/sub, and the chip-level containerWidth drag tracking.
Register multiSelect in the core column-type registry (read at call time) so
core hydration applies its colDef defaults natively, in Pro and Premium only.
A community DataGrid never loads the registration; a prop validator warns in
dev if a multiSelect column is used there. Deletes useGridMultiSelectPreProcessors;
the measurer now owns the overflow-metrics cache.
While a multiSelect column is being drag-resized, render every chip and let
the cell clip them (live preview via the column width CSS variable). The +N
overflow is recomputed once the resize is committed. Driven by a single
resizing-field selector — no per-tick work or width broadcasting.
A quick double-click to autosize toggles the resizing state in short bursts;
revealing every chip immediately flashed them. Debounce the reveal by 150ms so
only a held drag reveals, while a transient click/double-click clears first.
…iltering tests

- pastedValueParser now splits on the column's configured separator too, so a
  custom separator round-trips through copy/paste.
- Move multiSelect rendering/overflow/resize tests out of the filtering test
  file into a dedicated multiSelect.DataGridPro.test.tsx.
Comment thread packages/x-data-grid/src/colDef/gridColumnTypesRegistry.ts
multiSelect is registered in the shared core column-type registry, so importing
a Pro/Premium grid made it resolvable in a community DataGrid too (same bundle).
Gate column resolution on the grid signature: a community DataGrid falls back to
the default column type for any non-community-registered type, even when it is
registered globally. Drop the duplicate prop validator (checkMultiSelectColumns
already warns).

@cherniavskii cherniavskii left a comment

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.

Sorry for taking so long to review, and thank you for your patience and follow-ups!

Comment on lines +48 to +50
// Delay before revealing all chips on resize start, so a quick double-click (autosize) — which
// toggles the resizing state in short bursts — doesn't flash every chip.
const RESIZE_REVEAL_DELAY_MS = 150;

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.

Great small detail!

@MBilalShafi MBilalShafi left a comment

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.

Nice work 🙌

Overall looks good to me, just a side question, do you plan to ship with the current keyboard navigation experience or possibly align it with Data Grid modals (filter, columns management, etc.)?

(Ref #21157 (comment))

- Register edit multiSelect cell utility classes in gridClasses
- @ignore internal --hidden chip classes (view + edit)
- Fix sortComparator element-boundary collision (join with separator)
- Dedupe pasted multiSelect values
useGridProps update effect dropped signature, wiping state.props.signature
to undefined after any watched-prop change. Community DataGrid then stopped
falling back and resolved the globally-registered Pro multiSelect renderer.
Add regression test.
@siriwatknp
siriwatknp enabled auto-merge (squash) June 8, 2026 07:41
@siriwatknp
siriwatknp disabled auto-merge June 8, 2026 07:58
@siriwatknp
siriwatknp merged commit 9c238a7 into mui:master Jun 8, 2026
21 checks passed
mbrookes pushed a commit to mbrookes/mui-x that referenced this pull request Jun 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plan: Pro Impact at least one Pro user. scope: data grid Changes related to the data grid. type: new feature Expand the scope of the product to solve a new problem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[data grid] Add multipleSelect column type

8 participants