Skip to content

Commit

Permalink
fix: generate elements ids in a consistent manner
Browse files Browse the repository at this point in the history
  • Loading branch information
dhayab committed Sep 26, 2023
1 parent 973feaf commit ee60791
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 34 deletions.
67 changes: 42 additions & 25 deletions packages/autocomplete-core/src/getPropGetters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ import {
GetRootProps,
InternalAutocompleteOptions,
} from './types';
import { getActiveItem, isOrContainsNode, isSamsung } from './utils';
import {
getActiveItem,
getAutocompleteElementId,
isOrContainsNode,
isSamsung,
} from './utils';

interface GetPropGettersOptions<TItem extends BaseItem>
extends AutocompleteScopeApi<TItem> {
Expand Down Expand Up @@ -104,8 +109,15 @@ export function getPropGetters<
role: 'combobox',
'aria-expanded': store.getState().isOpen,
'aria-haspopup': 'listbox',
'aria-owns': store.getState().isOpen ? `${props.id}-list` : undefined,
'aria-labelledby': `${props.id}-label`,
'aria-owns': store.getState().isOpen
? store
.getState()
.collections.map(({ source }) =>
getAutocompleteElementId(props.id, 'list', source)
)
.join(' ')
: undefined,
'aria-labelledby': getAutocompleteElementId(props.id, 'label'),
...rest,
};
};
Expand Down Expand Up @@ -180,12 +192,23 @@ export function getPropGetters<
'aria-autocomplete': 'both',
'aria-activedescendant':
store.getState().isOpen && store.getState().activeItemId !== null
? `${props.id}-item-${store.getState().activeItemId}`
? getAutocompleteElementId(
props.id,
`item-${store.getState().activeItemId}`,
activeItem?.source
)
: undefined,
'aria-controls': store.getState().isOpen ? `${props.id}-list` : undefined,
'aria-labelledby': `${props.id}-label`,
'aria-controls': store.getState().isOpen
? store
.getState()
.collections.map(({ source }) =>
getAutocompleteElementId(props.id, 'list', source)
)
.join(' ')
: undefined,
'aria-labelledby': getAutocompleteElementId(props.id, 'label'),
value: store.getState().completion || store.getState().query,
id: `${props.id}-input`,
id: getAutocompleteElementId(props.id, 'input'),
autoComplete: 'off',
autoCorrect: 'off',
autoCapitalize: 'off',
Expand Down Expand Up @@ -241,29 +264,21 @@ export function getPropGetters<
};
};

const getAutocompleteId = (instanceId: string, sourceId?: number) => {
return typeof sourceId !== 'undefined'
? `${instanceId}-${sourceId}`
: instanceId;
};

const getLabelProps: GetLabelProps = (providedProps) => {
const { sourceIndex, ...rest } = providedProps || {};

const getLabelProps: GetLabelProps = (rest) => {
return {
htmlFor: `${getAutocompleteId(props.id, sourceIndex)}-input`,
id: `${getAutocompleteId(props.id, sourceIndex)}-label`,
htmlFor: getAutocompleteElementId(props.id, 'input'),
id: getAutocompleteElementId(props.id, 'label'),
...rest,
};
};

const getListProps: GetListProps = (providedProps) => {
const { sourceIndex, ...rest } = providedProps || {};
const { source, ...rest } = providedProps || {};

return {
role: 'listbox',
'aria-labelledby': `${getAutocompleteId(props.id, sourceIndex)}-label`,
id: `${getAutocompleteId(props.id, sourceIndex)}-list`,
'aria-labelledby': getAutocompleteElementId(props.id, 'label'),
id: getAutocompleteElementId(props.id, 'list', source),
...rest,
};
};
Expand All @@ -284,12 +299,14 @@ export function getPropGetters<
};

const getItemProps: GetItemProps<any, TMouseEvent> = (providedProps) => {
const { item, source, sourceIndex, ...rest } = providedProps;
const { item, source, ...rest } = providedProps;

return {
id: `${getAutocompleteId(props.id, sourceIndex as number)}-item-${
item.__autocomplete_id
}`,
id: getAutocompleteElementId(
props.id,
`item-${item.__autocomplete_id}`,
source
),
role: 'option',
'aria-selected': store.getState().activeItemId === item.__autocomplete_id,
onMouseMove(event) {
Expand Down
10 changes: 8 additions & 2 deletions packages/autocomplete-core/src/onKeyDown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
BaseItem,
InternalAutocompleteOptions,
} from './types';
import { getActiveItem } from './utils';
import { getActiveItem, getAutocompleteElementId } from './utils';

interface OnKeyDownOptions<TItem extends BaseItem>
extends AutocompleteScopeApi<TItem> {
Expand All @@ -25,8 +25,14 @@ export function onKeyDown<TItem extends BaseItem>({
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
// eslint-disable-next-line no-inner-declarations
function triggerScrollIntoView() {
const highlightedItem = getActiveItem(store.getState());

const nodeItem = props.environment.document.getElementById(
`${props.id}-item-${store.getState().activeItemId}`
getAutocompleteElementId(
props.id,
`item-${store.getState().activeItemId}`,
highlightedItem?.source
)
);

if (nodeItem) {
Expand Down
18 changes: 18 additions & 0 deletions packages/autocomplete-core/src/utils/getAutocompleteElementId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { InternalAutocompleteSource } from '../types';

/**
* Returns a full element id for an autocomplete element.
*
* @param autocompleteInstanceId The id of the autocomplete instance
* @param elementId The specific element id
* @param source The source of the element, when it needs to be scoped
*/
export function getAutocompleteElementId(
autocompleteInstanceId: string,
elementId: string,
source?: InternalAutocompleteSource<any>
) {
return [autocompleteInstanceId, source?.sourceId, elementId]
.filter(Boolean)
.join('-');
}
1 change: 1 addition & 0 deletions packages/autocomplete-core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './createConcurrentSafePromise';
export * from './getNextActiveItemId';
export * from './getNormalizedSources';
export * from './getActiveItem';
export * from './getAutocompleteElementId';
export * from './isOrContainsNode';
export * from './isSamsung';
export * from './mapToAlgoliaResponse';
3 changes: 1 addition & 2 deletions packages/autocomplete-js/src/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export function renderPanel<TItem extends BaseItem>(
{...propGetters.getListProps({
state,
props: autocomplete.getListProps({
sourceIndex,
source,
}),
...autocompleteScopeApi,
})}
Expand All @@ -147,7 +147,6 @@ export function renderPanel<TItem extends BaseItem>(
const itemProps = autocomplete.getItemProps({
item,
source,
sourceIndex,
});

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ export type GetFormProps<TEvent = Event> = (props: {
onReset(event: TEvent): void;
};

export type GetLabelProps = (props?: {
[key: string]: unknown;
sourceIndex?: number;
}) => {
export type GetLabelProps = (props?: { [key: string]: unknown }) => {
htmlFor: string;
id: string;
};
Expand Down Expand Up @@ -101,7 +98,7 @@ export type GetPanelProps<TMouseEvent> = (props?: {

export type GetListProps = (props?: {
[key: string]: unknown;
sourceIndex?: number;
source: InternalAutocompleteSource<any>;
}) => {
role: 'listbox';
'aria-labelledby': string;
Expand Down

0 comments on commit ee60791

Please sign in to comment.