@eccenca/gui-elements 23.7.0-rc.1 → 23.7.0-rc.3
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 +21 -1
- package/dist/cjs/cmem/markdown/Markdown.js +1 -1
- package/dist/cjs/cmem/markdown/Markdown.js.map +1 -1
- package/dist/cjs/components/AutocompleteField/AutoCompleteField.js +3 -3
- package/dist/cjs/components/AutocompleteField/AutoCompleteField.js.map +1 -1
- package/dist/cjs/components/Breadcrumb/BreadcrumbList.js +1 -1
- package/dist/cjs/components/Breadcrumb/BreadcrumbList.js.map +1 -1
- package/dist/cjs/components/Card/Card.js +3 -1
- package/dist/cjs/components/Card/Card.js.map +1 -1
- package/dist/cjs/components/MultiSelect/MultiSelect.js +8 -34
- package/dist/cjs/components/MultiSelect/MultiSelect.js.map +1 -1
- package/dist/cjs/components/Sticky/StickyTarget.js +46 -5
- package/dist/cjs/components/Sticky/StickyTarget.js.map +1 -1
- package/dist/cjs/components/TextField/TextArea.js +85 -8
- package/dist/cjs/components/TextField/TextArea.js.map +1 -1
- package/dist/esm/cmem/markdown/Markdown.js +1 -1
- package/dist/esm/cmem/markdown/Markdown.js.map +1 -1
- package/dist/esm/components/AutocompleteField/AutoCompleteField.js +3 -3
- package/dist/esm/components/AutocompleteField/AutoCompleteField.js.map +1 -1
- package/dist/esm/components/Breadcrumb/BreadcrumbList.js +1 -1
- package/dist/esm/components/Breadcrumb/BreadcrumbList.js.map +1 -1
- package/dist/esm/components/Card/Card.js +4 -2
- package/dist/esm/components/Card/Card.js.map +1 -1
- package/dist/esm/components/MultiSelect/MultiSelect.js +8 -34
- package/dist/esm/components/MultiSelect/MultiSelect.js.map +1 -1
- package/dist/esm/components/Sticky/StickyTarget.js +46 -5
- package/dist/esm/components/Sticky/StickyTarget.js.map +1 -1
- package/dist/esm/components/TextField/TextArea.js +86 -9
- package/dist/esm/components/TextField/TextArea.js.map +1 -1
- package/dist/types/cmem/ActivityControl/ActivityControlTypes.d.ts +1 -0
- package/dist/types/components/AutocompleteField/AutoCompleteField.d.ts +1 -1
- package/dist/types/components/Breadcrumb/BreadcrumbList.d.ts +2 -1
- package/dist/types/components/Card/Card.d.ts +8 -2
- package/dist/types/components/MultiSelect/MultiSelect.d.ts +14 -9
- package/dist/types/components/Sticky/StickyTarget.d.ts +14 -3
- package/dist/types/components/TextField/TextArea.d.ts +28 -3
- package/package.json +1 -1
- package/src/cmem/ActivityControl/ActivityControlTypes.ts +2 -0
- package/src/cmem/markdown/Markdown.tsx +1 -1
- package/src/components/Application/application.scss +0 -1
- package/src/components/AutocompleteField/AutoCompleteField.tsx +4 -4
- package/src/components/Breadcrumb/BreadcrumbList.tsx +3 -3
- package/src/components/Card/Card.tsx +15 -3
- package/src/components/Card/card.scss +6 -1
- package/src/components/Icon/stories/Icon.stories.tsx +1 -1
- package/src/components/MultiSelect/MultiSelect.tsx +30 -43
- package/src/components/MultiSuggestField/MultiSuggestField.stories.tsx +1 -2
- package/src/components/MultiSuggestField/tests/MultiSuggestField.test.tsx +90 -6
- package/src/components/Sticky/StickyTarget.tsx +63 -7
- package/src/components/Sticky/sticky.scss +71 -12
- package/src/components/TextField/TextArea.tsx +174 -12
- package/src/components/TextField/stories/TextArea.stories.tsx +39 -12
- package/src/components/TextField/textfield.scss +81 -11
- package/src/includes/blueprintjs/_requisits.scss +1 -1
- package/src/includes/blueprintjs/_variables.scss +3 -172
- package/src/includes/carbon-components/_requisits.scss +1 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { CardProps as BlueprintCardProps } from "@blueprintjs/core";
|
|
3
|
-
export interface CardProps extends BlueprintCardProps {
|
|
2
|
+
import { CardProps as BlueprintCardProps, Elevation as BlueprintCardElevation } from "@blueprintjs/core";
|
|
3
|
+
export interface CardProps extends Omit<BlueprintCardProps, "elevation"> {
|
|
4
4
|
/**
|
|
5
5
|
* `<Card />` element is included in DOM as simple `div` element.
|
|
6
6
|
* By default it is a HTML `section`.
|
|
@@ -14,6 +14,12 @@ export interface CardProps extends BlueprintCardProps {
|
|
|
14
14
|
* Background color is slightly altered to differ card display from other cards.
|
|
15
15
|
*/
|
|
16
16
|
elevated?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Controls the intensity of the drop shadow beneath the card.
|
|
19
|
+
* At elevation `0`, no drop shadow is applied.
|
|
20
|
+
* At elevation `-1`, the card is even borderless.
|
|
21
|
+
*/
|
|
22
|
+
elevation?: -1 | BlueprintCardElevation;
|
|
17
23
|
/**
|
|
18
24
|
* When card (or its children) get focus the card is scrolled into the viewport.
|
|
19
25
|
* Property value defined which part of the card is always scrolled in, this may important when the card is larger than the viewport.
|
|
@@ -9,11 +9,7 @@ export interface MultiSelectSelectionProps<T> {
|
|
|
9
9
|
createdItems: Partial<T>[];
|
|
10
10
|
}
|
|
11
11
|
export declare type SelectedParamsType<T> = MultiSelectSelectionProps<T>;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Predefined selected values
|
|
15
|
-
*/
|
|
16
|
-
selectedItems?: T[];
|
|
12
|
+
interface MultiSelectCommonProps<T> extends TestableComponent, Pick<BlueprintMultiSelectProps<T>, "items" | "placeholder" | "openOnKeyDown"> {
|
|
17
13
|
/**
|
|
18
14
|
* Additional class name, space separated.
|
|
19
15
|
*/
|
|
@@ -27,10 +23,6 @@ export interface MultiSelectProps<T> extends TestableComponent, Pick<BlueprintMu
|
|
|
27
23
|
* this would be used in the item selection list as well as the multi-select input
|
|
28
24
|
*/
|
|
29
25
|
itemLabel: (item: T) => string;
|
|
30
|
-
/**
|
|
31
|
-
* When set to true will set the multi-select value with all the items provided
|
|
32
|
-
*/
|
|
33
|
-
prePopulateWithItems?: boolean;
|
|
34
26
|
/**
|
|
35
27
|
* function handler that would be called anytime an item is selected/deselected or an item is created/removed
|
|
36
28
|
*/
|
|
@@ -107,6 +99,19 @@ export interface MultiSelectProps<T> extends TestableComponent, Pick<BlueprintMu
|
|
|
107
99
|
*/
|
|
108
100
|
wrapperProps?: React.HTMLAttributes<HTMLDivElement>;
|
|
109
101
|
}
|
|
102
|
+
export declare type MultiSelectProps<T> = MultiSelectCommonProps<T> & ({
|
|
103
|
+
/**
|
|
104
|
+
* Predefined selected values
|
|
105
|
+
*/
|
|
106
|
+
selectedItems?: T[];
|
|
107
|
+
prePopulateWithItems?: never;
|
|
108
|
+
} | {
|
|
109
|
+
selectedItems?: never;
|
|
110
|
+
/**
|
|
111
|
+
* When set to true will set the multi-select value with all the items provided
|
|
112
|
+
*/
|
|
113
|
+
prePopulateWithItems?: boolean;
|
|
114
|
+
});
|
|
110
115
|
/**
|
|
111
116
|
* **Element is deprecated for the current type of usage.**
|
|
112
117
|
* Use `MultiSuggestField` as replacement.
|
|
@@ -9,15 +9,26 @@ export interface StickyTargetProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
9
9
|
* The application header is not taken into offset calculation
|
|
10
10
|
*/
|
|
11
11
|
local?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Set additional distance to original sticky position.
|
|
14
|
+
*/
|
|
15
|
+
offset?: `${number}${string}`;
|
|
12
16
|
/**
|
|
13
17
|
* Set the background color used for the sticky area.
|
|
14
18
|
* As it can overlay other content readability could be harmed if the overlayed content is shining through.
|
|
15
19
|
*/
|
|
16
20
|
background?: "card" | "application" | "transparent";
|
|
17
21
|
/**
|
|
18
|
-
*
|
|
22
|
+
* In some situations there could be a gap between sticky target area and the border of the related scroll area.
|
|
23
|
+
* The main gap is the gap towards the direction of the sticky behaviour, specified by `to`.
|
|
24
|
+
* You can fill this gap with a gradient or full background color.
|
|
19
25
|
*/
|
|
20
|
-
|
|
26
|
+
fillMainGap?: "full" | "gradient";
|
|
27
|
+
/**
|
|
28
|
+
* The secondary gap is the gap against the direction of the sticky behaviour.
|
|
29
|
+
* So in case of `to="top"` this is rendered on the bottom of the sticky area.
|
|
30
|
+
*/
|
|
31
|
+
fillSecondaryGap?: "full" | "gradient";
|
|
21
32
|
/**
|
|
22
33
|
* Callback that returns an DOM element.
|
|
23
34
|
* The position of `StickyTarget` is then calculated relative to that element.
|
|
@@ -28,5 +39,5 @@ export interface StickyTargetProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
28
39
|
* Element wraps the content that need to be displayed sticky.
|
|
29
40
|
* The content then offset relative to its nearest scrolling ancestor and containing block (nearest block-level ancestor).
|
|
30
41
|
*/
|
|
31
|
-
export declare const StickyTarget: ({ className, to, local, background,
|
|
42
|
+
export declare const StickyTarget: ({ className, to, local, offset, background, fillMainGap, fillSecondaryGap, style, getConnectedElement, ...otherDivProps }: StickyTargetProps) => React.JSX.Element;
|
|
32
43
|
export default StickyTarget;
|
|
@@ -1,27 +1,52 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { TextAreaProps as BlueprintTextAreaProps } from "@blueprintjs/core";
|
|
2
|
+
import { MaybeElement, TextAreaProps as BlueprintTextAreaProps } from "@blueprintjs/core";
|
|
3
|
+
import { IntentTypes } from "../../common/Intent";
|
|
4
|
+
import { ValidIconName } from "../Icon/canonicalIconNames";
|
|
3
5
|
import { InvisibleCharacterWarningProps } from "./useTextValidation";
|
|
4
|
-
export interface TextAreaProps extends
|
|
6
|
+
export interface TextAreaProps extends Omit<BlueprintTextAreaProps, "intent"> {
|
|
5
7
|
/**
|
|
6
8
|
* when set to true the input takes a blue border color
|
|
9
|
+
* @deprecated Use the `intent` property.
|
|
7
10
|
*/
|
|
8
11
|
hasStatePrimary?: boolean;
|
|
9
12
|
/**
|
|
10
13
|
* when set to true the input takes a green border color
|
|
14
|
+
* @deprecated Use the `intent` property.
|
|
11
15
|
*/
|
|
12
16
|
hasStateSuccess?: boolean;
|
|
13
17
|
/**
|
|
14
18
|
* when set to true the input takes an orange border color
|
|
19
|
+
* @deprecated Use the `intent` property.
|
|
15
20
|
*/
|
|
16
21
|
hasStateWarning?: boolean;
|
|
17
22
|
/**
|
|
18
23
|
* when set to true the input takes a red border color
|
|
24
|
+
* @deprecated Use the `intent` property.
|
|
19
25
|
*/
|
|
20
26
|
hasStateDanger?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Intent state of the text area.
|
|
29
|
+
*/
|
|
30
|
+
intent?: IntentTypes | "edited" | "removed";
|
|
21
31
|
/**
|
|
22
32
|
* If set, allows to be informed of invisible, hard to spot characters in the string value.
|
|
23
33
|
*/
|
|
24
34
|
invisibleCharacterWarning?: InvisibleCharacterWarningProps;
|
|
35
|
+
/**
|
|
36
|
+
* Left aligned icon, can be a canonical icon name or an `Icon` element.
|
|
37
|
+
* This will update left padding on the text area.
|
|
38
|
+
*/
|
|
39
|
+
leftIcon?: ValidIconName | MaybeElement;
|
|
40
|
+
/**
|
|
41
|
+
* Element to render on right side of text area. Should be not too large.
|
|
42
|
+
* This will update right padding on the text area.
|
|
43
|
+
*/
|
|
44
|
+
rightElement?: JSX.Element;
|
|
45
|
+
/**
|
|
46
|
+
* Add HTML properties to the wrapper element.
|
|
47
|
+
* The element wraps `TextArea` in case of a given `wrapperDivProps`, `leftIcon` or `rightElement` property.
|
|
48
|
+
*/
|
|
49
|
+
wrapperDivProps?: Omit<React.HTMLAttributes<HTMLDivElement>, "children">;
|
|
25
50
|
}
|
|
26
|
-
export declare const TextArea: ({ className, hasStatePrimary, hasStateSuccess, hasStateWarning, hasStateDanger, rows, invisibleCharacterWarning, ...otherProps }: TextAreaProps) => React.JSX.Element;
|
|
51
|
+
export declare const TextArea: ({ className, hasStatePrimary, hasStateSuccess, hasStateWarning, hasStateDanger, rows, invisibleCharacterWarning, leftIcon, rightElement, wrapperDivProps, ...otherProps }: TextAreaProps) => React.JSX.Element;
|
|
27
52
|
export default TextArea;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eccenca/gui-elements",
|
|
3
3
|
"description": "GUI elements based on other libraries, usable in React application, written in Typescript.",
|
|
4
|
-
"version": "23.7.0-rc.
|
|
4
|
+
"version": "23.7.0-rc.3",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/eccenca/gui-elements",
|
|
7
7
|
"bugs": "https://github.com/eccenca/gui-elements/issues",
|
|
@@ -16,6 +16,8 @@ export interface IActivityStatus {
|
|
|
16
16
|
statusName: "Waiting" | "Finished" | "Idle" | "Running" | "Canceling";
|
|
17
17
|
// A number between 0 and 100
|
|
18
18
|
progress: number;
|
|
19
|
+
// timestamp for last update
|
|
20
|
+
lastUpdateTime:string;
|
|
19
21
|
// More information corresponding to the status
|
|
20
22
|
message: string;
|
|
21
23
|
// If the activity has been cancelled
|
|
@@ -3,7 +3,7 @@ import ReactMarkdown from "react-markdown";
|
|
|
3
3
|
import { PluggableList } from "react-markdown/lib/react-markdown";
|
|
4
4
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
|
5
5
|
import { materialLight } from "react-syntax-highlighter/dist/esm/styles/prism";
|
|
6
|
-
// @ts-
|
|
6
|
+
// @ts-ignore: No declaration file for module (TODO: should be @ts-expect-error but GUI elements is used inside project with `noImplicitAny=false`)
|
|
7
7
|
import remarkTypograf from "@mavrin/remark-typograf";
|
|
8
8
|
import rehypeRaw from "rehype-raw";
|
|
9
9
|
import { remarkDefinitionList } from "remark-definition-list";
|
|
@@ -157,7 +157,7 @@ export interface AutoCompleteFieldProps<T, UPDATE_VALUE> {
|
|
|
157
157
|
*/
|
|
158
158
|
fill?: boolean;
|
|
159
159
|
/** Utility that fetches more options when clicked*/
|
|
160
|
-
loadMoreResults?: () => Promise<T[]>;
|
|
160
|
+
loadMoreResults?: () => Promise<T[] | undefined>;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
export type IAutoCompleteFieldProps<T, UPDATE_VALUE> = AutoCompleteFieldProps<T, UPDATE_VALUE>;
|
|
@@ -452,13 +452,13 @@ export function AutoCompleteField<T, UPDATE_VALUE>(props: AutoCompleteFieldProps
|
|
|
452
452
|
const menu = event.target;
|
|
453
453
|
const { scrollTop, scrollHeight, clientHeight } = menu;
|
|
454
454
|
// Check if scrolled to the bottom of the list
|
|
455
|
-
if (scrollTop + clientHeight >= scrollHeight && loadMoreResults) {
|
|
455
|
+
if (Math.round(scrollTop + clientHeight) >= scrollHeight && loadMoreResults) {
|
|
456
456
|
const results = await loadMoreResults();
|
|
457
457
|
if (results) {
|
|
458
458
|
setFiltered((prev) => [...prev, ...results]);
|
|
459
459
|
setTimeout(() => {
|
|
460
|
-
menu.scrollTop =
|
|
461
|
-
menu.scrollTo({ left: 0, top:
|
|
460
|
+
menu.scrollTop = scrollTop; //safari adaptation
|
|
461
|
+
menu.scrollTo({ left: 0, top: scrollTop, behavior: "auto" });
|
|
462
462
|
});
|
|
463
463
|
}
|
|
464
464
|
}
|
|
@@ -10,7 +10,6 @@ import { TestableComponent } from "../interfaces";
|
|
|
10
10
|
import BreadcrumbItem from "./BreadcrumbItem";
|
|
11
11
|
import { BreadcrumbItemProps } from "./BreadcrumbItem";
|
|
12
12
|
|
|
13
|
-
// FIXME: enforce onItemClick later when href value can always be routed correctly
|
|
14
13
|
export interface BreadcrumbListProps
|
|
15
14
|
extends TestableComponent,
|
|
16
15
|
Omit<
|
|
@@ -28,7 +27,8 @@ export interface BreadcrumbListProps
|
|
|
28
27
|
*/
|
|
29
28
|
items: BreadcrumbItemProps[];
|
|
30
29
|
/**
|
|
31
|
-
|
|
30
|
+
Click handler used on all breadcrumb items using their `href` property.
|
|
31
|
+
Is only used if the breadcrumb item have not defined an own `onClick` handler.
|
|
32
32
|
*/
|
|
33
33
|
onItemClick?(itemUrl: string | undefined, event: object): boolean | void;
|
|
34
34
|
/**
|
|
@@ -80,7 +80,7 @@ export const BreadcrumbList = ({
|
|
|
80
80
|
/*itemDivider="/"*/
|
|
81
81
|
{...otherProps}
|
|
82
82
|
onClick={
|
|
83
|
-
onItemClick
|
|
83
|
+
onItemClick && propsBreadcrumb.href && !onClick
|
|
84
84
|
? (e) => {
|
|
85
85
|
onItemClick(propsBreadcrumb.href, e);
|
|
86
86
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Card as BlueprintCard,
|
|
4
|
+
CardProps as BlueprintCardProps,
|
|
5
|
+
Elevation as BlueprintCardElevation,
|
|
6
|
+
} from "@blueprintjs/core";
|
|
3
7
|
|
|
4
8
|
import { CLASSPREFIX as eccgui } from "../../configuration/constants";
|
|
5
9
|
|
|
6
|
-
export interface CardProps extends BlueprintCardProps {
|
|
10
|
+
export interface CardProps extends Omit<BlueprintCardProps, "elevation"> {
|
|
7
11
|
/**
|
|
8
12
|
* `<Card />` element is included in DOM as simple `div` element.
|
|
9
13
|
* By default it is a HTML `section`.
|
|
@@ -17,6 +21,12 @@ export interface CardProps extends BlueprintCardProps {
|
|
|
17
21
|
* Background color is slightly altered to differ card display from other cards.
|
|
18
22
|
*/
|
|
19
23
|
elevated?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Controls the intensity of the drop shadow beneath the card.
|
|
26
|
+
* At elevation `0`, no drop shadow is applied.
|
|
27
|
+
* At elevation `-1`, the card is even borderless.
|
|
28
|
+
*/
|
|
29
|
+
elevation?: -1 | BlueprintCardElevation;
|
|
20
30
|
/**
|
|
21
31
|
* When card (or its children) get focus the card is scrolled into the viewport.
|
|
22
32
|
* Property value defined which part of the card is always scrolled in, this may important when the card is larger than the viewport.
|
|
@@ -48,6 +58,7 @@ export const Card = ({
|
|
|
48
58
|
? {
|
|
49
59
|
tabIndex: 0,
|
|
50
60
|
onFocus: (e: any) => {
|
|
61
|
+
// FIXME: we should not have any hard relations to apps that using this lib
|
|
51
62
|
const el = e.target.closest(".diapp-iframewindow__content");
|
|
52
63
|
setTimeout(() => {
|
|
53
64
|
if (el)
|
|
@@ -68,9 +79,10 @@ export const Card = ({
|
|
|
68
79
|
(elevated ? ` ${eccgui}-card--elevated` : "") +
|
|
69
80
|
(scrollinOnFocus ? ` ${eccgui}-card--scrollonfocus` : "") +
|
|
70
81
|
(whitespaceAmount !== "medium" ? ` ${eccgui}-card--whitespace-${whitespaceAmount}` : "") +
|
|
82
|
+
(elevation < 0 ? ` ${eccgui}-card--whitespace-borderless` : "") +
|
|
71
83
|
(className ? ` ${className}` : "")
|
|
72
84
|
}
|
|
73
|
-
elevation={elevation}
|
|
85
|
+
elevation={Math.max(0, elevation) as BlueprintCardElevation}
|
|
74
86
|
interactive={otherProps.onClick ? true : interactive}
|
|
75
87
|
{...scrollIn}
|
|
76
88
|
{...otherProps}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
@use "sass:color";
|
|
2
|
+
|
|
1
3
|
$card-padding: 0 !default;
|
|
2
4
|
$card-background-color: $white !default;
|
|
3
5
|
$card-selected-background-color: rgba($blue3, 0.1);
|
|
4
6
|
$eccgui-size-card-spacing: $eccgui-size-typo-base !default;
|
|
5
7
|
|
|
6
|
-
@use "sass:color";
|
|
7
8
|
@import "~@blueprintjs/core/src/components/card/card";
|
|
8
9
|
|
|
9
10
|
.#{$eccgui}-card {
|
|
@@ -61,6 +62,10 @@ $eccgui-size-card-spacing: $eccgui-size-typo-base !default;
|
|
|
61
62
|
outline: none;
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
.#{$eccgui}-card--whitespace-borderless {
|
|
66
|
+
box-shadow: none;
|
|
67
|
+
}
|
|
68
|
+
|
|
64
69
|
.#{$eccgui}-card__header {
|
|
65
70
|
box-sizing: content-box;
|
|
66
71
|
flex-grow: 0;
|
|
@@ -21,14 +21,9 @@ export interface MultiSelectSelectionProps<T> {
|
|
|
21
21
|
// @deprecated use `MultiSelectSelectionProps<T>`
|
|
22
22
|
export type SelectedParamsType<T> = MultiSelectSelectionProps<T>;
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
interface MultiSelectCommonProps<T>
|
|
25
25
|
extends TestableComponent,
|
|
26
26
|
Pick<BlueprintMultiSelectProps<T>, "items" | "placeholder" | "openOnKeyDown"> {
|
|
27
|
-
/**
|
|
28
|
-
* Predefined selected values
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
selectedItems?: T[];
|
|
32
27
|
/**
|
|
33
28
|
* Additional class name, space separated.
|
|
34
29
|
*/
|
|
@@ -42,10 +37,7 @@ export interface MultiSelectProps<T>
|
|
|
42
37
|
* this would be used in the item selection list as well as the multi-select input
|
|
43
38
|
*/
|
|
44
39
|
itemLabel: (item: T) => string;
|
|
45
|
-
|
|
46
|
-
* When set to true will set the multi-select value with all the items provided
|
|
47
|
-
*/
|
|
48
|
-
prePopulateWithItems?: boolean;
|
|
40
|
+
|
|
49
41
|
/**
|
|
50
42
|
* function handler that would be called anytime an item is selected/deselected or an item is created/removed
|
|
51
43
|
*/
|
|
@@ -127,6 +119,24 @@ export interface MultiSelectProps<T>
|
|
|
127
119
|
wrapperProps?: React.HTMLAttributes<HTMLDivElement>;
|
|
128
120
|
}
|
|
129
121
|
|
|
122
|
+
export type MultiSelectProps<T> = MultiSelectCommonProps<T> &
|
|
123
|
+
(
|
|
124
|
+
| {
|
|
125
|
+
/**
|
|
126
|
+
* Predefined selected values
|
|
127
|
+
*/
|
|
128
|
+
selectedItems?: T[];
|
|
129
|
+
prePopulateWithItems?: never;
|
|
130
|
+
}
|
|
131
|
+
| {
|
|
132
|
+
selectedItems?: never;
|
|
133
|
+
/**
|
|
134
|
+
* When set to true will set the multi-select value with all the items provided
|
|
135
|
+
*/
|
|
136
|
+
prePopulateWithItems?: boolean;
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
|
|
130
140
|
/**
|
|
131
141
|
* **Element is deprecated for the current type of usage.**
|
|
132
142
|
* Use `MultiSuggestField` as replacement.
|
|
@@ -169,7 +179,7 @@ export function MultiSelect<T>({
|
|
|
169
179
|
const [externalItems, setExternalItems] = React.useState<T[]>([...items]);
|
|
170
180
|
// All options (created and passed) that match the query
|
|
171
181
|
const [filteredItems, setFilteredItems] = React.useState<T[]>([]);
|
|
172
|
-
// All options (created and passed) selected by a user
|
|
182
|
+
// All options (created and passed) selected by a user
|
|
173
183
|
const [selectedItems, setSelectedItems] = React.useState<T[]>(() =>
|
|
174
184
|
prePopulateWithItems ? [...items] : externalSelectedItems ? [...externalSelectedItems] : []
|
|
175
185
|
);
|
|
@@ -201,10 +211,6 @@ export function MultiSelect<T>({
|
|
|
201
211
|
break;
|
|
202
212
|
}
|
|
203
213
|
|
|
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
214
|
/** Update external items when they change
|
|
209
215
|
* e.g for auto-complete when query change
|
|
210
216
|
*/
|
|
@@ -214,15 +220,13 @@ export function MultiSelect<T>({
|
|
|
214
220
|
}, [items.map((item) => itemId(item)).join("|")]);
|
|
215
221
|
|
|
216
222
|
React.useEffect(() => {
|
|
217
|
-
|
|
218
|
-
onSelection &&
|
|
223
|
+
onSelection &&
|
|
219
224
|
onSelection({
|
|
220
225
|
newlySelected: selectedItems.slice(-1)[0],
|
|
221
226
|
createdItems: createdItems.current,
|
|
222
227
|
selectedItems,
|
|
223
228
|
});
|
|
224
229
|
}, [
|
|
225
|
-
isControlled,
|
|
226
230
|
onSelection,
|
|
227
231
|
selectedItems.map((item) => itemId(item)).join("|"),
|
|
228
232
|
createdItems.current.map((item) => itemId(item)).join("|"),
|
|
@@ -237,7 +241,7 @@ export function MultiSelect<T>({
|
|
|
237
241
|
}
|
|
238
242
|
|
|
239
243
|
setSelectedItems(externalSelectedItems);
|
|
240
|
-
}, [externalSelectedItems]);
|
|
244
|
+
}, [externalSelectedItems?.map((item) => itemId(item)).join("|")]);
|
|
241
245
|
|
|
242
246
|
/**
|
|
243
247
|
* using the equality prop specified checks if an item has already been selected
|
|
@@ -254,15 +258,7 @@ export function MultiSelect<T>({
|
|
|
254
258
|
*/
|
|
255
259
|
const removeItemSelection = (matcher: string) => {
|
|
256
260
|
const filteredItems = selectedItems.filter((item) => itemId(item) !== matcher);
|
|
257
|
-
|
|
258
|
-
if (isControlled) {
|
|
259
|
-
onSelection({
|
|
260
|
-
createdItems: createdItems.current,
|
|
261
|
-
selectedItems: filteredItems,
|
|
262
|
-
});
|
|
263
|
-
} else {
|
|
264
|
-
setSelectedItems(filteredItems);
|
|
265
|
-
}
|
|
261
|
+
setSelectedItems(filteredItems);
|
|
266
262
|
};
|
|
267
263
|
|
|
268
264
|
/**
|
|
@@ -273,12 +269,6 @@ export function MultiSelect<T>({
|
|
|
273
269
|
const onItemSelect = (item: T) => {
|
|
274
270
|
if (itemHasBeenSelectedAlready(itemId(item))) {
|
|
275
271
|
removeItemSelection(itemId(item));
|
|
276
|
-
} else if (isControlled) {
|
|
277
|
-
onSelection({
|
|
278
|
-
newlySelected: item,
|
|
279
|
-
createdItems: createdItems.current,
|
|
280
|
-
selectedItems: [...selectedItems, item],
|
|
281
|
-
});
|
|
282
272
|
} else {
|
|
283
273
|
setSelectedItems((items) => [...items, item]);
|
|
284
274
|
}
|
|
@@ -358,15 +348,7 @@ export function MultiSelect<T>({
|
|
|
358
348
|
const handleClear = () => {
|
|
359
349
|
requestState.current.query = "";
|
|
360
350
|
|
|
361
|
-
|
|
362
|
-
onSelection({
|
|
363
|
-
selectedItems: [],
|
|
364
|
-
createdItems: createdItems.current,
|
|
365
|
-
});
|
|
366
|
-
} else {
|
|
367
|
-
setSelectedItems([]);
|
|
368
|
-
}
|
|
369
|
-
|
|
351
|
+
setSelectedItems([]);
|
|
370
352
|
setFilteredItems([...externalItems, ...createdItems.current]);
|
|
371
353
|
};
|
|
372
354
|
|
|
@@ -460,6 +442,11 @@ export function MultiSelect<T>({
|
|
|
460
442
|
|
|
461
443
|
const contentMultiSelect = (
|
|
462
444
|
<BlueprintMultiSelect<T>
|
|
445
|
+
placeholder={
|
|
446
|
+
!otherMultiSelectProps.placeholder && createNewItemFromQuery
|
|
447
|
+
? "Search for item, or enter term to create new one..."
|
|
448
|
+
: undefined
|
|
449
|
+
}
|
|
463
450
|
{...otherMultiSelectProps}
|
|
464
451
|
query={requestState.current.query}
|
|
465
452
|
onQueryChange={onQueryChange}
|
|
@@ -125,7 +125,7 @@ uncontrolledNewItemCreation.args = {
|
|
|
125
125
|
};
|
|
126
126
|
|
|
127
127
|
const CreationTemplate: StoryFn = () => {
|
|
128
|
-
const [selectedValues, setSelectedValues] = useState<string[]>([]);
|
|
128
|
+
const [selectedValues, setSelectedValues] = useState<string[]>(["foo"]);
|
|
129
129
|
|
|
130
130
|
const items = useMemo<string[]>(() => ["foo", "bar", "baz"], []);
|
|
131
131
|
|
|
@@ -145,7 +145,6 @@ const CreationTemplate: StoryFn = () => {
|
|
|
145
145
|
itemId={identity}
|
|
146
146
|
itemLabel={identity}
|
|
147
147
|
createNewItemFromQuery={identity}
|
|
148
|
-
prePopulateWithItems
|
|
149
148
|
/>
|
|
150
149
|
);
|
|
151
150
|
};
|
|
@@ -47,7 +47,7 @@ export const TestComponent = (): JSX.Element => {
|
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
describe("MultiSuggestField", () => {
|
|
50
|
-
describe("uncontrolled", () => {
|
|
50
|
+
describe("uncontrolled (when only selectedItems or onSelect is provided)", () => {
|
|
51
51
|
it("should render default input", () => {
|
|
52
52
|
const { container } = render(<MultiSuggestField {...Default.args} />);
|
|
53
53
|
const [input] = container.getElementsByClassName("eccgui-multiselect");
|
|
@@ -262,7 +262,7 @@ describe("MultiSuggestField", () => {
|
|
|
262
262
|
});
|
|
263
263
|
});
|
|
264
264
|
|
|
265
|
-
describe("controlled", () => {
|
|
265
|
+
describe("controlled (when both selectedItems and onSelect are provided)", () => {
|
|
266
266
|
it("should render default selected items", async () => {
|
|
267
267
|
const onSelection = jest.fn();
|
|
268
268
|
|
|
@@ -287,7 +287,12 @@ describe("MultiSuggestField", () => {
|
|
|
287
287
|
});
|
|
288
288
|
|
|
289
289
|
const { container } = render(
|
|
290
|
-
<MultiSuggestField
|
|
290
|
+
<MultiSuggestField
|
|
291
|
+
{...dropdownOnFocus.args}
|
|
292
|
+
items={items}
|
|
293
|
+
selectedItems={[]}
|
|
294
|
+
onSelection={onSelection}
|
|
295
|
+
/>
|
|
291
296
|
);
|
|
292
297
|
|
|
293
298
|
const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
|
|
@@ -355,7 +360,6 @@ describe("MultiSuggestField", () => {
|
|
|
355
360
|
newlySelected: items[0],
|
|
356
361
|
selectedItems: [items[0]],
|
|
357
362
|
};
|
|
358
|
-
expect(onSelection).toHaveBeenCalledTimes(1);
|
|
359
363
|
expect(onSelection).toHaveBeenCalledWith(expectedObject);
|
|
360
364
|
});
|
|
361
365
|
|
|
@@ -381,7 +385,6 @@ describe("MultiSuggestField", () => {
|
|
|
381
385
|
selectedItems: [...selectedItems, items[0]],
|
|
382
386
|
};
|
|
383
387
|
|
|
384
|
-
expect(onSelection).toHaveBeenCalledTimes(2);
|
|
385
388
|
expect(onSelection).toHaveBeenCalledWith(expectedObject);
|
|
386
389
|
});
|
|
387
390
|
|
|
@@ -399,9 +402,90 @@ describe("MultiSuggestField", () => {
|
|
|
399
402
|
selectedItems: [],
|
|
400
403
|
};
|
|
401
404
|
|
|
402
|
-
expect(onSelection).toHaveBeenCalledTimes(3);
|
|
403
405
|
expect(onSelection).toHaveBeenCalledWith(expectedObject);
|
|
404
406
|
});
|
|
405
407
|
});
|
|
408
|
+
|
|
409
|
+
it("should set prePopulateWithItems as selected values and override passed values", async () => {
|
|
410
|
+
const onSelection = jest.fn((values) => {
|
|
411
|
+
// eslint-disable-next-line no-console
|
|
412
|
+
console.log("Mocked onSelection function values: ", values);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
const items = dropdownOnFocus.args.items;
|
|
416
|
+
|
|
417
|
+
const args = { ...dropdownOnFocus.args, onSelection: onSelection };
|
|
418
|
+
|
|
419
|
+
const { container } = render(
|
|
420
|
+
<MultiSuggestField {...args} data-test-id="multi-suggest-field" prePopulateWithItems />
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
await waitFor(() => {
|
|
424
|
+
const expectedObject = {
|
|
425
|
+
createdItems: [],
|
|
426
|
+
newlySelected: items.at(-1),
|
|
427
|
+
selectedItems: items,
|
|
428
|
+
};
|
|
429
|
+
expect(onSelection).toHaveBeenCalledWith(expectedObject);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const tags = container.querySelectorAll("span[data-tag-index]");
|
|
433
|
+
|
|
434
|
+
expect(tags.length).toBe(items.length);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it("should correctly deselect all tags from input", async () => {
|
|
438
|
+
const onSelection = jest.fn((values) => {
|
|
439
|
+
// eslint-disable-next-line no-console
|
|
440
|
+
console.log("Mocked onSelection function values 111: ", values);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const items = predefinedNotControlledValues.args.items;
|
|
444
|
+
|
|
445
|
+
const args = { ...predefinedNotControlledValues.args, selectedItems: undefined, onSelection: onSelection };
|
|
446
|
+
|
|
447
|
+
const { container } = render(
|
|
448
|
+
<MultiSuggestField {...args} data-test-id="multi-suggest-field" prePopulateWithItems />
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
await waitFor(() => {
|
|
452
|
+
const expectedObject = {
|
|
453
|
+
createdItems: [],
|
|
454
|
+
newlySelected: items.at(-1),
|
|
455
|
+
selectedItems: items,
|
|
456
|
+
};
|
|
457
|
+
expect(onSelection).toHaveBeenCalledWith(expectedObject);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
let tags = container.querySelectorAll("span[data-tag-index]");
|
|
461
|
+
expect(tags.length).toBe(items.length);
|
|
462
|
+
|
|
463
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
464
|
+
const tag = tags[0];
|
|
465
|
+
expect(tag.querySelector("span")).toHaveTextContent(items[i].testLabel);
|
|
466
|
+
|
|
467
|
+
const removeTagButton = tag.querySelector("button");
|
|
468
|
+
expect(removeTagButton).toBeTruthy();
|
|
469
|
+
|
|
470
|
+
fireEvent.click(removeTagButton!);
|
|
471
|
+
|
|
472
|
+
await waitFor(() => {
|
|
473
|
+
const selected = items.slice(i + 1);
|
|
474
|
+
|
|
475
|
+
const expectedObject = {
|
|
476
|
+
createdItems: [],
|
|
477
|
+
newlySelected: selected.at(-1),
|
|
478
|
+
selectedItems: selected,
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
expect(onSelection).toHaveBeenCalledWith(expectedObject);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
tags = container.querySelectorAll("span[data-tag-index]");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const tagsAfterRemove = container.querySelectorAll("span[data-tag-index]");
|
|
488
|
+
expect(tagsAfterRemove.length).toBe(0);
|
|
489
|
+
});
|
|
406
490
|
});
|
|
407
491
|
});
|