Merge branch 'master' into new-tree-flatarray-prop

This commit is contained in:
Bram 2024-12-02 17:56:25 +01:00
commit d1c00813fd
11 changed files with 182 additions and 30 deletions

View file

@ -17,6 +17,9 @@ jobs:
- name: Install dependencies
run: npm install
- name: Run lint
run: npm run lint
- name: Unit tests
run: npm run test

View file

@ -2,6 +2,12 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.86.2](https://github.com/carbon-design-system/carbon-components-svelte/compare/v0.86.1...v0.86.2) (2024-11-30)
### Bug Fixes
- **multi-select:** fix sorting behavior ([c3a390f](https://github.com/carbon-design-system/carbon-components-svelte/commit/c3a390f3fef072c6b736e33a85a2ae772df12e52)), closes [#2066](https://github.com/carbon-design-system/carbon-components-svelte/issues/2066)
## [0.86.1](https://github.com/carbon-design-system/carbon-components-svelte/compare/v0.86.0...v0.86.1) (2024-11-22)
### Bug Fixes

View file

@ -1,6 +1,6 @@
# Component Index
> 165 components exported from carbon-components-svelte@0.86.1.
> 165 components exported from carbon-components-svelte@0.86.2.
## Components

View file

@ -3235,10 +3235,7 @@
"ts": "interface DataTableCell<\n Row = DataTableRow,\n> {\n key:\n | DataTableKey<Row>\n | (string & {});\n value: DataTableValue;\n display?: (\n item: DataTableValue,\n row: DataTableRow,\n ) => DataTableValue;\n}\n"
}
],
"generics": [
"Row",
"Row extends DataTableRow = DataTableRow"
],
"generics": ["Row", "Row extends DataTableRow = DataTableRow"],
"rest_props": {
"type": "Element",
"name": "div"
@ -18078,4 +18075,4 @@
}
}
]
}
}

View file

@ -39,10 +39,10 @@
$: source = `https://github.com/carbon-design-system/carbon-components-svelte/tree/master/${component.filePath}`;
$: forwarded_events = component.events.filter(
(event) => event.type === "forwarded"
(event) => event.type === "forwarded",
);
$: dispatched_events = component.events.filter(
(event) => event.type === "dispatched"
(event) => event.type === "dispatched",
);
</script>
@ -116,20 +116,20 @@
>
<svelte:component
this={AsyncPreviewTypeScript}
type="inline"
code={typeMap[type]}
/>
</div>
type="inline"
code={typeMap[type]}
/>
</div>
{:else}
<div
style="display: inline-flex; max-width: 220px; word-break: break-word;"
>
<svelte:component
this={AsyncPreviewTypeScript}
type="inline"
code={type}
/>
</div>
type="inline"
code={type}
/>
</div>
{/if}
</div>
{/each}
@ -145,10 +145,16 @@
</div>
{/each}
{/if}
<div style:margin-top="var(--cds-layout-02)" style:margin-bottom="var(--cds-spacing-03)">
<div
style:margin-top="var(--cds-layout-02)"
style:margin-bottom="var(--cds-spacing-03)"
>
<strong>Default value</strong>
</div>
<div style:margin-bottom="var(--cds-layout-01)" style:max-width="85%">
<div
style:margin-bottom="var(--cds-layout-01)"
style:max-width="85%"
>
{#if prop.value === undefined}
<em>undefined</em>
{:else}

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "carbon-components-svelte",
"version": "0.86.1",
"version": "0.86.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "carbon-components-svelte",
"version": "0.86.1",
"version": "0.86.2",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "carbon-components-svelte",
"version": "0.86.1",
"version": "0.86.2",
"license": "Apache-2.0",
"description": "Svelte implementation of the Carbon Design System",
"type": "module",

View file

@ -5,11 +5,11 @@ import { format } from "prettier";
import plugin from "prettier/plugins/typescript";
const formatTypeScript = async (value) => {
return await format(value, {
parser: "typescript",
return await format(value, {
parser: "typescript",
plugins: [plugin],
printWidth: 40, // Force breaking onto new lines
bracketSameLine: false
bracketSameLine: false,
});
};

View file

@ -243,11 +243,11 @@
}
});
$: menuId = `menu-${id}`;
$: inline = type === "inline";
$: ariaLabel = $$props["aria-label"] || "Choose an item";
$: sortedItems = (() => {
if (selectionFeedback === "top" && selectedIds.length > 0) {
function sort() {
if (
selectionFeedback === "top" ||
selectionFeedback === "top-after-reopen"
) {
const checkedItems = items
.filter((item) => selectedIds.includes(item.id))
.map((item) => ({ ...item, checked: true }));
@ -269,7 +269,20 @@
checked: selectedIds.includes(item.id),
}))
.sort(sortItem);
})();
}
let sortedItems = sort();
$: menuId = `menu-${id}`;
$: inline = type === "inline";
$: ariaLabel = $$props["aria-label"] || "Choose an item";
$: if (
selectedIds &&
(selectionFeedback === "top" ||
(selectionFeedback === "top-after-reopen" && open === false))
) {
sortedItems = sort();
}
$: checked = sortedItems.filter(({ checked }) => checked);
$: unchecked = sortedItems.filter(({ checked }) => !checked);
$: filteredItems = sortedItems.filter((item) => filterItem(item, value));

