@eccenca/gui-elements 23.7.0-rc.0 → 23.7.0-rc.2

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.
Files changed (80) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/cjs/cmem/markdown/Markdown.js +1 -1
  3. package/dist/cjs/cmem/markdown/Markdown.js.map +1 -1
  4. package/dist/cjs/common/index.js +2 -0
  5. package/dist/cjs/common/index.js.map +1 -1
  6. package/dist/cjs/common/utils/getScrollParent.js +24 -0
  7. package/dist/cjs/common/utils/getScrollParent.js.map +1 -0
  8. package/dist/cjs/components/AutocompleteField/AutoCompleteField.js +19 -2
  9. package/dist/cjs/components/AutocompleteField/AutoCompleteField.js.map +1 -1
  10. package/dist/cjs/components/Breadcrumb/BreadcrumbList.js +1 -1
  11. package/dist/cjs/components/Breadcrumb/BreadcrumbList.js.map +1 -1
  12. package/dist/cjs/components/Card/Card.js +3 -1
  13. package/dist/cjs/components/Card/Card.js.map +1 -1
  14. package/dist/cjs/components/MultiSelect/MultiSelect.js +6 -5
  15. package/dist/cjs/components/MultiSelect/MultiSelect.js.map +1 -1
  16. package/dist/cjs/components/Sticky/StickyTarget.js +85 -0
  17. package/dist/cjs/components/Sticky/StickyTarget.js.map +1 -0
  18. package/dist/cjs/components/Sticky/index.js +14 -0
  19. package/dist/cjs/components/Sticky/index.js.map +1 -0
  20. package/dist/cjs/components/TextField/TextArea.js +85 -8
  21. package/dist/cjs/components/TextField/TextArea.js.map +1 -1
  22. package/dist/cjs/components/index.js +1 -0
  23. package/dist/cjs/components/index.js.map +1 -1
  24. package/dist/esm/cmem/markdown/Markdown.js +1 -1
  25. package/dist/esm/cmem/markdown/Markdown.js.map +1 -1
  26. package/dist/esm/common/index.js +2 -0
  27. package/dist/esm/common/index.js.map +1 -1
  28. package/dist/esm/common/utils/getScrollParent.js +20 -0
  29. package/dist/esm/common/utils/getScrollParent.js.map +1 -0
  30. package/dist/esm/components/AutocompleteField/AutoCompleteField.js +27 -2
  31. package/dist/esm/components/AutocompleteField/AutoCompleteField.js.map +1 -1
  32. package/dist/esm/components/Breadcrumb/BreadcrumbList.js +1 -1
  33. package/dist/esm/components/Breadcrumb/BreadcrumbList.js.map +1 -1
  34. package/dist/esm/components/Card/Card.js +4 -2
  35. package/dist/esm/components/Card/Card.js.map +1 -1
  36. package/dist/esm/components/MultiSelect/MultiSelect.js +6 -5
  37. package/dist/esm/components/MultiSelect/MultiSelect.js.map +1 -1
  38. package/dist/esm/components/Sticky/StickyTarget.js +89 -0
  39. package/dist/esm/components/Sticky/StickyTarget.js.map +1 -0
  40. package/dist/esm/components/Sticky/index.js +2 -0
  41. package/dist/esm/components/Sticky/index.js.map +1 -0
  42. package/dist/esm/components/TextField/TextArea.js +86 -9
  43. package/dist/esm/components/TextField/TextArea.js.map +1 -1
  44. package/dist/esm/components/index.js +1 -0
  45. package/dist/esm/components/index.js.map +1 -1
  46. package/dist/types/common/index.d.ts +2 -0
  47. package/dist/types/common/utils/getScrollParent.d.ts +9 -0
  48. package/dist/types/components/AutocompleteField/AutoCompleteField.d.ts +2 -0
  49. package/dist/types/components/Breadcrumb/BreadcrumbList.d.ts +2 -1
  50. package/dist/types/components/Card/Card.d.ts +8 -2
  51. package/dist/types/components/Sticky/StickyTarget.d.ts +32 -0
  52. package/dist/types/components/Sticky/index.d.ts +1 -0
  53. package/dist/types/components/TextField/TextArea.d.ts +28 -3
  54. package/dist/types/components/index.d.ts +1 -0
  55. package/package.json +1 -1
  56. package/src/cmem/markdown/Markdown.tsx +1 -1
  57. package/src/common/index.ts +3 -0
  58. package/src/common/utils/getScrollParent.ts +20 -0
  59. package/src/components/Application/application.scss +0 -1
  60. package/src/components/AutocompleteField/AutoCompleteField.tsx +28 -0
  61. package/src/components/AutocompleteField/autocompletefield.scss +1 -1
  62. package/src/components/Breadcrumb/BreadcrumbList.tsx +3 -3
  63. package/src/components/Card/Card.tsx +15 -3
  64. package/src/components/Card/card.scss +6 -1
  65. package/src/components/Icon/stories/Icon.stories.tsx +1 -1
  66. package/src/components/MultiSelect/MultiSelect.tsx +8 -4
  67. package/src/components/MultiSuggestField/MultiSuggestField.stories.tsx +76 -1
  68. package/src/components/MultiSuggestField/tests/MultiSuggestField.test.tsx +297 -109
  69. package/src/components/Sticky/StickyTarget.tsx +119 -0
  70. package/src/components/Sticky/index.ts +1 -0
  71. package/src/components/Sticky/sticky.scss +69 -0
  72. package/src/components/Sticky/stories/StickyTarget.stories.tsx +63 -0
  73. package/src/components/TextField/TextArea.tsx +174 -12
  74. package/src/components/TextField/stories/TextArea.stories.tsx +39 -12
  75. package/src/components/TextField/textfield.scss +78 -11
  76. package/src/components/index.scss +1 -0
  77. package/src/components/index.ts +1 -0
  78. package/src/includes/blueprintjs/_requisits.scss +1 -1
  79. package/src/includes/blueprintjs/_variables.scss +3 -172
  80. package/src/includes/carbon-components/_requisits.scss +1 -0
