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

fix: use a not random id for the list #464

Merged
merged 2 commits into from
May 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ Default: `''`

An initial value for the input, when you want to prefill the suggest.

#### id
Type: `String`
Default: `''`

Define an ID for the geosuggest. Needed when there are multiple instances on a page.

#### className
Type: `String`
Default: `''`
Expand Down
16 changes: 5 additions & 11 deletions src/Geosuggest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export default class extends React.Component<IProps, IState> {
this.onSuggestNoResults = this.onSuggestNoResults.bind(this);
this.hideSuggests = this.hideSuggests.bind(this);
this.selectSuggest = this.selectSuggest.bind(this);
this.listId = `geosuggest__list_${Math.random().toString(16).slice(2)}`;
this.listId = `geosuggest__list${props.id ? `--${props.id}` : ''}`;

if (props.queryDelay) {
this.onAfterInputChange = debounce(
Expand Down Expand Up @@ -600,7 +600,6 @@ export default class extends React.Component<IProps, IState> {
const classes = classnames('geosuggest', this.props.className, {
'geosuggest--loading': this.state.isLoading
});
const shouldRenderLabel = this.props.label && attributes.id;
const input = (
<Input
className={this.props.inputClassName}
Expand All @@ -621,6 +620,8 @@ export default class extends React.Component<IProps, IState> {
onEscape={this.hideSuggests}
isSuggestsHidden={this.state.isSuggestsHidden}
activeSuggest={this.state.activeSuggest}
label={this.props.label}
id={this.props.id}
listId={this.listId}
{...attributes}
/>
Expand Down Expand Up @@ -648,15 +649,8 @@ export default class extends React.Component<IProps, IState> {
);

return (
<div className={classes}>
<div className="geosuggest__input-wrapper">
{shouldRenderLabel && (
<label className="geosuggest__label" htmlFor={attributes.id}>
{this.props.label}
</label>
)}
{input}
</div>
<div className={classes} id={this.props.id}>
<div className="geosuggest__input-wrapper">{input}</div>
<div className="geosuggest__suggests-wrapper">{suggestionsList}</div>
</div>
);
Expand Down
1 change: 0 additions & 1 deletion src/filter-input-attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const allowedAttributes: string[] = [
'formNoValidate',
'formTarget',
'height',
'id',
'inputMode',
'maxLength',
'name',
Expand Down
55 changes: 33 additions & 22 deletions src/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ISuggest from './types/suggest';
interface IProps {
readonly value: string;
readonly className?: string;
readonly id?: string;
readonly doNotSubmitOnEnter?: boolean;
readonly ignoreEnter?: boolean;
readonly ignoreTab?: boolean;
Expand All @@ -16,6 +17,7 @@ interface IProps {
readonly isSuggestsHidden: boolean;
readonly activeSuggest: ISuggest | null;
readonly listId: string;
readonly label?: string;
readonly onChange: (value: string) => void;
readonly onSelect: () => void;
readonly onKeyDown?: (event: React.KeyboardEvent) => void;
Expand Down Expand Up @@ -149,34 +151,43 @@ export default class Input extends React.PureComponent<IProps, {}> {
render(): JSX.Element {
const attributes = filterInputAttributes(this.props);
const classes = classnames('geosuggest__input', this.props.className);
const shouldRenderLabel = this.props.label && this.props.id;

if (!attributes.tabIndex) {
attributes.tabIndex = 0;
}

return (
<input
className={classes}
ref={(i): HTMLInputElement | null => (this.input = i)}
type="text"
{...attributes}
value={this.props.value}
style={this.props.style}
onKeyDown={this.onInputKeyDown}
onChange={this.onChange}
onKeyPress={this.props.onKeyPress}
onFocus={this.props.onFocus}
onBlur={this.props.onBlur}
role="combobox"
aria-expanded={!this.props.isSuggestsHidden}
aria-activedescendant={
this.props.activeSuggest
? this.props.activeSuggest.placeId
: // eslint-disable-next-line no-undefined
undefined
}
aria-owns={this.props.listId}
/>
<>
{shouldRenderLabel && (
<label className="geosuggest__label" htmlFor={this.props.id}>
{this.props.label}
</label>
)}
<input
className={classes}
id={`geosuggest__input${this.props.id ? `--${this.props.id}` : ''}`}
ref={(i): HTMLInputElement | null => (this.input = i)}
type="text"
{...attributes}
value={this.props.value}
style={this.props.style}
onKeyDown={this.onInputKeyDown}
onChange={this.onChange}
onKeyPress={this.props.onKeyPress}
onFocus={this.props.onFocus}
onBlur={this.props.onBlur}
role="combobox"
aria-expanded={!this.props.isSuggestsHidden}
aria-activedescendant={
this.props.activeSuggest
? this.props.activeSuggest.placeId
: // eslint-disable-next-line no-undefined
undefined
}
aria-owns={this.props.listId}
/>
</>
);
}
}
1 change: 1 addition & 0 deletions src/types/props.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default interface IProps {
readonly initialValue?: string;
readonly placeholder?: string;
readonly disabled?: boolean;
readonly id?: string;
readonly className?: string;
readonly inputClassName?: string;
readonly suggestsClassName?: string;
Expand Down
52 changes: 45 additions & 7 deletions test/Geosuggest_spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -697,19 +697,20 @@ describe('Component: Geosuggest', () => {
});

describe('with label and id props', () => {
const props = {
id: 'geosuggest-id',
label: 'some label'
};
it('should render a <label> if the `label` and `id` props were supplied', () => {
const props = {
id: 'geosuggest-id',
label: 'some label'
};

beforeEach(() => render(props));
render(props);

it('should render a <label> if the `label` and `id` props were supplied', () => {
const label = TestUtils.findRenderedDOMComponentWithTag(
component,
'label'
);
expect(label).to.not.equal(null);

expect(label).to.not.be.null;
});
});

Expand Down Expand Up @@ -1080,5 +1081,42 @@ describe('Component: Geosuggest', () => {

expect(geoSuggestInput.getAttribute('aria-owns')).to.equal(listId);
});

it('should have aria-owns attribute set to the list id with the passed in ID', () => {
const props = {
id: 'test-id'
};
render(props);

const input = TestUtils.findRenderedDOMComponentWithClass(
component,
'geosuggest__input'
);
const suggests = TestUtils.scryRenderedDOMComponentsWithClass(
component,
'geosuggest__suggests'
);

const listId = suggests[0].getAttribute('id');

expect(input.getAttribute('aria-owns')).to.equal(listId);
expect(listId?.endsWith(props.id)).to.be.true;
});

it('should have id set to the input according to the passed in ID', () => {
const props = {
id: 'test-id'
};
render(props);

const input = TestUtils.findRenderedDOMComponentWithClass(
component,
'geosuggest__input'
);

expect(input.getAttribute('id')).to.equal(
`geosuggest__input--${props.id}`
);
});
});
});