View file

@ -0,0 +1,124 @@
import { render, screen } from "@testing-library/svelte";
import { MultiSelect } from "carbon-components-svelte";
import { user } from "../setup-tests";
describe("MultiSelect sorts items correctly", () => {
/** Opens the dropdown. */
const openMenu = async () =>
await user.click(
await screen.findByLabelText("Open menu", {
selector: `[role="button"]`,
}),
);
/** Closes the dropdown. */
const closeMenu = async () =>
await user.click(
await screen.findByLabelText("Close menu", {
selector: `[role="button"]`,
}),
);
/** Toggles an option, identifying it by its `text` value. */
const toggleOption = async (optionText: string) =>
await user.click(
await screen.findByText((text) => text.trim() === optionText),
);
/** Fetches the `text` value of the nth option in the MultiSelect component. */
const nthRenderedOptionText = (index: number) =>
screen.queryAllByRole("option").at(index)?.textContent?.trim();
it("initially sorts items alphabetically", async () => {
render(MultiSelect, {
items: [
{ id: "1", text: "C" },
{ id: "3", text: "A" },
{ id: "2", text: "B" },
],
});
await openMenu();
expect(nthRenderedOptionText(0)).toBe("A");
expect(nthRenderedOptionText(1)).toBe("B");
expect(nthRenderedOptionText(2)).toBe("C");
});
it("immediately moves selected items to the top (with selectionFeedback: top)", async () => {
render(MultiSelect, {
items: [
{ id: "3", text: "C" },
{ id: "1", text: "A" },
{ id: "2", text: "B" },
],
selectionFeedback: "top",
});
await openMenu();
expect(nthRenderedOptionText(0)).toBe("A");
await toggleOption("C");
expect(nthRenderedOptionText(0)).toBe("C");
await toggleOption("C");
expect(nthRenderedOptionText(0)).toBe("A");
});
it("sorts newly-toggled items only after the dropdown is reoponed (with selectionFeedback: top-after-reopen)", async () => {
render(MultiSelect, {
items: [
{ id: "3", text: "C" },
{ id: "1", text: "A" },
{ id: "2", text: "B" },
],
});
// Initially, items should be sorted alphabetically.
await openMenu();
expect(nthRenderedOptionText(0)).toBe("A");
// While the menu is still open, a newly-selected item should not move.
await toggleOption("C");
expect(nthRenderedOptionText(0)).toBe("A");
// The newly-selected item should move after the menu is closed and
// re-opened.
await closeMenu();
await openMenu();
expect(nthRenderedOptionText(0)).toBe("C");
// A deselected item should not move while the dropdown is still open.
await toggleOption("C");
expect(nthRenderedOptionText(0)).toBe("C");
// The deselected item should move after closing and re-opening the dropdown.
await closeMenu();
await openMenu();
expect(nthRenderedOptionText(0)).toBe("A");
});
it("never moves selected items to the top (with selectionFeedback: fixed)", async () => {
render(MultiSelect, {
items: [
{ id: "3", text: "C" },
{ id: "1", text: "A" },
{ id: "2", text: "B" },
],
selectionFeedback: "fixed",
});
// Items should be sorted alphabetically.
await openMenu();
expect(nthRenderedOptionText(0)).toBe("A");
// A newly-selected item should not move after the selection is made.
await toggleOption("C");
expect(nthRenderedOptionText(0)).toBe("A");
// The newly-selected item also shouldnt move after the dropdown is closed
// and reopened.
await closeMenu();
await openMenu();
expect(nthRenderedOptionText(0)).toBe("A");
});
});

View file

@ -3,4 +3,7 @@ import "@testing-library/jest-dom/vitest";
import { userEvent } from "@testing-library/user-event";
import "../css/all.css";
// Mock scrollIntoView since it's not implemented in JSDOM
Element.prototype.scrollIntoView = vi.fn();
export const user = userEvent.setup();