Skip to content
This repository was archived by the owner on Feb 6, 2023. It is now read-only.

Commit 8b8b9c0

Browse files
jdxfacebook-github-bot
authored andcommitted
Allow aria-describedby overriding
Summary: Currently it's not possible to have draft-js *not* emit an `aria-describedby` attribute. This diff allows the developer to remove it. This attribute points to the generated DOM id of the placeholder. Currently even if no placeholder was specified **[minor bug]**. I will need this in order to complete T67720986. It will also resolve this [GitHub issue](#1739). Fixing the GitHub issue meant that I needed a way to have the generated placeholder ID as well as a second specified one. The special token "{{PLACEHOLDER}}" can be used inside the prop to be replaced with that generated ID. Fixes #1739 Reviewed By: claudiopro Differential Revision: D21808668 fbshipit-source-id: c27fe0fa8237518f1e2b6830cc8a3e13b3a9d8c7
1 parent 1dc7863 commit 8b8b9c0

File tree

4 files changed

+118
-6
lines changed

4 files changed

+118
-6
lines changed

src/component/base/DraftEditor.react.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ class UpdateDraftEditorFlags extends React.Component<{
134134
*/
135135
class DraftEditor extends React.Component<DraftEditorProps, State> {
136136
static defaultProps: DraftEditorDefaultProps = {
137+
ariaDescribedBy: '{{editor_id_placeholder}}',
137138
blockRenderMap: DefaultDraftBlockRenderMap,
138139
blockRendererFn: function() {
139140
return null;
@@ -314,6 +315,22 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
314315
return null;
315316
}
316317

318+
/**
319+
* returns ariaDescribedBy prop with '{{editor_id_placeholder}}' replaced with
320+
* the DOM id of the placeholder (if it exists)
321+
* @returns aria-describedby attribute value
322+
*/
323+
_renderARIADescribedBy(): ?string {
324+
const describedBy = this.props.ariaDescribedBy || '';
325+
const placeholderID = this._showPlaceholder()
326+
? this._placeholderAccessibilityID
327+
: '';
328+
return (
329+
describedBy.replace('{{editor_id_placeholder}}', placeholderID) ||
330+
undefined
331+
);
332+
}
333+
317334
render(): React.Node {
318335
const {
319336
blockRenderMap,
@@ -381,9 +398,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
381398
}
382399
aria-autocomplete={readOnly ? null : this.props.ariaAutoComplete}
383400
aria-controls={readOnly ? null : this.props.ariaControls}
384-
aria-describedby={
385-
this.props.ariaDescribedBy || this._placeholderAccessibilityID
386-
}
401+
aria-describedby={this._renderARIADescribedBy()}
387402
aria-expanded={readOnly ? null : ariaExpanded}
388403
aria-label={this.props.ariaLabel}
389404
aria-labelledby={this.props.ariaLabelledBy}

src/component/base/DraftEditorProps.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ export type DraftEditorProps = {
8989
ariaActiveDescendantID?: string,
9090
ariaAutoComplete?: string,
9191
ariaControls?: string,
92+
/**
93+
* aria-describedby attribute. should point to the id of a descriptive
94+
* element. The substring, "{{editor_id_placeholder}}" will be replaced with
95+
* the DOM id of the placeholder element if it exists.
96+
* @default "{{editor_id_placeholder}}"
97+
*/
9298
ariaDescribedBy?: string,
9399
ariaExpanded?: boolean,
94100
ariaLabel?: string,
@@ -175,6 +181,7 @@ export type DraftEditorProps = {
175181
};
176182

177183
export type DraftEditorDefaultProps = {
184+
ariaDescribedBy: string,
178185
blockRenderMap: DraftBlockRenderMap,
179186
blockRendererFn: (block: BlockNodeRecord) => ?Object,
180187
blockStyleFn: (block: BlockNodeRecord) => string,

src/component/base/__tests__/DraftEditor.react-test.js

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* LICENSE file in the root directory of this source tree.
66
*
77
* @emails oncall+draft_js
8+
* @flow
89
* @format
910
*/
1011

@@ -13,18 +14,22 @@
1314
jest.mock('generateRandomKey');
1415

1516
const DraftEditor = require('DraftEditor.react');
17+
const EditorState = require('EditorState');
1618
const React = require('React');
1719

20+
// $FlowFixMe
1821
const ReactShallowRenderer = require('react-test-renderer/shallow');
1922

2023
let shallow;
24+
let editorState;
2125

2226
beforeEach(() => {
2327
shallow = new ReactShallowRenderer();
28+
editorState = EditorState.createEmpty();
2429
});
2530

2631
test('must has generated editorKey', () => {
27-
shallow.render(<DraftEditor />);
32+
shallow.render(<DraftEditor editorState={editorState} onChange={() => {}} />);
2833

2934
// internally at Facebook we use a newer version of the shallowRenderer
3035
// which has a different level of wrapping of the '_instance'
@@ -36,7 +41,13 @@ test('must has generated editorKey', () => {
3641
});
3742

3843
test('must has editorKey same as props', () => {
39-
shallow.render(<DraftEditor editorKey="hash" />);
44+
shallow.render(
45+
<DraftEditor
46+
editorState={editorState}
47+
onChange={() => {}}
48+
editorKey="hash"
49+
/>,
50+
);
4051

4152
// internally at Facebook we use a newer version of the shallowRenderer
4253
// which has a different level of wrapping of the '_instance'
@@ -46,3 +57,82 @@ test('must has editorKey same as props', () => {
4657
shallow._instance.getEditorKey || shallow._instance._instance.getEditorKey;
4758
expect(getEditorKey()).toMatchSnapshot();
4859
});
60+
61+
describe('ariaDescribedBy', () => {
62+
function getProps(elem) {
63+
const r = shallow.render(elem);
64+
const ec = r.props.children[1].props.children;
65+
return ec.props;
66+
}
67+
68+
describe('without placeholder', () => {
69+
test('undefined by default', () => {
70+
const props = getProps(
71+
<DraftEditor editorState={editorState} onChange={() => {}} />,
72+
);
73+
expect(props).toHaveProperty('aria-describedby', undefined);
74+
});
75+
76+
test('can be set to something arbitrary', () => {
77+
const props = getProps(
78+
<DraftEditor
79+
editorState={editorState}
80+
onChange={() => {}}
81+
ariaDescribedBy="abc"
82+
/>,
83+
);
84+
expect(props).toHaveProperty('aria-describedby', 'abc');
85+
});
86+
87+
test('can use special token', () => {
88+
const props = getProps(
89+
<DraftEditor
90+
editorState={editorState}
91+
onChange={() => {}}
92+
ariaDescribedBy="abc {{editor_id_placeholder}} xyz"
93+
/>,
94+
);
95+
expect(props).toHaveProperty('aria-describedby', 'abc xyz');
96+
});
97+
});
98+
99+
describe('with placeholder', () => {
100+
test('has placeholder id by default', () => {
101+
const props = getProps(
102+
<DraftEditor
103+
editorState={editorState}
104+
onChange={() => {}}
105+
editorKey="X"
106+
placeholder="place"
107+
/>,
108+
);
109+
expect(props).toHaveProperty('aria-describedby', 'placeholder-X');
110+
});
111+
112+
test('can be set to something arbitrary', () => {
113+
const props = getProps(
114+
<DraftEditor
115+
editorState={editorState}
116+
onChange={() => {}}
117+
editorKey="X"
118+
placeholder="place"
119+
ariaDescribedBy="abc"
120+
/>,
121+
);
122+
expect(props).toHaveProperty('aria-describedby', 'abc');
123+
});
124+
125+
test('can use special token', () => {
126+
const props = getProps(
127+
<DraftEditor
128+
editorState={editorState}
129+
onChange={() => {}}
130+
editorKey="X"
131+
placeholder="place"
132+
ariaDescribedBy="abc {{editor_id_placeholder}} xyz"
133+
/>,
134+
);
135+
expect(props).toHaveProperty('aria-describedby', 'abc placeholder-X xyz');
136+
});
137+
});
138+
});

src/component/base/__tests__/__snapshots__/DraftEditor.react-test.js.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
exports[`must has editorKey same as props 1`] = `"hash"`;
44

5-
exports[`must has generated editorKey 1`] = `"key0"`;
5+
exports[`must has generated editorKey 1`] = `"key1"`;

0 commit comments

Comments
 (0)