@@ -47,173 +47,361 @@ export const TestComponent = (): JSX.Element => {
47
47
  };
48
48
 
49
49
  describe("MultiSuggestField", () => {
50
- it("should render default input", () => {
51
- const { container } = render(<MultiSuggestField {...Default.args} />);
52
- const [input] = container.getElementsByClassName("eccgui-multiselect");
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
- expect(input).toBeInTheDocument();
55
- });
55
+ expect(input).toBeInTheDocument();
56
+ });
56
57
 
57
- it("should render default selected items", async () => {
58
- const { getByText } = render(<MultiSuggestField {...predefinedNotControlledValues.args} />);
58
+ it("should render default selected items", async () => {
59
+ const { getByText } = render(<MultiSuggestField {...predefinedNotControlledValues.args} />);
59
60
 
60
- const [firstSelected, secondSelected]: Array<string> = predefinedNotControlledValues.args.selectedItems.map(
61
- ({ testLabel }) => testLabel
62
- );
61
+ const [firstSelected, secondSelected]: Array<string> = predefinedNotControlledValues.args.selectedItems.map(
62
+ ({ testLabel }) => testLabel
63
+ );
63
64
 
64
- await waitFor(() => {
65
- expect(getByText(firstSelected)).toBeInTheDocument();
66
- expect(getByText(secondSelected)).toBeInTheDocument();
65
+ await waitFor(() => {
66
+ expect(getByText(firstSelected)).toBeInTheDocument();
67
+ expect(getByText(secondSelected)).toBeInTheDocument();
68
+ });
67
69
  });
68
- });
69
70
 
70
- it("should clear all selected items on clear button click for uncontrolled field", async () => {
71
- const { container } = render(
72
- <MultiSuggestField {...predefinedNotControlledValues.args} onSelection={undefined} />
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
- const selectedLength = predefinedNotControlledValues.args.selectedItems.length;
80
+ const selectedLength = predefinedNotControlledValues.args.selectedItems.length;
76
81
 
77
- expect(container.querySelectorAll("[data-tag-index]").length).toBe(selectedLength);
82
+ expect(container.querySelectorAll("[data-tag-index]").length).toBe(selectedLength);
78
83
 
79
- const clearButton = container.querySelector('[data-test-id="clear-all-items"');
84
+ const clearButton = container.querySelector('[data-test-id="clear-all-items"');
80
85
 
81
- expect(clearButton).toBeInTheDocument();
86
+ expect(clearButton).toBeInTheDocument();
82
87
 
83
- fireEvent.click(clearButton!);
88
+ fireEvent.click(clearButton!);
84
89
 
85
- await waitFor(() => {
86
- expect(container.querySelectorAll("[data-tag-index]").length).toBe(0);
90
+ await waitFor(() => {
91
+ expect(container.querySelectorAll("[data-tag-index]").length).toBe(0);
92
+ });
87
93
  });
88
- });
89
94
 
90
- it("should filter options and reset them if the query is empty", async () => {
91
- const { container } = render(<MultiSuggestField {...dropdownOnFocus.args} />);
95
+ it("should filter options and reset them if the query is empty", async () => {
96
+ const { container } = render(<MultiSuggestField {...dropdownOnFocus.args} />);
92
97
 
93
- const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
94
- const [input] = inputContainer.getElementsByTagName("input");
98
+ const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
99
+ const [input] = inputContainer.getElementsByTagName("input");
95
100
 
96
- fireEvent.click(input);
101
+ fireEvent.click(input);
97
102
 
98
- await waitFor(() => {
99
- const listbox = screen.getByRole("listbox");
100
- expect(listbox).toBeInTheDocument();
103
+ await waitFor(() => {
104
+ const listbox = screen.getByRole("listbox");
105
+ expect(listbox).toBeInTheDocument();
101
106
 
102
- const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
103
- expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
104
- });
107
+ const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
108
+ expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
109
+ });
105
110
 
106
- fireEvent.change(input, { target: { value: "ex" } });
111
+ fireEvent.change(input, { target: { value: "ex" } });
107
112
 
108
- await waitFor(() => {
109
- const listbox = screen.getByRole("listbox");
110
- const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
113
+ await waitFor(() => {
114
+ const listbox = screen.getByRole("listbox");
115
+ const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
111
116
 
112
- expect(menuItems.length).toBe(1);
117
+ expect(menuItems.length).toBe(1);
113
118
 
114
- const noResult = screen.queryByText(dropdownOnFocus.args.noResultText);
115
- expect(noResult).not.toBeInTheDocument();
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
- fireEvent.change(input, { target: { value: "ttt" } });
129
+ expect(menuItems.length).toBe(1);
119
130
 
120
- await waitFor(() => {
121
- const listbox = screen.getByRole("listbox");
122
- const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
131
+ const noResult = screen.queryByText(dropdownOnFocus.args.noResultText);
132
+ expect(noResult).toBeInTheDocument();
133
+ });
123
134
 
124
- expect(menuItems.length).toBe(1);
135
+ fireEvent.change(input, { target: { value: "" } });
125
136
 
126
- const noResult = screen.queryByText(dropdownOnFocus.args.noResultText);
127
- expect(noResult).toBeInTheDocument();
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
- fireEvent.change(input, { target: { value: "" } });
144
+ it("should render disable field with selected items", async () => {
145
+ const { container, getByText } = render(
146
+ <MultiSuggestField {...predefinedNotControlledValues.args} disabled />
147
+ );
131
148
 
132
- await waitFor(() => {
133
- const listbox = screen.getByRole("listbox");
134
- const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
135
- expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
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
- it("should render disable field with selected items", async () => {
140
- const { container } = render(<MultiSuggestField {...predefinedNotControlledValues.args} disabled />);
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
- const [inputTargetContainer] = container.getElementsByClassName("eccgui-multiselect__target");
166
+ const { rerender, container } = render(<MultiSuggestField {...args} data-test-id="multi-suggest-field" />);
143
167
 
144
- expect(inputTargetContainer.getAttribute("aria-disabled")).toBe("true");
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
- it("should call onSelection function with the selected items for the contolled field", async () => {
148
- const onSelection = jest.fn();
176
+ await waitFor(() => {
177
+ expect(screen.queryByText(firstSelected)).toBeNull();
178
+ expect(screen.queryByText(secondSelected)).toBeNull();
179
+ });
149
180
 
150
- const { container } = render(
151
- <MultiSuggestField {...dropdownOnFocus.args} items={items} onSelection={onSelection} />
152
- );
181
+ rerender(<MultiSuggestField {...predefinedNotControlledValues.args} data-test-id="multi-suggest-field" />);
153
182
 
154
- const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
155
- const [input] = inputContainer.getElementsByTagName("input");
183
+ await waitFor(() => {
184
+ expect(screen.getByText(firstSelected)).toBeInTheDocument();
185
+ expect(screen.getByText(secondSelected)).toBeInTheDocument();
186
+ });
156
187
 
157
- fireEvent.click(input);
188
+ await waitFor(() => {
189
+ const clearButtonAfter = container.querySelector("[data-test-id='clear-all-items'");
158
190
 
159
- await waitFor(() => {
160
- const listbox = screen.getByRole("listbox");
161
- expect(listbox).toBeInTheDocument();
191
+ expect(clearButtonAfter).toBeInTheDocument();
162
192
 
163
- const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
164
- expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
193
+ fireEvent.click(clearButtonAfter!);
194
+ });
165
195
 
166
- const item = menuItems[0];
167
- fireEvent.click(item);
196
+ await waitFor(() => {
197
+ expect(container.querySelectorAll("[data-tag-index]").length).toBe(0);
198
+ });
168
199
  });
169
200
 
170
- await waitFor(() => {
171
- const expectedObject = {
172
- createdItems: [],
173
- newlySelected: items[0],
174
- selectedItems: [items[0]],
175
- };
176
- expect(onSelection).toHaveBeenCalledWith(expectedObject);
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
- it("should reset values correctly with the pre-defined values for the contolled field", async () => {
181
- const { container, getByTestId } = render(<TestComponent />);
265
+ describe("controlled", () => {
266
+ it("should render default selected items", async () => {
267
+ const onSelection = jest.fn();
182
268
 
183
- const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
184
- const [input] = inputContainer.getElementsByTagName("input");
269
+ const { getByText } = render(
270
+ <MultiSuggestField {...predefinedNotControlledValues.args} onSelection={onSelection} />
271
+ );
185
272
 
186
- fireEvent.click(input);
273
+ const [firstSelected, secondSelected]: Array<string> = predefinedNotControlledValues.args.selectedItems.map(
274
+ ({ testLabel }) => testLabel
275
+ );
187
276
 
188
- await waitFor(() => {
189
- const listbox = screen.getByRole("listbox");
190
- expect(listbox).toBeInTheDocument();
277
+ await waitFor(() => {
278
+ expect(getByText(firstSelected)).toBeInTheDocument();
279
+ expect(getByText(secondSelected)).toBeInTheDocument();
280
+ });
281
+ });
191
282
 
192
- const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
193
- expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
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 selectedItems = inputContainer.querySelectorAll("[data-tag-index]");
196
- expect(selectedItems.length).toBe(1);
289
+ const { container } = render(
290
+ <MultiSuggestField {...dropdownOnFocus.args} items={items} onSelection={onSelection} />
291
+ );
197
292
 
198
- const item = menuItems[0];
199
- fireEvent.click(item);
293
+ const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
294
+ const [input] = inputContainer.getElementsByTagName("input");
200
295
 
201
- const otherItem = menuItems[menuItems.length - 1];
202
- fireEvent.click(otherItem);
296
+ fireEvent.click(input);
203
297
 
204
- const selectedItemsAfterSelection = inputContainer.querySelectorAll("[data-tag-index]");
298
+ await waitFor(() => {
299
+ const listbox = screen.getByRole("listbox");
300
+ expect(listbox).toBeInTheDocument();
205
301
 
206
- expect(selectedItemsAfterSelection.length).toBe(3);
302
+ const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
303
+ expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
207
304
 
208
- const resetButton = getByTestId("reset-button");
209
- expect(resetButton).toBeInTheDocument();
305
+ const item = menuItems[0];
306
+ fireEvent.click(item);
307
+ });
210
308
 
211
- fireEvent.click(resetButton);
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
- await waitFor(() => {
215
- const selectedItemsAfterReset = inputContainer.querySelectorAll("[data-tag-index]");
216
- expect(selectedItemsAfterReset.length).toBe(1);
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
+ }