@eccenca/gui-elements 23.6.0 → 23.7.0-rc.1
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/CHANGELOG.md +22 -0
- package/dist/cjs/cmem/markdown/Markdown.js +10 -2
- package/dist/cjs/cmem/markdown/Markdown.js.map +1 -1
- package/dist/cjs/cmem/react-flow/StickyNoteModal/StickyNoteModal.js +4 -4
- package/dist/cjs/cmem/react-flow/StickyNoteModal/StickyNoteModal.js.map +1 -1
- package/dist/cjs/common/index.js +2 -0
- package/dist/cjs/common/index.js.map +1 -1
- package/dist/cjs/common/utils/getScrollParent.js +24 -0
- package/dist/cjs/common/utils/getScrollParent.js.map +1 -0
- package/dist/cjs/components/AutocompleteField/AutoCompleteField.js +19 -2
- package/dist/cjs/components/AutocompleteField/AutoCompleteField.js.map +1 -1
- package/dist/cjs/components/MultiSelect/MultiSelect.js +66 -41
- package/dist/cjs/components/MultiSelect/MultiSelect.js.map +1 -1
- package/dist/cjs/components/Sticky/StickyTarget.js +85 -0
- package/dist/cjs/components/Sticky/StickyTarget.js.map +1 -0
- package/dist/cjs/components/Sticky/index.js +14 -0
- package/dist/cjs/components/Sticky/index.js.map +1 -0
- package/dist/cjs/components/index.js +1 -0
- package/dist/cjs/components/index.js.map +1 -1
- package/dist/cjs/extensions/codemirror/CodeMirror.js +1 -1
- package/dist/cjs/extensions/codemirror/CodeMirror.js.map +1 -1
- package/dist/cjs/extensions/react-flow/edges/EdgeDefault.js +7 -7
- package/dist/cjs/extensions/react-flow/edges/EdgeDefault.js.map +1 -1
- package/dist/cjs/extensions/react-flow/edges/EdgeLabel.js +3 -2
- package/dist/cjs/extensions/react-flow/edges/EdgeLabel.js.map +1 -1
- package/dist/cjs/extensions/react-flow/handles/HandleContent.js +14 -2
- package/dist/cjs/extensions/react-flow/handles/HandleContent.js.map +1 -1
- package/dist/cjs/extensions/react-flow/handles/HandleTools.js +1 -1
- package/dist/cjs/extensions/react-flow/handles/HandleTools.js.map +1 -1
- package/dist/cjs/extensions/react-flow/minimap/MiniMap.js +6 -8
- package/dist/cjs/extensions/react-flow/minimap/MiniMap.js.map +1 -1
- package/dist/esm/cmem/markdown/Markdown.js +10 -2
- package/dist/esm/cmem/markdown/Markdown.js.map +1 -1
- package/dist/esm/cmem/react-flow/StickyNoteModal/StickyNoteModal.js +5 -5
- package/dist/esm/cmem/react-flow/StickyNoteModal/StickyNoteModal.js.map +1 -1
- package/dist/esm/common/index.js +2 -0
- package/dist/esm/common/index.js.map +1 -1
- package/dist/esm/common/utils/getScrollParent.js +20 -0
- package/dist/esm/common/utils/getScrollParent.js.map +1 -0
- package/dist/esm/components/AutocompleteField/AutoCompleteField.js +27 -2
- package/dist/esm/components/AutocompleteField/AutoCompleteField.js.map +1 -1
- package/dist/esm/components/MultiSelect/MultiSelect.js +72 -52
- package/dist/esm/components/MultiSelect/MultiSelect.js.map +1 -1
- package/dist/esm/components/Sticky/StickyTarget.js +89 -0
- package/dist/esm/components/Sticky/StickyTarget.js.map +1 -0
- package/dist/esm/components/Sticky/index.js +2 -0
- package/dist/esm/components/Sticky/index.js.map +1 -0
- package/dist/esm/components/index.js +1 -0
- package/dist/esm/components/index.js.map +1 -1
- package/dist/esm/extensions/codemirror/CodeMirror.js +1 -1
- package/dist/esm/extensions/codemirror/CodeMirror.js.map +1 -1
- package/dist/esm/extensions/react-flow/edges/EdgeDefault.js +7 -7
- package/dist/esm/extensions/react-flow/edges/EdgeDefault.js.map +1 -1
- package/dist/esm/extensions/react-flow/edges/EdgeLabel.js +3 -2
- package/dist/esm/extensions/react-flow/edges/EdgeLabel.js.map +1 -1
- package/dist/esm/extensions/react-flow/handles/HandleContent.js +13 -2
- package/dist/esm/extensions/react-flow/handles/HandleContent.js.map +1 -1
- package/dist/esm/extensions/react-flow/handles/HandleTools.js +1 -1
- package/dist/esm/extensions/react-flow/handles/HandleTools.js.map +1 -1
- package/dist/esm/extensions/react-flow/minimap/MiniMap.js +6 -8
- package/dist/esm/extensions/react-flow/minimap/MiniMap.js.map +1 -1
- package/dist/types/cmem/react-flow/StickyNoteModal/StickyNoteModal.d.ts +5 -0
- package/dist/types/cmem/react-flow/configuration/graph.d.ts +9 -9
- package/dist/types/cmem/react-flow/configuration/unspecified.d.ts +2 -2
- package/dist/types/common/index.d.ts +2 -0
- package/dist/types/common/utils/getScrollParent.d.ts +9 -0
- package/dist/types/components/AutocompleteField/AutoCompleteField.d.ts +2 -0
- package/dist/types/components/MultiSelect/MultiSelect.d.ts +1 -1
- package/dist/types/components/Sticky/StickyTarget.d.ts +32 -0
- package/dist/types/components/Sticky/index.d.ts +1 -0
- package/dist/types/components/index.d.ts +1 -0
- package/dist/types/extensions/react-flow/edges/EdgeDefault.d.ts +6 -1
- package/dist/types/extensions/react-flow/edges/EdgeLabel.d.ts +1 -1
- package/dist/types/extensions/react-flow/edges/edgeTypes.d.ts +10 -10
- package/dist/types/extensions/react-flow/handles/HandleContent.d.ts +2 -3
- package/dist/types/extensions/react-flow/minimap/MiniMap.d.ts +12 -1
- package/package.json +3 -1
- package/src/cmem/markdown/Markdown.stories.tsx +8 -1
- package/src/cmem/markdown/Markdown.tsx +22 -1
- package/src/cmem/react-flow/ReactFlow/ReactFlow.stories.tsx +10 -4
- package/src/cmem/react-flow/StickyNoteModal/StickyNoteModal.tsx +8 -3
- package/src/common/index.ts +3 -0
- package/src/common/utils/getScrollParent.ts +20 -0
- package/src/components/AutocompleteField/AutoCompleteField.tsx +28 -0
- package/src/components/AutocompleteField/autocompletefield.scss +1 -1
- package/src/components/MultiSelect/MultiSelect.tsx +72 -47
- package/src/components/MultiSuggestField/MultiSuggestField.stories.tsx +146 -26
- package/src/components/MultiSuggestField/tests/MultiSuggestField.test.tsx +363 -61
- package/src/components/Sticky/StickyTarget.tsx +119 -0
- package/src/components/Sticky/index.ts +1 -0
- package/src/components/Sticky/sticky.scss +69 -0
- package/src/components/Sticky/stories/StickyTarget.stories.tsx +63 -0
- package/src/components/index.scss +1 -0
- package/src/components/index.ts +1 -0
- package/src/extensions/codemirror/CodeMirror.tsx +1 -1
- package/src/extensions/react-flow/edges/EdgeDefault.tsx +70 -62
- package/src/extensions/react-flow/edges/EdgeLabel.tsx +14 -2
- package/src/extensions/react-flow/edges/stories/EdgeDefault.stories.tsx +11 -5
- package/src/extensions/react-flow/edges/stories/EdgeLabel.stories.tsx +2 -0
- package/src/extensions/react-flow/handles/HandleContent.tsx +28 -25
- package/src/extensions/react-flow/handles/HandleTools.tsx +1 -0
- package/src/extensions/react-flow/handles/stories/HandleDefault.stories.tsx +5 -1
- package/src/extensions/react-flow/minimap/MiniMap.stories.tsx +62 -0
- package/src/extensions/react-flow/minimap/MiniMap.tsx +23 -7
- package/src/extensions/react-flow/nodes/stories/NodeContent.stories.tsx +2 -0
- package/src/extensions/react-flow/nodes/stories/NodeContentExtension.stories.tsx +2 -0
- package/src/extensions/react-flow/nodes/stories/NodeDefault.stories.tsx +0 -1
|
@@ -1,105 +1,407 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useCallback, useState } from "react";
|
|
2
2
|
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
3
3
|
|
|
4
4
|
import "@testing-library/jest-dom";
|
|
5
5
|
|
|
6
6
|
import { MultiSuggestField } from "../MultiSuggestField";
|
|
7
|
-
import { Default, dropdownOnFocus,
|
|
7
|
+
import { Default, dropdownOnFocus, predefinedNotControlledValues } from "../MultiSuggestField.stories";
|
|
8
|
+
|
|
9
|
+
const testLabels = ["label1", "label2", "label3", "label4", "label5"];
|
|
10
|
+
|
|
11
|
+
const items = new Array(5).fill(undefined).map((_, id) => {
|
|
12
|
+
const testLabel = testLabels[id];
|
|
13
|
+
return { testLabel, testId: `${testLabel}-id` };
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const TestComponent = (): JSX.Element => {
|
|
17
|
+
const copy: Array<{ testLabel: string; testId: string }> = [items[2]];
|
|
18
|
+
|
|
19
|
+
const [selected, setSelected] = useState(copy);
|
|
20
|
+
|
|
21
|
+
const handleOnSelect = useCallback((params) => {
|
|
22
|
+
const items = params.selectedItems;
|
|
23
|
+
setSelected(items);
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
const handleReset = (): void => {
|
|
27
|
+
setSelected(copy);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div>
|
|
32
|
+
<button data-testid="reset-button" onClick={handleReset}>
|
|
33
|
+
Reset
|
|
34
|
+
</button>
|
|
35
|
+
<br />
|
|
36
|
+
<br />
|
|
37
|
+
<MultiSuggestField<{ testLabel: string; testId: string }>
|
|
38
|
+
items={items}
|
|
39
|
+
createNewItemFromQuery={(query) => ({ testId: `${query}-id`, testLabel: query })}
|
|
40
|
+
onSelection={handleOnSelect}
|
|
41
|
+
itemId={({ testId }) => testId}
|
|
42
|
+
itemLabel={({ testLabel }) => testLabel}
|
|
43
|
+
selectedItems={selected}
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
8
48
|
|
|
9
49
|
describe("MultiSuggestField", () => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
50
|
+
describe("uncontrolled", () => {
|
|
51
|
+
it("should render default input", () => {
|
|
52
|
+
const { container } = render(<MultiSuggestField {...Default.args} />);
|
|
53
|
+
const [input] = container.getElementsByClassName("eccgui-multiselect");
|
|
13
54
|
|
|
14
|
-
|
|
15
|
-
|
|
55
|
+
expect(input).toBeInTheDocument();
|
|
56
|
+
});
|
|
16
57
|
|
|
17
|
-
|
|
18
|
-
|
|
58
|
+
it("should render default selected items", async () => {
|
|
59
|
+
const { getByText } = render(<MultiSuggestField {...predefinedNotControlledValues.args} />);
|
|
19
60
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
61
|
+
const [firstSelected, secondSelected]: Array<string> = predefinedNotControlledValues.args.selectedItems.map(
|
|
62
|
+
({ testLabel }) => testLabel
|
|
63
|
+
);
|
|
23
64
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
65
|
+
await waitFor(() => {
|
|
66
|
+
expect(getByText(firstSelected)).toBeInTheDocument();
|
|
67
|
+
expect(getByText(secondSelected)).toBeInTheDocument();
|
|
68
|
+
});
|
|
27
69
|
});
|
|
28
|
-
});
|
|
29
70
|
|
|
30
|
-
|
|
31
|
-
|
|
71
|
+
it("should clear all selected items on clear button click", async () => {
|
|
72
|
+
const { container } = render(
|
|
73
|
+
<MultiSuggestField
|
|
74
|
+
data-test-id="multi-suggest-field"
|
|
75
|
+
{...predefinedNotControlledValues.args}
|
|
76
|
+
onSelection={undefined}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
32
79
|
|
|
33
|
-
|
|
34
|
-
({ testLabel }) => testLabel.trim()
|
|
35
|
-
);
|
|
80
|
+
const selectedLength = predefinedNotControlledValues.args.selectedItems.length;
|
|
36
81
|
|
|
37
|
-
|
|
82
|
+
expect(container.querySelectorAll("[data-tag-index]").length).toBe(selectedLength);
|
|
38
83
|
|
|
39
|
-
|
|
84
|
+
const clearButton = container.querySelector('[data-test-id="clear-all-items"');
|
|
40
85
|
|
|
41
|
-
|
|
86
|
+
expect(clearButton).toBeInTheDocument();
|
|
42
87
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
88
|
+
fireEvent.click(clearButton!);
|
|
89
|
+
|
|
90
|
+
await waitFor(() => {
|
|
91
|
+
expect(container.querySelectorAll("[data-tag-index]").length).toBe(0);
|
|
92
|
+
});
|
|
46
93
|
});
|
|
47
|
-
});
|
|
48
94
|
|
|
49
|
-
|
|
50
|
-
|
|
95
|
+
it("should filter options and reset them if the query is empty", async () => {
|
|
96
|
+
const { container } = render(<MultiSuggestField {...dropdownOnFocus.args} />);
|
|
97
|
+
|
|
98
|
+
const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
|
|
99
|
+
const [input] = inputContainer.getElementsByTagName("input");
|
|
100
|
+
|
|
101
|
+
fireEvent.click(input);
|
|
102
|
+
|
|
103
|
+
await waitFor(() => {
|
|
104
|
+
const listbox = screen.getByRole("listbox");
|
|
105
|
+
expect(listbox).toBeInTheDocument();
|
|
106
|
+
|
|
107
|
+
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
108
|
+
expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
fireEvent.change(input, { target: { value: "ex" } });
|
|
112
|
+
|
|
113
|
+
await waitFor(() => {
|
|
114
|
+
const listbox = screen.getByRole("listbox");
|
|
115
|
+
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
116
|
+
|
|
117
|
+
expect(menuItems.length).toBe(1);
|
|
118
|
+
|
|
119
|
+
const noResult = screen.queryByText(dropdownOnFocus.args.noResultText);
|
|
120
|
+
expect(noResult).not.toBeInTheDocument();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
fireEvent.change(input, { target: { value: "ttt" } });
|
|
51
124
|
|
|
52
|
-
|
|
53
|
-
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
const listbox = screen.getByRole("listbox");
|
|
127
|
+
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
54
128
|
|
|
55
|
-
|
|
129
|
+
expect(menuItems.length).toBe(1);
|
|
56
130
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
131
|
+
const noResult = screen.queryByText(dropdownOnFocus.args.noResultText);
|
|
132
|
+
expect(noResult).toBeInTheDocument();
|
|
133
|
+
});
|
|
60
134
|
|
|
61
|
-
|
|
62
|
-
|
|
135
|
+
fireEvent.change(input, { target: { value: "" } });
|
|
136
|
+
|
|
137
|
+
await waitFor(() => {
|
|
138
|
+
const listbox = screen.getByRole("listbox");
|
|
139
|
+
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
140
|
+
expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
|
|
141
|
+
});
|
|
63
142
|
});
|
|
64
143
|
|
|
65
|
-
|
|
144
|
+
it("should render disable field with selected items", async () => {
|
|
145
|
+
const { container, getByText } = render(
|
|
146
|
+
<MultiSuggestField {...predefinedNotControlledValues.args} disabled />
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const [inputTargetContainer] = container.getElementsByClassName("eccgui-multiselect__target");
|
|
66
150
|
|
|
67
|
-
|
|
68
|
-
const listbox = screen.getByRole("listbox");
|
|
69
|
-
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
151
|
+
expect(inputTargetContainer.getAttribute("aria-disabled")).toBe("true");
|
|
70
152
|
|
|
71
|
-
|
|
153
|
+
const [firstSelected, secondSelected]: Array<string> = predefinedNotControlledValues.args.selectedItems.map(
|
|
154
|
+
({ testLabel }) => testLabel
|
|
155
|
+
);
|
|
72
156
|
|
|
73
|
-
|
|
74
|
-
|
|
157
|
+
await waitFor(() => {
|
|
158
|
+
expect(getByText(firstSelected)).toBeInTheDocument();
|
|
159
|
+
expect(getByText(secondSelected)).toBeInTheDocument();
|
|
160
|
+
});
|
|
75
161
|
});
|
|
76
162
|
|
|
77
|
-
|
|
163
|
+
it("should set deferred selection correctly when only selected items provided and remove selection", async () => {
|
|
164
|
+
const args = { ...predefinedNotControlledValues.args, selectedItems: [] };
|
|
165
|
+
|
|
166
|
+
const { rerender, container } = render(<MultiSuggestField {...args} data-test-id="multi-suggest-field" />);
|
|
167
|
+
|
|
168
|
+
const clearButtonBefore = container.querySelector("[data-test-id='clear-all-items'");
|
|
169
|
+
|
|
170
|
+
expect(clearButtonBefore).not.toBeInTheDocument();
|
|
171
|
+
|
|
172
|
+
const [firstSelected, secondSelected]: Array<string> = predefinedNotControlledValues.args.selectedItems.map(
|
|
173
|
+
({ testLabel }) => testLabel
|
|
174
|
+
);
|
|
78
175
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
176
|
+
await waitFor(() => {
|
|
177
|
+
expect(screen.queryByText(firstSelected)).toBeNull();
|
|
178
|
+
expect(screen.queryByText(secondSelected)).toBeNull();
|
|
179
|
+
});
|
|
82
180
|
|
|
83
|
-
|
|
181
|
+
rerender(<MultiSuggestField {...predefinedNotControlledValues.args} data-test-id="multi-suggest-field" />);
|
|
84
182
|
|
|
85
|
-
|
|
86
|
-
|
|
183
|
+
await waitFor(() => {
|
|
184
|
+
expect(screen.getByText(firstSelected)).toBeInTheDocument();
|
|
185
|
+
expect(screen.getByText(secondSelected)).toBeInTheDocument();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
await waitFor(() => {
|
|
189
|
+
const clearButtonAfter = container.querySelector("[data-test-id='clear-all-items'");
|
|
190
|
+
|
|
191
|
+
expect(clearButtonAfter).toBeInTheDocument();
|
|
192
|
+
|
|
193
|
+
fireEvent.click(clearButtonAfter!);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
await waitFor(() => {
|
|
197
|
+
expect(container.querySelectorAll("[data-tag-index]").length).toBe(0);
|
|
198
|
+
});
|
|
87
199
|
});
|
|
88
200
|
|
|
89
|
-
|
|
201
|
+
it("should render disable field with deferred selected items", async () => {
|
|
202
|
+
const { container, rerender } = render(
|
|
203
|
+
<MultiSuggestField {...predefinedNotControlledValues.args} selectedItems={[]} disabled />
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const [inputTargetContainer] = container.getElementsByClassName("eccgui-multiselect__target");
|
|
90
207
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
208
|
+
expect(inputTargetContainer.getAttribute("aria-disabled")).toBe("true");
|
|
209
|
+
|
|
210
|
+
const [firstSelected, secondSelected]: Array<string> = predefinedNotControlledValues.args.selectedItems.map(
|
|
211
|
+
({ testLabel }) => testLabel
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
await waitFor(() => {
|
|
215
|
+
expect(screen.queryByText(firstSelected)).toBeNull();
|
|
216
|
+
expect(screen.queryByText(secondSelected)).toBeNull();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
rerender(<MultiSuggestField {...predefinedNotControlledValues.args} disabled />);
|
|
220
|
+
|
|
221
|
+
const [updatedInputTargetContainer] = container.getElementsByClassName("eccgui-multiselect__target");
|
|
222
|
+
|
|
223
|
+
expect(updatedInputTargetContainer.getAttribute("aria-disabled")).toBe("true");
|
|
224
|
+
|
|
225
|
+
await waitFor(() => {
|
|
226
|
+
expect(screen.getByText(firstSelected)).toBeInTheDocument();
|
|
227
|
+
expect(screen.getByText(secondSelected)).toBeInTheDocument();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("should call onSelection function with the selected items", async () => {
|
|
232
|
+
const onSelection = jest.fn();
|
|
233
|
+
|
|
234
|
+
const { container } = render(
|
|
235
|
+
<MultiSuggestField {...dropdownOnFocus.args} items={items} onSelection={onSelection} />
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
|
|
239
|
+
const [input] = inputContainer.getElementsByTagName("input");
|
|
240
|
+
|
|
241
|
+
fireEvent.click(input);
|
|
242
|
+
|
|
243
|
+
await waitFor(() => {
|
|
244
|
+
const listbox = screen.getByRole("listbox");
|
|
245
|
+
expect(listbox).toBeInTheDocument();
|
|
246
|
+
|
|
247
|
+
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
248
|
+
expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
|
|
249
|
+
|
|
250
|
+
const item = menuItems[0];
|
|
251
|
+
fireEvent.click(item);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
await waitFor(() => {
|
|
255
|
+
const expectedObject = {
|
|
256
|
+
createdItems: [],
|
|
257
|
+
newlySelected: items[0],
|
|
258
|
+
selectedItems: [items[0]],
|
|
259
|
+
};
|
|
260
|
+
expect(onSelection).toHaveBeenCalledWith(expectedObject);
|
|
261
|
+
});
|
|
95
262
|
});
|
|
96
263
|
});
|
|
97
264
|
|
|
98
|
-
|
|
99
|
-
|
|
265
|
+
describe("controlled", () => {
|
|
266
|
+
it("should render default selected items", async () => {
|
|
267
|
+
const onSelection = jest.fn();
|
|
268
|
+
|
|
269
|
+
const { getByText } = render(
|
|
270
|
+
<MultiSuggestField {...predefinedNotControlledValues.args} onSelection={onSelection} />
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const [firstSelected, secondSelected]: Array<string> = predefinedNotControlledValues.args.selectedItems.map(
|
|
274
|
+
({ testLabel }) => testLabel
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
await waitFor(() => {
|
|
278
|
+
expect(getByText(firstSelected)).toBeInTheDocument();
|
|
279
|
+
expect(getByText(secondSelected)).toBeInTheDocument();
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("should call onSelection function with the selected items", async () => {
|
|
284
|
+
const onSelection = jest.fn((values) => {
|
|
285
|
+
// eslint-disable-next-line no-console
|
|
286
|
+
console.log("Mocked onSelection function values: ", values);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const { container } = render(
|
|
290
|
+
<MultiSuggestField {...dropdownOnFocus.args} items={items} onSelection={onSelection} />
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
|
|
294
|
+
const [input] = inputContainer.getElementsByTagName("input");
|
|
295
|
+
|
|
296
|
+
fireEvent.click(input);
|
|
297
|
+
|
|
298
|
+
await waitFor(() => {
|
|
299
|
+
const listbox = screen.getByRole("listbox");
|
|
300
|
+
expect(listbox).toBeInTheDocument();
|
|
301
|
+
|
|
302
|
+
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
303
|
+
expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
|
|
304
|
+
|
|
305
|
+
const item = menuItems[0];
|
|
306
|
+
fireEvent.click(item);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
await waitFor(() => {
|
|
310
|
+
const expectedObject = {
|
|
311
|
+
createdItems: [],
|
|
312
|
+
newlySelected: items[0],
|
|
313
|
+
selectedItems: [items[0]],
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
expect(onSelection).toHaveBeenCalledWith(expectedObject);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("should set deferred selection correctly and reset values", async () => {
|
|
321
|
+
const onSelection = jest.fn((values) => {
|
|
322
|
+
// eslint-disable-next-line no-console
|
|
323
|
+
console.log("Mocked onSelection function values: ", values);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const items = predefinedNotControlledValues.args.items;
|
|
100
327
|
|
|
101
|
-
|
|
328
|
+
const args = { ...predefinedNotControlledValues.args, selectedItems: [], onSelection: onSelection };
|
|
102
329
|
|
|
103
|
-
|
|
330
|
+
const { container, rerender } = render(<MultiSuggestField {...args} data-test-id="multi-suggest-field" />);
|
|
331
|
+
|
|
332
|
+
const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
|
|
333
|
+
const [input] = inputContainer.getElementsByTagName("input");
|
|
334
|
+
|
|
335
|
+
const clearButtonBefore = container.querySelector("[data-test-id='clear-all-items'");
|
|
336
|
+
|
|
337
|
+
expect(clearButtonBefore).not.toBeInTheDocument();
|
|
338
|
+
|
|
339
|
+
fireEvent.click(input);
|
|
340
|
+
|
|
341
|
+
await waitFor(() => {
|
|
342
|
+
const listbox = screen.getByRole("listbox");
|
|
343
|
+
expect(listbox).toBeInTheDocument();
|
|
344
|
+
|
|
345
|
+
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
346
|
+
expect(menuItems.length).toBe(predefinedNotControlledValues.args.items.length);
|
|
347
|
+
|
|
348
|
+
const item = menuItems[0];
|
|
349
|
+
fireEvent.click(item);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
await waitFor(() => {
|
|
353
|
+
const expectedObject = {
|
|
354
|
+
createdItems: [],
|
|
355
|
+
newlySelected: items[0],
|
|
356
|
+
selectedItems: [items[0]],
|
|
357
|
+
};
|
|
358
|
+
expect(onSelection).toHaveBeenCalledTimes(1);
|
|
359
|
+
expect(onSelection).toHaveBeenCalledWith(expectedObject);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const selectedItems = items.slice(2);
|
|
363
|
+
|
|
364
|
+
rerender(<MultiSuggestField {...args} selectedItems={selectedItems} data-test-id="multi-suggest-field" />);
|
|
365
|
+
|
|
366
|
+
await waitFor(() => {
|
|
367
|
+
const listbox = screen.getByRole("listbox");
|
|
368
|
+
expect(listbox).toBeInTheDocument();
|
|
369
|
+
|
|
370
|
+
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
371
|
+
expect(menuItems.length).toBe(predefinedNotControlledValues.args.items.length);
|
|
372
|
+
|
|
373
|
+
const item = menuItems[0];
|
|
374
|
+
fireEvent.click(item);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
await waitFor(() => {
|
|
378
|
+
const expectedObject = {
|
|
379
|
+
createdItems: [],
|
|
380
|
+
newlySelected: items[0],
|
|
381
|
+
selectedItems: [...selectedItems, items[0]],
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
expect(onSelection).toHaveBeenCalledTimes(2);
|
|
385
|
+
expect(onSelection).toHaveBeenCalledWith(expectedObject);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
await waitFor(() => {
|
|
389
|
+
const clearButtonAfter = container.querySelector("[data-test-id='clear-all-items'");
|
|
390
|
+
|
|
391
|
+
expect(clearButtonAfter).toBeInTheDocument();
|
|
392
|
+
|
|
393
|
+
fireEvent.click(clearButtonAfter!);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
await waitFor(() => {
|
|
397
|
+
const expectedObject = {
|
|
398
|
+
createdItems: [],
|
|
399
|
+
selectedItems: [],
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
expect(onSelection).toHaveBeenCalledTimes(3);
|
|
403
|
+
expect(onSelection).toHaveBeenCalledWith(expectedObject);
|
|
404
|
+
});
|
|
405
|
+
});
|
|
104
406
|
});
|
|
105
407
|
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import React, { CSSProperties } from "react";
|
|
2
|
+
|
|
3
|
+
import { utils } from "../../common/";
|
|
4
|
+
import { CLASSPREFIX as eccgui } from "../../configuration/constants";
|
|
5
|
+
|
|
6
|
+
export interface StickyTargetProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/**
|
|
8
|
+
* Set the side the element need to be sticky on.
|
|
9
|
+
*/
|
|
10
|
+
to?: "top" | "bottom";
|
|
11
|
+
/**
|
|
12
|
+
* The sticky area is positioned relatively to a local scroll area.
|
|
13
|
+
* The application header is not taken into offset calculation
|
|
14
|
+
*/
|
|
15
|
+
local?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Set the background color used for the sticky area.
|
|
18
|
+
* As it can overlay other content readability could be harmed if the overlayed content is shining through.
|
|
19
|
+
*/
|
|
20
|
+
background?: "card" | "application" | "transparent";
|
|
21
|
+
/**
|
|
22
|
+
* Set additional distance to original sticky position.
|
|
23
|
+
*/
|
|
24
|
+
offset?: `${number}${string}`;
|
|
25
|
+
/**
|
|
26
|
+
* Callback that returns an DOM element.
|
|
27
|
+
* The position of `StickyTarget` is then calculated relative to that element.
|
|
28
|
+
*/
|
|
29
|
+
getConnectedElement?: (ref: React.MutableRefObject<HTMLDivElement | null>) => Element | false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Element wraps the content that need to be displayed sticky.
|
|
34
|
+
* The content then offset relative to its nearest scrolling ancestor and containing block (nearest block-level ancestor).
|
|
35
|
+
*/
|
|
36
|
+
export const StickyTarget = ({
|
|
37
|
+
className,
|
|
38
|
+
to = "top",
|
|
39
|
+
local = false,
|
|
40
|
+
background = "transparent",
|
|
41
|
+
offset,
|
|
42
|
+
style,
|
|
43
|
+
getConnectedElement,
|
|
44
|
+
...otherDivProps
|
|
45
|
+
}: StickyTargetProps) => {
|
|
46
|
+
const stickyTargetRef = React.useRef<HTMLDivElement | null>(null);
|
|
47
|
+
|
|
48
|
+
let offsetStyle = {};
|
|
49
|
+
if (typeof offset !== "undefined") {
|
|
50
|
+
offsetStyle = { ...style, "--eccgui-sticky-target-localoffset": offset } as CSSProperties;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let connectedOffset = 0;
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
/**
|
|
56
|
+
* If the target should be sticky to a defined element then:
|
|
57
|
+
* * check for the element and its scroll parent
|
|
58
|
+
* * listen to scroll events and use the elements position as offset
|
|
59
|
+
*/
|
|
60
|
+
if (getConnectedElement && stickyTargetRef) {
|
|
61
|
+
const stickyConnection = getConnectedElement(stickyTargetRef);
|
|
62
|
+
if (stickyConnection) {
|
|
63
|
+
const scrollParent = utils.getScrollParent(stickyConnection);
|
|
64
|
+
const scrollParentFallback = !scrollParent ? document.documentElement : false;
|
|
65
|
+
if (scrollParent || scrollParentFallback) {
|
|
66
|
+
const updateTargetOffset = () => {
|
|
67
|
+
const scrollParentPosition = (
|
|
68
|
+
(scrollParent || scrollParentFallback) as HTMLElement
|
|
69
|
+
).getBoundingClientRect();
|
|
70
|
+
const stickyConnectionPosition = stickyConnection.getBoundingClientRect();
|
|
71
|
+
if (to === "top") {
|
|
72
|
+
connectedOffset =
|
|
73
|
+
stickyConnectionPosition.top -
|
|
74
|
+
Math.max(0, scrollParentPosition.top) +
|
|
75
|
+
stickyConnectionPosition.height;
|
|
76
|
+
}
|
|
77
|
+
if (to === "bottom") {
|
|
78
|
+
connectedOffset =
|
|
79
|
+
Math.max(scrollParentPosition.height, scrollParentPosition.bottom) -
|
|
80
|
+
stickyConnectionPosition.bottom +
|
|
81
|
+
stickyConnectionPosition.height;
|
|
82
|
+
}
|
|
83
|
+
stickyTargetRef.current?.style.setProperty(
|
|
84
|
+
"--eccgui-sticky-target-applicationoffset",
|
|
85
|
+
`${connectedOffset}px`
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
updateTargetOffset();
|
|
89
|
+
const eventListeningTarget = scrollParent || window;
|
|
90
|
+
const eventListeningMethod = (_event: Event) => {
|
|
91
|
+
updateTargetOffset();
|
|
92
|
+
};
|
|
93
|
+
eventListeningTarget.addEventListener("scroll", eventListeningMethod);
|
|
94
|
+
return () => {
|
|
95
|
+
eventListeningTarget.removeEventListener("scroll", eventListeningMethod);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}, [getConnectedElement, stickyTargetRef, to]);
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div
|
|
105
|
+
ref={stickyTargetRef}
|
|
106
|
+
className={
|
|
107
|
+
`${eccgui}-sticky__target` +
|
|
108
|
+
(to ? ` ${eccgui}-sticky__target--${to}` : "") +
|
|
109
|
+
(local ? ` ${eccgui}-sticky__target--localscrollarea` : "") +
|
|
110
|
+
(background ? ` ${eccgui}-sticky__target--bg-${background}` : "") +
|
|
111
|
+
(className ? ` ${className}` : "")
|
|
112
|
+
}
|
|
113
|
+
style={offset ? offsetStyle : style}
|
|
114
|
+
{...otherDivProps}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export default StickyTarget;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./StickyTarget";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
@use "sass:color";
|
|
2
|
+
|
|
3
|
+
// Sticky target area
|
|
4
|
+
|
|
5
|
+
.#{$eccgui}-sticky__target {
|
|
6
|
+
position: sticky;
|
|
7
|
+
z-index: 1;
|
|
8
|
+
|
|
9
|
+
--eccgui-sticky-target-applicationoffset: 0px;
|
|
10
|
+
|
|
11
|
+
.#{$eccgui}-application__content &:not(.#{$eccgui}-sticky__target--localscrollarea) {
|
|
12
|
+
--eccgui-sticky-target-applicationoffset: $eccgui-size-block-whitespace;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.#{$eccgui}-application__header
|
|
16
|
+
+ .#{$eccgui}-application__content
|
|
17
|
+
&:not(.#{$eccgui}-sticky__target--localscrollarea) {
|
|
18
|
+
--eccgui-sticky-target-applicationoffset: calc(#{mini-units(8)} + #{$eccgui-size-block-whitespace});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.#{$eccgui}-sticky__target--top {
|
|
23
|
+
top: calc(var(--eccgui-sticky-target-applicationoffset) + var(--eccgui-sticky-target-localoffset, 0px));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.#{$eccgui}-sticky__target--bottom {
|
|
27
|
+
--eccgui-sticky-target-applicationoffset: 0px;
|
|
28
|
+
|
|
29
|
+
bottom: calc(var(--eccgui-sticky-target-applicationoffset) + var(--eccgui-sticky-target-localoffset, 0px));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.#{$eccgui}-sticky__target--bg-card {
|
|
33
|
+
background-color: $card-background-color;
|
|
34
|
+
|
|
35
|
+
.#{$eccgui}-card.#{$eccgui}-intent--primary & {
|
|
36
|
+
background-color: color.mix($eccgui-color-primary, $card-background-color, 5%);
|
|
37
|
+
}
|
|
38
|
+
.#{$eccgui}-card.#{$eccgui}-intent--accent & {
|
|
39
|
+
background-color: color.mix($eccgui-color-accent, $card-background-color, 10%);
|
|
40
|
+
}
|
|
41
|
+
.#{$eccgui}-card.#{$eccgui}-intent--success & {
|
|
42
|
+
background-color: $eccgui-color-success-background;
|
|
43
|
+
}
|
|
44
|
+
.#{$eccgui}-card.#{$eccgui}-intent--info & {
|
|
45
|
+
background-color: $eccgui-color-info-background;
|
|
46
|
+
}
|
|
47
|
+
.#{$eccgui}-card.#{$eccgui}-intent--warning & {
|
|
48
|
+
background-color: $eccgui-color-warning-background;
|
|
49
|
+
}
|
|
50
|
+
.#{$eccgui}-card.#{$eccgui}-intent--danger & {
|
|
51
|
+
background-color: $eccgui-color-danger-background;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.#{$eccgui}-card.#{$ns}-interactive:hover & {
|
|
55
|
+
background-color: $button-background-color-hover;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.#{$eccgui}-card.#{$ns}-selected & {
|
|
59
|
+
background-color: $card-selected-background-color;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.#{$eccgui}-card--elevated & {
|
|
63
|
+
background-color: $button-background-color-active;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.#{$eccgui}-sticky__target--bg-application {
|
|
68
|
+
background-color: $eccgui-color-application-background;
|
|
69
|
+
}
|