@eccenca/gui-elements 23.0.0-rc.5 → 23.0.0-rc.7

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 (174) hide show
  1. package/CHANGELOG.md +4 -1
  2. package/dist/cjs/cmem/markdown/Markdown.js +14 -2
  3. package/dist/cjs/cmem/markdown/Markdown.js.map +1 -1
  4. package/dist/cjs/cmem/react-flow/ReactFlow/ReactFlow.js +0 -4
  5. package/dist/cjs/cmem/react-flow/ReactFlow/ReactFlow.js.map +1 -1
  6. package/dist/cjs/components/Accordion/Accordion.js +7 -2
  7. package/dist/cjs/components/Accordion/Accordion.js.map +1 -1
  8. package/dist/cjs/components/Accordion/AccordionItem.js.map +1 -1
  9. package/dist/cjs/components/AutoSuggestion/AutoSuggestion.js +59 -33
  10. package/dist/cjs/components/AutoSuggestion/AutoSuggestion.js.map +1 -1
  11. package/dist/cjs/components/AutoSuggestion/AutoSuggestionList.js +10 -1
  12. package/dist/cjs/components/AutoSuggestion/AutoSuggestionList.js.map +1 -1
  13. package/dist/cjs/components/AutoSuggestion/SingleLineCodeEditor.js +10 -4
  14. package/dist/cjs/components/AutoSuggestion/SingleLineCodeEditor.js.map +1 -1
  15. package/dist/cjs/components/AutocompleteField/AutoCompleteField.js +13 -8
  16. package/dist/cjs/components/AutocompleteField/AutoCompleteField.js.map +1 -1
  17. package/dist/cjs/components/Button/Button.js.map +1 -1
  18. package/dist/cjs/components/Checkbox/Checkbox.js +1 -0
  19. package/dist/cjs/components/Checkbox/Checkbox.js.map +1 -1
  20. package/dist/cjs/components/Form/FieldItem.js.map +1 -1
  21. package/dist/cjs/components/Icon/canonicalIconNames.js +12 -16
  22. package/dist/cjs/components/Icon/canonicalIconNames.js.map +1 -1
  23. package/dist/cjs/components/Menu/Menu.js +2 -2
  24. package/dist/cjs/components/Menu/Menu.js.map +1 -1
  25. package/dist/cjs/components/Menu/MenuDivider.js +2 -2
  26. package/dist/cjs/components/Menu/MenuDivider.js.map +1 -1
  27. package/dist/cjs/components/Menu/MenuItem.js +5 -3
  28. package/dist/cjs/components/Menu/MenuItem.js.map +1 -1
  29. package/dist/cjs/components/MultiSelect/MultiSelect.js +17 -4
  30. package/dist/cjs/components/MultiSelect/MultiSelect.js.map +1 -1
  31. package/dist/cjs/components/Pagination/Pagination.js.map +1 -1
  32. package/dist/cjs/components/RadioButton/RadioButton.js +1 -0
  33. package/dist/cjs/components/RadioButton/RadioButton.js.map +1 -1
  34. package/dist/cjs/components/Separation/Spacing.js +4 -1
  35. package/dist/cjs/components/Separation/Spacing.js.map +1 -1
  36. package/dist/cjs/components/Spinner/Spinner.js +3 -3
  37. package/dist/cjs/components/Spinner/Spinner.js.map +1 -1
  38. package/dist/cjs/components/Switch/Switch.js.map +1 -1
  39. package/dist/cjs/extensions/react-flow/edges/EdgeDefault.js.map +1 -1
  40. package/dist/esm/cmem/markdown/Markdown.js +13 -2
  41. package/dist/esm/cmem/markdown/Markdown.js.map +1 -1
  42. package/dist/esm/cmem/react-flow/ReactFlow/ReactFlow.js +0 -4
  43. package/dist/esm/cmem/react-flow/ReactFlow/ReactFlow.js.map +1 -1
  44. package/dist/esm/components/Accordion/Accordion.js +8 -3
  45. package/dist/esm/components/Accordion/Accordion.js.map +1 -1
  46. package/dist/esm/components/Accordion/AccordionItem.js +1 -1
  47. package/dist/esm/components/Accordion/AccordionItem.js.map +1 -1
  48. package/dist/esm/components/AutoSuggestion/AutoSuggestion.js +71 -45
  49. package/dist/esm/components/AutoSuggestion/AutoSuggestion.js.map +1 -1
  50. package/dist/esm/components/AutoSuggestion/AutoSuggestionList.js +11 -2
  51. package/dist/esm/components/AutoSuggestion/AutoSuggestionList.js.map +1 -1
  52. package/dist/esm/components/AutoSuggestion/SingleLineCodeEditor.js +11 -5
  53. package/dist/esm/components/AutoSuggestion/SingleLineCodeEditor.js.map +1 -1
  54. package/dist/esm/components/AutocompleteField/AutoCompleteField.js +19 -14
  55. package/dist/esm/components/AutocompleteField/AutoCompleteField.js.map +1 -1
  56. package/dist/esm/components/Button/Button.js.map +1 -1
  57. package/dist/esm/components/Checkbox/Checkbox.js +1 -0
  58. package/dist/esm/components/Checkbox/Checkbox.js.map +1 -1
  59. package/dist/esm/components/Form/FieldItem.js.map +1 -1
  60. package/dist/esm/components/Icon/canonicalIconNames.js +12 -16
  61. package/dist/esm/components/Icon/canonicalIconNames.js.map +1 -1
  62. package/dist/esm/components/Menu/Menu.js +3 -3
  63. package/dist/esm/components/Menu/Menu.js.map +1 -1
  64. package/dist/esm/components/Menu/MenuDivider.js +3 -3
  65. package/dist/esm/components/Menu/MenuDivider.js.map +1 -1
  66. package/dist/esm/components/Menu/MenuItem.js +5 -3
  67. package/dist/esm/components/Menu/MenuItem.js.map +1 -1
  68. package/dist/esm/components/MultiSelect/MultiSelect.js +22 -9
  69. package/dist/esm/components/MultiSelect/MultiSelect.js.map +1 -1
  70. package/dist/esm/components/Pagination/Pagination.js +1 -1
  71. package/dist/esm/components/Pagination/Pagination.js.map +1 -1
  72. package/dist/esm/components/RadioButton/RadioButton.js +2 -1
  73. package/dist/esm/components/RadioButton/RadioButton.js.map +1 -1
  74. package/dist/esm/components/Separation/Spacing.js +4 -1
  75. package/dist/esm/components/Separation/Spacing.js.map +1 -1
  76. package/dist/esm/components/Spinner/Spinner.js +3 -4
  77. package/dist/esm/components/Spinner/Spinner.js.map +1 -1
  78. package/dist/esm/components/Switch/Switch.js +1 -1
  79. package/dist/esm/components/Switch/Switch.js.map +1 -1
  80. package/dist/esm/extensions/react-flow/edges/EdgeDefault.js.map +1 -1
  81. package/dist/types/cmem/markdown/Markdown.d.ts +19 -3
  82. package/dist/types/cmem/react-flow/ReactFlow/ReactFlow.d.ts +1 -1
  83. package/dist/types/components/Accordion/Accordion.d.ts +12 -1
  84. package/dist/types/components/Accordion/AccordionItem.d.ts +21 -6
  85. package/dist/types/components/AutoSuggestion/AutoSuggestionList.d.ts +4 -2
  86. package/dist/types/components/AutocompleteField/AutoCompleteField.d.ts +7 -0
  87. package/dist/types/components/Button/Button.d.ts +1 -1
  88. package/dist/types/components/Checkbox/Checkbox.d.ts +4 -1
  89. package/dist/types/components/Form/FieldItem.d.ts +32 -2
  90. package/dist/types/components/Icon/canonicalIconNames.d.ts +1 -1
  91. package/dist/types/components/Menu/Menu.d.ts +4 -1
  92. package/dist/types/components/Menu/MenuDivider.d.ts +4 -1
  93. package/dist/types/components/Menu/MenuItem.d.ts +5 -2
  94. package/dist/types/components/Pagination/Pagination.d.ts +9 -5
  95. package/dist/types/components/RadioButton/RadioButton.d.ts +4 -1
  96. package/dist/types/components/Separation/Spacing.d.ts +17 -3
  97. package/dist/types/components/Spinner/Spinner.d.ts +38 -5
  98. package/dist/types/components/Switch/Switch.d.ts +6 -3
  99. package/dist/types/extensions/react-flow/edges/EdgeDefault.d.ts +15 -0
  100. package/package.json +4 -6
  101. package/src/cmem/markdown/Markdown.stories.tsx +55 -0
  102. package/src/cmem/markdown/Markdown.tsx +21 -9
  103. package/src/cmem/react-flow/ReactFlow/ReactFlow.stories.tsx +15 -86
  104. package/src/cmem/react-flow/ReactFlow/ReactFlow.tsx +1 -5
  105. package/src/cmem/react-flow/nodes/_stickynote.scss +1 -1
  106. package/src/components/Accordion/Accordion.tsx +33 -3
  107. package/src/components/Accordion/AccordionItem.tsx +23 -33
  108. package/src/components/Accordion/Stories/Accordion.stories.tsx +33 -0
  109. package/src/components/Accordion/Stories/AccordionItem.stories.tsx +27 -0
  110. package/src/components/Accordion/accordion.scss +20 -1
  111. package/src/components/AutoSuggestion/AutoSuggestion.tsx +73 -39
  112. package/src/components/AutoSuggestion/AutoSuggestionList.tsx +15 -2
  113. package/src/components/AutoSuggestion/SingleLineCodeEditor.tsx +17 -9
  114. package/src/components/AutoSuggestion/tests/AutoSuggestionList.test.tsx +9 -7
  115. package/src/components/AutocompleteField/AutoCompleteField.tsx +24 -6
  116. package/src/components/AutocompleteField/autoCompleteFieldUtils.tsx +1 -1
  117. package/src/components/AutocompleteField/autocompletefield.scss +1 -1
  118. package/src/components/AutocompleteField/stories/AutoCompleteField.stories.tsx +1 -1
  119. package/src/components/Button/Button.stories.tsx +1 -107
  120. package/src/components/Button/Button.tsx +2 -2
  121. package/src/components/Card/stories/Card.stories.tsx +2 -21
  122. package/src/components/Checkbox/Checkbox.tsx +9 -2
  123. package/src/components/Checkbox/Stories/Checkbox.stories.tsx +30 -0
  124. package/src/components/Checkbox/checkbox.scss +4 -0
  125. package/src/components/ContextOverlay/ContextMenu.stories.tsx +2 -2
  126. package/src/components/Dialog/stories/Modal.stories.tsx +1 -61
  127. package/src/components/Dialog/stories/SimpleDialog.stories.tsx +1 -2
  128. package/src/components/Form/FieldItem.tsx +32 -2
  129. package/src/components/Form/Stories/FieldItem.stories.tsx +27 -0
  130. package/src/components/Form/Stories/FieldItemRow.stories.tsx +31 -0
  131. package/src/components/Icon/canonicalIconNames.tsx +113 -119
  132. package/src/components/Label/Label.stories.tsx +1 -1
  133. package/src/components/Menu/Menu.tsx +21 -3
  134. package/src/components/Menu/MenuDivider.tsx +20 -3
  135. package/src/components/Menu/MenuItem.tsx +13 -10
  136. package/src/components/Menu/Stories/Menu.stories.tsx +31 -0
  137. package/src/components/Menu/Stories/MenuItem.stories.tsx +25 -0
  138. package/src/components/MultiSelect/MultiSelect.stories.tsx +2 -21
  139. package/src/components/MultiSelect/MultiSelect.tsx +22 -5
  140. package/src/components/Notification/Notification.stories.tsx +1 -29
  141. package/src/components/Pagination/Pagination.tsx +13 -6
  142. package/src/components/Pagination/Stories/Pagination.stories.tsx +32 -0
  143. package/src/components/Pagination/pagination.scss +2 -2
  144. package/src/components/ProgressBar/Stories/ProgressBar.stories.tsx +25 -0
  145. package/src/components/RadioButton/RadioButton.tsx +9 -2
  146. package/src/components/RadioButton/Stories/RadioButton.stories.tsx +30 -0
  147. package/src/components/Select/Select.stories.tsx +1 -1
  148. package/src/components/Separation/Spacing.tsx +17 -4
  149. package/src/components/Separation/Stories/Spacing.stories.tsx +25 -0
  150. package/src/components/Spinner/Spinner.tsx +39 -7
  151. package/src/components/Spinner/Stories/spinner.stories.tsx +26 -0
  152. package/src/components/Switch/Stories/Switch.stories.tsx +27 -0
  153. package/src/components/Switch/Switch.tsx +9 -3
  154. package/src/components/Tabs/stories/Tab.stories.tsx +1 -32
  155. package/src/components/Tabs/stories/Tabs.stories.tsx +1 -39
  156. package/src/components/Tag/Tag.stories.tsx +1 -9
  157. package/src/components/TextField/stories/TextArea.stories.tsx +10 -25
  158. package/src/components/TextField/stories/TextField.stories.tsx +2 -73
  159. package/src/components/Typography/typography.scss +3 -3
  160. package/src/extensions/react-flow/edges/EdgeDefault.tsx +19 -0
  161. package/src/extensions/react-flow/edges/stories/EdgeDefault.stories.tsx +121 -0
  162. package/src/extensions/react-flow/nodes/stories/NodeContent.stories.tsx +1 -1
  163. package/dist/cjs/cmem/react-flow/configuration/customLabel.js +0 -13
  164. package/dist/cjs/cmem/react-flow/configuration/customLabel.js.map +0 -1
  165. package/dist/cjs/cmem/react-flow/configuration/inverseEdge.js +0 -13
  166. package/dist/cjs/cmem/react-flow/configuration/inverseEdge.js.map +0 -1
  167. package/dist/esm/cmem/react-flow/configuration/customLabel.js +0 -10
  168. package/dist/esm/cmem/react-flow/configuration/customLabel.js.map +0 -1
  169. package/dist/esm/cmem/react-flow/configuration/inverseEdge.js +0 -10
  170. package/dist/esm/cmem/react-flow/configuration/inverseEdge.js.map +0 -1
  171. package/dist/types/cmem/react-flow/configuration/customLabel.d.ts +0 -8
  172. package/dist/types/cmem/react-flow/configuration/inverseEdge.d.ts +0 -8
  173. package/src/cmem/react-flow/configuration/customLabel.ts +0 -11
  174. package/src/cmem/react-flow/configuration/inverseEdge.ts +0 -11
