Skip to content

Commit

Permalink
Further improve CB performance (#4251)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshwooding authored Oct 8, 2024
1 parent b2af86a commit 97643fb
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 16 deletions.
44 changes: 33 additions & 11 deletions packages/core/src/list-control/ListControlState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,28 +58,51 @@ function findElementPosition(
element: HTMLElement,
) {
if (elements.length === 0) {
return 0;
return [0, false] as const;
}

if (
element.compareDocumentPosition(elements[elements.length - 1].element) &
Node.DOCUMENT_POSITION_PRECEDING
) {
return -1;
return [-1, false] as const;
}

if (
element.compareDocumentPosition(elements[0].element) &
Node.DOCUMENT_POSITION_FOLLOWING
) {
return 0;
return [0, false] as const;
}

return elements.findIndex(
(option) =>
option.element.compareDocumentPosition(element) &
Node.DOCUMENT_POSITION_PRECEDING,
);
let left = 0;
let right = elements.length;
let leftLast = 0;
let rightLast = right;

let exists = false;

while (left < right) {
const inPos = Math.floor((right + left) / 2);
const compared = element.compareDocumentPosition(elements[inPos].element);
if (compared & Node.DOCUMENT_POSITION_PRECEDING) {
left = inPos;
} else if (compared & Node.DOCUMENT_POSITION_FOLLOWING) {
right = inPos;
} else {
right = inPos;
left = inPos;
exists = true;
}
// nothing has changed, must have found limits. insert between.
if (leftLast === left && rightLast === right) {
break;
}
leftLast = left;
rightLast = right;
}

return [right, exists] as const;
}

export function defaultValueToString<Item>(item: Item): string {
Expand Down Expand Up @@ -193,10 +216,9 @@ export function useListControl<Item>(props: ListControlProps<Item>) {
const register = useCallback(
(optionValue: OptionValue<Item>, element: HTMLElement) => {
const { id } = optionValue;
const option = optionsRef.current.find((item) => item.data.id === id);
const index = findElementPosition(optionsRef.current, element);
const [index, exists] = findElementPosition(optionsRef.current, element);

if (!option) {
if (!exists) {
if (index === -1) {
optionsRef.current.push({ data: optionValue, element });
} else {
Expand Down
53 changes: 48 additions & 5 deletions packages/core/stories/combo-box/combo-box.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -912,12 +912,55 @@ export const Bordered = () => {
);
};

export const PerformanceTest = () => {
const hugeArray = Array.from({ length: 10000 }).map(
(_, index) => `Option ${index}`,
);

export const PerformanceTest: StoryFn<ComboBoxProps> = (args) => {
const [value, setValue] = useState(getTemplateDefaultValue(args));

const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
// React 16 backwards compatibility
event.persist();

const value = event.target.value;
setValue(value);
};

const handleSelectionChange = (
event: SyntheticEvent,
newSelected: string[],
) => {
// React 16 backwards compatibility
event.persist();

args.onSelectionChange?.(event, newSelected);

if (args.multiselect) {
setValue("");
return;
}

if (newSelected.length === 1) {
setValue(newSelected[0]);
} else {
setValue("");
}
};

const filteredItems = hugeArray.filter((item) =>
item.toLowerCase().includes(value.trim().toLowerCase()),
);

return (
<ComboBox>
{Array.from({ length: 10000 }).map((_, index) => (
<Option key={index} value={`option-${index}`}>
Option {index + 1}
<ComboBox
value={value}
onChange={handleChange}
onSelectionChange={handleSelectionChange}
>
{filteredItems.map((item) => (
<Option key={item} value={item}>
{item}
</Option>
))}
</ComboBox>
Expand Down

0 comments on commit 97643fb

Please sign in to comment.