Skip to content

Fix windowed Select/SelectEditor menu constrained to control width#4452

Merged
lbwexler merged 6 commits into
developfrom
fix-windowed-select-menu-width
Jun 25, 2026
Merged

Fix windowed Select/SelectEditor menu constrained to control width#4452
lbwexler merged 6 commits into
developfrom
fix-windowed-select-menu-width

Conversation

@lbwexler

Copy link
Copy Markdown
Member

Summary

Windowed (enableWindowed) Select and SelectEditor dropdown menus were constrained to the width of the control/cell, truncating option text, whereas standard (non-windowed) menus auto-size to fit their content.

Root cause: virtualized menus can't auto-size via CSS. react-window absolutely-positions its rows at width: 100%, so option text never widens the menu and it collapses to the control width (the width: auto / minWidth: 100% menu style resolves to the control width). This is inherent to virtualization, not specific to react-windowed-select - the v5 upgrade in #4382 did not resolve it.

Fix: restore content-based auto-sizing by measuring the widest option label up front (canvas measureText, the same technique used by the grid's column autosize) and applying an explicit pixel width to the menu in windowed mode. No custom MenuList needed - react-windowed-select keeps handling the virtualization.

Changes

  • Select.ts - compute windowedMenuWidth from measured option labels (when options change) and apply it as the menu width in windowed mode. An explicit menuWidth prop and app-provided rsOptions.styles still take precedence; minWidth: '100%' floors the menu at the control width. Falls back to control width when labels aren't measurable strings (e.g. fully custom optionRenderer content).
  • SelectEditor.ts - its width: 'auto' menu workaround now applies only in non-windowed mode; otherwise it would override the measured width and re-collapse the menu.

Testing

Verified in Toolbox (running against this branch via --env inlineHoist): on the windowed Select demo with a narrow control, the menu now grows to fit long option labels while still virtualizing (scrollbar present). tsc --noEmit and eslint clean.

Fixes #4325
Related: #4057

Virtualized (windowed) menus can't auto-size to their content the way standard
react-select menus do: react-window absolutely-positions its rows at width:100%,
so option text never widens the menu and it collapses to the control/cell width.

Restore content-based auto-sizing by measuring the widest option label up front
(canvas measureText, reusing the same technique as the grid's autosize) and
applying an explicit pixel width to the menu in windowed mode. An explicit
menuWidth and app-provided rsOptions still take precedence; minWidth:'100%'
floors the menu at the control width.

SelectEditor's width:'auto' menu workaround now applies only in non-windowed
mode - it would otherwise override the measured width and re-collapse the menu.

Fixes #4325, #4057
@haynesjm42 haynesjm42 self-requested a review June 24, 2026 19:25

@haynesjm42 haynesjm42 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.

I think this might need to account for a custom optionRenderer - at least to not apply the calculated width in that case. Looks like the calculation is specifically accounting for the default option renderer.

Is there an approach more like the column autosizing where we find the longest label and then render it (with formatOptionLabel) and measure that rendered content width? Or do we just want to leave it up to the developer if they are providing a custom optionRenderer to also handle the width?

Address review feedback re: custom optionRenderer support.

- Size windowed menus by rendering each option's actual menu markup (via the configured
  optionRenderer, if any), canvas-estimating width, then measuring the widest sample's displayed
  width in a hidden probe - mirroring the grid's ColumnWidthCalculator. Custom optionRenderers now
  size correctly instead of being measured by their label string.
- Extract the logic to impl/CalcWindowedMenuWidth.ts to keep Select.ts focused.
- SelectEditor: pass an empty rsOptions object (rather than undefined) in windowed mode.
@lbwexler

Copy link
Copy Markdown
Member Author

Good catch, thanks @haynesjm42. Reworked the sizing to use the same render-and-measure approach as the grid's column autosize (ColumnWidthCalculator), so a custom optionRenderer is handled correctly rather than measuring the label string:

  • Each option is rendered to its actual menu markup (via the configured optionRenderer when provided, renderToStaticMarkup) and canvas-estimated, the widest sample is then measured for real clientWidth in a hidden probe. So icons / multi-element option content size the menu correctly.
  • Falls back to the control width if an option can't be statically rendered (e.g. a renderer relying on context/hooks).

On your "render the longest and measure it" point - that's exactly what it does now. I considered ranking candidates cheaply by label text to bound the work, but per your instinct it's no cheaper-per-item than the grid already accepts (the grid renders every value too), and a Select only sizes one menu once per options-change vs. the grid autosizing many columns over thousands of records - so it now renders all options to rank, like the grid.

Pulled the logic out into impl/CalcWindowedMenuWidth.ts to keep Select.ts focused. Verified in Toolbox (windowed Select with long labels + a custom optionRenderer, narrow control): menu grows to fit content while still virtualizing.

@lbwexler lbwexler merged commit b227912 into develop Jun 25, 2026
3 checks passed
@lbwexler lbwexler deleted the fix-windowed-select-menu-width branch June 25, 2026 11:41
lbwexler added a commit to xh/toolbox that referenced this pull request Jun 25, 2026
Demo changes that exercise the hoist-react fix for windowed dropdown
menus being constrained to the control/cell width.

Paired with xh/hoist-react#4452 (fixes xh/hoist-react#4325).

## Changes

- **Forms > Select** - "Large list (windowed)": varied `LARGE_OPTIONS`
label lengths so the virtualized menu visibly auto-sizes to its widest
content instead of looking identical to a content-width menu.
- **Grids > Inline Editing** - the `category` editor now uses
`enableWindowed` with a large option list in a narrow (~80px) cell. This
is the exact #4325 scenario. Real category values (`US`, `EU`, `BRIC`,
...) are preserved at the front of the list so the seed data and the
`category === 'US'` amount-validation rule still apply.

Before the hoist-react fix these menus were clamped to the control/cell
width and truncated long options; now they grow to fit their content
while still virtualizing.

## Testing

Verified both demos in the browser against the hoist-react branch
(`--env inlineHoist`): the windowed menus auto-size to fit long labels,
scroll (virtualized), and the inline-editing validation still behaves.
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.

Windowed selectEditor menu constrained to cell width

2 participants