Skip to content

Commit a9a451c

Browse files
fix: ensure isReadonly applies to all non-literal date segments (#7969)
* fix: force data-readonly when datefield is readonly * chore: simplify isEditable * adding tests * update based on expectations * fix formatting * Apply suggestion from @snowystinger --------- Co-authored-by: Robert Snow <rsnow@adobe.com> Co-authored-by: Robert Snow <snowystinger@gmail.com>
1 parent a1c99d5 commit a9a451c

File tree

4 files changed

+82
-10
lines changed

4 files changed

+82
-10
lines changed

packages/react-aria-components/src/DateField.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ export const DateField = /*#__PURE__*/ (forwardRef as forwardRefType)(function D
111111
slot={props.slot || undefined}
112112
data-invalid={state.isInvalid || undefined}
113113
data-disabled={state.isDisabled || undefined} />
114-
<HiddenDateInput
114+
<HiddenDateInput
115115
autoComplete={props.autoComplete}
116116
name={props.name}
117117
isDisabled={props.isDisabled}
118-
state={state} />
118+
state={state} />
119119
</Provider>
120120
);
121121
});
@@ -180,7 +180,7 @@ export const TimeField = /*#__PURE__*/ (forwardRef as forwardRefType)(function T
180180
{...renderProps}
181181
ref={ref}
182182
slot={props.slot || undefined}
183-
data-invalid={state.isInvalid || undefined}
183+
data-invalid={state.isInvalid || undefined}
184184
data-disabled={state.isDisabled || undefined} />
185185
</Provider>
186186
);
@@ -269,6 +269,7 @@ const DateInputInner = forwardRef((props: DateInputProps, ref: ForwardedRef<HTML
269269
ref={ref}
270270
slot={props.slot || undefined}
271271
className={className ?? 'react-aria-DateInput'}
272+
isReadOnly={state.isReadOnly}
272273
isInvalid={state.isInvalid}
273274
isDisabled={state.isDisabled}>
274275
{state.segments.map((segment, i) => cloneElement(children(segment), {key: i}))}
@@ -337,12 +338,11 @@ export const DateSegment = /*#__PURE__*/ (forwardRef as forwardRefType)(function
337338
let {segmentProps} = useDateSegment(segment, state, domRef);
338339
let {focusProps, isFocused, isFocusVisible} = useFocusRing();
339340
let {hoverProps, isHovered} = useHover({...otherProps, isDisabled: state.isDisabled || segment.type === 'literal'});
340-
let {isEditable, ...segmentRest} = segment;
341341
let renderProps = useRenderProps({
342342
...otherProps,
343343
values: {
344-
...segmentRest,
345-
isReadOnly: !isEditable,
344+
...segment,
345+
isReadOnly: state.isReadOnly,
346346
isInvalid: state.isInvalid,
347347
isDisabled: state.isDisabled,
348348
isHovered,
@@ -361,7 +361,7 @@ export const DateSegment = /*#__PURE__*/ (forwardRef as forwardRefType)(function
361361
ref={domRef}
362362
data-placeholder={segment.isPlaceholder || undefined}
363363
data-invalid={state.isInvalid || undefined}
364-
data-readonly={!isEditable || undefined}
364+
data-readonly={state.isReadOnly || undefined}
365365
data-disabled={state.isDisabled || undefined}
366366
data-type={segment.type}
367367
data-hovered={isHovered || undefined}

packages/react-aria-components/src/Group.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ export interface GroupProps extends AriaLabelingProps, Omit<HTMLAttributes<HTMLE
4848
isDisabled?: boolean,
4949
/** Whether the group is invalid. */
5050
isInvalid?: boolean,
51+
/** Whether the group is read only. */
52+
isReadOnly?: boolean,
5153
/**
5254
* An accessibility role for the group. By default, this is set to `'group'`.
5355
* Use `'region'` when the contents of the group is important enough to be
@@ -65,7 +67,7 @@ export const GroupContext = createContext<ContextValue<GroupProps, HTMLDivElemen
6567
*/
6668
export const Group = /*#__PURE__*/ (forwardRef as forwardRefType)(function Group(props: GroupProps, ref: ForwardedRef<HTMLDivElement>) {
6769
[props, ref] = useContextProps(props, ref, GroupContext);
68-
let {isDisabled, isInvalid, onHoverStart, onHoverChange, onHoverEnd, ...otherProps} = props;
70+
let {isDisabled, isInvalid, isReadOnly, onHoverStart, onHoverChange, onHoverEnd, ...otherProps} = props;
6971

7072
let {hoverProps, isHovered} = useHover({onHoverStart, onHoverChange, onHoverEnd, isDisabled});
7173
let {isFocused, isFocusVisible, focusProps} = useFocusRing({
@@ -92,7 +94,8 @@ export const Group = /*#__PURE__*/ (forwardRef as forwardRefType)(function Group
9294
data-hovered={isHovered || undefined}
9395
data-focus-visible={isFocusVisible || undefined}
9496
data-disabled={isDisabled || undefined}
95-
data-invalid={isInvalid || undefined}>
97+
data-invalid={isInvalid || undefined}
98+
data-readonly={isReadOnly || undefined}>
9699
{renderProps.children}
97100
</div>
98101
);

packages/react-aria-components/stories/DateField.stories.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ export default {
4343
isInvalid: {
4444
control: 'boolean'
4545
},
46+
isDisabled: {
47+
control: 'boolean'
48+
},
49+
isReadOnly: {
50+
control: 'boolean'
51+
},
4652
validationBehavior: {
4753
control: 'select',
4854
options: ['native', 'aria']

packages/react-aria-components/test/DateField.test.js

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ describe('DateField', () => {
5656
expect(segment).toHaveAttribute('data-placeholder', 'true');
5757
expect(segment).toHaveAttribute('data-type');
5858
expect(segment).toHaveAttribute('data-test', 'test');
59+
expect(segment).not.toHaveAttribute('data-readonly');
60+
}
61+
62+
for (let literal of [...input.children].filter(child => child.getAttribute('data-type') === 'literal')) {
63+
expect(literal).not.toHaveAttribute('data-readonly');
5964
}
6065
});
6166

@@ -164,7 +169,7 @@ describe('DateField', () => {
164169
});
165170

166171
it('should support disabled state', () => {
167-
let {getByRole} = render(
172+
let {getByRole, getAllByRole} = render(
168173
<DateField isDisabled>
169174
<Label>Birth date</Label>
170175
<DateInput className={({isDisabled}) => isDisabled ? 'disabled' : ''}>
@@ -175,6 +180,64 @@ describe('DateField', () => {
175180
let group = getByRole('group');
176181
expect(group).toHaveAttribute('data-disabled');
177182
expect(group).toHaveClass('disabled');
183+
184+
for (let segment of getAllByRole('spinbutton')) {
185+
expect(segment).not.toHaveAttribute('data-readonly');
186+
expect(segment).toHaveAttribute('data-disabled');
187+
}
188+
for (let literal of [...group.children].filter(child => child.getAttribute('data-type') === 'literal')) {
189+
expect(literal).not.toHaveAttribute('data-readonly');
190+
expect(literal).toHaveAttribute('data-disabled');
191+
}
192+
});
193+
194+
it('should support readonly with disabled state', () => {
195+
let {getByRole, getAllByRole} = render(
196+
<DateField isReadOnly isDisabled>
197+
<Label>Birth date</Label>
198+
<DateInput>
199+
{segment => <DateSegment segment={segment} />}
200+
</DateInput>
201+
</DateField>
202+
);
203+
204+
let group = getByRole('group');
205+
expect(group).toHaveAttribute('data-readonly');
206+
expect(group).toHaveAttribute('data-disabled');
207+
208+
for (let segment of getAllByRole('spinbutton')) {
209+
expect(segment).toHaveAttribute('data-readonly');
210+
expect(segment).toHaveAttribute('data-disabled');
211+
}
212+
for (let literal of [...group.children].filter(child => child.getAttribute('data-type') === 'literal')) {
213+
expect(literal).toHaveAttribute('data-readonly');
214+
expect(literal).toHaveAttribute('data-disabled');
215+
}
216+
});
217+
218+
it('should support readonly state', () => {
219+
let {getByRole, getAllByRole} = render(
220+
<DateField isReadOnly>
221+
<Label>Birth date</Label>
222+
<DateInput>
223+
{segment => <DateSegment segment={segment} />}
224+
</DateInput>
225+
</DateField>
226+
);
227+
228+
let group = getByRole('group');
229+
expect(group).toHaveAttribute('data-readonly');
230+
expect(group).not.toHaveAttribute('data-disabled');
231+
expect(group).not.toHaveClass('disabled');
232+
233+
for (let segment of getAllByRole('spinbutton')) {
234+
expect(segment).toHaveAttribute('data-readonly');
235+
expect(segment).not.toHaveAttribute('data-disabled');
236+
}
237+
for (let literal of [...group.children].filter(child => child.getAttribute('data-type') === 'literal')) {
238+
expect(literal).toHaveAttribute('data-readonly');
239+
expect(literal).not.toHaveAttribute('data-disabled');
240+
}
178241
});
179242

180243
it('should support render props', () => {

0 commit comments

Comments
 (0)