Skip to content

Allow selecting text of pills in the MultiselectColumn and ListColumn#13663

Merged
lukasmasuch merged 13 commits intodevelopfrom
lukasmasuch/custom-multiselect-cell
Jan 22, 2026
Merged

Allow selecting text of pills in the MultiselectColumn and ListColumn#13663
lukasmasuch merged 13 commits intodevelopfrom
lukasmasuch/custom-multiselect-cell

Conversation

@lukasmasuch
Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch commented Jan 21, 2026

Describe your changes

Allows selecting text of pills in the MultiselectColumn and ListColumn. To do this, I have moved a fixed version of the multi-select cell implementation from glide-data-grid to Streamlit since the release process is currently broken on glide-data-grid. This will be a bit more of a temporary solution, and it's planned to switch back soon.

Testing Plan

  • Added unit tests.

Contribution License Agreement

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.

Implement a custom multi-select cell based on react-select to replace the glide-data-grid-cells MultiSelectCell. This provides better control over styling, behavior, and features.

- Add MultiSelectCell.tsx with custom renderer and editor
- Support for options with labels and colors
- Configurable duplicate and creation permissions
- Enhanced text selection in pill labels
- Comprehensive test coverage
- Register custom cell in useCustomRenderer hook

Co-Authored-By: Claude <[email protected]>
Copilot AI review requested due to automatic review settings January 21, 2026 20:27
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Jan 21, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-13663/streamlit-1.53.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-13663.streamlit.app (☁️ Deploy here if not accessible)

@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Jan 21, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Copy link
Copy Markdown
Contributor

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 custom MultiSelectCell implementation for DataFrame using react-select to replace the existing glide-data-grid-cells MultiSelectCell. The implementation provides enhanced control over styling, behavior, and features including text selection in pill labels, configurable duplication and creation permissions, and comprehensive paste support.

Changes:

  • Added react-select dependency to enable multi-select functionality with better customization
  • Implemented custom MultiSelectCell renderer with canvas drawing, editor component, and paste handler
  • Added comprehensive test coverage with 13+ test cases covering core functionality
  • Removed dependency on glide-data-grid-cells MultiSelectCell while maintaining backward compatibility

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
yarn.lock Lockfile appears to be reset/incomplete - requires regeneration with full dependency tree
frontend/lib/package.json Added react-select@^5.10.1 dependency for multi-select functionality
frontend/lib/src/components/widgets/DataFrame/hooks/useCustomRenderer.ts Removed MultiSelectCell from glide-data-grid-cells imports and customRenderers array
frontend/lib/src/components/widgets/DataFrame/columns/index.ts Exported MultiSelectCellRenderer and added to CustomCells array
frontend/lib/src/components/widgets/DataFrame/columns/cells/MultiSelectCell.tsx Implemented complete custom MultiSelectCell with editor, renderer, and paste support (643 lines)
frontend/lib/src/components/widgets/DataFrame/columns/cells/MultiSelectCell.test.tsx Added comprehensive test coverage including unit tests for utility functions and editor behavior (706 lines)

lukasmasuch and others added 3 commits January 21, 2026 21:36
- Wrap colorStyles in useMemo to prevent unnecessary re-renders
- Memoize components object with useMemo for referential stability
- Remove export from test helper functions (selectOption, hasOption)
- Fix prepareOptions type safety: null/undefined now return empty string
- Change border-radius from 4px to 0.25rem for relative units
- Remove unnecessary async from onChange and wrap with useCallback
- Wrap onMenuOpen/onMenuClose with useCallback
- Wrap noOptionsMessage with useCallback

Co-Authored-By: Claude <[email protected]>
@lukasmasuch lukasmasuch added security-assessment-completed change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Jan 21, 2026
@lukasmasuch
Copy link
Copy Markdown
Collaborator Author

@cursor review

@lukasmasuch lukasmasuch changed the title Add custom MultiSelectCell implementation for DataFrame Allow selecting text of pills in the MultiselectColumn and ListColumn Jan 21, 2026
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Jan 21, 2026

📉 Frontend coverage change detected

The frontend unit test (vitest) coverage has decreased by 0.1800%

  • Current PR: 86.2800% (13502 lines, 1852 missed)
  • Latest develop: 86.4600% (13347 lines, 1807 missed)

💡 Consider adding more unit tests to maintain or improve coverage.

📊 View detailed coverage comparison

@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Jan 21, 2026
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is a temporary migration into our codebase (see comment above). its planned to remove this again soon.

