Skip to content
Open
30 changes: 16 additions & 14 deletions src/PickerInput/Popup/PresetPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,23 @@ export default function PresetPanel<DateType extends object = any>(

return (
<div className={`${prefixCls}-presets`}>
<ul>
<ul role="list">
{presets.map(({ label, value }, index) => (
<li
key={index}
onClick={() => {
onClick(executeValue(value));
}}
onMouseEnter={() => {
onHover(executeValue(value));
}}
onMouseLeave={() => {
onHover(null);
}}
>
{label}
<li key={index}>
<button
type="button"
onClick={() => {
onClick(executeValue(value));
}}
onMouseEnter={() => {
onHover(executeValue(value));
}}
onMouseLeave={() => {
onHover(null);
}}
>
{label}
</button>
</li>
))}
</ul>
Expand Down
4 changes: 3 additions & 1 deletion src/PickerInput/Popup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default function Popup<DateType extends object = any>(props: PopupProps<D
styles,
} = props;

const { prefixCls } = React.useContext(PickerContext);
const { prefixCls, popupId } = React.useContext(PickerContext);
const panelPrefixCls = `${prefixCls}-panel`;

const rtl = direction === 'rtl';
Expand Down Expand Up @@ -220,8 +220,10 @@ export default function Popup<DateType extends object = any>(props: PopupProps<D
let renderNode = (
<div
ref={containerRef}
id={popupId}
onMouseDown={onPanelMouseDown}
tabIndex={-1}
role="dialog"
className={clsx(
containerPrefixCls,
// Used for Today Button style, safe to remove if no need
Expand Down
5 changes: 5 additions & 0 deletions src/PickerInput/RangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
pickAttrs,
useControlledState,
useEvent,
useId,
useLayoutEffect,
warning,
} from '@rc-component/util';
Expand Down Expand Up @@ -713,6 +714,8 @@ function RangePicker<DateType extends object = any>(
};

// ======================= Context ========================
const popupId = `${useId()}-panel`;

const context = React.useMemo(
() => ({
prefixCls,
Expand All @@ -722,6 +725,7 @@ function RangePicker<DateType extends object = any>(
input: components.input,
classNames: mergedClassNames,
styles: mergedStyles,
popupId,
}),
[
prefixCls,
Expand All @@ -731,6 +735,7 @@ function RangePicker<DateType extends object = any>(
components.input,
mergedClassNames,
mergedStyles,
popupId,
],
);

Expand Down
8 changes: 8 additions & 0 deletions src/PickerInput/Selector/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElem
format?: string;
validateFormat: (value: string) => boolean;
active?: boolean;
open?: boolean;
/** Used for single picker only */
showActiveCls?: boolean;
suffix?: React.ReactNode;
Expand All @@ -52,6 +53,7 @@ const Input = React.forwardRef<InputRef, InputProps>((props, ref) => {
const {
className,
active,
open,
showActiveCls = true,
suffix,
format,
Expand All @@ -75,6 +77,7 @@ const Input = React.forwardRef<InputRef, InputProps>((props, ref) => {
input: Component = 'input',
classNames,
styles,
popupId,
} = React.useContext(PickerContext);
const inputPrefixCls = `${prefixCls}-input`;

Expand Down Expand Up @@ -404,7 +407,12 @@ const Input = React.forwardRef<InputRef, InputProps>((props, ref) => {
>
<Component
ref={inputRef}
role="combobox"
aria-invalid={invalid}
aria-haspopup="dialog"
aria-expanded={!!open}
// Only reference the popup once it's rendered to avoid a dangling IDREF
aria-controls={open ? popupId : undefined}
autoComplete="off"
Comment on lines 408 to 416

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

According to ARIA specifications, the aria-expanded attribute is not supported on a standard textbox input element unless it has role="combobox". Adding role="combobox" ensures that screen readers correctly interpret and announce the expanded/collapsed state of the date picker popup.

Suggested change
<Component
ref={inputRef}
aria-invalid={invalid}
aria-haspopup="dialog"
aria-expanded={open}
autoComplete="off"
<Component
ref={inputRef}
role="combobox"
aria-invalid={invalid}
aria-haspopup="dialog"
aria-expanded={open}
autoComplete="off"

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.

Gemini's suggestion can take a look.

{...restProps}
onKeyDown={onSharedKeyDown}
Expand Down
2 changes: 2 additions & 0 deletions src/PickerInput/Selector/hooks/useInputProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ export default function useInputProps<DateType extends object = any>(

active: activeIndex === index,

open,

helped: allHelp || (activeHelp && activeIndex === index),

disabled: getProp(disabled),
Expand Down
13 changes: 12 additions & 1 deletion src/PickerInput/SinglePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { omit, pickAttrs, useControlledState, useEvent, useLayoutEffect } from '@rc-component/util';
import {
omit,
pickAttrs,
useControlledState,
useEvent,
useId,
useLayoutEffect,
} from '@rc-component/util';
import { clsx } from 'clsx';
import * as React from 'react';
import useToggleDates from '../hooks/useToggleDates';
Expand Down Expand Up @@ -642,6 +649,8 @@ function Picker<DateType extends object = any>(
};

// ======================= Context ========================
const popupId = `${useId()}-panel`;

const context = React.useMemo(
() => ({
prefixCls,
Expand All @@ -651,6 +660,7 @@ function Picker<DateType extends object = any>(
input: components.input,
classNames: mergedClassNames,
styles: mergedStyles,
popupId,
}),
[
prefixCls,
Expand All @@ -660,6 +670,7 @@ function Picker<DateType extends object = any>(
components.input,
mergedClassNames,
mergedStyles,
popupId,
],
);

Expand Down
2 changes: 2 additions & 0 deletions src/PickerInput/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export interface PickerContextProps<DateType = any> {
input?: Components['input'];
classNames: FilledClassNames;
styles: FilledStyles;
/** Id of the popup panel. Used by the input `aria-controls` to reference the popup */
popupId: string;
}

const PickerContext = React.createContext<PickerContextProps>(null!);
Expand Down
66 changes: 43 additions & 23 deletions src/PickerPanel/DatePanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,40 @@ export default function DatePanel<DateType extends object = any>(props: DatePane
const weekFirstDay = generateConfig.locale.getWeekFirstDay(locale.locale);
const monthStartDate = generateConfig.setDate(pickerValue, 1);
const baseDate = getWeekStartDate(locale.locale, generateConfig, monthStartDate);

const yearLabel = formatValue(pickerValue, {
locale,
format: locale.yearFormat,
generateConfig,
});

const monthsLocale: string[] =
locale.shortMonths ||
(generateConfig.locale.getShortMonths
? generateConfig.locale.getShortMonths(locale.locale)
: []);
const month = generateConfig.getMonth(pickerValue);
const monthLabel = locale.monthFormat
? formatValue(pickerValue, {
locale,
format: locale.monthFormat,
generateConfig,
})
: monthsLocale[month];

// =========================== PrefixColumn ===========================
const showPrefixColumn = showWeek === undefined ? isWeek : showWeek;
const prefixColumn = showPrefixColumn
? (date: DateType) => {
// >>> Additional check for disabled
const disabled = disabledDate?.(date, { type: 'week' });
const label = generateConfig.locale.getWeek(locale.locale, date);

return (
<td
key="week"
role="rowheader"
aria-label={`${locale.week} ${label}`}
className={clsx(cellPrefixCls, `${cellPrefixCls}-week`, {
[`${cellPrefixCls}-disabled`]: disabled,
})}
Expand All @@ -80,9 +102,7 @@ export default function DatePanel<DateType extends object = any>(props: DatePane
}
}}
>
<div className={`${cellPrefixCls}-inner`}>
{generateConfig.locale.getWeek(locale.locale, date)}
</div>
<div className={`${cellPrefixCls}-inner`}>{label}</div>
</td>
);
}
Expand Down Expand Up @@ -132,13 +152,14 @@ export default function DatePanel<DateType extends object = any>(props: DatePane
return classObj;
};

// ========================= Header =========================
const monthsLocale: string[] =
locale.shortMonths ||
(generateConfig.locale.getShortMonths
? generateConfig.locale.getShortMonths(locale.locale)
: []);
const getCellAttributes = (date: DateType): React.TdHTMLAttributes<HTMLTableCellElement> => {
if (isSameDate(generateConfig, date, now)) {
return { 'aria-current': 'date' };
}
return {};
};

// ========================= Header =========================
const yearNode: React.ReactNode = (
<button
type="button"
Expand All @@ -147,14 +168,9 @@ export default function DatePanel<DateType extends object = any>(props: DatePane
onClick={() => {
onModeChange('year', pickerValue);
}}
tabIndex={-1}
className={`${prefixCls}-year-btn`}
>
{formatValue(pickerValue, {
locale,
format: locale.yearFormat,
generateConfig,
})}
{yearLabel}
</button>
);
const monthNode: React.ReactNode = (
Expand All @@ -165,20 +181,16 @@ export default function DatePanel<DateType extends object = any>(props: DatePane
onClick={() => {
onModeChange('month', pickerValue);
}}
tabIndex={-1}
className={`${prefixCls}-month-btn`}
>
{locale.monthFormat
? formatValue(pickerValue, {
locale,
format: locale.monthFormat,
generateConfig,
})
: monthsLocale[month]}
{monthLabel}
</button>
);

const monthYearNodes = locale.monthBeforeYear ? [monthNode, yearNode] : [yearNode, monthNode];
const tableLabel = locale.monthBeforeYear
? `${monthLabel} ${yearLabel}`
: `${yearLabel} ${monthLabel}`;

// ========================= Render =========================
return (
Expand All @@ -196,6 +208,12 @@ export default function DatePanel<DateType extends object = any>(props: DatePane
clone = generateConfig.addMonth(clone, 1);
return generateConfig.addDate(clone, -1);
}}
labels={{
superPrev: locale.previousYear,
prev: locale.previousMonth,
next: locale.nextMonth,
superNext: locale.nextYear,
}}
>
{monthYearNodes}
</PanelHeader>
Expand All @@ -213,8 +231,10 @@ export default function DatePanel<DateType extends object = any>(props: DatePane
getCellDate={getCellDate}
getCellText={getCellText}
getCellClassName={getCellClassName}
getCellAttributes={getCellAttributes}
prefixColumn={prefixColumn}
cellSelection={!isWeek}
tableLabel={tableLabel}
/>
</div>
</PanelContext.Provider>
Expand Down
14 changes: 12 additions & 2 deletions src/PickerPanel/DecadePanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ export default function DecadePanel<DateType extends object = any>(
};
};

const getCellAttributes = (date: DateType): React.TdHTMLAttributes<HTMLTableCellElement> => {
if (isSameDecade(generateConfig, date, generateConfig.getNow())) {
return { 'aria-current': 'date' };
}
return {};
};

// ======================== Disabled ========================
const mergedDisabledDate: DisabledDate<DateType> = disabledDate
? (currentDate, disabledInfo) => {
Expand All @@ -81,7 +88,7 @@ export default function DecadePanel<DateType extends object = any>(
: null;

// ========================= Header =========================
const yearNode = `${formatValue(startYearDate, {
const yearLabel = `${formatValue(startYearDate, {
locale,
format: locale.yearFormat,
generateConfig,
Expand All @@ -102,8 +109,9 @@ export default function DecadePanel<DateType extends object = any>(
// Limitation
getStart={getStartYear}
getEnd={getEndYear}
labels={{ superPrev: locale.previousCentury, superNext: locale.nextCentury }}
>
{yearNode}
{yearLabel}
</PanelHeader>

{/* Body */}
Expand All @@ -113,10 +121,12 @@ export default function DecadePanel<DateType extends object = any>(
colNum={3}
rowNum={4}
baseDate={baseDate}
tableLabel={yearLabel}
// Body
getCellDate={getCellDate}
getCellText={getCellText}
getCellClassName={getCellClassName}
getCellAttributes={getCellAttributes}
/>
</div>
</PanelContext.Provider>
Expand Down
Loading
Loading