@eccenca/gui-elements 23.6.0 → 23.7.0-rc.0
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 +15 -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/components/MultiSelect/MultiSelect.js +67 -41
- package/dist/cjs/components/MultiSelect/MultiSelect.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/components/MultiSelect/MultiSelect.js +73 -52
- package/dist/esm/components/MultiSelect/MultiSelect.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/components/MultiSelect/MultiSelect.d.ts +1 -1
- 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/components/MultiSelect/MultiSelect.tsx +73 -47
- package/src/components/MultiSuggestField/MultiSuggestField.stories.tsx +70 -25
- package/src/components/MultiSuggestField/tests/MultiSuggestField.test.tsx +129 -15
- 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
|
@@ -13,7 +13,7 @@ import { TestableComponent } from "../interfaces";
|
|
|
13
13
|
import { ContextOverlayProps, Highlighter, IconButton, MenuItem, OverflowText, Spinner } from "./../../index";
|
|
14
14
|
|
|
15
15
|
export interface MultiSelectSelectionProps<T> {
|
|
16
|
-
newlySelected
|
|
16
|
+
newlySelected?: T;
|
|
17
17
|
selectedItems: T[];
|
|
18
18
|
createdItems: Partial<T>[];
|
|
19
19
|
}
|
|
@@ -136,7 +136,7 @@ export interface MultiSelectProps<T>
|
|
|
136
136
|
*/
|
|
137
137
|
export function MultiSelect<T>({
|
|
138
138
|
items,
|
|
139
|
-
selectedItems: externalSelectedItems
|
|
139
|
+
selectedItems: externalSelectedItems,
|
|
140
140
|
prePopulateWithItems,
|
|
141
141
|
itemId,
|
|
142
142
|
itemLabel,
|
|
@@ -163,13 +163,17 @@ export function MultiSelect<T>({
|
|
|
163
163
|
wrapperProps,
|
|
164
164
|
...otherMultiSelectProps
|
|
165
165
|
}: MultiSelectProps<T>) {
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
const [
|
|
166
|
+
// Options created by a user
|
|
167
|
+
const createdItems = useRef<T[]>([]);
|
|
168
|
+
// Options passed ouside (f.e. from the backend)
|
|
169
|
+
const [externalItems, setExternalItems] = React.useState<T[]>([...items]);
|
|
170
|
+
// All options (created and passed) that match the query
|
|
171
|
+
const [filteredItems, setFilteredItems] = React.useState<T[]>([]);
|
|
172
|
+
// All options (created and passed) selected by a user, if the component is uncontrolled
|
|
170
173
|
const [selectedItems, setSelectedItems] = React.useState<T[]>(() =>
|
|
171
|
-
prePopulateWithItems ? [...items] : [...externalSelectedItems]
|
|
174
|
+
prePopulateWithItems ? [...items] : externalSelectedItems ? [...externalSelectedItems] : []
|
|
172
175
|
);
|
|
176
|
+
|
|
173
177
|
//currently focused element in popover list
|
|
174
178
|
const [focusedItem, setFocusedItem] = React.useState<T | null>(null);
|
|
175
179
|
const [showSpinner, setShowSpinner] = React.useState(false);
|
|
@@ -197,27 +201,45 @@ export function MultiSelect<T>({
|
|
|
197
201
|
break;
|
|
198
202
|
}
|
|
199
203
|
|
|
200
|
-
|
|
204
|
+
// If the component is contolled from outside, we don't need to store selected state within the component
|
|
205
|
+
// when user selects or removes selection - options will be set in a parent component
|
|
206
|
+
const isControlled = !!(externalSelectedItems && onSelection);
|
|
207
|
+
|
|
208
|
+
/** Update external items when they change
|
|
201
209
|
* e.g for auto-complete when query change
|
|
202
210
|
*/
|
|
203
211
|
React.useEffect(() => {
|
|
204
|
-
|
|
205
|
-
|
|
212
|
+
setExternalItems(items);
|
|
213
|
+
setFilteredItems([...items, ...createdItems.current]);
|
|
206
214
|
}, [items.map((item) => itemId(item)).join("|")]);
|
|
207
215
|
|
|
208
216
|
React.useEffect(() => {
|
|
209
|
-
|
|
217
|
+
!isControlled &&
|
|
218
|
+
onSelection &&
|
|
210
219
|
onSelection({
|
|
211
220
|
newlySelected: selectedItems.slice(-1)[0],
|
|
212
|
-
createdItems:
|
|
221
|
+
createdItems: createdItems.current,
|
|
213
222
|
selectedItems,
|
|
214
223
|
});
|
|
215
224
|
}, [
|
|
225
|
+
isControlled,
|
|
216
226
|
onSelection,
|
|
217
227
|
selectedItems.map((item) => itemId(item)).join("|"),
|
|
218
|
-
|
|
228
|
+
createdItems.current.map((item) => itemId(item)).join("|"),
|
|
219
229
|
]);
|
|
220
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Update selected items if the component is controlled and we get
|
|
233
|
+
* new selected items from outside
|
|
234
|
+
*/
|
|
235
|
+
React.useEffect(() => {
|
|
236
|
+
if (!isControlled) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
setSelectedItems(externalSelectedItems);
|
|
241
|
+
}, [isControlled, externalSelectedItems]);
|
|
242
|
+
|
|
221
243
|
/**
|
|
222
244
|
* using the equality prop specified checks if an item has already been selected
|
|
223
245
|
* @param matcher
|
|
@@ -232,7 +254,16 @@ export function MultiSelect<T>({
|
|
|
232
254
|
* @param matcher
|
|
233
255
|
*/
|
|
234
256
|
const removeItemSelection = (matcher: string) => {
|
|
235
|
-
|
|
257
|
+
const filteredItems = selectedItems.filter((item) => itemId(item) !== matcher);
|
|
258
|
+
|
|
259
|
+
if (isControlled) {
|
|
260
|
+
onSelection({
|
|
261
|
+
createdItems: createdItems.current,
|
|
262
|
+
selectedItems: filteredItems,
|
|
263
|
+
});
|
|
264
|
+
} else {
|
|
265
|
+
setSelectedItems(filteredItems);
|
|
266
|
+
}
|
|
236
267
|
};
|
|
237
268
|
|
|
238
269
|
/**
|
|
@@ -243,24 +274,16 @@ export function MultiSelect<T>({
|
|
|
243
274
|
const onItemSelect = (item: T) => {
|
|
244
275
|
if (itemHasBeenSelectedAlready(itemId(item))) {
|
|
245
276
|
removeItemSelection(itemId(item));
|
|
277
|
+
} else if (isControlled) {
|
|
278
|
+
onSelection({
|
|
279
|
+
newlySelected: item,
|
|
280
|
+
createdItems: createdItems.current,
|
|
281
|
+
selectedItems: [...selectedItems, item],
|
|
282
|
+
});
|
|
246
283
|
} else {
|
|
247
284
|
setSelectedItems((items) => [...items, item]);
|
|
248
285
|
}
|
|
249
286
|
|
|
250
|
-
//remove if already exist
|
|
251
|
-
if (createdSelectedItems.find((t) => itemLabel(t) === itemLabel(item))) {
|
|
252
|
-
setCreatedSelectedItems((prevItems) =>
|
|
253
|
-
prevItems.filter((prevItem) => itemLabel(prevItem) !== itemLabel(item))
|
|
254
|
-
);
|
|
255
|
-
} else {
|
|
256
|
-
const wasNewlyCreated = createdItems.find((t) => itemLabel(t) === itemLabel(item));
|
|
257
|
-
//only add to createdSelectedItems if it was previously created and not
|
|
258
|
-
// from the initial items or a possible query response
|
|
259
|
-
if (wasNewlyCreated) {
|
|
260
|
-
setCreatedSelectedItems((prevItems) => [...prevItems, item]);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
287
|
if (clearQueryOnSelection) {
|
|
265
288
|
requestState.current.query = "";
|
|
266
289
|
inputRef.current?.focus();
|
|
@@ -281,12 +304,13 @@ export function MultiSelect<T>({
|
|
|
281
304
|
}
|
|
282
305
|
const fn = async () => {
|
|
283
306
|
setShowSpinner(true);
|
|
284
|
-
|
|
307
|
+
setFilteredItems([]);
|
|
285
308
|
const resultFromQuery = runOnQueryChange && (await runOnQueryChange(removeExtraSpaces(query)));
|
|
286
309
|
if (requestState.current.query === query) {
|
|
287
310
|
// Only use most recent request
|
|
288
|
-
|
|
289
|
-
|
|
311
|
+
const outsideOptions = [...(resultFromQuery ?? externalItems)];
|
|
312
|
+
setFilteredItems(
|
|
313
|
+
[...outsideOptions, ...createdItems.current].filter((item) =>
|
|
290
314
|
itemLabel(item).toLowerCase().includes(query.toLowerCase())
|
|
291
315
|
)
|
|
292
316
|
);
|
|
@@ -297,7 +321,7 @@ export function MultiSelect<T>({
|
|
|
297
321
|
} else if (!query.length) {
|
|
298
322
|
// if the query is empty we need to show all options and reset current query
|
|
299
323
|
requestState.current.query = "";
|
|
300
|
-
|
|
324
|
+
setFilteredItems(() => [...externalItems, ...createdItems.current]);
|
|
301
325
|
}
|
|
302
326
|
};
|
|
303
327
|
|
|
@@ -314,7 +338,7 @@ export function MultiSelect<T>({
|
|
|
314
338
|
return null;
|
|
315
339
|
}
|
|
316
340
|
let label = itemLabel(item);
|
|
317
|
-
if (createdItems.find((created) => itemId(created) === itemId(item))) {
|
|
341
|
+
if (createdItems.current.find((created) => itemId(created) === itemId(item))) {
|
|
318
342
|
label += newItemPostfix;
|
|
319
343
|
}
|
|
320
344
|
return (
|
|
@@ -334,8 +358,17 @@ export function MultiSelect<T>({
|
|
|
334
358
|
*/
|
|
335
359
|
const handleClear = () => {
|
|
336
360
|
requestState.current.query = "";
|
|
337
|
-
|
|
338
|
-
|
|
361
|
+
|
|
362
|
+
if (isControlled) {
|
|
363
|
+
onSelection({
|
|
364
|
+
selectedItems: [],
|
|
365
|
+
createdItems: createdItems.current,
|
|
366
|
+
});
|
|
367
|
+
} else {
|
|
368
|
+
setSelectedItems([]);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
setFilteredItems([...externalItems, ...createdItems.current]);
|
|
339
372
|
};
|
|
340
373
|
|
|
341
374
|
/**
|
|
@@ -343,9 +376,8 @@ export function MultiSelect<T>({
|
|
|
343
376
|
* @param label
|
|
344
377
|
* @param index
|
|
345
378
|
*/
|
|
346
|
-
const removeTagFromSelectionViaIndex = (
|
|
379
|
+
const removeTagFromSelectionViaIndex = (_label: React.ReactNode, index: number) => {
|
|
347
380
|
setSelectedItems([...selectedItems.slice(0, index), ...selectedItems.slice(index + 1)]);
|
|
348
|
-
setCreatedSelectedItems((items) => items.filter((item) => itemLabel(item) !== label));
|
|
349
381
|
};
|
|
350
382
|
|
|
351
383
|
/**
|
|
@@ -354,9 +386,8 @@ export function MultiSelect<T>({
|
|
|
354
386
|
const createNewItem = (query: string): T => {
|
|
355
387
|
const newItem = createNewItemFromQuery!(query);
|
|
356
388
|
//set new items
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
setFilteredItemList((items) => [...items, newItem]);
|
|
389
|
+
createdItems.current = [...createdItems.current, newItem];
|
|
390
|
+
setFilteredItems((items) => [...items, newItem]);
|
|
360
391
|
requestState.current.query = "";
|
|
361
392
|
return newItem;
|
|
362
393
|
};
|
|
@@ -366,12 +397,7 @@ export function MultiSelect<T>({
|
|
|
366
397
|
* @param event
|
|
367
398
|
*/
|
|
368
399
|
const handleOnKeyUp = (event: React.KeyboardEvent<HTMLElement>) => {
|
|
369
|
-
if (
|
|
370
|
-
event.key === "Enter" &&
|
|
371
|
-
!filteredItemList.length &&
|
|
372
|
-
!!requestState.current.query &&
|
|
373
|
-
createNewItemFromQuery
|
|
374
|
-
) {
|
|
400
|
+
if (event.key === "Enter" && !filteredItems.length && !!requestState.current.query && createNewItemFromQuery) {
|
|
375
401
|
createNewItem(requestState.current.query);
|
|
376
402
|
}
|
|
377
403
|
inputRef.current?.focus();
|
|
@@ -438,7 +464,7 @@ export function MultiSelect<T>({
|
|
|
438
464
|
{...otherMultiSelectProps}
|
|
439
465
|
query={requestState.current.query}
|
|
440
466
|
onQueryChange={onQueryChange}
|
|
441
|
-
items={
|
|
467
|
+
items={filteredItems}
|
|
442
468
|
onItemSelect={onItemSelect}
|
|
443
469
|
itemRenderer={onItemRenderer}
|
|
444
470
|
itemsEqual={(a: T, b: T) => itemId(a) === itemId(b)}
|
|
@@ -4,22 +4,6 @@ import { Meta, StoryFn } from "@storybook/react";
|
|
|
4
4
|
|
|
5
5
|
import { MultiSelectSelectionProps, MultiSuggestField } from "./../../../index";
|
|
6
6
|
|
|
7
|
-
export default {
|
|
8
|
-
title: "Forms/MultiSuggestField",
|
|
9
|
-
component: MultiSuggestField,
|
|
10
|
-
argTypes: {
|
|
11
|
-
items: {
|
|
12
|
-
control: "none",
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
} as Meta<typeof MultiSuggestField>;
|
|
16
|
-
|
|
17
|
-
const Template: StoryFn<typeof MultiSuggestField> = (args) => (
|
|
18
|
-
<div>
|
|
19
|
-
<MultiSuggestField {...args} />
|
|
20
|
-
</div>
|
|
21
|
-
);
|
|
22
|
-
|
|
23
7
|
const testLabels = loremIpsum({
|
|
24
8
|
p: 1,
|
|
25
9
|
avgSentencesPerParagraph: 5,
|
|
@@ -28,13 +12,32 @@ const testLabels = loremIpsum({
|
|
|
28
12
|
random: false,
|
|
29
13
|
})
|
|
30
14
|
.toString()
|
|
31
|
-
.split(".")
|
|
15
|
+
.split(".")
|
|
16
|
+
.map((item) => item.trim());
|
|
32
17
|
|
|
33
18
|
const items = new Array(5).fill(undefined).map((_, id) => {
|
|
34
19
|
const testLabel = testLabels[id];
|
|
35
20
|
return { testLabel, testId: `${testLabel}-id` };
|
|
36
21
|
});
|
|
37
22
|
|
|
23
|
+
export default {
|
|
24
|
+
title: "Forms/MultiSuggestField",
|
|
25
|
+
component: MultiSuggestField,
|
|
26
|
+
argTypes: {
|
|
27
|
+
items: {
|
|
28
|
+
control: "none",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
} as Meta<typeof MultiSuggestField>;
|
|
32
|
+
|
|
33
|
+
const Template: StoryFn<typeof MultiSuggestField> = (args) => {
|
|
34
|
+
return (
|
|
35
|
+
<div>
|
|
36
|
+
<MultiSuggestField {...args} />
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
38
41
|
export const Default = Template.bind({});
|
|
39
42
|
Default.args = {
|
|
40
43
|
items,
|
|
@@ -64,20 +67,22 @@ const selectedItems = items.slice(1, 3);
|
|
|
64
67
|
/**
|
|
65
68
|
* Set the default selected values from the client code.
|
|
66
69
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
|
|
71
|
+
export const predefinedNotControlledValues = Template.bind({});
|
|
72
|
+
predefinedNotControlledValues.args = {
|
|
69
73
|
items,
|
|
70
74
|
selectedItems,
|
|
71
75
|
prePopulateWithItems: false,
|
|
76
|
+
onSelection: undefined,
|
|
72
77
|
itemId: (item) => item.testId,
|
|
73
|
-
itemLabel: (item) =>
|
|
78
|
+
itemLabel: (item) => item.testLabel,
|
|
74
79
|
};
|
|
75
80
|
|
|
76
81
|
/**
|
|
77
82
|
* New item creation, add to a existing list
|
|
78
83
|
*/
|
|
79
|
-
export const
|
|
80
|
-
|
|
84
|
+
export const uncontrolledNewItemCreation = Template.bind({});
|
|
85
|
+
uncontrolledNewItemCreation.args = {
|
|
81
86
|
items,
|
|
82
87
|
createNewItemFromQuery: (query) => ({ testId: `${query}-id`, testLabel: query }),
|
|
83
88
|
prePopulateWithItems: false,
|
|
@@ -88,7 +93,7 @@ newItemCreation.args = {
|
|
|
88
93
|
const CreationTemplate: StoryFn = () => {
|
|
89
94
|
const [selectedValues, setSelectedValues] = useState<string[]>([]);
|
|
90
95
|
|
|
91
|
-
const
|
|
96
|
+
const items = useMemo<string[]>(() => ["foo", "bar", "baz"], []);
|
|
92
97
|
|
|
93
98
|
const identity = useCallback((item: string): string => item, []);
|
|
94
99
|
|
|
@@ -100,7 +105,7 @@ const CreationTemplate: StoryFn = () => {
|
|
|
100
105
|
|
|
101
106
|
return (
|
|
102
107
|
<MultiSuggestField<string>
|
|
103
|
-
items={
|
|
108
|
+
items={items}
|
|
104
109
|
selectedItems={selectedValues}
|
|
105
110
|
onSelection={handleOnSelect}
|
|
106
111
|
itemId={identity}
|
|
@@ -114,4 +119,44 @@ const CreationTemplate: StoryFn = () => {
|
|
|
114
119
|
/**
|
|
115
120
|
* Completely create all items from quieries
|
|
116
121
|
*/
|
|
117
|
-
export const
|
|
122
|
+
export const conrolledNewItemCreation = CreationTemplate.bind({});
|
|
123
|
+
|
|
124
|
+
const WithResetButtonComponent = (): JSX.Element => {
|
|
125
|
+
const copy: Array<{ testLabel: string; testId: string }> = [items[2]];
|
|
126
|
+
|
|
127
|
+
const [selected, setSelected] = useState(copy);
|
|
128
|
+
|
|
129
|
+
const handleOnSelect = useCallback((params) => {
|
|
130
|
+
const items = params.selectedItems;
|
|
131
|
+
setSelected(items);
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
const handleReset = (): void => {
|
|
135
|
+
setSelected(copy);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div>
|
|
140
|
+
<button onClick={handleReset}>Reset</button>
|
|
141
|
+
<br />
|
|
142
|
+
<br />
|
|
143
|
+
<MultiSuggestField<{ testLabel: string; testId: string }>
|
|
144
|
+
items={items}
|
|
145
|
+
selectedItems={selected}
|
|
146
|
+
onSelection={handleOnSelect}
|
|
147
|
+
itemId={({ testId }) => testId}
|
|
148
|
+
itemLabel={({ testLabel }) => testLabel}
|
|
149
|
+
createNewItemFromQuery={(query) => ({ testId: `${query}-id`, testLabel: query })}
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const WithResetButton: StoryFn = () => {
|
|
156
|
+
return <WithResetButtonComponent />;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Reset values
|
|
161
|
+
*/
|
|
162
|
+
export const withResetItemAndCreation = WithResetButton.bind({});
|
|
@@ -1,10 +1,50 @@
|
|
|
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
50
|
it("should render default input", () => {
|
|
@@ -15,25 +55,27 @@ describe("MultiSuggestField", () => {
|
|
|
15
55
|
});
|
|
16
56
|
|
|
17
57
|
it("should render default selected items", async () => {
|
|
18
|
-
const {
|
|
58
|
+
const { getByText } = render(<MultiSuggestField {...predefinedNotControlledValues.args} />);
|
|
19
59
|
|
|
20
|
-
const [firstSelected, secondSelected]: Array<string> =
|
|
21
|
-
({ testLabel }) => testLabel
|
|
60
|
+
const [firstSelected, secondSelected]: Array<string> = predefinedNotControlledValues.args.selectedItems.map(
|
|
61
|
+
({ testLabel }) => testLabel
|
|
22
62
|
);
|
|
23
63
|
|
|
24
64
|
await waitFor(() => {
|
|
25
|
-
expect(
|
|
26
|
-
expect(
|
|
65
|
+
expect(getByText(firstSelected)).toBeInTheDocument();
|
|
66
|
+
expect(getByText(secondSelected)).toBeInTheDocument();
|
|
27
67
|
});
|
|
28
68
|
});
|
|
29
69
|
|
|
30
|
-
it("should clear all selected items on clear button click", async () => {
|
|
31
|
-
const {
|
|
32
|
-
|
|
33
|
-
const [firstSelected, secondSelected]: Array<string> = predefinedValues.args.selectedItems.map(
|
|
34
|
-
({ testLabel }) => testLabel.trim()
|
|
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} />
|
|
35
73
|
);
|
|
36
74
|
|
|
75
|
+
const selectedLength = predefinedNotControlledValues.args.selectedItems.length;
|
|
76
|
+
|
|
77
|
+
expect(container.querySelectorAll("[data-tag-index]").length).toBe(selectedLength);
|
|
78
|
+
|
|
37
79
|
const clearButton = container.querySelector('[data-test-id="clear-all-items"');
|
|
38
80
|
|
|
39
81
|
expect(clearButton).toBeInTheDocument();
|
|
@@ -41,8 +83,7 @@ describe("MultiSuggestField", () => {
|
|
|
41
83
|
fireEvent.click(clearButton!);
|
|
42
84
|
|
|
43
85
|
await waitFor(() => {
|
|
44
|
-
expect(
|
|
45
|
-
expect(queryByTestId(secondSelected)).not.toBeInTheDocument();
|
|
86
|
+
expect(container.querySelectorAll("[data-tag-index]").length).toBe(0);
|
|
46
87
|
});
|
|
47
88
|
});
|
|
48
89
|
|
|
@@ -96,10 +137,83 @@ describe("MultiSuggestField", () => {
|
|
|
96
137
|
});
|
|
97
138
|
|
|
98
139
|
it("should render disable field with selected items", async () => {
|
|
99
|
-
const { container } = render(<MultiSuggestField {...
|
|
140
|
+
const { container } = render(<MultiSuggestField {...predefinedNotControlledValues.args} disabled />);
|
|
100
141
|
|
|
101
142
|
const [inputTargetContainer] = container.getElementsByClassName("eccgui-multiselect__target");
|
|
102
143
|
|
|
103
144
|
expect(inputTargetContainer.getAttribute("aria-disabled")).toBe("true");
|
|
104
145
|
});
|
|
146
|
+
|
|
147
|
+
it("should call onSelection function with the selected items for the contolled field", async () => {
|
|
148
|
+
const onSelection = jest.fn();
|
|
149
|
+
|
|
150
|
+
const { container } = render(
|
|
151
|
+
<MultiSuggestField {...dropdownOnFocus.args} items={items} onSelection={onSelection} />
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
|
|
155
|
+
const [input] = inputContainer.getElementsByTagName("input");
|
|
156
|
+
|
|
157
|
+
fireEvent.click(input);
|
|
158
|
+
|
|
159
|
+
await waitFor(() => {
|
|
160
|
+
const listbox = screen.getByRole("listbox");
|
|
161
|
+
expect(listbox).toBeInTheDocument();
|
|
162
|
+
|
|
163
|
+
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
164
|
+
expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
|
|
165
|
+
|
|
166
|
+
const item = menuItems[0];
|
|
167
|
+
fireEvent.click(item);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await waitFor(() => {
|
|
171
|
+
const expectedObject = {
|
|
172
|
+
createdItems: [],
|
|
173
|
+
newlySelected: items[0],
|
|
174
|
+
selectedItems: [items[0]],
|
|
175
|
+
};
|
|
176
|
+
expect(onSelection).toHaveBeenCalledWith(expectedObject);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("should reset values correctly with the pre-defined values for the contolled field", async () => {
|
|
181
|
+
const { container, getByTestId } = render(<TestComponent />);
|
|
182
|
+
|
|
183
|
+
const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
|
|
184
|
+
const [input] = inputContainer.getElementsByTagName("input");
|
|
185
|
+
|
|
186
|
+
fireEvent.click(input);
|
|
187
|
+
|
|
188
|
+
await waitFor(() => {
|
|
189
|
+
const listbox = screen.getByRole("listbox");
|
|
190
|
+
expect(listbox).toBeInTheDocument();
|
|
191
|
+
|
|
192
|
+
const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
|
|
193
|
+
expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
|
|
194
|
+
|
|
195
|
+
const selectedItems = inputContainer.querySelectorAll("[data-tag-index]");
|
|
196
|
+
expect(selectedItems.length).toBe(1);
|
|
197
|
+
|
|
198
|
+
const item = menuItems[0];
|
|
199
|
+
fireEvent.click(item);
|
|
200
|
+
|
|
201
|
+
const otherItem = menuItems[menuItems.length - 1];
|
|
202
|
+
fireEvent.click(otherItem);
|
|
203
|
+
|
|
204
|
+
const selectedItemsAfterSelection = inputContainer.querySelectorAll("[data-tag-index]");
|
|
205
|
+
|
|
206
|
+
expect(selectedItemsAfterSelection.length).toBe(3);
|
|
207
|
+
|
|
208
|
+
const resetButton = getByTestId("reset-button");
|
|
209
|
+
expect(resetButton).toBeInTheDocument();
|
|
210
|
+
|
|
211
|
+
fireEvent.click(resetButton);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await waitFor(() => {
|
|
215
|
+
const selectedItemsAfterReset = inputContainer.querySelectorAll("[data-tag-index]");
|
|
216
|
+
expect(selectedItemsAfterReset.length).toBe(1);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
105
219
|
});
|