@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Jan 21, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR introduces a custom MultiSelectCell implementation to replace the one from @glideapps/glide-data-grid-cells. The primary motivation is to enable text selection in pill labels within multiselect columns, which was previously not possible. The implementation uses react-select directly, providing better control over styling, behavior, and the specific text selection enhancement.

Key Changes:

  • Added new MultiSelectCell.tsx with custom renderer and editor (~700 lines)
  • Added comprehensive unit tests MultiSelectCell.test.tsx (~709 lines)
  • Added E2E test for text selection and copy functionality
  • Registered the new cell renderer in the DataFrame columns index
  • Removed the MultiSelectCell import from @glideapps/glide-data-grid-cells
  • Added react-select as a direct dependency

Code Quality

Strengths:

  1. Well-structured component architecture: The code follows Streamlit's patterns well, with proper separation of styled components (prefixed with Styled), module-level constants, and memoized values.

  2. Good TypeScript usage: Proper type definitions for MultiSelectCell, SelectOption, and MultiSelectCellProps. Type assertions are documented and justified where necessary.

  3. Proper React patterns:

    • Uses useCallback and useMemo for referential stability
    • Module-level component definitions to avoid nested components inside the Editor
    • Follows the "You Might Not Need an Effect" guidelines - no unnecessary useEffect usage
  4. Good documentation: JSDoc comments explain the purpose of functions and components, particularly the SelectableMultiValueLabel which documents the side effects of the text selection enhancement.

  5. Accessibility consideration: The implementation maintains keyboard navigation support and doesn't break accessibility features.

Minor observations:

  1. MultiSelectCell.test.tsx line 17: The React import appears unused (JSX transformation is handled by the compiler). This is not a blocker.

  2. MultiSelectCell.tsx lines 260-262: The portal target initialization uses useState with an initializer function that calls document.getElementById("portal"). This is a common pattern and acceptable, but the comment explaining why portalElementRef from glide-data-grid isn't used is helpful.

  3. The use of // eslint-disable-next-line streamlit-custom/no-hardcoded-theme-values is appropriate since the CSS variables are from glide-data-grid, not hardcoded values.

Test Coverage

Unit Tests (MultiSelectCell.test.tsx):

