Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[YearPicker] Scroll to the current year even with autoFocus=false #6089

Merged
merged 13 commits into from
Sep 14, 2022
30 changes: 30 additions & 0 deletions packages/x-date-pickers/src/CalendarPicker/CalendarPicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
createPickerRenderer,
} from '../../../../test/utils/pickers-utils';

const isJSDOM = /jsdom/.test(window.navigator.userAgent);

describe('<CalendarPicker />', () => {
const { render, clock } = createPickerRenderer({ clock: 'fake' });

Expand Down Expand Up @@ -410,5 +412,33 @@ describe('<CalendarPicker />', () => {
expect(onChange.callCount).to.equal(0);
expect(screen.getByMuiTest('calendar-month-and-year-text')).to.have.text('January 2022');
});

it('should scroll to show the selected year', function test() {
if (isJSDOM) {
this.skip(); // Needs layout
}
render(
<CalendarPicker
date={adapterToUse.date(new Date(2019, 3, 29))}
onChange={() => {}}
views={['year']}
openTo="year"
/>,
);

// const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!;
//
const rootElement = document.querySelector('.MuiCalendarPicker-root')!;
const selectedButton = document.querySelector('.Mui-selected')!;

expect(rootElement).not.to.equal(null);
expect(selectedButton).not.to.equal(null);

const parentBoundingBox = rootElement.getBoundingClientRect();
const buttonBoundingBox = selectedButton.getBoundingClientRect();

expect(parentBoundingBox.top).not.to.greaterThan(buttonBoundingBox.top);
expect(parentBoundingBox.bottom).not.to.lessThan(buttonBoundingBox.bottom);
});
});
});
151 changes: 95 additions & 56 deletions packages/x-date-pickers/src/CalendarPicker/CalendarPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,15 @@ const CalendarPickerViewTransitionContainer = styled(PickersFadeTransitionGroup,
name: 'MuiCalendarPicker',
slot: 'ViewTransitionContainer',
overridesResolver: (props, styles) => styles.viewTransitionContainer,
})<{ ownerState: CalendarPickerProps<any> }>({
})<{ ownerState: CalendarPickerProps<any> }>({});

const CalendarPickerViewContainer = styled('div', {
name: 'MuiCalendarPicker',
slot: 'ViewContainer',
overridesResolver: (props, styles) => styles.viewTransitionContainer,
})({
overflowY: 'auto',
position: 'relative',
});

type CalendarPickerComponent = (<TDate>(
Expand Down Expand Up @@ -447,6 +454,36 @@ export const CalendarPicker = React.forwardRef(function CalendarPicker<TDate>(
},
);

const scrollerRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (autoFocus || openView !== 'year' || scrollerRef.current === null) {
return;
}

const selectedButton = scrollerRef.current.getElementsByClassName(
'Mui-selected',
)[0] as HTMLElement;
if (!selectedButton) {
return;
}

// Taken from useScroll in x-data-grid, but vertically centered
const offsetHeight = selectedButton.offsetHeight;
const offsetTop = selectedButton.offsetTop;

const clientHeight = scrollerRef.current.clientHeight;
const scrollTop = scrollerRef.current.scrollTop;

const elementBottom = offsetTop + offsetHeight;

if (offsetHeight > clientHeight || offsetTop < scrollTop) {
// Button already visible
return;
}
// return
scrollerRef.current.scrollTop = elementBottom - clientHeight / 2 - offsetHeight / 2;
}, [autoFocus, openView]);

