-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Expand file tree
/
Copy pathAccessibleTextArea.tsx
More file actions
99 lines (90 loc) · 3.19 KB
/
AccessibleTextArea.tsx
File metadata and controls
99 lines (90 loc) · 3.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/* eslint no-magic-numbers: ["error", { "ignore": [-1] }] */
import React, {
type ChangeEventHandler,
type FocusEventHandler,
forwardRef,
type KeyboardEventHandler,
type MouseEventHandler,
type ReactEventHandler,
useRef
} from 'react';
// Differences between <textarea> and <AccessibleTextArea>:
// - Disable behavior
// - When the widget is disabled
// - Set "aria-disabled" attribute to "true"
// - Set "readonly" attribute
// - Set "tabIndex" to -1
// - Remove "onChange" handler
// - Why this is needed
// - Browser compatibility: when the widget is disabled, different browser send focus to different places
// - When the widget is disabled, it's reasonable to keep the focus on the same widget for an extended period of time
// - When the user presses TAB after the current widget is disabled, it should move the focus to the next non-disabled widget
// Developers using this accessible widget will need to:
// - Style the disabled widget themselves using CSS query `:disabled, [aria-disabled="true"] {}`
// - Modify all the code that checks disabled through the "disabled" attribute to use aria-disabled="true" instead
// - aria-disabled="true" is the source of truth
// - If the widget is contained by a <form>, the developer need to filter out some `onSubmit` event caused by this widget
type AccessibleTextAreaProps = Readonly<{
className?: string;
disabled?: boolean;
inputMode?: 'text' | 'none' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
onChange?: ChangeEventHandler<HTMLTextAreaElement>;
onClick?: MouseEventHandler<HTMLTextAreaElement> | undefined;
onFocus?: FocusEventHandler<HTMLTextAreaElement>;
onKeyDown?: KeyboardEventHandler<HTMLTextAreaElement>;
onKeyDownCapture?: KeyboardEventHandler<HTMLTextAreaElement>;
onKeyPress?: KeyboardEventHandler<HTMLTextAreaElement>;
onSelect?: ReactEventHandler<HTMLTextAreaElement>;
placeholder?: string;
readOnly?: boolean;
rows?: number;
tabIndex?: number;
value?: string;
}>;
const AccessibleTextArea = forwardRef<HTMLTextAreaElement, AccessibleTextAreaProps>(
(
{
className,
disabled,
inputMode,
onChange,
onClick,
onFocus,
onKeyDown,
onKeyDownCapture,
onKeyPress,
onSelect,
placeholder,
readOnly,
rows,
tabIndex,
...props
},
forwardedRef
) => {
const targetRef = useRef();
const ref = forwardedRef || targetRef;
return (
<textarea
aria-disabled={disabled || undefined}
className={className}
inputMode={inputMode}
onChange={disabled ? undefined : onChange}
onClick={onClick}
onFocus={disabled ? undefined : onFocus}
onKeyDown={disabled ? undefined : onKeyDown}
onKeyDownCapture={disabled ? undefined : onKeyDownCapture}
onKeyPress={disabled ? undefined : onKeyPress}
onSelect={disabled ? undefined : onSelect}
placeholder={placeholder}
readOnly={readOnly || disabled}
ref={ref}
rows={rows}
tabIndex={disabled ? -1 : tabIndex}
{...props}
/>
);
}
);
AccessibleTextArea.displayName = 'AccessibleTextArea';
export default AccessibleTextArea;