Skip to content

Commit 5718342

Browse files
(Picklist): add a scrollIntoView() behavoir
1 parent c89d412 commit 5718342

File tree

1 file changed

+50
-1
lines changed

1 file changed

+50
-1
lines changed

src/scripts/Picklist.tsx

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,18 +210,65 @@ export const Picklist: (<MultiSelect extends boolean | undefined>(
210210
[getOptionValues]
211211
);
212212

213+
// Scroll focused element into view
214+
const scrollFocusedElementIntoView = useEventCallback(
215+
(nextFocusedValue: PicklistValue | undefined) => {
216+
if (!nextFocusedValue || !dropdownElRef.current) {
217+
return;
218+
}
219+
220+
const dropdownContainer = dropdownElRef.current;
221+
const targetElement = dropdownContainer.querySelector(
222+
`#option-${nextFocusedValue}`
223+
);
224+
225+
if (!(targetElement instanceof HTMLElement)) {
226+
return;
227+
}
228+
229+
// Calculate element position within container
230+
const elementTopPosition = targetElement.offsetTop;
231+
const elementBottomPosition =
232+
elementTopPosition + targetElement.offsetHeight;
233+
234+
// Calculate currently visible area
235+
const currentScrollPosition = dropdownContainer.scrollTop;
236+
const visibleAreaHeight = dropdownContainer.clientHeight;
237+
const visibleAreaTop = currentScrollPosition;
238+
const visibleAreaBottom = currentScrollPosition + visibleAreaHeight;
239+
240+
// Check if element is outside the visible area
241+
const isAbove = elementTopPosition < visibleAreaTop;
242+
const isBelow = elementBottomPosition > visibleAreaBottom;
243+
244+
// Scroll only if element is not currently visible
245+
if (isAbove || isBelow) {
246+
targetElement.scrollIntoView({
247+
block: 'center',
248+
});
249+
}
250+
}
251+
);
252+
213253
// Set initial focus when dropdown opens
214254
useEffect(() => {
215255
if (opened && !focusedValue) {
216256
// Focus on first selected value or first option
217257
const initialFocus =
218258
values.length > 0 ? values[0] : getOptionValues()[0];
219259
setFocusedValue(initialFocus);
260+
scrollFocusedElementIntoView(initialFocus);
220261
} else if (!opened) {
221262
// Reset focus when dropdown closes
222263
setFocusedValue(undefined);
223264
}
224-
}, [opened, values, getOptionValues, focusedValue]);
265+
}, [
266+
opened,
267+
values,
268+
getOptionValues,
269+
focusedValue,
270+
scrollFocusedElementIntoView,
271+
]);
225272

226273
const elRef = useRef<HTMLDivElement | null>(null);
227274
const elementRef = useMergeRefs([elRef, elementRef_]);
@@ -307,6 +354,7 @@ export const Picklist: (<MultiSelect extends boolean | undefined>(
307354
// Navigate to next option
308355
const nextValue = getNextValue(focusedValue);
309356
setFocusedValue(nextValue);
357+
scrollFocusedElementIntoView(nextValue);
310358
}
311359
} else if (e.keyCode === 38) {
312360
// up
@@ -318,6 +366,7 @@ export const Picklist: (<MultiSelect extends boolean | undefined>(
318366
// Navigate to previous option
319367
const prevValue = getPrevValue(focusedValue);
320368
setFocusedValue(prevValue);
369+
scrollFocusedElementIntoView(prevValue);
321370
}
322371
} else if (e.keyCode === 27) {
323372
// ESC

0 commit comments

Comments
 (0)