From e06473a6b81daae04c6945f49fd6eadc5d647836 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Thu, 16 Oct 2025 19:08:07 +0200 Subject: [PATCH 1/7] fix: prevent list item jump on hover in virtual scrolling fix: inconsistent dropdown styling with virtual scroll refactor: deduplicate code --- .../components/selectable-item/styles.scss | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/internal/components/selectable-item/styles.scss b/src/internal/components/selectable-item/styles.scss index a621b07121..b71c4e06a1 100644 --- a/src/internal/components/selectable-item/styles.scss +++ b/src/internal/components/selectable-item/styles.scss @@ -206,6 +206,60 @@ &:first-of-type:not(.selected, .highlighted) { border-block-start-color: awsui.$color-border-dropdown-item-top; } + + // When using virtual scrolling, use box-shadows to mimic changing border + // width and set the actual border to a fixed width to prevent jumping + // behaviour when hovering selectable items + + $virtual-border-offset: calc(#{awsui.$border-item-width} - #{awsui.$border-divider-list-width}); + $virtual-border-offset-double: calc(2 * #{awsui.$border-item-width} - #{awsui.$border-divider-list-width}); + + &.highlighted:not(.selected), + &.selected { + border-width: awsui.$border-divider-list-width; + padding-block: calc(#{styles.$option-padding-vertical} + #{$virtual-border-offset}); + padding-inline: calc(#{styles.$control-padding-horizontal} + #{$virtual-border-offset}); + + &.pad-bottom { + padding-block-end: calc(#{styles.$option-padding-vertical} + #{awsui.$space-xxxs} + #{$virtual-border-offset}); + } + + &.child { + padding-inline-start: calc(#{awsui.$space-xxl} + #{$virtual-border-offset}); + } + + &.parent.interactiveGroups { + padding-block: calc(#{styles.$group-option-padding-vertical} + #{$virtual-border-offset}); + } + } + + &.highlighted:not(.selected) { + box-shadow: inset 0 0 0 $virtual-border-offset awsui.$color-border-dropdown-item-hover; + + &.is-keyboard { + box-shadow: inset 0 0 0 $virtual-border-offset awsui.$color-border-dropdown-item-focused; + } + } + + &.selected { + box-shadow: inset 0 0 0 $virtual-border-offset awsui.$color-border-dropdown-item-selected; + + &.highlighted { + box-shadow: + inset 0 0 0 $virtual-border-offset awsui.$color-border-dropdown-item-selected, + inset 0 0 0 $virtual-border-offset-double awsui.$color-border-dropdown-item-hover; + + &.is-keyboard { + box-shadow: + inset 0 0 0 $virtual-border-offset awsui.$color-border-dropdown-item-selected, + inset 0 0 0 $virtual-border-offset-double awsui.$color-border-dropdown-item-focused; + } + } + } + + &.parent:not(.interactiveGroups) { + border-block-start-color: awsui.$color-border-dropdown-group; + } } } From ca2ed7a18c9672745ae646c9d7715e3873db9321 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Mon, 20 Oct 2025 17:50:57 +0200 Subject: [PATCH 2/7] fix: double borders in virtual scroll mode - Each item is shifted up by its index in pixels - The layout strut height is reduced by the number of items to account for the cumulative overlap --- src/select/parts/virtual-list.tsx | 6 +++++- src/select/utils/render-options.tsx | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/select/parts/virtual-list.tsx b/src/select/parts/virtual-list.tsx index 5db64cda75..c6b0e8be62 100644 --- a/src/select/parts/virtual-list.tsx +++ b/src/select/parts/virtual-list.tsx @@ -100,6 +100,10 @@ const VirtualListOpen = forwardRef( withScrollbar, }); + // Adjust totalSize to account for 1px overlap per item (matching the position adjustment in renderOptions) + const overlapAdjustment = filteredOptions.length; + const adjustedTotalSize = totalSize - overlapAdjustment; + return ( {finalOptions} @@ -107,7 +111,7 @@ const VirtualListOpen = forwardRef( aria-hidden="true" key="total-size" className={styles['layout-strut']} - style={{ height: totalSize - stickySize }} + style={{ height: adjustedTotalSize - stickySize }} /> {listBottom ? (
diff --git a/src/select/utils/render-options.tsx b/src/select/utils/render-options.tsx index 614f0d2dd6..580c1a06c9 100644 --- a/src/select/utils/render-options.tsx +++ b/src/select/utils/render-options.tsx @@ -65,11 +65,15 @@ export const renderOptions = ({ const ListItem = useInteractiveGroups ? MultiselectItem : Item; const isSticky = firstOptionSticky && globalIndex === 0; + // Adjust virtual position to create 1px overlap between items (matching non-virtual behavior) + // Subtract globalIndex to shift each item up by 1px per item + const adjustedVirtualPosition = virtualItem ? virtualItem.start - globalIndex : undefined; + return ( Date: Fri, 21 Nov 2025 22:48:02 +0100 Subject: [PATCH 3/7] fix: prevent whitespace in virtual scrolling with large lists Use virtual window index instead of global index when calculating position offset. This prevents excessive whitespace when scrolling through large lists with virtual scrolling enabled. --- src/select/utils/render-options.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/select/utils/render-options.tsx b/src/select/utils/render-options.tsx index 580c1a06c9..6e18de4282 100644 --- a/src/select/utils/render-options.tsx +++ b/src/select/utils/render-options.tsx @@ -65,9 +65,8 @@ export const renderOptions = ({ const ListItem = useInteractiveGroups ? MultiselectItem : Item; const isSticky = firstOptionSticky && globalIndex === 0; - // Adjust virtual position to create 1px overlap between items (matching non-virtual behavior) - // Subtract globalIndex to shift each item up by 1px per item - const adjustedVirtualPosition = virtualItem ? virtualItem.start - globalIndex : undefined; + // Adjust virtual position to create 1px overlap between consecutive selected items in multiselect + const adjustedVirtualPosition = virtualItem ? virtualItem.start - index : undefined; return ( Date: Sat, 22 Nov 2025 18:54:50 +0100 Subject: [PATCH 4/7] feat: add test page for long lists --- pages/select/virtual-scroll.page.tsx | 47 ++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 pages/select/virtual-scroll.page.tsx diff --git a/pages/select/virtual-scroll.page.tsx b/pages/select/virtual-scroll.page.tsx new file mode 100644 index 0000000000..cca33ed037 --- /dev/null +++ b/pages/select/virtual-scroll.page.tsx @@ -0,0 +1,47 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React, { useState } from 'react'; + +import { SpaceBetween } from '~components'; +import Select, { SelectProps } from '~components/select'; + +import ScreenshotArea from '../utils/screenshot-area'; + +const options: SelectProps.Options = Array.from({ length: 1000 }, (_, i) => ({ + value: `${i}`, + label: `Option ${i + 1}`, +})); + +export default function () { + const [selected, setSelected] = useState(null); + + return ( + <> +

Virtual Scroll

+ + + +