Skip to content

Commit

Permalink
Improve list based control's performance when lots of items are displ…
Browse files Browse the repository at this point in the history
…ayed (#4246)
  • Loading branch information
joshwooding authored Oct 7, 2024
1 parent 3a9d518 commit 65bfefb
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/tiny-fishes-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@salt-ds/core": patch
---

Improved list based control's performance when lots of items are displayed.
21 changes: 21 additions & 0 deletions packages/core/src/__tests__/__e2e__/combo-box/ComboBox.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const {
MultiplePillsTruncated,
SelectOnTab,
LongList,
PerformanceTest,
} = composeStories(comboBoxStories);

describe("Given a ComboBox", () => {
Expand Down Expand Up @@ -730,4 +731,24 @@ describe("Given a ComboBox", () => {
cy.findAllByRole("option").eq(0).realClick();
cy.get("@blurSpy").should("not.have.been.called");
});

it("should support 10000 items without much delay", () => {
cy.mount(<PerformanceTest />);

cy.findByRole("combobox").should("be.visible");

cy.window().its("performance").invoke("mark", "open_start");

cy.findByRole("combobox").realClick();

cy.findByRole("listbox", { timeout: 30000 }).should("be.visible");

cy.window().its("performance").invoke("mark", "open_end");

cy.window()
.its("performance")
.invoke("measure", "open_duration", "open_start", "open_end")
.its("duration", { timeout: 0 })
.should("be.lessThan", 5000);
});
});
36 changes: 30 additions & 6 deletions packages/core/src/list-control/ListControlState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,35 @@ export type ListControlProps<Item> = {
valueToString?: (item: Item) => string;
};

function findElementPosition(
elements: { element: HTMLElement }[],
element: HTMLElement,
) {
if (elements.length === 0) {
return 0;
}

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

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

return elements.findIndex(
(option) =>
option.element.compareDocumentPosition(element) &
Node.DOCUMENT_POSITION_PRECEDING,
);
}

export function defaultValueToString<Item>(item: Item): string {
return String(item);
}
Expand Down Expand Up @@ -165,12 +194,7 @@ export function useListControl<Item>(props: ListControlProps<Item>) {
(optionValue: OptionValue<Item>, element: HTMLElement) => {
const { id } = optionValue;
const option = optionsRef.current.find((item) => item.data.id === id);
const index = optionsRef.current.findIndex((option) => {
return (
option.element.compareDocumentPosition(element) &
Node.DOCUMENT_POSITION_PRECEDING
);
});
const index = findElementPosition(optionsRef.current, element);

if (!option) {
if (index === -1) {
Expand Down
12 changes: 12 additions & 0 deletions packages/core/stories/combo-box/combo-box.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -911,3 +911,15 @@ export const Bordered = () => {
</StackLayout>
);
};

export const PerformanceTest = () => {
return (
<ComboBox>
{Array.from({ length: 10000 }).map((_, index) => (
<Option key={index} value={`option-${index}`}>
Option {index + 1}
</Option>
))}
</ComboBox>
);
};

0 comments on commit 65bfefb

Please sign in to comment.