@@ -1,45 +1,35 @@
1
1
  import React from "react";
2
- import {AccordionItem as CarbonAccordionItem} from "carbon-components-react";
2
+ import {
3
+ AccordionItem as CarbonAccordionItem,
4
+ AccordionItemProps as CarbonAccordionItemProps,
5
+ } from "carbon-components-react";
3
6
  import { CLASSPREFIX as eccgui } from "../../configuration/constants";
4
7
 
5
- export interface AccordionItemProps extends React.LiHTMLAttributes<HTMLLIElement> {
6
- /*
7
- additional user class name
8
- */
8
+ export interface AccordionItemProps extends Omit<CarbonAccordionItemProps, "title" | "iconDescription" | "renderExpando"> {
9
+ /**
10
+ * additional user class name
11
+ */
9
12
  className?: string;
10
- /*
11
- content of accordion item
12
- */
13
- children: string | JSX.Element | JSX.Element[];
14
- /*
15
- disable accordion item
16
- */
17
- disabled?: boolean;
18
- /*
19
- set accordion item expliciteky as open when displayed first
20
- */
21
- open?: boolean;
22
- /*
23
- header of accordion item
24
- */
13
+ /**
14
+ * header of accordion item
15
+ */
25
16
  label: string | JSX.Element;
26
- /*
27
- use full available width for content
28
- */
17
+ /**
18
+ * use full available width for content
19
+ */
29
20
  fullWidth?: boolean;
30
- /*
31
- minimize white space and paddings
32
- */
21
+ /**
22
+ * minimize white space and paddings
23
+ */
33
24
  condensed?: boolean;
34
- /*
35
- do not use borders as visible separations on accordion item
36
- */
25
+ /**
26
+ * do not use borders as visible separations on accordion item
27
+ */
37
28
  noBorder?: boolean;
38
- /*
39
- highlight accordion item by different background color
40
- */
29
+ /**
30
+ * highlight accordion item by different background color
31
+ */
41
32
  elevated?: boolean;
42
- title?: never;
43
33
  };
44
34
 
45
35
  function AccordionItem({
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+ import { ComponentStory, ComponentMeta } from "@storybook/react";
3
+ import { Accordion, AccordionItem } from "../../../../index";
4
+ import { Default as AccordionStoryItem } from '../Stories/AccordionItem.stories'
5
+
6
+ export default {
7
+ title: "Components/Accordion",
8
+ component: Accordion,
9
+ subcomponents: { AccordionItem },
10
+ argTypes: {
11
+ children: {
12
+ control: "none",
13
+ description: "Elements to include into the Accordion component"
14
+ },
15
+ }
16
+
17
+ } as ComponentMeta<typeof Accordion>;
18
+
19
+ const TemplateIcons: ComponentStory<typeof Accordion> = (args) => (
20
+ <Accordion {...args} />
21
+ );
22
+ export const Default = TemplateIcons.bind({});
23
+ Default.args = {
24
+ children : [
25
+ <AccordionItem {...AccordionStoryItem.args} label="Accordion item 1" />,
26
+ <AccordionItem {...AccordionStoryItem.args} label="Accordion item 2 (elevated)" elevated />,
27
+ <AccordionItem {...AccordionStoryItem.args} label="Accordion item 3 (initially opened)" open />,
28
+ <AccordionItem {...AccordionStoryItem.args} label="Accordion item 4 (disabled)" disabled />,
29
+ <AccordionItem {...AccordionStoryItem.args} label="Accordion item 5" />,
30
+ ],
31
+ align: "start",
32
+ size: "medium"
33
+ };
@@ -0,0 +1,27 @@
1
+ import React from "react";
2
+ import { ComponentStory, ComponentMeta } from "@storybook/react";
3
+ import { Accordion, AccordionItem, HtmlContentBlock } from "../../../../index";
4
+ import { LoremIpsum } from 'react-lorem-ipsum';
5
+
6
+ export default {
7
+ title: "Components/Accordion/AccordionItem",
8
+ component: AccordionItem,
9
+ argTypes: {
10
+ children: {
11
+ control: "none",
12
+ description: "content of accordion item"
13
+ },
14
+ }
15
+
16
+ } as ComponentMeta<typeof AccordionItem>;
17
+
18
+ const Template : ComponentStory<typeof AccordionItem> = (args) => (
19
+ <Accordion>
20
+ <AccordionItem {...args} />
21
+ </Accordion>
22
+ );
23
+ export const Default = Template.bind({});
24
+ Default.args = {
25
+ label: "Title of accordion item",
26
+ children : <HtmlContentBlock><LoremIpsum p={2} avgSentencesPerParagraph={4} random={false} /></HtmlContentBlock>
27
+ };
@@ -1,3 +1,8 @@
1
+ // own vars
2
+ $eccgui-color-accordion-background-elevated: rgba($eccgui-color-accent, 0.1) !default;
3
+ $eccgui-color-accordion-toggler-hover: $menu-item-color-hover !default;
4
+ $eccgui-color-accordion-toggler-elevated-hover: mix($eccgui-color-accordion-background-elevated, $eccgui-color-accordion-toggler-hover, 50%) !default;
5
+
1
6
  // lib import
2
7
  @import "~carbon-components/scss/components/accordion/accordion";
3
8
 
@@ -57,8 +62,22 @@
57
62
  }
58
63
  }
59
64
 
65
+ .#{$prefix}--accordion__heading {
66
+ &:hover::before,
67
+ &:focus::before {
68
+ background-color: $eccgui-color-accordion-toggler-hover;
69
+ }
70
+ }
71
+
60
72
  .#{$eccgui}-accordion__item--elevated {