return (
<CalendarPickerRoot ref={ref} className={clsx(classes.root, className)} ownerState={ownerState}>
<PickersCalendarHeader
Expand All @@ -464,61 +501,63 @@ export const CalendarPicker = React.forwardRef(function CalendarPicker<TDate>(
reduceAnimations={reduceAnimations}
labelId={gridLabelId}
/>
<CalendarPickerViewTransitionContainer
reduceAnimations={reduceAnimations}
className={classes.viewTransitionContainer}
transKey={openView}
ownerState={ownerState}
>
<div>
{openView === 'year' && (
<YearPicker
{...other}
{...baseDateValidationProps}
{...commonViewProps}
autoFocus={autoFocus}
date={date}
onChange={handleDateYearChange}
shouldDisableYear={shouldDisableYear}
hasFocus={hasFocus}
onFocusedViewChange={handleFocusedViewChange('year')}
/>
)}

{openView === 'month' && (
<MonthPicker
{...baseDateValidationProps}
{...commonViewProps}
autoFocus={autoFocus}
hasFocus={hasFocus}
className={className}
date={date}
onChange={handleDateMonthChange}
shouldDisableMonth={shouldDisableMonth}
onFocusedViewChange={handleFocusedViewChange('month')}
/>
)}

{openView === 'day' && (
<DayPicker
{...other}
{...calendarState}
{...baseDateValidationProps}
{...commonViewProps}
autoFocus={autoFocus}
onMonthSwitchingAnimationEnd={onMonthSwitchingAnimationEnd}
onFocusedDayChange={changeFocusedDay}
reduceAnimations={reduceAnimations}
selectedDays={[date]}
onSelectedDaysChange={onSelectedDayChange}
shouldDisableDate={shouldDisableDate}
hasFocus={hasFocus}
onFocusedViewChange={handleFocusedViewChange('day')}
gridLabelId={gridLabelId}
/>
)}
</div>
</CalendarPickerViewTransitionContainer>
<CalendarPickerViewContainer ref={scrollerRef}>
<CalendarPickerViewTransitionContainer
reduceAnimations={reduceAnimations}
className={classes.viewTransitionContainer}
transKey={openView}
ownerState={ownerState}
>
<div>
{openView === 'year' && (
<YearPicker
{...other}
{...baseDateValidationProps}
{...commonViewProps}
autoFocus={autoFocus}
date={date}
onChange={handleDateYearChange}
shouldDisableYear={shouldDisableYear}
hasFocus={hasFocus}
onFocusedViewChange={handleFocusedViewChange('year')}
/>
)}

{openView === 'month' && (
<MonthPicker
{...baseDateValidationProps}
{...commonViewProps}
autoFocus={autoFocus}
hasFocus={hasFocus}
className={className}
date={date}
onChange={handleDateMonthChange}
shouldDisableMonth={shouldDisableMonth}
onFocusedViewChange={handleFocusedViewChange('month')}
/>
)}

{openView === 'day' && (
<DayPicker
{...other}
{...calendarState}
{...baseDateValidationProps}
{...commonViewProps}
autoFocus={autoFocus}
onMonthSwitchingAnimationEnd={onMonthSwitchingAnimationEnd}
onFocusedDayChange={changeFocusedDay}
reduceAnimations={reduceAnimations}
selectedDays={[date]}
onSelectedDaysChange={onSelectedDayChange}
shouldDisableDate={shouldDisableDate}
hasFocus={hasFocus}
onFocusedViewChange={handleFocusedViewChange('day')}
gridLabelId={gridLabelId}
/>
)}
</div>
</CalendarPickerViewTransitionContainer>
</CalendarPickerViewContainer>
</CalendarPickerRoot>
);
}) as CalendarPickerComponent;
Expand Down
1 change: 1 addition & 0 deletions packages/x-date-pickers/src/YearPicker/PickersYear.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export const PickersYear = React.forwardRef<HTMLButtonElement, YearProps>(functi
const classes = useUtilityClasses(ownerState);

// TODO: Can we just forward this to the button?
// > No because it's a native button, not a mui Button
alexfauquette marked this conversation as resolved.
Show resolved Hide resolved
React.useEffect(() => {
if (autoFocus) {
// `ref.current` being `null` would be a bug in MUIu
Expand Down
2 changes: 0 additions & 2 deletions packages/x-date-pickers/src/YearPicker/YearPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ export const YearPicker = React.forwardRef(function YearPicker<TDate>(
}, [now, date, utils, disableHighlightToday]);

const wrapperVariant = React.useContext(WrapperVariantContext);
const selectedYearRef = React.useRef<HTMLButtonElement>(null);
const [focusedYear, setFocusedYear] = React.useState<number>(
() => currentYear || utils.getYear(now),
);
Expand Down Expand Up @@ -265,7 +264,6 @@ export const YearPicker = React.forwardRef(function YearPicker<TDate>(
onClick={handleYearSelection}
onKeyDown={handleKeyDown}
autoFocus={internalHasFocus && yearNumber === focusedYear}
ref={selected ? selectedYearRef : undefined}
disabled={disabled || isYearDisabled(year)}
tabIndex={yearNumber === focusedYear ? 0 : -1}
onFocus={handleFocus}
Expand Down