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 @@ -412,5 +414,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
value={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);
});
});
});
9 changes: 6 additions & 3 deletions packages/x-date-pickers/src/CalendarPicker/CalendarPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,7 @@ const CalendarPickerViewTransitionContainer = styled(PickersFadeTransitionGroup,
name: 'MuiCalendarPicker',
slot: 'ViewTransitionContainer',
overridesResolver: (props, styles) => styles.viewTransitionContainer,
})<{ ownerState: CalendarPickerProps<any> }>({
overflowY: 'auto',
});
})<{ ownerState: CalendarPickerProps<any> }>({});

type CalendarPickerComponent = (<TDate>(
props: CalendarPickerProps<TDate> & React.RefAttributes<HTMLDivElement>,
Expand Down Expand Up @@ -462,6 +460,11 @@ export const CalendarPicker = React.forwardRef(function CalendarPicker<TDate>(
},
);

React.useEffect(() => {
// Set focus to the button when switching from a view to another
handleFocusedViewChange(openView)(true);
}, [openView, handleFocusedViewChange]);

return (
<CalendarPickerRoot
ref={ref}
Expand Down
2 changes: 1 addition & 1 deletion packages/x-date-pickers/src/YearPicker/PickersYear.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ const PickersYearRaw = (props: PickersYearProps) => {

const classes = useUtilityClasses(ownerState);

// TODO: Can we just forward this to the button?
// We can't forward the `autoFocus` to the button because it is a native button, not a MUI Button
React.useEffect(() => {
if (autoFocus) {
// `ref.current` being `null` would be a bug in MUI.
Expand Down
36 changes: 33 additions & 3 deletions packages/x-date-pickers/src/YearPicker/YearPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import clsx from 'clsx';
import { SxProps, useTheme } from '@mui/system';
import { styled, useThemeProps, Theme } from '@mui/material/styles';
import { unstable_composeClasses as composeClasses } from '@mui/material';
import '@mui/material/utils';
import { useForkRef } from '@mui/material/utils';
import {
unstable_useControlled as useControlled,
unstable_useEventCallback as useEventCallback,
Expand Down Expand Up @@ -60,7 +60,8 @@ const YearPickerRoot = styled('div', {
flexWrap: 'wrap',
overflowY: 'auto',
height: '100%',
margin: '0 4px',
padding: '0 4px',
maxHeight: '304px',
});

export interface YearPickerProps<TDate>
Expand Down Expand Up @@ -149,6 +150,7 @@ export const YearPicker = React.forwardRef(function YearPicker<TDate>(

return utils.getYear(now);
}, [now, value, utils, disableHighlightToday]);

const [focusedYear, setFocusedYear] = React.useState(() => selectedYear || todayYear);

const [internalHasFocus, setInternalHasFocus] = useControlled<boolean>({
Expand Down Expand Up @@ -243,9 +245,37 @@ export const YearPicker = React.forwardRef(function YearPicker<TDate>(
}
});

const scrollerRef = React.useRef<HTMLDivElement>(null);
const handleRef = useForkRef(ref, scrollerRef);
React.useEffect(() => {
if (autoFocus || scrollerRef.current === null) {
return;
}
const tabbableButton = scrollerRef.current.querySelector<HTMLElement>('[tabindex="0"]');
if (!tabbableButton) {
return;
}

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

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

const elementBottom = offsetTop + offsetHeight;

if (offsetHeight > clientHeight || offsetTop < scrollTop) {
// Button already visible
return;
}

scrollerRef.current.scrollTop = elementBottom - clientHeight / 2 - offsetHeight / 2;
}, [autoFocus]);

return (
<YearPickerRoot
ref={ref}
ref={handleRef}
className={clsx(classes.root, className)}
ownerState={ownerState}
{...other}
Expand Down