61
- background-color: $data-table-zebra-color; //rgba($ui-03, alpha($ui-03) * $eccgui-opacity-muted);
73
+ background-color: $eccgui-color-accordion-background-elevated;
74
+
75
+ .#{$prefix}--accordion__heading {
76
+ &:hover::before,
77
+ &:focus::before {
78
+ background-color: $eccgui-color-accordion-toggler-elevated-hover;
79
+ }
80
+ }
62
81
  }
63
82
 
64
83
  .#{$eccgui}-accordion__item--noborder {
@@ -1,5 +1,5 @@
1
1
  import React, {useEffect, useMemo, useState} from "react";
2
- import CodeMirror from "codemirror";
2
+ import CodeMirror, {Position} from "codemirror";
3
3
  import {FieldItem, IconButton, Spinner, Toolbar, ToolbarSection, ContextOverlay} from "./../../";
4
4
  import {Classes as BlueprintClassNames} from "@blueprintjs/core";
5
5
  import { CLASSPREFIX as eccgui } from "../../configuration/constants";
@@ -111,6 +111,13 @@ export interface IProps {
111
111
  validationRequestDelay?: number
112
112
  }
113
113
 
114
+ // Meta data regarding a request
115
+ interface RequestMetaData {
116
+ requestId: string | undefined
117
+ }
118
+
119
+ type HorizontalShiftCallbackFunction = (shift: number) => any
120
+
114
121
  /** Input component that allows partial, fine-grained auto-completion, i.e. of sub-strings of the input string.
115
122
  * This is comparable to a one line code editor. */
