@eccenca/gui-elements 23.7.0-rc.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 +7 -0
- 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 +3 -4
- 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/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 +3 -4
- 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/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/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/package.json +1 -1
- 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 +3 -4
- package/src/components/MultiSuggestField/MultiSuggestField.stories.tsx +76 -1
- package/src/components/MultiSuggestField/tests/MultiSuggestField.test.tsx +297 -109
- 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
|
@@ -47,173 +47,361 @@ export const TestComponent = (): JSX.Element => {
|
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
describe("MultiSuggestField", () => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
describe("uncontrolled", () => {
|
|
51
|
+
it("should render default input", () => {
|
|
52
|
+
const { container } = render(<MultiSuggestField {...Default.args} />);
|
|
53
|
+
const [input] = container.getElementsByClassName("eccgui-multiselect");
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
expect(input).toBeInTheDocument();
|
|
56
|
+
});
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
it("should render default selected items", async () => {
|
|
59
|
+
const { getByText } = render(<MultiSuggestField {...predefinedNotControlledValues.args} />);
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const [firstSelected, secondSelected]: Array<string> = predefinedNotControlledValues.args.selectedItems.map(
|
|
62
|
+
({ testLabel }) => testLabel
|
|
63
|
+
);
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
await waitFor(() => {
|
|
66
|
+
expect(getByText(firstSelected)).toBeInTheDocument();
|
|
67
|
+
expect(getByText(secondSelected)).toBeInTheDocument();
|
|
68
|
+
});
|
|
67
69
|
});
|
|
68
|
-
});
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
+
);
|
|
74
79
|
|
|
75
|
-
|
|
80
|
+
const selectedLength = predefinedNotControlledValues.args.selectedItems.length;
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
expect(container.querySelectorAll("[data-tag-index]").length).toBe(selectedLength);
|
|
78
83
|
|
|
79
|
-
|
|
84
|
+
const clearButton = container.querySelector('[data-test-id="clear-all-items"');
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
expect(clearButton).toBeInTheDocument();
|
|
82
87
|
|
|
83
|
-
|
|
88
|
+
fireEvent.click(clearButton!);
|
|
84
89
|
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
await waitFor(() => {
|
|
91
|
+
expect(container.querySelectorAll("[data-tag-index]").length).toBe(0);
|
|
92
|
+
});
|
|
87
93
|
});
|
|
88
|
-
});
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
it("should filter options and reset them if the query is empty", async () => {
|
|
96
|
+
const { container } = render(<MultiSuggestField {...dropdownOnFocus.args} />);
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
|
|
99
|
+
const [input] = inputContainer.getElementsByTagName("input");
|
|
95
100
|
|
|
96
|
-
|
|
101
|
+
fireEvent.click(input);
|
|
97
102
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
await waitFor(() => {
|
|
104
|
+
const listbox = screen.getByRole("listbox");
|
|
105
|
+
expect(listbox).toBeInTheDocument();
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
108
|
+
expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
|
|
109
|
+
});
|
|
105
110
|
|
|
106
|
-
|
|
111
|
+
fireEvent.change(input, { target: { value: "ex" } });
|
|
107
112
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
await waitFor(() => {
|
|
114
|
+
const listbox = screen.getByRole("listbox");
|
|
115
|
+
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
111
116
|
|
|
112
|
-
|
|
117
|
+
expect(menuItems.length).toBe(1);
|
|
113
118
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
const noResult = screen.queryByText(dropdownOnFocus.args.noResultText);
|
|
120
|
+
expect(noResult).not.toBeInTheDocument();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
fireEvent.change(input, { target: { value: "ttt" } });
|
|
124
|
+
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
const listbox = screen.getByRole("listbox");
|
|
127
|
+
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
117
128
|
|
|
118
|
-
|
|
129
|
+
expect(menuItems.length).toBe(1);
|
|
119
130
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
131
|
+
const noResult = screen.queryByText(dropdownOnFocus.args.noResultText);
|
|
132
|
+
expect(noResult).toBeInTheDocument();
|
|
133
|
+
});
|
|
123
134
|
|
|
124
|
-
|
|
135
|
+
fireEvent.change(input, { target: { value: "" } });
|
|
125
136
|
|
|
126
|
-
|
|
127
|
-
|
|
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
|
+
});
|
|
128
142
|
});
|
|
129
143
|
|
|
130
|
-
|
|
144
|
+
it("should render disable field with selected items", async () => {
|
|
145
|
+
const { container, getByText } = render(
|
|
146
|
+
<MultiSuggestField {...predefinedNotControlledValues.args} disabled />
|
|
147
|
+
);
|
|
131
148
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
149
|
+
const [inputTargetContainer] = container.getElementsByClassName("eccgui-multiselect__target");
|
|
150
|
+
|
|
151
|
+
expect(inputTargetContainer.getAttribute("aria-disabled")).toBe("true");
|
|
152
|
+
|
|
153
|
+
const [firstSelected, secondSelected]: Array<string> = predefinedNotControlledValues.args.selectedItems.map(
|
|
154
|
+
({ testLabel }) => testLabel
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
await waitFor(() => {
|
|
158
|
+
expect(getByText(firstSelected)).toBeInTheDocument();
|
|
159
|
+
expect(getByText(secondSelected)).toBeInTheDocument();
|
|
160
|
+
});
|
|
136
161
|
});
|
|
137
|
-
});
|
|
138
162
|
|
|
139
|
-
|
|
140
|
-
|
|
163
|
+
it("should set deferred selection correctly when only selected items provided and remove selection", async () => {
|
|
164
|
+
const args = { ...predefinedNotControlledValues.args, selectedItems: [] };
|
|
141
165
|
|
|
142
|
-
|
|
166
|
+
const { rerender, container } = render(<MultiSuggestField {...args} data-test-id="multi-suggest-field" />);
|
|
143
167
|
|
|
144
|
-
|
|
145
|
-
|
|
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
|
+
);
|
|
146
175
|
|
|
147
|
-
|
|
148
|
-
|
|
176
|
+
await waitFor(() => {
|
|
177
|
+
expect(screen.queryByText(firstSelected)).toBeNull();
|
|
178
|
+
expect(screen.queryByText(secondSelected)).toBeNull();
|
|
179
|
+
});
|
|
149
180
|
|
|
150
|
-
|
|
151
|
-
<MultiSuggestField {...dropdownOnFocus.args} items={items} onSelection={onSelection} />
|
|
152
|
-
);
|
|
181
|
+
rerender(<MultiSuggestField {...predefinedNotControlledValues.args} data-test-id="multi-suggest-field" />);
|
|
153
182
|
|
|
154
|
-
|
|
155
|
-
|
|
183
|
+
await waitFor(() => {
|
|
184
|
+
expect(screen.getByText(firstSelected)).toBeInTheDocument();
|
|
185
|
+
expect(screen.getByText(secondSelected)).toBeInTheDocument();
|
|
186
|
+
});
|
|
156
187
|
|
|
157
|
-
|
|
188
|
+
await waitFor(() => {
|
|
189
|
+
const clearButtonAfter = container.querySelector("[data-test-id='clear-all-items'");
|
|
158
190
|
|
|
159
|
-
|
|
160
|
-
const listbox = screen.getByRole("listbox");
|
|
161
|
-
expect(listbox).toBeInTheDocument();
|
|
191
|
+
expect(clearButtonAfter).toBeInTheDocument();
|
|
162
192
|
|
|
163
|
-
|
|
164
|
-
|
|
193
|
+
fireEvent.click(clearButtonAfter!);
|
|
194
|
+
});
|
|
165
195
|
|
|
166
|
-
|
|
167
|
-
|
|
196
|
+
await waitFor(() => {
|
|
197
|
+
expect(container.querySelectorAll("[data-tag-index]").length).toBe(0);
|
|
198
|
+
});
|
|
168
199
|
});
|
|
169
200
|
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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");
|
|
207
|
+
|
|
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
|
+
});
|
|
177
262
|
});
|
|
178
263
|
});
|
|
179
264
|
|
|
180
|
-
|
|
181
|
-
|
|
265
|
+
describe("controlled", () => {
|
|
266
|
+
it("should render default selected items", async () => {
|
|
267
|
+
const onSelection = jest.fn();
|
|
182
268
|
|
|
183
|
-
|
|
184
|
-
|
|
269
|
+
const { getByText } = render(
|
|
270
|
+
<MultiSuggestField {...predefinedNotControlledValues.args} onSelection={onSelection} />
|
|
271
|
+
);
|
|
185
272
|
|
|
186
|
-
|
|
273
|
+
const [firstSelected, secondSelected]: Array<string> = predefinedNotControlledValues.args.selectedItems.map(
|
|
274
|
+
({ testLabel }) => testLabel
|
|
275
|
+
);
|
|
187
276
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
277
|
+
await waitFor(() => {
|
|
278
|
+
expect(getByText(firstSelected)).toBeInTheDocument();
|
|
279
|
+
expect(getByText(secondSelected)).toBeInTheDocument();
|
|
280
|
+
});
|
|
281
|
+
});
|
|
191
282
|
|
|
192
|
-
|
|
193
|
-
|
|
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
|
+
});
|
|
194
288
|
|
|
195
|
-
const
|
|
196
|
-
|
|
289
|
+
const { container } = render(
|
|
290
|
+
<MultiSuggestField {...dropdownOnFocus.args} items={items} onSelection={onSelection} />
|
|
291
|
+
);
|
|
197
292
|
|
|
198
|
-
const
|
|
199
|
-
|
|
293
|
+
const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
|
|
294
|
+
const [input] = inputContainer.getElementsByTagName("input");
|
|
200
295
|
|
|
201
|
-
|
|
202
|
-
fireEvent.click(otherItem);
|
|
296
|
+
fireEvent.click(input);
|
|
203
297
|
|
|
204
|
-
|
|
298
|
+
await waitFor(() => {
|
|
299
|
+
const listbox = screen.getByRole("listbox");
|
|
300
|
+
expect(listbox).toBeInTheDocument();
|
|
205
301
|
|
|
206
|
-
|
|
302
|
+
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
303
|
+
expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
|
|
207
304
|
|
|
208
|
-
|
|
209
|
-
|
|
305
|
+
const item = menuItems[0];
|
|
306
|
+
fireEvent.click(item);
|
|
307
|
+
});
|
|
210
308
|
|
|
211
|
-
|
|
309
|
+
await waitFor(() => {
|
|
310
|
+
const expectedObject = {
|
|
311
|
+
createdItems: [],
|
|
312
|
+
newlySelected: items[0],
|
|
313
|
+
selectedItems: [items[0]],
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
expect(onSelection).toHaveBeenCalledWith(expectedObject);
|
|
317
|
+
});
|
|
212
318
|
});
|
|
213
319
|
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
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;
|
|
327
|
+
|
|
328
|
+
const args = { ...predefinedNotControlledValues.args, selectedItems: [], onSelection: onSelection };
|
|
329
|
+
|
|
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
|
+
});
|
|
217
405
|
});
|
|
218
406
|
});
|
|
219
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
|
+
}
|