@simplysm/solid 13.0.70 → 13.0.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/components/disclosure/Dropdown.d.ts +6 -4
- package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
- package/dist/components/disclosure/Dropdown.js +24 -8
- package/dist/components/disclosure/Dropdown.js.map +2 -2
- package/dist/components/disclosure/dialogZIndex.d.ts +2 -0
- package/dist/components/disclosure/dialogZIndex.d.ts.map +1 -1
- package/dist/components/disclosure/dialogZIndex.js +4 -0
- package/dist/components/disclosure/dialogZIndex.js.map +1 -1
- package/dist/components/features/crud-detail/CrudDetail.d.ts.map +1 -1
- package/dist/components/features/crud-detail/CrudDetail.js +16 -7
- package/dist/components/features/crud-detail/CrudDetail.js.map +2 -2
- package/dist/components/features/crud-sheet/CrudSheet.d.ts.map +1 -1
- package/dist/components/features/crud-sheet/CrudSheet.js +14 -5
- package/dist/components/features/crud-sheet/CrudSheet.js.map +2 -2
- package/dist/components/features/crudRegistry.d.ts +16 -0
- package/dist/components/features/crudRegistry.d.ts.map +1 -0
- package/dist/components/features/crudRegistry.js +37 -0
- package/dist/components/features/crudRegistry.js.map +6 -0
- package/dist/components/features/permission-table/PermissionTable.d.ts.map +1 -1
- package/dist/components/features/permission-table/PermissionTable.js +71 -86
- package/dist/components/features/permission-table/PermissionTable.js.map +2 -2
- package/dist/components/features/shared-data/SharedDataSelect.js +2 -4
- package/dist/components/features/shared-data/SharedDataSelect.js.map +2 -2
- package/dist/components/features/shared-data/SharedDataSelectList.d.ts +2 -4
- package/dist/components/features/shared-data/SharedDataSelectList.d.ts.map +1 -1
- package/dist/components/features/shared-data/SharedDataSelectList.js +11 -46
- package/dist/components/features/shared-data/SharedDataSelectList.js.map +2 -2
- package/dist/components/form-control/select/Select.d.ts.map +1 -1
- package/dist/components/form-control/select/Select.js +1 -1
- package/dist/components/form-control/select/Select.js.map +1 -1
- package/dist/helpers/createAppStructure.d.ts.map +1 -1
- package/dist/helpers/createAppStructure.js +3 -2
- package/dist/helpers/createAppStructure.js.map +1 -1
- package/dist/helpers/createHmrSafeContext.d.ts +3 -0
- package/dist/helpers/createHmrSafeContext.d.ts.map +1 -0
- package/dist/helpers/createHmrSafeContext.js +10 -0
- package/dist/helpers/createHmrSafeContext.js.map +6 -0
- package/dist/hooks/createSelectionGroup.d.ts.map +1 -1
- package/dist/hooks/createSelectionGroup.js +3 -2
- package/dist/hooks/createSelectionGroup.js.map +2 -2
- package/package.json +6 -5
- package/src/components/disclosure/Dropdown.tsx +31 -17
- package/src/components/disclosure/dialogZIndex.ts +5 -0
- package/src/components/features/crud-detail/CrudDetail.tsx +16 -5
- package/src/components/features/crud-sheet/CrudSheet.tsx +13 -3
- package/src/components/features/crudRegistry.ts +60 -0
- package/src/components/features/permission-table/PermissionTable.tsx +49 -46
- package/src/components/features/shared-data/SharedDataSelect.tsx +2 -2
- package/src/components/features/shared-data/SharedDataSelectList.tsx +11 -36
- package/src/components/form-control/select/Select.tsx +1 -5
- package/src/helpers/createAppStructure.ts +3 -2
- package/src/helpers/createHmrSafeContext.ts +8 -0
- package/src/hooks/createSelectionGroup.tsx +4 -2
- package/tests/components/data/List.spec.tsx +52 -52
- package/tests/components/data/Pagination.spec.tsx +43 -43
- package/tests/components/data/Table.spec.tsx +4 -4
- package/tests/components/data/kanban/Kanban.selection.spec.tsx +21 -21
- package/tests/components/data/sheet/DataSheet.spec.tsx +50 -50
- package/tests/components/disclosure/Collapse.spec.tsx +24 -24
- package/tests/components/disclosure/Dialog.spec.tsx +33 -33
- package/tests/components/disclosure/DialogProvider.spec.tsx +9 -9
- package/tests/components/disclosure/Dropdown.spec.tsx +134 -14
- package/tests/components/disclosure/Tabs.spec.tsx +21 -21
- package/tests/components/disclosure/dialogZIndex.spec.ts +45 -0
- package/tests/components/display/Alert.spec.tsx +4 -4
- package/tests/components/display/Barcode.spec.tsx +7 -7
- package/tests/components/display/Card.spec.tsx +3 -3
- package/tests/components/display/Link.spec.tsx +5 -5
- package/tests/components/display/Tag.spec.tsx +4 -4
- package/tests/components/features/address/AddressSearch.spec.tsx +3 -3
- package/tests/components/features/crudRegistry.spec.ts +119 -0
- package/tests/components/features/data-select-button/DataSelectButton.spec.tsx +8 -8
- package/tests/components/features/permission-table/PermissionTable.spec.tsx +43 -43
- package/tests/components/features/shared-data/SharedDataSelectList.spec.tsx +2 -17
- package/tests/components/feedback/busy/BusyContainer.spec.tsx +7 -7
- package/tests/components/feedback/notification/NotificationBell.spec.tsx +9 -9
- package/tests/components/feedback/print/Print.spec.tsx +4 -4
- package/tests/components/form-control/Button.spec.tsx +18 -18
- package/tests/components/form-control/checkbox/Checkbox.spec.tsx +20 -20
- package/tests/components/form-control/checkbox/CheckboxGroup.spec.tsx +12 -12
- package/tests/components/form-control/checkbox/Radio.spec.tsx +21 -21
- package/tests/components/form-control/checkbox/RadioGroup.spec.tsx +12 -12
- package/tests/components/form-control/color-picker/ColorPicker.spec.tsx +10 -10
- package/tests/components/form-control/combobox/Combobox.spec.tsx +16 -16
- package/tests/components/form-control/combobox/ComboboxItem.spec.tsx +7 -7
- package/tests/components/form-control/date-range-picker/DateRangePicker.spec.tsx +24 -24
- package/tests/components/form-control/field/DatePicker.spec.tsx +50 -50
- package/tests/components/form-control/field/DateTimePicker.spec.tsx +47 -47
- package/tests/components/form-control/field/NumberInput.spec.tsx +54 -54
- package/tests/components/form-control/field/TextInput.spec.tsx +49 -49
- package/tests/components/form-control/field/Textarea.spec.tsx +33 -33
- package/tests/components/form-control/field/TimePicker.spec.tsx +42 -42
- package/tests/components/form-control/numpad/Numpad.spec.tsx +40 -40
- package/tests/components/form-control/select/Select.spec.tsx +9 -9
- package/tests/components/form-control/select/SelectItem.spec.tsx +10 -10
- package/tests/helpers/createAppStructure.spec.tsx +57 -57
- package/tests/helpers/mergeStyles.spec.ts +31 -31
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { createEffect, createMemo, createSignal, For, type JSX, Show, splitProps } from "solid-js";
|
|
2
|
-
import { IconExternalLink } from "@tabler/icons-solidjs";
|
|
3
2
|
import clsx from "clsx";
|
|
4
3
|
import { twMerge } from "tailwind-merge";
|
|
5
4
|
import { type SharedDataAccessor } from "../../../providers/shared-data/SharedDataContext";
|
|
6
5
|
import { List } from "../../data/list/List";
|
|
7
6
|
import { Pagination } from "../../data/Pagination";
|
|
8
|
-
import { Button } from "../../form-control/Button";
|
|
9
|
-
import { Icon } from "../../display/Icon";
|
|
10
7
|
import { TextInput } from "../../form-control/field/TextInput";
|
|
11
|
-
import { useDialog } from "../../disclosure/DialogContext";
|
|
12
8
|
import { useI18nOptional } from "../../../providers/i18n/I18nContext";
|
|
13
9
|
import { textMuted } from "../../../styles/tokens.styles";
|
|
14
10
|
import { createSlotSignal } from "../../../hooks/createSlotSignal";
|
|
@@ -39,10 +35,8 @@ export interface SharedDataSelectListProps<TItem> {
|
|
|
39
35
|
canChange?: (item: TItem | undefined) => boolean | Promise<boolean>;
|
|
40
36
|
/** Page size (shows Pagination if provided) */
|
|
41
37
|
pageSize?: number;
|
|
42
|
-
/** Header
|
|
43
|
-
header?:
|
|
44
|
-
/** Management modal component factory */
|
|
45
|
-
modal?: () => JSX.Element;
|
|
38
|
+
/** Header content */
|
|
39
|
+
header?: JSX.Element;
|
|
46
40
|
|
|
47
41
|
/** Compound sub-components (ItemTemplate, Filter) */
|
|
48
42
|
children?: JSX.Element;
|
|
@@ -57,8 +51,6 @@ export interface SharedDataSelectListProps<TItem> {
|
|
|
57
51
|
|
|
58
52
|
const containerClass = clsx("flex-col gap-1");
|
|
59
53
|
|
|
60
|
-
const headerClass = clsx("flex items-center gap-1 px-2 py-1 text-sm font-semibold");
|
|
61
|
-
|
|
62
54
|
// ─── Component ───────────────────────────────────────────
|
|
63
55
|
|
|
64
56
|
export interface SharedDataSelectListComponent {
|
|
@@ -83,10 +75,8 @@ export const SharedDataSelectList: SharedDataSelectListComponent = (<TItem,>(
|
|
|
83
75
|
"canChange",
|
|
84
76
|
"pageSize",
|
|
85
77
|
"header",
|
|
86
|
-
"modal",
|
|
87
78
|
]);
|
|
88
79
|
|
|
89
|
-
const dialog = useDialog();
|
|
90
80
|
const i18n = useI18nOptional();
|
|
91
81
|
|
|
92
82
|
// ─── Slot signals ──────────────────────────────────────
|
|
@@ -192,13 +182,6 @@ export const SharedDataSelectList: SharedDataSelectListComponent = (<TItem,>(
|
|
|
192
182
|
}
|
|
193
183
|
};
|
|
194
184
|
|
|
195
|
-
// ─── Open modal ────────────────────────────────────────
|
|
196
|
-
|
|
197
|
-
const handleOpenModal = async () => {
|
|
198
|
-
if (!local.modal) return;
|
|
199
|
-
await dialog.show(local.modal, {});
|
|
200
|
-
};
|
|
201
|
-
|
|
202
185
|
// ─── Render ────────────────────────────────────────────
|
|
203
186
|
|
|
204
187
|
return (
|
|
@@ -212,26 +195,18 @@ export const SharedDataSelectList: SharedDataSelectListComponent = (<TItem,>(
|
|
|
212
195
|
style={local.style}
|
|
213
196
|
>
|
|
214
197
|
{/* Header */}
|
|
215
|
-
<Show when={local.header != null
|
|
216
|
-
<div class={headerClass}>
|
|
217
|
-
<Show when={local.header != null}>{local.header}</Show>
|
|
218
|
-
<Show when={local.modal != null}>
|
|
219
|
-
<Button size="sm" onClick={() => void handleOpenModal()}>
|
|
220
|
-
<Icon icon={IconExternalLink} />
|
|
221
|
-
</Button>
|
|
222
|
-
</Show>
|
|
223
|
-
</div>
|
|
224
|
-
</Show>
|
|
198
|
+
<Show when={local.header != null}>{local.header}</Show>
|
|
225
199
|
|
|
226
200
|
{/* Search input: when Filter compound is absent and getSearchText exists */}
|
|
227
201
|
<Show when={!filter() && local.data.getSearchText}>
|
|
228
|
-
<
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
202
|
+
<div class={"p-1"}>
|
|
203
|
+
<TextInput
|
|
204
|
+
value={searchText()}
|
|
205
|
+
onValueChange={setSearchText}
|
|
206
|
+
placeholder={i18n?.t("sharedDataSelectList.searchPlaceholder") ?? "Search..."}
|
|
207
|
+
class={"w-full"}
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
235
210
|
</Show>
|
|
236
211
|
|
|
237
212
|
{/* Custom Filter */}
|
|
@@ -44,11 +44,7 @@ const searchInputClass = clsx(
|
|
|
44
44
|
"w-full",
|
|
45
45
|
"rounded-none",
|
|
46
46
|
"border-0 border-b",
|
|
47
|
-
borderSubtle
|
|
48
|
-
"bg-transparent dark:bg-transparent",
|
|
49
|
-
"h-auto",
|
|
50
|
-
"py-1.5",
|
|
51
|
-
"text-sm",
|
|
47
|
+
borderSubtle
|
|
52
48
|
);
|
|
53
49
|
|
|
54
50
|
// Select all/deselect all button area styles
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Component, ParentComponent } from "solid-js";
|
|
2
|
-
import { type Accessor,
|
|
2
|
+
import { type Accessor, createMemo, createRoot, useContext } from "solid-js";
|
|
3
|
+
import { createHmrSafeContext } from "./createHmrSafeContext";
|
|
3
4
|
import type { IconProps } from "@tabler/icons-solidjs";
|
|
4
5
|
|
|
5
6
|
// ── Input Types ──
|
|
@@ -497,7 +498,7 @@ export function createAppStructure<TModule, const TItems extends AppStructureIte
|
|
|
497
498
|
} {
|
|
498
499
|
type TRet = AppStructure<TModule> & { perms: InferPerms<TItems> };
|
|
499
500
|
|
|
500
|
-
const Ctx =
|
|
501
|
+
const Ctx = createHmrSafeContext<TRet>("AppStructure");
|
|
501
502
|
|
|
502
503
|
const AppStructureProvider: ParentComponent = (props) => {
|
|
503
504
|
const structure = buildAppStructure(getOpts());
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Context, createContext } from "solid-js";
|
|
2
|
+
|
|
3
|
+
const CACHE_KEY = "__simplysm_ctx__";
|
|
4
|
+
const cache = ((globalThis as unknown as Record<string, Record<string, unknown>>)[CACHE_KEY] ??= {});
|
|
5
|
+
|
|
6
|
+
export function createHmrSafeContext<TValue>(key: string): Context<TValue | undefined> {
|
|
7
|
+
return (cache[key] ??= createContext<TValue>()) as Context<TValue | undefined>;
|
|
8
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type JSX,
|
|
3
3
|
type ParentComponent,
|
|
4
|
-
createContext,
|
|
5
4
|
createMemo,
|
|
6
5
|
splitProps,
|
|
7
6
|
useContext,
|
|
8
7
|
} from "solid-js";
|
|
8
|
+
import { createHmrSafeContext } from "../helpers/createHmrSafeContext";
|
|
9
9
|
import { twMerge } from "tailwind-merge";
|
|
10
10
|
import { createControllableSignal } from "./createControllableSignal";
|
|
11
11
|
import { Invalid } from "../components/form-control/Invalid";
|
|
@@ -106,7 +106,9 @@ export function createSelectionGroup(config: MultiGroupConfig | SingleGroupConfi
|
|
|
106
106
|
Item: <TValue = unknown>(props: SelectionGroupItemProps<TValue>) => JSX.Element;
|
|
107
107
|
};
|
|
108
108
|
} {
|
|
109
|
-
const Context =
|
|
109
|
+
const Context = createHmrSafeContext<MultiSelectContext | SingleSelectContext>(
|
|
110
|
+
`SelectionGroup_${config.contextName}`,
|
|
111
|
+
);
|
|
110
112
|
const ItemComponent = config.ItemComponent;
|
|
111
113
|
|
|
112
114
|
function ItemInner<TValue>(props: SelectionGroupItemProps<TValue>) {
|
|
@@ -4,9 +4,9 @@ import { createSignal } from "solid-js";
|
|
|
4
4
|
import { IconCheck } from "@tabler/icons-solidjs";
|
|
5
5
|
import { List } from "../../../src/components/data/list/List";
|
|
6
6
|
|
|
7
|
-
describe("List
|
|
7
|
+
describe("List component", () => {
|
|
8
8
|
describe("basic rendering", () => {
|
|
9
|
-
it("children
|
|
9
|
+
it("renders children inside List", () => {
|
|
10
10
|
const { getByText } = render(() => (
|
|
11
11
|
<List>
|
|
12
12
|
<List.Item>Item 1</List.Item>
|
|
@@ -16,7 +16,7 @@ describe("List 컴포넌트", () => {
|
|
|
16
16
|
expect(getByText("Item 1")).toBeTruthy();
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
it("role=tree
|
|
19
|
+
it("applies role=tree", () => {
|
|
20
20
|
const { getByRole } = render(() => (
|
|
21
21
|
<List>
|
|
22
22
|
<List.Item>Item</List.Item>
|
|
@@ -26,7 +26,7 @@ describe("List 컴포넌트", () => {
|
|
|
26
26
|
expect(getByRole("tree")).toBeTruthy();
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
it("data-list
|
|
29
|
+
it("applies data-list attribute", () => {
|
|
30
30
|
const { container } = render(() => (
|
|
31
31
|
<List>
|
|
32
32
|
<List.Item>Item</List.Item>
|
|
@@ -37,8 +37,8 @@ describe("List 컴포넌트", () => {
|
|
|
37
37
|
});
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
describe("inset
|
|
41
|
-
it("
|
|
40
|
+
describe("inset prop", () => {
|
|
41
|
+
it("applies different styles based on inset prop", () => {
|
|
42
42
|
const { container: defaultContainer } = render(() => (
|
|
43
43
|
<List>
|
|
44
44
|
<List.Item>Item</List.Item>
|
|
@@ -57,8 +57,8 @@ describe("List 컴포넌트", () => {
|
|
|
57
57
|
});
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
describe("
|
|
61
|
-
it("ArrowDown
|
|
60
|
+
describe("keyboard navigation", () => {
|
|
61
|
+
it("ArrowDown moves focus to next item", () => {
|
|
62
62
|
const { getAllByRole } = render(() => (
|
|
63
63
|
<List>
|
|
64
64
|
<List.Item>Item 1</List.Item>
|
|
@@ -75,7 +75,7 @@ describe("List 컴포넌트", () => {
|
|
|
75
75
|
expect(document.activeElement).toBe(items[1]);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
it("ArrowUp
|
|
78
|
+
it("ArrowUp moves focus to previous item", () => {
|
|
79
79
|
const { getAllByRole } = render(() => (
|
|
80
80
|
<List>
|
|
81
81
|
<List.Item>Item 1</List.Item>
|
|
@@ -92,7 +92,7 @@ describe("List 컴포넌트", () => {
|
|
|
92
92
|
expect(document.activeElement).toBe(items[0]);
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
it("Home
|
|
95
|
+
it("Home moves focus to first item", () => {
|
|
96
96
|
const { getAllByRole } = render(() => (
|
|
97
97
|
<List>
|
|
98
98
|
<List.Item>Item 1</List.Item>
|
|
@@ -109,7 +109,7 @@ describe("List 컴포넌트", () => {
|
|
|
109
109
|
expect(document.activeElement).toBe(items[0]);
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
-
it("End
|
|
112
|
+
it("End moves focus to last item", () => {
|
|
113
113
|
const { getAllByRole } = render(() => (
|
|
114
114
|
<List>
|
|
115
115
|
<List.Item>Item 1</List.Item>
|
|
@@ -126,7 +126,7 @@ describe("List 컴포넌트", () => {
|
|
|
126
126
|
expect(document.activeElement).toBe(items[2]);
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
-
it("Space
|
|
129
|
+
it("Space toggles item", () => {
|
|
130
130
|
const { getAllByRole } = render(() => (
|
|
131
131
|
<List>
|
|
132
132
|
<List.Item>
|
|
@@ -148,7 +148,7 @@ describe("List 컴포넌트", () => {
|
|
|
148
148
|
expect(items[0].getAttribute("aria-expanded")).toBe("true");
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
-
it("Enter
|
|
151
|
+
it("Enter toggles item", () => {
|
|
152
152
|
const { getAllByRole } = render(() => (
|
|
153
153
|
<List>
|
|
154
154
|
<List.Item>
|
|
@@ -168,7 +168,7 @@ describe("List 컴포넌트", () => {
|
|
|
168
168
|
expect(items[0].getAttribute("aria-expanded")).toBe("true");
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
-
it("ArrowRight
|
|
171
|
+
it("ArrowRight opens a closed item", () => {
|
|
172
172
|
const { getAllByRole } = render(() => (
|
|
173
173
|
<List>
|
|
174
174
|
<List.Item>
|
|
@@ -190,7 +190,7 @@ describe("List 컴포넌트", () => {
|
|
|
190
190
|
expect(items[0].getAttribute("aria-expanded")).toBe("true");
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
-
it("ArrowRight
|
|
193
|
+
it("ArrowRight moves focus to first child when item is open", () => {
|
|
194
194
|
const { getAllByRole } = render(() => (
|
|
195
195
|
<List>
|
|
196
196
|
<List.Item open>
|
|
@@ -210,7 +210,7 @@ describe("List 컴포넌트", () => {
|
|
|
210
210
|
expect(document.activeElement).toBe(items[1]);
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
-
it("ArrowLeft
|
|
213
|
+
it("ArrowLeft closes an open item", () => {
|
|
214
214
|
const { getAllByRole } = render(() => (
|
|
215
215
|
<List>
|
|
216
216
|
<List.Item open>
|
|
@@ -232,7 +232,7 @@ describe("List 컴포넌트", () => {
|
|
|
232
232
|
expect(items[0].getAttribute("aria-expanded")).toBe("false");
|
|
233
233
|
});
|
|
234
234
|
|
|
235
|
-
it("ArrowLeft
|
|
235
|
+
it("ArrowLeft moves focus to parent from a closed item", () => {
|
|
236
236
|
const { getAllByRole } = render(() => (
|
|
237
237
|
<List>
|
|
238
238
|
<List.Item open>
|
|
@@ -254,9 +254,9 @@ describe("List 컴포넌트", () => {
|
|
|
254
254
|
});
|
|
255
255
|
});
|
|
256
256
|
|
|
257
|
-
describe("List.Item
|
|
257
|
+
describe("List.Item component", () => {
|
|
258
258
|
describe("basic rendering", () => {
|
|
259
|
-
it("children
|
|
259
|
+
it("renders children inside List.Item", () => {
|
|
260
260
|
const { getByText } = render(() => (
|
|
261
261
|
<List>
|
|
262
262
|
<List.Item>Test Item</List.Item>
|
|
@@ -266,7 +266,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
266
266
|
expect(getByText("Test Item")).toBeTruthy();
|
|
267
267
|
});
|
|
268
268
|
|
|
269
|
-
it("role=treeitem
|
|
269
|
+
it("applies role=treeitem", () => {
|
|
270
270
|
const { getByRole } = render(() => (
|
|
271
271
|
<List>
|
|
272
272
|
<List.Item>Item</List.Item>
|
|
@@ -276,7 +276,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
276
276
|
expect(getByRole("treeitem")).toBeTruthy();
|
|
277
277
|
});
|
|
278
278
|
|
|
279
|
-
it("data-list-item
|
|
279
|
+
it("applies data-list-item attribute", () => {
|
|
280
280
|
const { container } = render(() => (
|
|
281
281
|
<List>
|
|
282
282
|
<List.Item>Item</List.Item>
|
|
@@ -287,8 +287,8 @@ describe("List.Item 컴포넌트", () => {
|
|
|
287
287
|
});
|
|
288
288
|
});
|
|
289
289
|
|
|
290
|
-
describe("
|
|
291
|
-
it("List.Item.Children
|
|
290
|
+
describe("nested list", () => {
|
|
291
|
+
it("toggles collapse on click when List.Item.Children is present", () => {
|
|
292
292
|
const { getByRole } = render(() => (
|
|
293
293
|
<List>
|
|
294
294
|
<List.Item>
|
|
@@ -313,7 +313,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
313
313
|
expect(item.getAttribute("aria-expanded")).toBe("false");
|
|
314
314
|
});
|
|
315
315
|
|
|
316
|
-
it("List.Item.Children
|
|
316
|
+
it("shows chevron icon when List.Item.Children is present", () => {
|
|
317
317
|
const { container } = render(() => (
|
|
318
318
|
<List>
|
|
319
319
|
<List.Item>
|
|
@@ -329,7 +329,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
329
329
|
expect(svg).toBeTruthy();
|
|
330
330
|
});
|
|
331
331
|
|
|
332
|
-
it("List.Item.Children
|
|
332
|
+
it("hides chevron icon when List.Item.Children is absent", () => {
|
|
333
333
|
const { container } = render(() => (
|
|
334
334
|
<List>
|
|
335
335
|
<List.Item>Simple Item</List.Item>
|
|
@@ -341,7 +341,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
341
341
|
expect(svg).toBeFalsy();
|
|
342
342
|
});
|
|
343
343
|
|
|
344
|
-
it("aria-expanded
|
|
344
|
+
it("no aria-expanded means no List.Item.Children", () => {
|
|
345
345
|
const { getByRole } = render(() => (
|
|
346
346
|
<List>
|
|
347
347
|
<List.Item>Simple Item</List.Item>
|
|
@@ -352,7 +352,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
352
352
|
expect(item.hasAttribute("aria-expanded")).toBe(false);
|
|
353
353
|
});
|
|
354
354
|
|
|
355
|
-
it("List.Item.Children
|
|
355
|
+
it("applies role=group inside List.Item.Children", () => {
|
|
356
356
|
const { getByRole } = render(() => (
|
|
357
357
|
<List>
|
|
358
358
|
<List.Item open>
|
|
@@ -368,8 +368,8 @@ describe("List.Item 컴포넌트", () => {
|
|
|
368
368
|
});
|
|
369
369
|
});
|
|
370
370
|
|
|
371
|
-
describe("selected
|
|
372
|
-
it("
|
|
371
|
+
describe("selected state", () => {
|
|
372
|
+
it("applies different styles based on selected prop", () => {
|
|
373
373
|
const { container: defaultContainer } = render(() => (
|
|
374
374
|
<List>
|
|
375
375
|
<List.Item>Item</List.Item>
|
|
@@ -389,7 +389,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
389
389
|
expect(defaultClass).not.toBe(selectedClass);
|
|
390
390
|
});
|
|
391
391
|
|
|
392
|
-
it("aria-selected
|
|
392
|
+
it("sets aria-selected", () => {
|
|
393
393
|
const { getByRole } = render(() => (
|
|
394
394
|
<List>
|
|
395
395
|
<List.Item selected>Selected Item</List.Item>
|
|
@@ -401,8 +401,8 @@ describe("List.Item 컴포넌트", () => {
|
|
|
401
401
|
});
|
|
402
402
|
});
|
|
403
403
|
|
|
404
|
-
describe("readonly
|
|
405
|
-
it("
|
|
404
|
+
describe("readonly state", () => {
|
|
405
|
+
it("does not call onClick when readonly=true", () => {
|
|
406
406
|
const onClick = vi.fn();
|
|
407
407
|
const { getByRole } = render(() => (
|
|
408
408
|
<List>
|
|
@@ -418,7 +418,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
418
418
|
expect(onClick).not.toHaveBeenCalled();
|
|
419
419
|
});
|
|
420
420
|
|
|
421
|
-
it("
|
|
421
|
+
it("applies different styles based on readonly prop", () => {
|
|
422
422
|
const { container: defaultContainer } = render(() => (
|
|
423
423
|
<List>
|
|
424
424
|
<List.Item>Item</List.Item>
|
|
@@ -440,7 +440,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
440
440
|
});
|
|
441
441
|
|
|
442
442
|
describe("disabled state", () => {
|
|
443
|
-
it("
|
|
443
|
+
it("applies different styles based on disabled prop", () => {
|
|
444
444
|
const { container: defaultContainer } = render(() => (
|
|
445
445
|
<List>
|
|
446
446
|
<List.Item>Item</List.Item>
|
|
@@ -460,7 +460,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
460
460
|
expect(defaultClass).not.toBe(disabledClass);
|
|
461
461
|
});
|
|
462
462
|
|
|
463
|
-
it("disabled=true
|
|
463
|
+
it("not clickable when disabled=true", () => {
|
|
464
464
|
const onClick = vi.fn();
|
|
465
465
|
const { getByRole } = render(() => (
|
|
466
466
|
<List>
|
|
@@ -476,7 +476,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
476
476
|
expect(onClick).not.toHaveBeenCalled();
|
|
477
477
|
});
|
|
478
478
|
|
|
479
|
-
it("
|
|
479
|
+
it("applies tabindex=-1 when disabled=true", () => {
|
|
480
480
|
const { getByRole } = render(() => (
|
|
481
481
|
<List>
|
|
482
482
|
<List.Item disabled>Disabled Item</List.Item>
|
|
@@ -487,7 +487,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
487
487
|
expect(item.getAttribute("tabindex")).toBe("-1");
|
|
488
488
|
});
|
|
489
489
|
|
|
490
|
-
it("aria-disabled
|
|
490
|
+
it("sets aria-disabled", () => {
|
|
491
491
|
const { getByRole } = render(() => (
|
|
492
492
|
<List>
|
|
493
493
|
<List.Item disabled>Disabled Item</List.Item>
|
|
@@ -500,7 +500,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
500
500
|
});
|
|
501
501
|
|
|
502
502
|
describe("selectedIcon", () => {
|
|
503
|
-
it("selectedIcon
|
|
503
|
+
it("shows icon when selectedIcon is provided and no List.Item.Children", () => {
|
|
504
504
|
const { container } = render(() => (
|
|
505
505
|
<List>
|
|
506
506
|
<List.Item selectedIcon={IconCheck}>Item</List.Item>
|
|
@@ -511,7 +511,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
511
511
|
expect(svg).toBeTruthy();
|
|
512
512
|
});
|
|
513
513
|
|
|
514
|
-
it("
|
|
514
|
+
it("applies different icon styles based on selectedIcon and selected state", () => {
|
|
515
515
|
const { container: unselectedContainer } = render(() => (
|
|
516
516
|
<List>
|
|
517
517
|
<List.Item selectedIcon={IconCheck}>Item</List.Item>
|
|
@@ -531,7 +531,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
531
531
|
expect(unselectedSvg?.className).not.toBe(selectedSvg?.className);
|
|
532
532
|
});
|
|
533
533
|
|
|
534
|
-
it("selectedIcon
|
|
534
|
+
it("hides selectedIcon when List.Item.Children is present", () => {
|
|
535
535
|
const { container } = render(() => (
|
|
536
536
|
<List>
|
|
537
537
|
<List.Item selectedIcon={IconCheck}>
|
|
@@ -546,13 +546,13 @@ describe("List.Item 컴포넌트", () => {
|
|
|
546
546
|
const button = container.querySelector("[data-list-item]") as HTMLElement;
|
|
547
547
|
const svgs = button.querySelectorAll("svg");
|
|
548
548
|
|
|
549
|
-
// chevron
|
|
549
|
+
// only chevron should be present (selectedIcon is hidden)
|
|
550
550
|
expect(svgs.length).toBe(1);
|
|
551
551
|
});
|
|
552
552
|
});
|
|
553
553
|
|
|
554
554
|
describe("onClick", () => {
|
|
555
|
-
it("
|
|
555
|
+
it("calls onClick on click when no List.Item.Children", () => {
|
|
556
556
|
const onClick = vi.fn();
|
|
557
557
|
const { getByRole } = render(() => (
|
|
558
558
|
<List>
|
|
@@ -566,7 +566,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
566
566
|
expect(onClick).toHaveBeenCalledTimes(1);
|
|
567
567
|
});
|
|
568
568
|
|
|
569
|
-
it("
|
|
569
|
+
it("toggles collapse instead of onClick when List.Item.Children is present", () => {
|
|
570
570
|
const onClick = vi.fn();
|
|
571
571
|
const { getByRole } = render(() => (
|
|
572
572
|
<List>
|
|
@@ -587,8 +587,8 @@ describe("List.Item 컴포넌트", () => {
|
|
|
587
587
|
});
|
|
588
588
|
});
|
|
589
589
|
|
|
590
|
-
describe("controlled
|
|
591
|
-
it("open
|
|
590
|
+
describe("controlled mode", () => {
|
|
591
|
+
it("operates in controlled mode when open and onOpenChange are provided", () => {
|
|
592
592
|
const onOpenChange = vi.fn();
|
|
593
593
|
|
|
594
594
|
const { getByRole } = render(() => (
|
|
@@ -606,11 +606,11 @@ describe("List.Item 컴포넌트", () => {
|
|
|
606
606
|
fireEvent.click(item);
|
|
607
607
|
|
|
608
608
|
expect(onOpenChange).toHaveBeenCalledWith(true);
|
|
609
|
-
// controlled
|
|
609
|
+
// controlled mode: actual state does not change
|
|
610
610
|
expect(item.getAttribute("aria-expanded")).toBe("false");
|
|
611
611
|
});
|
|
612
612
|
|
|
613
|
-
it("
|
|
613
|
+
it("reflects state change when open prop changes", () => {
|
|
614
614
|
const [open, setOpen] = createSignal(false);
|
|
615
615
|
|
|
616
616
|
const { getByRole } = render(() => (
|
|
@@ -635,7 +635,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
635
635
|
});
|
|
636
636
|
|
|
637
637
|
describe("aria-level", () => {
|
|
638
|
-
it("aria-level
|
|
638
|
+
it("sets aria-level based on nesting depth", () => {
|
|
639
639
|
const { getAllByRole } = render(() => (
|
|
640
640
|
<List>
|
|
641
641
|
<List.Item open>
|
|
@@ -661,7 +661,7 @@ describe("List.Item 컴포넌트", () => {
|
|
|
661
661
|
});
|
|
662
662
|
|
|
663
663
|
describe("Roving tabindex", () => {
|
|
664
|
-
it("
|
|
664
|
+
it("only focused item gets tabindex=0", () => {
|
|
665
665
|
const { getAllByRole } = render(() => (
|
|
666
666
|
<List>
|
|
667
667
|
<List.Item>Item 1</List.Item>
|
|
@@ -672,15 +672,15 @@ describe("List.Item 컴포넌트", () => {
|
|
|
672
672
|
|
|
673
673
|
const items = getAllByRole("treeitem");
|
|
674
674
|
|
|
675
|
-
//
|
|
675
|
+
// initial state: all items have tabindex=0
|
|
676
676
|
items.forEach((item) => {
|
|
677
677
|
expect(item.getAttribute("tabindex")).toBe("0");
|
|
678
678
|
});
|
|
679
679
|
|
|
680
|
-
//
|
|
680
|
+
// focus second item
|
|
681
681
|
fireEvent.focus(items[1]);
|
|
682
682
|
|
|
683
|
-
//
|
|
683
|
+
// only focused item has tabindex=0, others are -1
|
|
684
684
|
expect(items[0].getAttribute("tabindex")).toBe("-1");
|
|
685
685
|
expect(items[1].getAttribute("tabindex")).toBe("0");
|
|
686
686
|
expect(items[2].getAttribute("tabindex")).toBe("-1");
|