116
123
  const AutoSuggestion = ({
@@ -132,20 +139,22 @@ const AutoSuggestion = ({
132
139
  autoCompletionRequestDelay = 1000,
133
140
  validationRequestDelay = 200
134
141
  }: IProps) => {
135
- const [value, setValue] = React.useState(initialValue);
136
- const [cursorPosition, setCursorPosition] = React.useState(0);
137
- const [horizontalShift, setHorizontalShift] = React.useState<number>(0);
142
+ const value = React.useRef<string>(initialValue)
143
+ const cursorPosition = React.useRef(0);
144
+ const horizontalShiftSubscriber = React.useRef<HorizontalShiftCallbackFunction | undefined>(undefined)
138
145
  const [shouldShowDropdown, setShouldShowDropdown] = React.useState(false);
139
146
  const [suggestions, setSuggestions] = React.useState<ISuggestionWithReplacementInfo[]>([]);
140
147
  const [suggestionsPending, setSuggestionsPending] = React.useState(false);
148
+ const suggestionRequestData = React.useRef<RequestMetaData>({requestId: undefined})
141
149
  const [pathValidationPending, setPathValidationPending] = React.useState(false)
150
+ const validationRequestData = React.useRef<RequestMetaData>({requestId: undefined})
142
151
  const [, setErrorMarkers] = React.useState<CodeMirror.TextMarker[]>([]);
143
152
  const [validationResponse, setValidationResponse] = useState<IValidationResult | undefined>(undefined)
144
153
  const [suggestionResponse, setSuggestionResponse] = useState<IPartialAutoCompleteResult | undefined>(undefined)
145
154
  // The element that should be used for replacement highlighting
146
155
  const [highlightedElement, setHighlightedElement] = useState<ISuggestionWithReplacementInfo | undefined>(undefined)
147
156
  const [editorInstance, setEditorInstance] = React.useState<CodeMirror.Editor>();
148
- const [isFocused, setIsFocused] = React.useState(false);
157
+ const isFocused = React.useRef(false);
149
158
  /** Mutable editor state, since this needs to be current in scope of the SingleLineEditorComponent. */
150
159
  const [editorState] = React.useState<{
151
160
  index: number,
@@ -155,7 +164,7 @@ const AutoSuggestion = ({
155
164
  }>({index: 0, suggestions: [], dropdownShown: false})
156
165
  /** This is for the AutoSuggestionList component in order to re-render. */
157
166
  const [focusedIndex, setFocusedIndex] = React.useState(0)
158
- const [selectedTextRanges, setSelectedTextRanges] = useState<IRange[]>([])
167
+ const selectedTextRanges = React.useRef<IRange[]>([])
159
168
 
160
169
  const pathIsValid = validationResponse?.valid ?? true;
161
170
 
@@ -170,8 +179,10 @@ const AutoSuggestion = ({
170
179
  }, [editorInstance, editorState])
171
180
 
172
181
  React.useEffect(() => {
173
- setValue(initialValue)
174
- }, [initialValue])
182
+ if(initialValue != null) {
183
+ editorInstance?.setValue(initialValue)
184
+ }
185
+ }, [initialValue, editorInstance])
175
186
 
176
187
  React.useEffect(() => {
177
188
  editorState.dropdownShown = shouldShowDropdown
@@ -181,7 +192,7 @@ const AutoSuggestion = ({
181
192
  useEffect(() => {
182
193
  if (highlightedElement && editorInstance) {
183
194
  const { from, length } = highlightedElement;
184
- if(length > 0 && selectedTextRanges.length === 0) {
195
+ if(length > 0 && selectedTextRanges.current.length === 0) {
185
196
  const to = from + length;
186
197
  const marker = editorInstance.markText(
187
198
  {line: 0, ch: from},
@@ -216,7 +227,8 @@ const AutoSuggestion = ({
216
227
  return []
217
228
  })
218
229
  }
219
- onInputChecked && onInputChecked(!!validationResponse?.valid)
230
+ const isValid = validationResponse?.valid === undefined || validationResponse.valid
231
+ onInputChecked && onInputChecked(isValid)
220
232
  }, [validationResponse?.valid, validationResponse?.parseError, editorInstance, onInputChecked]);
221
233
 
222
234
  /** generate suggestions and also populate the replacement indexes dict */
@@ -245,9 +257,10 @@ const AutoSuggestion = ({
245
257
  }, [suggestionResponse, editorState]);
246
258
 
247
259
  const asyncCheckInput = useMemo(() => async (inputString: string) => {
248
- if(!checkInput) {
260
+ if(!checkInput || inputString !== value.current || validationRequestData.current.requestId === inputString) {
249
261
  return
250
262
  }
263
+ validationRequestData.current.requestId = inputString
251
264
  setPathValidationPending(true)
252
265
  try {
253
266
  const result: IValidationResult | undefined = await checkInput(inputString)
@@ -266,10 +279,17 @@ const AutoSuggestion = ({
266
279
  )
267
280
 
268
281
  const asyncHandleEditorInputChange = useMemo(() => async (inputString: string, cursorPosition: number) => {
282
+ const requestId = `${inputString} ${cursorPosition}`
283
+ if(requestId === suggestionRequestData.current.requestId) {
284
+ return
285
+ }
286
+ suggestionRequestData.current.requestId = requestId
269
287
  setSuggestionsPending(true)
270
288
  try {
271
289
  const result: IPartialAutoCompleteResult | undefined = await fetchSuggestions(inputString, cursorPosition)
272
- setSuggestionResponse(result)
290
+ if(value.current === inputString) {
291
+ setSuggestionResponse(result)
292
+ }
273
293
  } catch(e) {
274
294
  setSuggestionResponse(undefined)
275
295
  // TODO: Error handling
@@ -283,29 +303,24 @@ const AutoSuggestion = ({
283
303
  [asyncHandleEditorInputChange, autoCompletionRequestDelay]
284
304
  )
285
305
 
286
- React.useEffect(() => {
287
- if (isFocused) {
288
- setShouldShowDropdown(true);
289
- handleEditorInputChange(value, cursorPosition)
290
- return handleEditorInputChange.cancel
291
- }
292
- return;
293
- }, [cursorPosition, value, isFocused, handleEditorInputChange]);
294
-
295
- // Trigger input validation
296
- useEffect(() => {
297
- checkValuePathValidity(value)
298
- return checkValuePathValidity.cancel
299
- }, [value, checkValuePathValidity])
300
-
301
306
  const handleChange = (val: string) => {
302
- setValue(val);
307
+ value.current = val
308
+ checkValuePathValidity.cancel()
309
+ checkValuePathValidity(value.current)
303
310
  onChange(val)
304
311
  };
305
312
 
306
- const handleCursorChange = (pos: any, coords: any, scrollinfo: any) => {
307
- setCursorPosition(pos.ch);
308
- setHorizontalShift(Math.min(coords.left, Math.max(coords.left - scrollinfo.left, 0)));
313
+ const handleCursorChange = (pos: Position, coords: any, scrollinfo: any) => {
314
+ cursorPosition.current = pos.ch;
315
+ // cursor change is fired after onChange, so we put the auto-complete logic here
316
+ if (isFocused.current) {
317
+ setShouldShowDropdown(true);
318
+ handleEditorInputChange.cancel()
319
+ handleEditorInputChange(value.current, cursorPosition.current)
320
+ }
321
+ horizontalShiftSubscriber.current && horizontalShiftSubscriber.current(
322
+ Math.min(coords.left, Math.max(coords.left - scrollinfo.left, 0))
323
+ )
309
324
  };
310
325
 
311
326
  const handleInputEditorKeyPress = (event: KeyboardEvent) => {
@@ -333,15 +348,26 @@ const AutoSuggestion = ({
333
348
  };
334
349
 
335
350
  const handleInputEditorClear = () => {
336
- handleChange("");
337
- setValue("")
351
+ editorInstance?.setValue("")
352
+ cursorPosition.current = 0
353
+ handleChange("")
338
354
  editorInstance?.focus();
339
355
  };
340
356
 
341
357
  const handleInputFocus = (focusState: boolean) => {
342
358
  onFocusChange && onFocusChange(focusState)
343
- setIsFocused(focusState)
344
359
  focusState ? setShouldShowDropdown(true) : closeDropDown()
360
+ if(!isFocused.current && focusState) {
361
+ // Just got focus
362
+ // Clear suggestions and repeat suggestion request, something else might have changed while this component was not focused
363
+ setSuggestions([])
364
+ suggestionRequestData.current.requestId = undefined
365
+ isFocused.current = focusState
366
+ handleEditorInputChange.cancel()
367
+ handleEditorInputChange(value.current, cursorPosition.current)
368
+ } else {
369
+ isFocused.current = focusState
370
+ }
345
371
  };
346
372
 
347
373
  //keyboard handlers
@@ -390,7 +416,15 @@ const AutoSuggestion = ({
390
416
  setHighlightedElement(item)
391
417
  }, [])
392
418
 
393
- const hasError = !!value && !pathIsValid && !pathValidationPending;
419
+ const onSelection = React.useMemo(() => (ranges: IRange[]) => {
420
+ selectedTextRanges.current = ranges
421
+ }, [])
422
+
423
+ const subscribeToHorizontalShift = React.useMemo(() => (callback: HorizontalShiftCallbackFunction) => {
424
+ horizontalShiftSubscriber.current = callback
425
+ }, [])
426
+
427
+ const hasError = !!value.current && !pathIsValid && !pathValidationPending;
394
428
  const autoSuggestionInput = (
395
429
  <div id={id} className={`${eccgui}-autosuggestion`}>
396
430
  <div className={`${eccgui}-autosuggestion__inputfield ${BlueprintClassNames.INPUT_GROUP} ${BlueprintClassNames.FILL} ${hasError ? BlueprintClassNames.INTENT_DANGER : ""}`}>
@@ -404,7 +438,7 @@ const AutoSuggestion = ({
404
438
  content={(
405
439
  <AutoSuggestionList
406
440
  id={id+"__dropdown"}
407
- left={horizontalShift}
441
+ registerForHorizontalShift={subscribeToHorizontalShift}
408
442
  loading={suggestionsPending}
409
443
  options={suggestions}
410
444
  isOpen={!suggestionsPending && shouldShowDropdown}
@@ -419,16 +453,16 @@ const AutoSuggestion = ({
419
453
  setEditorInstance={setEditorInstance}
420
454
  onChange={handleChange}
421
455
  onCursorChange={handleCursorChange}
422
- initialValue={value}
456
+ initialValue={initialValue}
423
457
  onFocusChange={handleInputFocus}
424
458
  onKeyDown={handleInputEditorKeyPress}
425
459
  enableTab={useTabForCompletions}
426
460
  placeholder={placeholder}
427
- onSelection={setSelectedTextRanges}
461
+ onSelection={onSelection}
428
462
  showScrollBar={showScrollBar}
429
463
  />
430
464
  </ContextOverlay>
431
- {!!value && (
465
+ {!!value.current && (
432
466
  <span className={BlueprintClassNames.INPUT_ACTION}>
433
467
  <IconButton
434
468
  data-test-id={"value-path-clear-btn"}
@@ -24,13 +24,16 @@ export interface IDropdownProps extends Omit<React.HTMLAttributes<HTMLDivElement
24
24
  isOpen: boolean;
25
25
  // If the drop down should show a loading state
26
26
  loading?: boolean;
27
- left?: number;
27
+ // Register for changes in horizontal shift
28
+ registerForHorizontalShift?: (callback: HorizontalShiftCallbackFunction) => any
28
29
  // The item from the drop down that is active
29
30
  currentlyFocusedIndex: number;
30
31
  // Callback indicating what item should currently being highlighted, i.e. is either active or is hovered over
31
32
  itemToHighlight: (item: ISuggestionWithReplacementInfo | undefined) => any;
32
33
  }
33
34
 
35
+ type HorizontalShiftCallbackFunction = (shift: number) => any
36
+
34
37
  const ListItem = ({ item }: any, ref: any) => {
35
38
  const listItem = (
36
39
  <OverviewItem densityHigh={true}>
@@ -76,7 +79,7 @@ export const AutoSuggestionList = ({
76
79
  options,
77
80
  loading,
78
81
  onItemSelectionChange,
79
- left,
82
+ registerForHorizontalShift,
80
83
  currentlyFocusedIndex,
81
84
  itemToHighlight,
82
85
  style,
@@ -85,6 +88,7 @@ export const AutoSuggestionList = ({
85
88
  const [hoveredItem, setHoveredItem] = React.useState<
86
89
  ISuggestionWithReplacementInfo | undefined
87
90
  >(undefined);
91
+ const [left, setLeft] = React.useState(0)
88
92
  // Refs of list items
89
93
  const [refs] = React.useState<React.RefObject<Element>[]>([])
90
94
  const dropdownRef = React.useRef<HTMLDivElement>(null);
@@ -95,6 +99,15 @@ export const AutoSuggestionList = ({
95
99
  return refs[index];
96
100
  };
97
101
 
102
+ React.useEffect(() => {
103
+ if(registerForHorizontalShift) {
104
+ const callback = (shift: number) => {
105
+ setTimeout(() => setLeft(shift), 1)
106
+ }
107
+ registerForHorizontalShift(callback)
108
+ }
109
+ }, [registerForHorizontalShift])
110
+
98
111
  React.useEffect(() => {
99
112
  const listIndexNode = refs[currentlyFocusedIndex];
100
113
  if (dropdownRef?.current && listIndexNode?.current) {
@@ -1,10 +1,10 @@
1
1
  import "codemirror/addon/display/placeholder.js"
2
2
  import "codemirror/mode/sparql/sparql.js";
3
3
  import React from "react";
4
- import { Controlled as ControlledEditor } from "react-codemirror2";
5
- import { Classes as BlueprintClassNames } from "@blueprintjs/core";
4
+ import {UnControlled as UnControlledEditor} from "react-codemirror2";
5
+ import {Classes as BlueprintClassNames} from "@blueprintjs/core";
6
6
  import {Editor as CodeMirrorEditor} from "codemirror";
7
- import { CLASSPREFIX as eccgui } from "../../configuration/constants";
7
+ import {CLASSPREFIX as eccgui} from "../../configuration/constants";
8
8
 
9
9
  export interface IEditorProps {
10
10
  // Is called with the editor instance that allows access via the CodeMirror API
@@ -51,9 +51,9 @@ const SingleLineCodeEditor = ({
51
51
  }: IEditorProps) => {
52
52
  return (
53
53
  <div className={`${eccgui}-singlelinecodeeditor ${BlueprintClassNames.INPUT}`}>
54
- <ControlledEditor
55
- editorDidMount={(editor) => {
56
- editor.on("beforeChange", (_, change) => {
54
+ <UnControlledEditor
55
+ editorDidMount={(editor: any) => {
56
+ editor.on("beforeChange", (_: any, change: any) => {
57
57
  // Prevent the user from entering new-line characters, since this is supposed to be a one-line editor.
58
58
  const newText = change.text.join("").replace(/\n/g, "");
59
59
  //failing unexpectedly during undo and redo
@@ -89,10 +89,18 @@ const SingleLineCodeEditor = ({
89
89
  editor.getScrollInfo()
90
90
  );
91
91
  }}
92
- onBeforeChange={(_editor, _data, value) => {
93
- const trimmedValue = value.replace(/\n/g, "");
94
- onChange(trimmedValue);
92
+ onBeforeChange={(_editor, _data, value, next) => {
93
+ // Remove entered new lines
94
+ const trimmedValue = value.replace(/[\r\n]/g, "");
95
+ if (trimmedValue !== value) {
96
+ _editor.setValue(trimmedValue)
97
+ }
98
+ next()
95
99
  }}
100
+ onChange={(_editor, _data, value) => {
101
+ onChange(value);
102
+ }
103
+ }
96
104
  onKeyDown={(_, event) => onKeyDown(event)}
97
105
  />
98
106
  </div>
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import '@testing-library/jest-dom'
3
- import {fireEvent, render} from '@testing-library/react'
3
+ import {fireEvent, render, waitFor} from '@testing-library/react'
4
4
  import {CLASSPREFIX as eccgui} from "../../../configuration/constants";
5
5
  import {AutoSuggestionList, IDropdownProps} from '../AutoSuggestionList'
6
6
 
@@ -10,7 +10,6 @@ describe("Dropdown list", () => {
10
10
  props = {
11
11
  currentlyFocusedIndex: 0,
12
12
  loading: false,
13
- left:0,
14
13
  isOpen: false,
15
14
  options: [],
16
15
  itemToHighlight: () => {},
@@ -62,18 +61,21 @@ describe("Dropdown list", () => {
62
61
  expect(dropdownItems.length).toBe(2)
63
62
  })
64
63
 
65
- it("should move horizontally for every cursor movement", () => {
64
+ it("should move horizontally for every cursor movement", async () => {
65
+ const offset = 10
66
66
  props = {
67
67
  ...props,
68
68
  options: mockOptions,
69
69
  loading: false,
70
70
  isOpen: true,
71
- left: 10
71
+ registerForHorizontalShift: (callback => callback(offset)),
72
72
  }
73
73
  const {container} = render(<AutoSuggestionList {...props} />)
74
- const parentDiv:HTMLElement = container.querySelector(`.${eccgui}-autosuggestion__dropdown`)!!
75
- const leftOffset = Number(parentDiv.style.left.replace(/px$/,""));
76
- expect(leftOffset).toBe(props.left)
74
+ await waitFor(() => {
75
+ const parentDiv:HTMLElement = container.querySelector(`.${eccgui}-autosuggestion__dropdown`)!!
76
+ const leftOffset = Number(parentDiv.style.left.replace(/px$/,""));
77
+ expect(leftOffset).toBe(offset)
78
+ })
77
79
  })
78
80
 
79
81
  it("should have one active item at a time", () => {
@@ -128,6 +128,9 @@ export interface IAutoCompleteFieldProps<T extends any, UPDATE_VALUE extends any
128
128
  showNewItemOptionFirst?: boolean
129
129
  };
130
130
 
131
+ /** Dropdown is only rendered when the query has a value (input field is not empty). */
132
+ onlyDropdownWithQuery?: boolean;
133
+
131
134
  /** If true the input field will be disabled. */
132
135
  disabled?: boolean;
133
136
 
@@ -140,6 +143,12 @@ export interface IAutoCompleteFieldProps<T extends any, UPDATE_VALUE extends any
140
143
 
141
144
  /** If an error occurs during the auto-completion request, the error details will be prefixed with this string. */
142
145
  requestErrorPrefix?: string
146
+
147
+ /** Creates a backdrop when the popover is shown that captures outside clicks in order to close the popover.
148
+ * This is needed if other components on the same page are swallowing events, e.g. the react-flow canvas.
149
+ * hasBackDrop should then be set to true in these cases otherwise the popover won't close when clicking those other components.
150
+ **/
151
+ hasBackDrop?: boolean
143
152
  }
144
153
 
145
154
  AutoCompleteField.defaultProps = {
@@ -166,6 +175,7 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
166
175
  reset,
167
176
  noResultText,
168
177
  disabled,
178
+ onlyDropdownWithQuery = false,
169
179
  itemValueSelector,
170
180
  itemRenderer,
171
181
  onSearch,
@@ -177,6 +187,7 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
177
187
  resetQueryToValue,
178
188
  itemValueString,
179
189
  requestErrorPrefix = "",
190
+ hasBackDrop = false,
180
191
  ...otherProps
181
192
  } = props;
182
193
  const [selectedItem, setSelectedItem] = useState<T | undefined>(initialValue);
@@ -184,7 +195,8 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
184
195
  const [listLoading, setListLoading] = useState<boolean>(false);
185
196
 
186
197
  const [query, setQuery] = useState<string>("");
187
- const [hasFocus, setHasFocus] = useState<boolean>(false);
198
+ // If the input field has focus
199
+ const [inputHasFocus, setInputHasFocus] = useState<boolean>(false);
188
200
  const [highlightingEnabled, setHighlightingEnabled] = useState<boolean>(true);
189
201
  const [requestError, setRequestError] = useState<string | undefined>(undefined)
190
202
 
@@ -222,7 +234,7 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
222
234
  );
223
235
 
224
236
  useEffect(() => {
225
- if (!disabled && !readOnly && hasFocus) {
237
+ if (!disabled && !readOnly && inputHasFocus) {
226
238
  setListLoading(true);
227
239
  const timeout: number = window.setTimeout(async () => {
228
240
  fetchQueryResults(query);
@@ -235,18 +247,18 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
235
247
  return;
236
248
  },
237
249
  // eslint-disable-next-line react-hooks/exhaustive-deps
238
- [hasFocus, query]
250
+ [inputHasFocus, query]
239
251
  );
240
252
 
241
253
  const fieldWidthLimits = elementWidth(fieldRef);
242
254
 
243
255
  // We need to fire some actions when the auto-complete widget gets or loses focus
244
256
  const handleOnFocusIn = () => {
245
- setHasFocus(true);
257
+ setInputHasFocus(true);
246
258
  };
247
259
 
248
260
  const handleOnFocusOut = () => {
249
- setHasFocus(false);
261
+ setInputHasFocus(false);
250
262
  };
251
263
 
252
264
  // On popover close reset query to selected item
@@ -380,6 +392,8 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
380
392
  onClosed: onPopoverClose,
381
393
  ...otherProps.contextOverlayProps,
382
394
  ...preventOverlayOnReadonly,
395
+ // Needed to capture clicks outside of the popover, e.g. in order to close it.
396
+ hasBackdrop: hasBackDrop
383
397
  }
384
398
  if(selectedItem !== undefined) {
385
399
  // Makes sure that even when an empty string is selected, the placeholder won't be shown.
@@ -405,7 +419,11 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
405
419
  className={`${eccgui}-autocompletefield__input`}
406
420
  disabled={disabled}
407
421
  // Need to display error messages in list
408
- items={requestError ? [requestError as T] : filtered}
422
+ items={requestError ?
423
+ [requestError as T] :
424
+ filtered
425
+ }
426
+ initialContent={onlyDropdownWithQuery ? null : undefined}
409
427
  inputValueRenderer={selectedItem !== undefined ? itemValueRenderer : () => ""}
410
428
  itemRenderer={requestError ? requestErrorRenderer : optionRenderer}
411
429
  itemsEqual={areEqualItems}
@@ -17,7 +17,7 @@ export const createNewItemRendererFactory = (
17
17
  let textElement = itemTextRenderer(query, modifiers.styleWidth)
18
18
  if(typeof textElement === "string") {
19
19
  textElement = <OverflowText style={modifiers.styleWidth}>
20
- {`Create option '${query}'`}
20
+ {textElement.trim() !== "" ? textElement : `Create option '${query}'`}
21
21
  </OverflowText>
22
22
  }
23
23
  return (
@@ -1,5 +1,5 @@
1
1
  .#{$eccgui}-autocompletefield__options {
2
- .#{$ns}-select-popover {
2
+ .#{$ns}-popover2-content {
3
3
  .#{$ns}-menu {
4
4
  max-height: 45vh;
5
5
  overflow: auto;