@eccenca/gui-elements 22.1.0-rc.0 → 22.1.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.
Files changed (172) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/cjs/cmem/ActivityControl/ActivityControlWidget.js +13 -9
  3. package/dist/cjs/cmem/ActivityControl/ActivityControlWidget.js.map +1 -1
  4. package/dist/cjs/cmem/ActivityControl/ActivityExecutionErrorReportModal.js +23 -6
  5. package/dist/cjs/cmem/ActivityControl/ActivityExecutionErrorReportModal.js.map +1 -1
  6. package/dist/cjs/cmem/ActivityControl/SilkActivityControl.js +1 -1
  7. package/dist/cjs/cmem/ActivityControl/SilkActivityControl.js.map +1 -1
  8. package/dist/cjs/cmem/react-flow/configuration/_colors-linking.module.scss +1 -1
  9. package/dist/cjs/common/utils/stringUtils.js +7 -0
  10. package/dist/cjs/common/utils/stringUtils.js.map +1 -0
  11. package/dist/cjs/components/Application/ApplicationContent.js +1 -7
  12. package/dist/cjs/components/Application/ApplicationContent.js.map +1 -1
  13. package/dist/cjs/components/Application/ApplicationTitle.js +1 -7
  14. package/dist/cjs/components/Application/ApplicationTitle.js.map +1 -1
  15. package/dist/cjs/components/AutoSuggestion/AutoSuggestion.js +0 -1
  16. package/dist/cjs/components/AutoSuggestion/AutoSuggestion.js.map +1 -1
  17. package/dist/cjs/components/AutocompleteField/AutoCompleteField.js +5 -4
  18. package/dist/cjs/components/AutocompleteField/AutoCompleteField.js.map +1 -1
  19. package/dist/cjs/components/Breadcrumb/BreadcrumbItem.js +1 -1
  20. package/dist/cjs/components/Button/Button.js +1 -1
  21. package/dist/cjs/components/Button/Button.js.map +1 -1
  22. package/dist/cjs/components/Card/Card.js +18 -3
  23. package/dist/cjs/components/Card/Card.js.map +1 -1
  24. package/dist/cjs/components/Form/FieldItemRow.js +2 -3
  25. package/dist/cjs/components/Form/FieldItemRow.js.map +1 -1
  26. package/dist/cjs/components/MultiSelect/MultiSelect.js +34 -41
  27. package/dist/cjs/components/MultiSelect/MultiSelect.js.map +1 -1
  28. package/dist/cjs/components/Notification/Notification.js +8 -2
  29. package/dist/cjs/components/Notification/Notification.js.map +1 -1
  30. package/dist/cjs/components/OverviewItem/OverviewItemList.js +1 -1
  31. package/dist/cjs/components/OverviewItem/OverviewItemList.js.map +1 -1
  32. package/dist/cjs/components/Spinner/Spinner.js +1 -1
  33. package/dist/cjs/components/Spinner/Spinner.js.map +1 -1
  34. package/dist/cjs/components/Tabs/Tab.js +1 -2
  35. package/dist/cjs/components/Tabs/Tab.js.map +1 -1
  36. package/dist/cjs/components/Tabs/Tabs.js +1 -4
  37. package/dist/cjs/components/Tabs/Tabs.js.map +1 -1
  38. package/dist/cjs/components/Typography/HtmlContentBlock.js +6 -6
  39. package/dist/cjs/components/Typography/HtmlContentBlock.js.map +1 -1
  40. package/dist/cjs/components/Typography/classnames.js +2 -1
  41. package/dist/cjs/components/Typography/classnames.js.map +1 -1
  42. package/dist/cjs/extensions/react-flow/edges/EdgeDefault.js.map +1 -1
  43. package/dist/cjs/extensions/react-flow/edges/getSmoothStepPath.js +98 -0
  44. package/dist/cjs/extensions/react-flow/edges/getSmoothStepPath.js.map +1 -0
  45. package/dist/cjs/extensions/react-flow/edges/utils.js +21 -20
  46. package/dist/cjs/extensions/react-flow/edges/utils.js.map +1 -1
  47. package/dist/cjs/extensions/react-flow/nodes/NodeContent.js +6 -4
  48. package/dist/cjs/extensions/react-flow/nodes/NodeContent.js.map +1 -1
  49. package/dist/cjs/extensions/react-flow/nodes/NodeContentExtension.js +3 -2
  50. package/dist/cjs/extensions/react-flow/nodes/NodeContentExtension.js.map +1 -1
  51. package/dist/cjs/extensions/react-flow/nodes/NodeTools.js +11 -9
  52. package/dist/cjs/extensions/react-flow/nodes/NodeTools.js.map +1 -1
  53. package/dist/es5/cmem/ActivityControl/ActivityControlWidget.js +14 -10
  54. package/dist/es5/cmem/ActivityControl/ActivityControlWidget.js.map +1 -1
  55. package/dist/es5/cmem/ActivityControl/ActivityExecutionErrorReportModal.js +21 -4
  56. package/dist/es5/cmem/ActivityControl/ActivityExecutionErrorReportModal.js.map +1 -1
  57. package/dist/es5/cmem/ActivityControl/SilkActivityControl.js +1 -1
  58. package/dist/es5/cmem/ActivityControl/SilkActivityControl.js.map +1 -1
  59. package/dist/es5/cmem/react-flow/configuration/_colors-linking.module.scss +1 -1
  60. package/dist/es5/common/utils/stringUtils.js +3 -0
  61. package/dist/es5/common/utils/stringUtils.js.map +1 -0
  62. package/dist/es5/components/Application/ApplicationContent.js +1 -7
  63. package/dist/es5/components/Application/ApplicationContent.js.map +1 -1
  64. package/dist/es5/components/Application/ApplicationTitle.js +1 -7
  65. package/dist/es5/components/Application/ApplicationTitle.js.map +1 -1
  66. package/dist/es5/components/AutoSuggestion/AutoSuggestion.js +0 -1
  67. package/dist/es5/components/AutoSuggestion/AutoSuggestion.js.map +1 -1
  68. package/dist/es5/components/AutocompleteField/AutoCompleteField.js +5 -4
  69. package/dist/es5/components/AutocompleteField/AutoCompleteField.js.map +1 -1
  70. package/dist/es5/components/Breadcrumb/BreadcrumbItem.js +1 -1
  71. package/dist/es5/components/Button/Button.js +1 -1
  72. package/dist/es5/components/Button/Button.js.map +1 -1
  73. package/dist/es5/components/Card/Card.js +18 -3
  74. package/dist/es5/components/Card/Card.js.map +1 -1
  75. package/dist/es5/components/Form/FieldItemRow.js +2 -3
  76. package/dist/es5/components/Form/FieldItemRow.js.map +1 -1
  77. package/dist/es5/components/MultiSelect/MultiSelect.js +40 -51
  78. package/dist/es5/components/MultiSelect/MultiSelect.js.map +1 -1
  79. package/dist/es5/components/Notification/Notification.js +10 -3
  80. package/dist/es5/components/Notification/Notification.js.map +1 -1
  81. package/dist/es5/components/OverviewItem/OverviewItemList.js +1 -1
  82. package/dist/es5/components/OverviewItem/OverviewItemList.js.map +1 -1
  83. package/dist/es5/components/Spinner/Spinner.js +2 -2
  84. package/dist/es5/components/Spinner/Spinner.js.map +1 -1
  85. package/dist/es5/components/Tabs/Tab.js +1 -2
  86. package/dist/es5/components/Tabs/Tab.js.map +1 -1
  87. package/dist/es5/components/Tabs/Tabs.js +1 -4
  88. package/dist/es5/components/Tabs/Tabs.js.map +1 -1
  89. package/dist/es5/components/Typography/HtmlContentBlock.js +17 -8
  90. package/dist/es5/components/Typography/HtmlContentBlock.js.map +1 -1
  91. package/dist/es5/components/Typography/classnames.js +1 -0
  92. package/dist/es5/components/Typography/classnames.js.map +1 -1
  93. package/dist/es5/extensions/react-flow/edges/EdgeDefault.js.map +1 -1
  94. package/dist/es5/extensions/react-flow/edges/getSmoothStepPath.js +119 -0
  95. package/dist/es5/extensions/react-flow/edges/getSmoothStepPath.js.map +1 -0
  96. package/dist/es5/extensions/react-flow/edges/utils.js +21 -20
  97. package/dist/es5/extensions/react-flow/edges/utils.js.map +1 -1
  98. package/dist/es5/extensions/react-flow/nodes/NodeContent.js +7 -5
  99. package/dist/es5/extensions/react-flow/nodes/NodeContent.js.map +1 -1
  100. package/dist/es5/extensions/react-flow/nodes/NodeContentExtension.js +3 -2
  101. package/dist/es5/extensions/react-flow/nodes/NodeContentExtension.js.map +1 -1
  102. package/dist/es5/extensions/react-flow/nodes/NodeTools.js +12 -10
  103. package/dist/es5/extensions/react-flow/nodes/NodeTools.js.map +1 -1
  104. package/dist/es5/index.js +1 -3
  105. package/dist/es5/index.js.map +1 -1
  106. package/dist/types/cmem/ActivityControl/ActivityControlWidget.d.ts +9 -0
  107. package/dist/types/cmem/ActivityControl/SilkActivityControl.d.ts +2 -0
  108. package/dist/types/common/utils/stringUtils.d.ts +2 -0
  109. package/dist/types/components/Card/Card.d.ts +9 -4
  110. package/dist/types/components/Form/FieldItemRow.d.ts +1 -0
  111. package/dist/types/components/MultiSelect/MultiSelect.d.ts +16 -9
  112. package/dist/types/components/Notification/Notification.d.ts +55 -2
  113. package/dist/types/components/OverviewItem/OverviewItemList.d.ts +1 -1
  114. package/dist/types/components/Spinner/Spinner.d.ts +1 -1
  115. package/dist/types/components/Typography/HtmlContentBlock.d.ts +15 -5
  116. package/dist/types/components/Typography/classnames.d.ts +1 -0
  117. package/dist/types/extensions/react-flow/edges/getSmoothStepPath.d.ts +18 -0
  118. package/dist/types/extensions/react-flow/nodes/NodeContent.d.ts +8 -2
  119. package/dist/types/extensions/react-flow/nodes/NodeContentExtension.d.ts +5 -1
  120. package/dist/types/extensions/react-flow/nodes/NodeTools.d.ts +7 -1
  121. package/dist/types/index.d.ts +1 -3
  122. package/package.json +22 -13
  123. package/src/cmem/ActivityControl/ActivityControlWidget.stories.tsx +6 -6
  124. package/src/cmem/ActivityControl/ActivityControlWidget.tsx +27 -14
  125. package/src/cmem/ActivityControl/ActivityExecutionErrorReportModal.tsx +11 -3
  126. package/src/cmem/ActivityControl/SilkActivityControl.tsx +5 -1
  127. package/src/cmem/react-flow/_nodes.scss +12 -6
  128. package/src/cmem/react-flow/configuration/_colors-linking.module.scss +1 -1
  129. package/src/common/utils/stringUtils.ts +2 -0
  130. package/src/components/Application/ApplicationContent.tsx +0 -5
  131. package/src/components/Application/ApplicationTitle.tsx +0 -5
  132. package/src/components/AutoSuggestion/AutoSuggestion.tsx +0 -1
  133. package/src/components/AutocompleteField/AutoCompleteField.tsx +5 -4
  134. package/src/components/Breadcrumb/BreadcrumbItem.tsx +5 -5
  135. package/src/components/Breadcrumb/BreadcrumbList.tsx +1 -1
  136. package/src/components/Button/Button.stories.tsx +1 -1
  137. package/src/components/Button/Button.tsx +1 -1
  138. package/src/components/Card/Card.tsx +23 -4
  139. package/src/components/Card/card.scss +4 -0
  140. package/src/components/Checkbox/checkbox.scss +4 -0
  141. package/src/components/Form/FieldItem.tsx +3 -3
  142. package/src/components/Form/FieldItemRow.tsx +2 -4
  143. package/src/components/Icon/IconButton.tsx +2 -2
  144. package/src/components/Menu/MenuItem.tsx +1 -1
  145. package/src/components/MultiSelect/MultiSelect.stories.tsx +15 -7
  146. package/src/components/MultiSelect/MultiSelect.tsx +63 -60
  147. package/src/components/Notification/Notification.stories.tsx +82 -0
  148. package/src/components/Notification/Notification.tsx +62 -4
  149. package/src/components/Notification/notification.scss +2 -1
  150. package/src/components/OverviewItem/OverviewItemList.tsx +2 -2
  151. package/src/components/RadioButton/radiobutton.scss +4 -0
  152. package/src/components/Spinner/Spinner.tsx +1 -1
  153. package/src/components/Switch/switch.scss +1 -0
  154. package/src/components/Tabs/Tab.tsx +1 -2
  155. package/src/components/Tabs/Tabs.tsx +1 -5
  156. package/src/components/Tabs/stories/Tabs.stories.tsx +3 -3
  157. package/src/components/Tag/tag.scss +1 -1
  158. package/src/components/Toolbar/toolbar.scss +1 -1
  159. package/src/components/Typography/HtmlContentBlock.tsx +27 -4
  160. package/src/components/Typography/classnames.ts +1 -0
  161. package/src/components/Typography/typography.scss +8 -0
  162. package/src/extensions/react-flow/_react-flow.scss +4 -0
  163. package/src/extensions/react-flow/edges/EdgeDefault.tsx +0 -1
  164. package/src/extensions/react-flow/edges/getSmoothStepPath.ts +118 -0
  165. package/src/extensions/react-flow/edges/utils.ts +21 -29
  166. package/src/extensions/react-flow/nodes/NodeContent.tsx +13 -7
  167. package/src/extensions/react-flow/nodes/NodeContentExtension.tsx +6 -0
  168. package/src/extensions/react-flow/nodes/NodeTools.tsx +25 -9
  169. package/src/extensions/react-flow/nodes/_nodes.scss +31 -12
  170. package/src/extensions/react-flow/nodes/stories/NodeContent.stories.tsx +10 -1
  171. package/src/extensions/react-flow/nodes/stories/NodeContentExtension.stories.tsx +23 -0
  172. package/src/index.ts +3 -3