The test coverage is comprehensive and follows Streamlit's TypeScript testing best practices:

  • prepareOptions: Tests 10 different input scenarios including strings, objects, null, undefined, and mixed arrays
  • resolveValues: Tests 6 scenarios including empty, null, undefined, duplicates with/without allowDuplicates
  • onPaste: Tests 10 scenarios covering various edge cases (empty, duplicates, unknown options, special characters)
  • Editor component: Tests selection behavior, readonly mode, duplicate handling, and importantly:
    • Tests that text selection is now possible (onMouseDown doesn't prevent default)
    • Tests that pill removal still works after the text selection enhancement (anti-regression)
  • Uses it.each for parameterized tests as recommended

E2E Test (st_data_editor_config_test.py):

The new test test_multiselect_pill_text_selection_and_copy follows best practices:

  • Uses expect for Playwright assertions (not assert) for auto-wait behavior
  • Has descriptive docstring explaining what's being tested
  • Includes negative assertion: verifies values are NOT removed after text selection
  • Properly handles browser-specific behavior (clipboard only tested in Chromium)
  • Uses appropriate helpers from app_utils and dataframe_utils

Suggestion (non-blocking): The E2E test uses assert for clipboard verification (line 429-431). Consider wrapping this in a wait_until pattern for better reliability across different system performance levels.

Backwards Compatibility

No breaking changes identified. The implementation is a drop-in replacement:

  1. The MultiSelectCellProps interface maintains the same structure expected by the grid
  2. The cell renderer uses the same kind: "multi-select-cell" identifier
  3. The onPaste, measure, and draw functions maintain compatible behavior
  4. The values, options, allowCreation, and allowDuplicates props work identically
  5. The change is purely internal - no Python API changes

The removal of MultiSelectCell from @glideapps/glide-data-grid-cells import in useCustomRenderer.ts is safe since the new custom renderer is now registered via CustomCells.

Security & Risk

No security concerns identified:

  1. The clipboard access is browser-controlled and read-only for verification purposes
  2. No new user input vectors are introduced
  3. The react-select dependency (v5.10.2) is a well-maintained library already used by glide-data-grid-cells

Low regression risk:

  • The implementation is well-tested
  • The custom cell follows the same patterns as the existing JsonCell
  • The glide-data-grid integration is straightforward

Recommendations

  1. Consider adding a brief comment in the PR description or code about the plan to switch back to glide-data-grid's implementation once their release process is fixed. This helps future maintainers understand the context.

  2. Non-blocking: In MultiSelectCell.test.tsx, the unused React import on line 17 could be removed:

    - import React from "react"
  3. Non-blocking: Consider using expect(selected_text).toContain("Exploration") pattern in E2E test for consistency with other Playwright assertions, though the current assert with descriptive error message is acceptable.

  4. The existing E2E tests for multiselect (test_multiselect_cell_editing, test_multiselect_cell_editing_with_new_options) should provide good regression coverage alongside the new text selection test.

Verdict

APPROVED: This PR is well-implemented with comprehensive test coverage. The custom MultiSelectCell implementation maintains backwards compatibility while adding the valuable text selection enhancement. The code follows Streamlit's established patterns and best practices. The minor suggestions above are non-blocking and can be addressed in follow-up work if desired.


This is an automated AI review using opus-4.5-thinking. Please verify the feedback and use your judgment.

Copy link
Copy Markdown
Collaborator

@kmcgrady kmcgrady left a comment

Choose a reason for hiding this comment

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

Just a few questions and requests. I guess it's weird because we are expecting this to be removed because this should be included in the Glide Data Grid?

I guess I can say it's not as important to get this perfect, but I am not sure what is specifically Glide vs what is our code?

> div {
border-radius: 0.25rem;
border: 1px solid var(--gdg-border-color);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Any reason we are not doing the object styled that we usually have recommended? I would recommend breaking this into the styled-components.ts like we've done in the past.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah @lukasmasuch I see that we use emotion's styled vs Glide using linaria? Are there any other differences?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The initial thought here was not to spread out this "temporary" code into potentially shared files. Also, changing this to object notation requires some eslint ignores. But no string opinion either way, I can also move it to styled-components if preferred.

emotion's styled vs Glide using linaria?

Only minor differences afaik, not impacting this component. So migration here to emotion is unproblematic.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah okay. I am at least valid to know that this code is slightly modified from Glide at the very least. That being said, if the plan is to delete the code in favor of a Glide update, I am open to it.

Copy link
Copy Markdown
Collaborator Author

@lukasmasuch lukasmasuch Jan 22, 2026

Choose a reason for hiding this comment

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

Yeah, I wrote down a note and will tweak this if it turns out that this will stay long-term in our codebase

return {
value: option.value,
label: option.label ?? option.value ?? "",
color: option.color ?? undefined,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The ?? undefined is probably not necessary since that is what it could possibly be unless we want to handle the empty string "" in which case it could be option.color || undefined. Happy for more explicit, but wanted to check.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

removed the redundant ?? undefined`

options: readonly SelectOption[],
allowDuplicates?: boolean
): { value: string; label?: string; color?: string }[] => {
if (values === undefined || values === null) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

isNullOrUndefined(values)? I am not sure if we have a standard to use the convenience function or not, but it's used above so I figured we should stay consistent?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Updated 👍

const { ctx, theme, rect, highlighted } = args
const { values, options: optionsIn } = cell.data

if (values === undefined || values === null) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same here for isNullOrUndefined

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Updated 👍

@lukasmasuch
Copy link
Copy Markdown
Collaborator Author

but I am not sure what is specifically Glide vs what is our code?

The MultiSelectCell.tsx‎ is the fixed version from the glide-data-grid repo and should eventually be removed once the release is working again.

@lukasmasuch lukasmasuch merged commit 1bf047d into develop Jan 22, 2026
43 checks passed
@lukasmasuch lukasmasuch deleted the lukasmasuch/custom-multiselect-cell branch January 22, 2026 18:34
github-actions bot pushed a commit that referenced this pull request Jan 22, 2026
…umn` (#13663)

## Describe your changes

Allows selecting text of pills in the MultiselectColumn and ListColumn.
To do this, I have moved a fixed version of the multi-select cell
implementation from glide-data-grid to Streamlit since the release
process is currently broken on glide-data-grid. This will be a bit more
of a temporary solution, and it's planned to switch back soon.

- Closes #13646

## Testing Plan

- Added unit tests. 

---

**Contribution License Agreement**

By submitting this pull request you agree that all contributions to this
project are made under the Apache 2.0 license.

---------

Co-authored-by: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multiselect with format func getting cleared right after selecting an element since 1.53.0

4 participants