@@ -272,7 +272,8 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
272
272
  // Disable highlighting, since we used empty string search
273
273
  enableHighlighting = false;
274
274
  // Put selected item at the top if it is not in the result list
275
- if (!!selectedItem && itemIndexOf(emptyStringResults, selectedItem) === -1) {
275
+ if (!!selectedItem && itemIndexOf(emptyStringResults, selectedItem) > -1) {
276
+ emptyStringResults.splice(itemIndexOf(emptyStringResults, selectedItem), 1)
276
277
  result = [selectedItem, ...emptyStringResults];
277
278
  } else {
278
279
  result = emptyStringResults;
@@ -346,7 +347,7 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
346
347
  };
347
348
  const updatedPopOverProps: Partial<IPopoverProps> = {
348
349
  minimal: true,
349
- position: "bottom",
350
+ position: "bottom-left",
350
351
  popoverClassName: `${eccgui}-autocompletefield__options`,
351
352
  wrapperTagName: "div",
352
353
  boundary: "window",
@@ -385,12 +386,12 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
385
386
  onQueryChange={(q) => setQuery(q)}
386
387
  closeOnSelect={true}
387
388
  query={query}
388
- // FIXME: This leads to odd compile errors without "as any"
389
+ // This leads to odd compile errors without "as any"
389
390
  popoverProps={updatedPopOverProps as any}
390
391
  selectedItem={selectedItem}
391
392
  fill
392
393
  {...createNewItemProps}
393
- // FIXME: This leads to odd compile errors without "as any"
394
+ // This leads to odd compile errors without "as any"
394
395
  inputProps={updatedInputProps as any}
395
396
  itemListRenderer={listLoading ? () => <Menu><MenuItem disabled={true} text={<Spinner position={"inline"} />} style={fieldWidthLimits} /></Menu> : undefined}
396
397
  />
@@ -7,7 +7,7 @@ import {
7
7
  import { CLASSPREFIX as eccgui } from "../../configuration/constants";
8
8
  import { openInNewTab } from "../../common/utils/openInNewTab";
9
9
 
10
- // TODO: enforce href and remove onClick later
10
+ // FIXME: enforce href and remove onClick later
11
11
  export type IBreadcrumbItemProps = Omit<
12
12
  IBlueprintBreadcrumbItemProps,
13
13
  // we remove some properties that are currently not necessary, required usage should be discussed
@@ -19,14 +19,14 @@ export type IBreadcrumbItemProps = Omit<
19
19
 
20
20
  function BreadcrumbItem({
21
21
  className = "",
22
- onClick,
23
- href,
22
+ onClick,
23
+ href,
24
24
  //itemDivider='',
25
25
  ...otherBlueprintBreadcrumbProps
26
26
  }: IBreadcrumbItemProps) {
27
-
27
+
28
28
  /*
29
- TODO: adding `data-divider` does not work this way because BlueprintJS
29
+ FIXME: adding `data-divider` does not work this way because BlueprintJS
30
30
  breadcrumb component does not support (and forward) it on HTML element
31
31
  level. The idea is to add the divider as data-* property to use it via
32
32
  CSS/Sass as content for the pseudo element, currently done static in CSS
@@ -18,7 +18,7 @@ type ReducedBreadcrumbsProps = Omit<
18
18
  "popoverProps"
19
19
  >;
20
20
 
21
- // TODO: enforce onItemClick later
21
+ // FIXME: enforce onItemClick later when href value can always be routed correctly
22
22
  interface IBreadcrumbListProps extends ReducedBreadcrumbsProps {
23
23
  /**
24
24
  list of breadcrumb items to display
@@ -19,7 +19,7 @@ export default {
19
19
  },
20
20
  className: {
21
21
  description: "A space-delimited list of class names.",
22
- control: "boolean",
22
+ control: "text",
23
23
  table: {
24
24
  defaultValue: { summary: undefined },
25
25
  type: { summary: "string" },
@@ -116,7 +116,7 @@ function Button({
116
116
  </ButtonType>
117
117
  );
118
118
 
119
- return tooltip ? (
119
+ return (tooltip && !restProps.loading) ? (
120
120
  <Tooltip content={tooltip} {...tooltipProperties}>
121
121
  <span>{button}</span>
122
122
  </Tooltip>
@@ -7,18 +7,23 @@ import { CLASSPREFIX as eccgui } from "../../configuration/constants";
7
7
 
8
8
  export interface CardProps extends BlueprintCardProps {
9
9
  /**
10
- * When set to `true` card is included as simple HTML `div` element.
10
+ * `<Card />` element is included in DOM as simple `div` element.
11
11
  * By default it is a HTML `section`.
12
12
  */
13
13
  isOnlyLayout?: boolean;
14
14
  /**
15
- * When set to true, will take the full height of container
15
+ * Take the full height of container to display the card.
16
16
  */
17
17
  fullHeight?: boolean;
18
18
  /**
19
- * when set to true will invert the background color and slightly darken
19
+ * Background color is slightly altered to differ card display from other cards.
20
20
  */
21
21
  elevated?: boolean;
22
+ /**
23
+ * When card (or its children) get focus the card is scrolled into the viewport.
24
+ * Property value defined which part of the card is always scrolled in, this may important when the card is larger than the viewport.
25
+ */
26
+ scrollinOnFocus?: "start" | "center" | "end";
22
27
  }
23
28
 
24
29
  /**
@@ -32,26 +37,40 @@ function Card({
32
37
  isOnlyLayout=false,
33
38
  fullHeight=false,
34
39
  elevated=false,
40
+ scrollinOnFocus,
35
41
  interactive,
36
42
  ...otherProps
37
43
  }: CardProps) {
44
+ const scrollIn = !!scrollinOnFocus ? {
45
+ tabIndex: 0,
46
+ onFocus: (e: any) => {
47
+ const el = e.target.closest(".diapp-iframewindow__content");
48
+ setTimeout(()=>{if (el) el.scrollIntoView({
49
+ behavior: "smooth",
50
+ block: scrollinOnFocus,
51
+ inline: scrollinOnFocus,
52
+ })}, 100);
53
+ }
54
+ } : {}
38
55
  const cardElement = (
39
56
  <BlueprintCard
40
57
  className={
41
58
  `${eccgui}-card ` +
42
59
  (fullHeight ? ` ${eccgui}-card--fullheight ` : '') +
43
60
  (elevated ? ` ${eccgui}-card--elevated ` : '') +
61
+ (!!scrollinOnFocus ? ` ${eccgui}-card--scrollonfocus ` : '') +
44
62
  className
45
63
  }
46
64
  elevation={elevation}
47
65
  interactive={!!otherProps.onClick ? true : interactive}
66
+ {...scrollIn}
48
67
  {...otherProps}
49
68
  >
50
69
  {children}
51
70
  </BlueprintCard>
52
71
  );
53
72
 
54
- // TODO: improve Card element so it is itself a section html element
73
+ // FIXME: improve Card element so it is itself a section html element
55
74
  return isOnlyLayout === false ? <section>{cardElement}</section> : cardElement;
56
75
  };
57
76
 
@@ -44,6 +44,10 @@ $eccgui-size-card-spacing: $eccgui-size-typo-base !default;
44
44
  @extend .#{$eccgui}-grid__row--fullheight;
45
45
  }
46
46
 
47
+ .#{$eccgui}-card--scrollonfocus {
48
+ outline: none;
49
+ }
50
+
47
51
  .#{$eccgui}-card__header {
48
52
  padding: $eccgui-size-card-spacing/2 $eccgui-size-card-spacing/2 $eccgui-size-card-spacing/2 $eccgui-size-card-spacing;
49
53
  box-sizing: content-box;
@@ -35,6 +35,10 @@ $switch-checked-background-color-disabled: rgba($switch-checked-background-color
35
35
  box-shadow: none;
36
36
  }
37
37
 
38
+ input:focus ~ .#{$ns}-control-indicator {
39
+ outline-offset: 0;
40
+ }
41
+
38
42
  input:disabled ~ .#{$ns}-control-indicator,
39
43
  input:disabled:checked ~ .#{$ns}-control-indicator,
40
44
  {
@@ -5,10 +5,10 @@ import { CLASSPREFIX as eccgui } from "../../configuration/constants";
5
5
  import {TestableComponent} from "../interfaces";
6
6
 
7
7
  /*
8
- TODO:
8
+ FIXME: Improve development convenience and prevent development errors
9
9
 
10
- * disabled stae could be automatically forwarded to inserted input element,
11
- currently this need to be dome explicitely .
10
+ * disabled state could be automatically forwarded to inserted input element,
11
+ currently this need to be dome explicitly .
12
12
  * input id could be forwarded to label and input element
13
13
  * input id could be created when not given
14
14
  */
@@ -2,11 +2,9 @@ import React from "react";
2
2
  import { CLASSPREFIX as eccgui } from "../../configuration/constants";
3
3
 
4
4
  /*
5
- TODO:
6
-
7
- * allow grow factors for children
5
+ FIXME: allow grow factors for children
8
6
  */
9
-
7
+ /** Allows to layout field items horizontally. */
10
8
  function FieldItemRow({ children, className, ...otherProps }: any) {
11
9
  return <div className={`${eccgui}-fielditem__row` + (className ? " " + className : "")}>{children}</div>;
12
10
  }
@@ -10,7 +10,7 @@ interface IconButtonProps extends ButtonProps {
10
10
  className?: string
11
11
  // Tooltip text
12
12
  text?: string,
13
- // Time after tooltip text is viible when icon is hovered/focuses
13
+ // Time after tooltip text is visible when icon is hovered/focuses
14
14
  tooltipOpenDelay?: number,
15
15
  // Display large icon version
16
16
  large?: boolean,
@@ -18,7 +18,7 @@ interface IconButtonProps extends ButtonProps {
18
18
  small?: boolean
19
19
  // If the tooltip should be set as title attribute instead of the Tooltip component. If true, all other tooltip parameters are ignored.
20
20
  tooltipAsTitle?: boolean
21
- // If set, button will become an anchor button. FIXME: Setting this parameter should not change the type of the button. Split into several components.
21
+ // If set, button will become an anchor button. FIXME: CMEM-3742: Setting this parameter should not change the type of the button. Split into several components.
22
22
  href?: string
23
23
  // Additional description of the icon function to improve accessibility, if not defined then the text label is used as fallback
24
24
  description?: string
@@ -11,7 +11,7 @@ interface IProps {
11
11
  icon?: ValidIconName | string[]
12
12
  // Props defined by the Blueprint component that should be forwarded
13
13
  internalProps?: Partial<MenuItemProps> & React.AnchorHTMLAttributes<HTMLAnchorElement>
14
- // FIXME: For backward compatibility, should be avoided in all code bases
14
+ // FIXME: CMEM-3742: For backward compatibility, should be avoided in all code bases
15
15
  [key: string]: any
16
16
  }
17
17
 
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import { ComponentStory, ComponentMeta } from "@storybook/react";
3
- import { name } from "react-lorem-ipsum";
3
+ import { loremIpsum } from "react-lorem-ipsum";
4
4
 
5
5
  import MultiSelectExample from "./MultiSelect";
6
6
 
@@ -36,8 +36,16 @@ const Template: ComponentStory<typeof MultiSelectExample> = (args) => <MultiSele
36
36
 
37
37
  export const Default = Template.bind({});
38
38
 
39
- const items = new Array(5).fill(undefined).map((_) => {
40
- const testLabel = name();
39
+ const testLabels = loremIpsum({
40
+ p: 1,
41
+ avgSentencesPerParagraph: 5,
42
+ avgWordsPerSentence: 1,
43
+ startWithLoremIpsum: false,
44
+ random: false
45
+ }).toString().split(".");
46
+
47
+ const items = new Array(5).fill(undefined).map((_, id) => {
48
+ const testLabel = testLabels[id];
41
49
  return { testLabel, testId: `${testLabel}-id` };
42
50
  });
43
51
 
@@ -45,8 +53,8 @@ Default.args = {
45
53
  items,
46
54
  canCreateNewItem: true,
47
55
  prePopulateWithItems: false,
48
- equalityProp: "testId",
49
- labelProp: "testLabel",
56
+ itemId: item => item.testId,
57
+ itemLabel: item => item.testLabel,
50
58
  openOnKeyDown: true,
51
59
  };
52
60
 
@@ -58,6 +66,6 @@ openDropdownWhenFocused.args = {
58
66
  items,
59
67
  canCreateNewItem: true,
60
68
  prePopulateWithItems: false,
61
- equalityProp: "testId",
62
- labelProp: "testLabel",
69
+ itemId: item => item.testId,
70
+ itemLabel: item => item.testLabel,
63
71
  };
@@ -1,10 +1,12 @@
1
1
  import React from "react";
2
- import { Intent as BlueprintIntent } from "@blueprintjs/core";
3
- import { MultiSelect as BlueprintMultiSelect, MultiSelectProps, IItemRendererProps } from "@blueprintjs/select";
2
+ import {Intent as BlueprintIntent} from "@blueprintjs/core";
3
+ import {IItemRendererProps, MultiSelect as BlueprintMultiSelect, MultiSelectProps} from "@blueprintjs/select";
4
4
  import MenuItem from "../Menu/MenuItem";
5
5
  import Highlighter from "../Typography/Highlighter";
6
6
  import Button from "../Button/Button";
7
7
  import OverflowText from "../Typography/OverflowText";
8
+ import {HTMLInputProps} from "@blueprintjs/core/src/common/props";
9
+ import {removeExtraSpaces} from "../../common/utils/stringUtils";
8
10
 
9
11
  interface SelectedParamsType<T> {
10
12
  newlySelected: T;
@@ -14,18 +16,14 @@ interface SelectedParamsType<T> {
14
16
 
15
17
  interface IProps<T> extends Pick<MultiSelectProps<T>, "items" | "placeholder" | "openOnKeyDown"> {
16
18
  /**
17
- * field in an item, that differentiates on item from the other.
19
+ * Returns the unique ID of an item. This will be used for equality of items.
18
20
  */
19
- equalityProp: string;
21
+ itemId: (item: T) => string;
20
22
  /**
21
- * field in the item object that would be used to describe the item.
23
+ * Returns the label of an item.
22
24
  * this would be used in the item selection list as well as the multi-select input
23
25
  */
24
- labelProp: string;
25
- /**
26
- * if new items that are not in the original item list can be created and appended
27
- */
28
- canCreateNewItem?: boolean;
26
+ itemLabel: (item: T) => string;
29
27
  /**
30
28
  * When set to true will set the multi-select value with all the items provided
31
29
  */
@@ -43,6 +41,9 @@ interface IProps<T> extends Pick<MultiSelectProps<T>, "items" | "placeholder" |
43
41
  */
44
42
  tagInputProps?: MultiSelectProps<T>["tagInputProps"];
45
43
 
44
+ /** Additional properties for the (query) input field of the multi-selection. */
45
+ inputProps?: HTMLInputProps;
46
+
46
47
  /**
47
48
  * prop to listen for query changes, when text is entered in the multi-select input
48
49
  */
@@ -62,6 +63,14 @@ interface IProps<T> extends Pick<MultiSelectProps<T>, "items" | "placeholder" |
62
63
  * If omitted, "No results." will be rendered in this case.
63
64
  */
64
65
  newItemCreationText?: string;
66
+ /**
67
+ * Allows to creates new item from a given query. If this is not provided then no new items can be created.
68
+ */
69
+ createNewItemFromQuery?: (query: string) => T
70
+ /**
71
+ * Items that were newly created and not taken from the list will be post-fixed with this string.
72
+ */
73
+ newItemPostfix?: string
65
74
  /**
66
75
  * The input element is displayed with primary color scheme.
67
76
  */
@@ -87,21 +96,23 @@ interface IProps<T> extends Pick<MultiSelectProps<T>, "items" | "placeholder" |
87
96
  function MultiSelect<T>({
88
97
  items,
89
98
  prePopulateWithItems,
90
- equalityProp,
91
- labelProp,
99
+ itemId,
100
+ itemLabel,
92
101
  onSelection,
93
- canCreateNewItem,
94
102
  popoverProps,
95
103
  tagInputProps,
104
+ inputProps,
96
105
  runOnQueryChange,
97
106
  fullWidth = true,
98
107
  noResultText = "No results.",
99
- newItemCreationText = "Add new tag",
108
+ newItemCreationText = "Add new item",
109
+ newItemPostfix = " (new item)",
100
110
  hasStatePrimary,
101
111
  hasStateDanger,
102
112
  hasStateSuccess,
103
113
  hasStateWarning,
104
114
  disabled,
115
+ createNewItemFromQuery,
105
116
  ...otherProps
106
117
  }: IProps<T>) {
107
118
  const [createdItems, setCreatedItems] = React.useState<T[]>([]);
@@ -111,7 +122,7 @@ function MultiSelect<T>({
111
122
  const [query, setQuery] = React.useState<string | undefined>(undefined);
112
123
  //currently focused element in popover list
113
124
  const [focusedItem, setFocusedItem] = React.useState<T | null>(null);
114
- const tagInputRef = React.useRef<any>();
125
+ const inputRef = React.useRef<HTMLInputElement>(null);
115
126
 
116
127
  let intent;
117
128
  switch (true) {
@@ -138,7 +149,7 @@ function MultiSelect<T>({
138
149
  setItemsCopy([...items, ...createdItems]);
139
150
  setFilteredItemList([...items, ...createdItems]);
140
151
  /* eslint-disable react-hooks/exhaustive-deps */
141
- }, [items.map((item) => item[equalityProp as keyof T]).join("|")]);
152
+ }, [items.map((item) => itemId(item)).join("|")]);
142
153
 
143
154
  React.useEffect(() => {
144
155
  onSelection &&
@@ -150,8 +161,8 @@ function MultiSelect<T>({
150
161
  /* eslint-disable react-hooks/exhaustive-deps */
151
162
  }, [
152
163
  onSelection,
153
- selectedItems.map((item) => item[equalityProp as keyof T]).join("|"),
154
- createdItems.map((item) => item[equalityProp as keyof T]).join("|"),
164
+ selectedItems.map((item) => itemId(item)).join("|"),
165
+ createdItems.map((item) => itemId(item)).join("|"),
155
166
  ]);
156
167
 
157
168
  /**
@@ -160,7 +171,7 @@ function MultiSelect<T>({
160
171
  * @returns
161
172
  */
162
173
  const itemHasBeenSelectedAlready = (matcher: string) => {
163
- return !!selectedItems.find((item) => String(item[equalityProp as keyof typeof item]).toString() === matcher);
174
+ return !!selectedItems.find((item) => itemId(item) === matcher);
164
175
  };
165
176
 
166
177
  /**
@@ -168,7 +179,7 @@ function MultiSelect<T>({
168
179
  * @param matcher
169
180
  */
170
181
  const removeItemSelection = (matcher: string) => {
171
- setSelectedItems((items) => items.filter((t) => String(t[equalityProp as keyof typeof t]).toString() !== matcher));
182
+ setSelectedItems((items) => items.filter((item) => itemId(item) !== matcher));
172
183
  };
173
184
 
174
185
  /**
@@ -177,12 +188,12 @@ function MultiSelect<T>({
177
188
  * @param item
178
189
  */
179
190
  const onItemSelect = (item: T) => {
180
- if (itemHasBeenSelectedAlready(String(item[equalityProp as keyof T]).toString())) {
181
- removeItemSelection(String(item[equalityProp as keyof T]).toString());
191
+ if (itemHasBeenSelectedAlready(itemId(item))) {
192
+ removeItemSelection(itemId(item));
182
193
  } else {
183
194
  setSelectedItems((items) => [...items, item]);
184
195
  }
185
- setQuery("");
196
+ inputRef.current?.select()
186
197
  };
187
198
 
188
199
  /**
@@ -192,10 +203,11 @@ function MultiSelect<T>({
192
203
  const onQueryChange = async (query: string) => {
193
204
  if (query.length) {
194
205
  setQuery(query);
206
+ setFilteredItemList([])
195
207
  const resultFromQuery = runOnQueryChange && (await runOnQueryChange(removeExtraSpaces(query)));
196
208
  setFilteredItemList(() =>
197
- [...(resultFromQuery ?? itemsCopy), ...createdItems].filter((t) =>
198
- String(t[labelProp as keyof typeof t]).toLowerCase().includes(query.toLowerCase())
209
+ [...(resultFromQuery ?? itemsCopy), ...createdItems].filter(item =>
210
+ itemLabel(item).toLowerCase().includes(query.toLowerCase())
199
211
  )
200
212
  );
201
213
  }
@@ -208,24 +220,22 @@ function MultiSelect<T>({
208
220
 
209
221
  /**
210
222
  * defines how an item in the item list is displayed
211
- * @param tag
212
- * @param param
213
- * @returns
214
223
  */
215
224
  const onItemRenderer = (item: T, { handleClick, modifiers }: IItemRendererProps) => {
216
225
  if (!modifiers.matchesPredicate) {
217
226
  return null;
218
227
  }
219
- const label = createdItems.find((createdItem) => createdItem[labelProp as keyof T] === item[labelProp as keyof T])
220
- ? `${item[labelProp as keyof T]} (new tag)`
221
- : item[labelProp as keyof T];
228
+ let label = itemLabel(item)
229
+ if(createdItems.find(created => itemId(created) === itemId(item))) {
230
+ label += newItemPostfix
231
+ }
222
232
  return (
223
233
  <MenuItem
224
234
  active={modifiers.active}
225
- key={String(item[equalityProp as keyof T]).toString()}
226
- icon={itemHasBeenSelectedAlready(String(item[equalityProp as keyof T]).toString()) ? "state-checked" : "state-unchecked"}
235
+ key={itemId(item)}
236
+ icon={itemHasBeenSelectedAlready(itemId(item)) ? "state-checked" : "state-unchecked"}
227
237
  onClick={handleClick}
228
- text={optionRenderer(String(label).toString())}
238
+ text={optionRenderer(label)}
229
239
  shouldDismissPopover={false}
230
240
  />
231
241
  );
@@ -246,18 +256,14 @@ function MultiSelect<T>({
246
256
  */
247
257
  const removeTagFromSelectionViaIndex = (label: string, index: number) => {
248
258
  setSelectedItems([...selectedItems.slice(0, index), ...selectedItems.slice(index + 1)]);
249
- setCreatedItems((items) => items.filter((t) => String(t[labelProp as keyof typeof t]).toString() !== label));
259
+ setCreatedItems(items => items.filter(item => itemLabel(item) !== label));
250
260
  };
251
261
 
252
- const removeExtraSpaces = (text: string) => text.replace(/\s+/g, " ").trim();
253
-
254
262
  /**
255
- * utility function to create a new Item
256
- * @param event
257
- * @param label
263
+ * Utility function to create a new Item. createNewItemFromQuery is assumed to be defined!
258
264
  */
259
- const createNewItem = (event: React.KeyboardEvent<HTMLElement> | React.MouseEvent<HTMLElement>, label: string): T => {
260
- const newItem = { [labelProp]: removeExtraSpaces(label), [equalityProp]: removeExtraSpaces(label) } as any;
265
+ const createNewItem = (query: string): T => {
266
+ const newItem = createNewItemFromQuery!!(query);
261
267
  //set new items
262
268
  setCreatedItems((items) => [...items, newItem]);
263
269
  setQuery("");
@@ -270,10 +276,10 @@ function MultiSelect<T>({
270
276
  * @param event
271
277
  */
272
278
  const handleOnKeyUp = (event: React.KeyboardEvent<HTMLElement>) => {
273
- if (event.key === "Enter" && !filteredItemList.length && !!query) {
274
- createNewItem(event, query);
279
+ if (event.key === "Enter" && !filteredItemList.length && !!query && createNewItemFromQuery) {
280
+ createNewItem(query);
275
281
  }
276
- tagInputRef.current?.focus();
282
+ inputRef.current?.focus();
277
283
  };
278
284
 
279
285
  /**
@@ -284,8 +290,9 @@ function MultiSelect<T>({
284
290
  const handleOnKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
285
291
  if (event.key === "Tab" && !!query) {
286
292
  event.preventDefault();
287
- focusedItem ? onItemSelect(focusedItem) : onItemSelect(createNewItem(event, query));
288
- setTimeout(() => tagInputRef.current?.focus());
293
+ focusedItem ? onItemSelect(focusedItem) : onItemSelect(createNewItem(query));
294
+ setQuery("");
295
+ setTimeout(() => inputRef.current?.focus());
289
296
  }
290
297
  };
291
298
 
@@ -297,14 +304,14 @@ function MultiSelect<T>({
297
304
  * @returns
298
305
  */
299
306
  const newItemRenderer = (label: string, active: boolean, handleClick: React.MouseEventHandler<HTMLElement>) => {
300
- if (!canCreateNewItem) return undefined;
307
+ if (!createNewItemFromQuery) return undefined;
301
308
  const clickHandler = (e: React.MouseEvent<HTMLElement>) => {
302
- createNewItem(e, label);
309
+ createNewItem(label);
303
310
  handleClick(e);
304
311
  };
305
312
  return (
306
313
  <MenuItem
307
- id={"new-tag"}
314
+ id={"new-item"}
308
315
  icon="item-add-artefact"
309
316
  active={active}
310
317
  key={label}
@@ -320,32 +327,28 @@ function MultiSelect<T>({
320
327
  ) : undefined;
321
328
 
322
329
  return (
323
- <BlueprintMultiSelect
330
+ <BlueprintMultiSelect<T>
324
331
  {...otherProps}
325
332
  query={query}
326
333
  onQueryChange={onQueryChange}
327
334
  items={filteredItemList}
328
335
  onItemSelect={onItemSelect}
329
336
  itemRenderer={onItemRenderer}
330
- itemsEqual={(a: T, b: T) => a[labelProp as keyof T] === b[labelProp as keyof T]}
337
+ itemsEqual={(a: T, b: T) => itemId(a) === itemId(b)}
331
338
  selectedItems={selectedItems}
332
339
  noResults={<MenuItem disabled={true} text={noResultText} />}
333
- tagRenderer={(tag) => tag[labelProp as keyof typeof tag]}
340
+ tagRenderer={item => itemLabel(item)}
334
341
  createNewItemRenderer={newItemRenderer}
335
342
  onActiveItemChange={(activeItem) => setFocusedItem(activeItem)}
336
343
  fill={fullWidth}
337
- createNewItemFromQuery={(query) =>
338
- ({
339
- [labelProp]: removeExtraSpaces(query),
340
- [equalityProp]: removeExtraSpaces(query),
341
- } as any)
342
- }
344
+ createNewItemFromQuery={createNewItemFromQuery}
343
345
  tagInputProps={{
344
346
  inputProps: {
345
347
  id: "item",
346
348
  autoComplete: "off",
349
+ ...inputProps
347
350
  },
348
- inputRef: tagInputRef,
351
+ inputRef: inputRef,
349
352
  intent,
350
353
  addOnBlur: true,
351
354
  onKeyDown: handleOnKeyDown,