@eccenca/gui-elements 23.0.0-rc.6 → 23.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +25 -19
  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/Menu/Menu.js +2 -2
  22. package/dist/cjs/components/Menu/Menu.js.map +1 -1
  23. package/dist/cjs/components/Menu/MenuDivider.js +2 -2
  24. package/dist/cjs/components/Menu/MenuDivider.js.map +1 -1
  25. package/dist/cjs/components/Menu/MenuItem.js +5 -3
  26. package/dist/cjs/components/Menu/MenuItem.js.map +1 -1
  27. package/dist/cjs/components/MultiSelect/MultiSelect.js +17 -4
  28. package/dist/cjs/components/MultiSelect/MultiSelect.js.map +1 -1
  29. package/dist/cjs/components/Pagination/Pagination.js.map +1 -1
  30. package/dist/cjs/components/RadioButton/RadioButton.js +1 -0
  31. package/dist/cjs/components/RadioButton/RadioButton.js.map +1 -1
  32. package/dist/cjs/components/Separation/Spacing.js +4 -1
  33. package/dist/cjs/components/Separation/Spacing.js.map +1 -1
  34. package/dist/cjs/components/Spinner/Spinner.js +3 -3
  35. package/dist/cjs/components/Spinner/Spinner.js.map +1 -1
  36. package/dist/cjs/components/Switch/Switch.js.map +1 -1
  37. package/dist/cjs/extensions/react-flow/edges/EdgeDefault.js.map +1 -1
  38. package/dist/esm/cmem/markdown/Markdown.js +13 -2
  39. package/dist/esm/cmem/markdown/Markdown.js.map +1 -1
  40. package/dist/esm/cmem/react-flow/ReactFlow/ReactFlow.js +0 -4
  41. package/dist/esm/cmem/react-flow/ReactFlow/ReactFlow.js.map +1 -1
  42. package/dist/esm/components/Accordion/Accordion.js +8 -3
  43. package/dist/esm/components/Accordion/Accordion.js.map +1 -1
  44. package/dist/esm/components/Accordion/AccordionItem.js +1 -1
  45. package/dist/esm/components/Accordion/AccordionItem.js.map +1 -1
  46. package/dist/esm/components/AutoSuggestion/AutoSuggestion.js +71 -45
  47. package/dist/esm/components/AutoSuggestion/AutoSuggestion.js.map +1 -1
  48. package/dist/esm/components/AutoSuggestion/AutoSuggestionList.js +11 -2
  49. package/dist/esm/components/AutoSuggestion/AutoSuggestionList.js.map +1 -1
  50. package/dist/esm/components/AutoSuggestion/SingleLineCodeEditor.js +11 -5
  51. package/dist/esm/components/AutoSuggestion/SingleLineCodeEditor.js.map +1 -1
  52. package/dist/esm/components/AutocompleteField/AutoCompleteField.js +19 -14
  53. package/dist/esm/components/AutocompleteField/AutoCompleteField.js.map +1 -1
  54. package/dist/esm/components/Button/Button.js.map +1 -1
  55. package/dist/esm/components/Checkbox/Checkbox.js +1 -0
  56. package/dist/esm/components/Checkbox/Checkbox.js.map +1 -1
  57. package/dist/esm/components/Form/FieldItem.js.map +1 -1
  58. package/dist/esm/components/Menu/Menu.js +3 -3
  59. package/dist/esm/components/Menu/Menu.js.map +1 -1
  60. package/dist/esm/components/Menu/MenuDivider.js +3 -3
  61. package/dist/esm/components/Menu/MenuDivider.js.map +1 -1
  62. package/dist/esm/components/Menu/MenuItem.js +5 -3
  63. package/dist/esm/components/Menu/MenuItem.js.map +1 -1
  64. package/dist/esm/components/MultiSelect/MultiSelect.js +22 -9
  65. package/dist/esm/components/MultiSelect/MultiSelect.js.map +1 -1
  66. package/dist/esm/components/Pagination/Pagination.js +1 -1
  67. package/dist/esm/components/Pagination/Pagination.js.map +1 -1
  68. package/dist/esm/components/RadioButton/RadioButton.js +2 -1
  69. package/dist/esm/components/RadioButton/RadioButton.js.map +1 -1
  70. package/dist/esm/components/Separation/Spacing.js +4 -1
  71. package/dist/esm/components/Separation/Spacing.js.map +1 -1
  72. package/dist/esm/components/Spinner/Spinner.js +3 -4
  73. package/dist/esm/components/Spinner/Spinner.js.map +1 -1
  74. package/dist/esm/components/Switch/Switch.js +1 -1
  75. package/dist/esm/components/Switch/Switch.js.map +1 -1
  76. package/dist/esm/extensions/react-flow/edges/EdgeDefault.js.map +1 -1
  77. package/dist/types/cmem/markdown/Markdown.d.ts +3 -2
  78. package/dist/types/cmem/react-flow/ReactFlow/ReactFlow.d.ts +1 -1
  79. package/dist/types/components/Accordion/Accordion.d.ts +12 -1
  80. package/dist/types/components/Accordion/AccordionItem.d.ts +21 -6
  81. package/dist/types/components/AutoSuggestion/AutoSuggestionList.d.ts +4 -2
  82. package/dist/types/components/AutocompleteField/AutoCompleteField.d.ts +5 -0
  83. package/dist/types/components/Button/Button.d.ts +1 -1
  84. package/dist/types/components/Checkbox/Checkbox.d.ts +4 -1
  85. package/dist/types/components/Form/FieldItem.d.ts +32 -2
  86. package/dist/types/components/Menu/Menu.d.ts +4 -1
  87. package/dist/types/components/Menu/MenuDivider.d.ts +4 -1
  88. package/dist/types/components/Menu/MenuItem.d.ts +5 -2
  89. package/dist/types/components/Pagination/Pagination.d.ts +9 -5
  90. package/dist/types/components/RadioButton/RadioButton.d.ts +4 -1
  91. package/dist/types/components/Separation/Spacing.d.ts +17 -3
  92. package/dist/types/components/Spinner/Spinner.d.ts +38 -5
  93. package/dist/types/components/Switch/Switch.d.ts +6 -3
  94. package/dist/types/extensions/react-flow/edges/EdgeDefault.d.ts +15 -0
  95. package/package.json +4 -6
  96. package/src/cmem/markdown/Markdown.tsx +5 -4
  97. package/src/cmem/react-flow/ReactFlow/ReactFlow.stories.tsx +15 -86
  98. package/src/cmem/react-flow/ReactFlow/ReactFlow.tsx +1 -5
  99. package/src/components/Accordion/Accordion.tsx +33 -3
  100. package/src/components/Accordion/AccordionItem.tsx +23 -33
  101. package/src/components/Accordion/Stories/Accordion.stories.tsx +33 -0
  102. package/src/components/Accordion/Stories/AccordionItem.stories.tsx +27 -0
  103. package/src/components/Accordion/accordion.scss +20 -1
  104. package/src/components/AutoSuggestion/AutoSuggestion.tsx +73 -39
  105. package/src/components/AutoSuggestion/AutoSuggestionList.tsx +15 -2
  106. package/src/components/AutoSuggestion/SingleLineCodeEditor.tsx +17 -9
  107. package/src/components/AutoSuggestion/tests/AutoSuggestionList.test.tsx +9 -7
  108. package/src/components/AutocompleteField/AutoCompleteField.tsx +19 -6
  109. package/src/components/AutocompleteField/autoCompleteFieldUtils.tsx +1 -1
  110. package/src/components/AutocompleteField/stories/AutoCompleteField.stories.tsx +1 -1
  111. package/src/components/Button/Button.stories.tsx +1 -107
  112. package/src/components/Button/Button.tsx +2 -2
  113. package/src/components/Card/stories/Card.stories.tsx +2 -21
  114. package/src/components/Checkbox/Checkbox.tsx +9 -2
  115. package/src/components/Checkbox/Stories/Checkbox.stories.tsx +30 -0
  116. package/src/components/Checkbox/checkbox.scss +4 -0
  117. package/src/components/ContextOverlay/ContextMenu.stories.tsx +2 -2
  118. package/src/components/Dialog/stories/Modal.stories.tsx +1 -61
  119. package/src/components/Dialog/stories/SimpleDialog.stories.tsx +1 -2
  120. package/src/components/Form/FieldItem.tsx +32 -2
  121. package/src/components/Form/Stories/FieldItem.stories.tsx +27 -0
  122. package/src/components/Form/Stories/FieldItemRow.stories.tsx +31 -0
  123. package/src/components/Label/Label.stories.tsx +1 -1
  124. package/src/components/Menu/Menu.tsx +21 -3
  125. package/src/components/Menu/MenuDivider.tsx +20 -3
  126. package/src/components/Menu/MenuItem.tsx +13 -10
  127. package/src/components/Menu/Stories/Menu.stories.tsx +31 -0
  128. package/src/components/Menu/Stories/MenuItem.stories.tsx +25 -0
  129. package/src/components/MultiSelect/MultiSelect.stories.tsx +2 -21
  130. package/src/components/MultiSelect/MultiSelect.tsx +22 -5
  131. package/src/components/Notification/Notification.stories.tsx +1 -29
  132. package/src/components/Pagination/Pagination.tsx +13 -6
  133. package/src/components/Pagination/Stories/Pagination.stories.tsx +32 -0
  134. package/src/components/Pagination/pagination.scss +2 -2
  135. package/src/components/ProgressBar/Stories/ProgressBar.stories.tsx +25 -0
  136. package/src/components/RadioButton/RadioButton.tsx +9 -2
  137. package/src/components/RadioButton/Stories/RadioButton.stories.tsx +30 -0
  138. package/src/components/Select/Select.stories.tsx +1 -1
  139. package/src/components/Separation/Spacing.tsx +17 -4
  140. package/src/components/Separation/Stories/Spacing.stories.tsx +25 -0
  141. package/src/components/Spinner/Spinner.tsx +39 -7
  142. package/src/components/Spinner/Stories/spinner.stories.tsx +26 -0
  143. package/src/components/Switch/Stories/Switch.stories.tsx +27 -0
  144. package/src/components/Switch/Switch.tsx +9 -3
  145. package/src/components/Tabs/stories/Tab.stories.tsx +1 -32
  146. package/src/components/Tabs/stories/Tabs.stories.tsx +1 -39
  147. package/src/components/Tag/Tag.stories.tsx +1 -9
  148. package/src/components/TextField/stories/TextArea.stories.tsx +10 -25
  149. package/src/components/TextField/stories/TextField.stories.tsx +2 -73
  150. package/src/extensions/react-flow/edges/EdgeDefault.tsx +19 -0
  151. package/src/extensions/react-flow/edges/stories/EdgeDefault.stories.tsx +121 -0
  152. package/src/extensions/react-flow/nodes/stories/NodeContent.stories.tsx +1 -1
  153. package/dist/cjs/cmem/react-flow/configuration/customLabel.js +0 -13
  154. package/dist/cjs/cmem/react-flow/configuration/customLabel.js.map +0 -1
  155. package/dist/cjs/cmem/react-flow/configuration/inverseEdge.js +0 -13
  156. package/dist/cjs/cmem/react-flow/configuration/inverseEdge.js.map +0 -1
  157. package/dist/esm/cmem/react-flow/configuration/customLabel.js +0 -10
  158. package/dist/esm/cmem/react-flow/configuration/customLabel.js.map +0 -1
  159. package/dist/esm/cmem/react-flow/configuration/inverseEdge.js +0 -10
  160. package/dist/esm/cmem/react-flow/configuration/inverseEdge.js.map +0 -1
  161. package/dist/types/cmem/react-flow/configuration/customLabel.d.ts +0 -8
  162. package/dist/types/cmem/react-flow/configuration/inverseEdge.d.ts +0 -8
  163. package/src/cmem/react-flow/configuration/customLabel.ts +0 -11
  164. package/src/cmem/react-flow/configuration/inverseEdge.ts +0 -11
@@ -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", () => {
@@ -143,6 +143,12 @@ export interface IAutoCompleteFieldProps<T extends any, UPDATE_VALUE extends any
143
143
 
144
144
  /** If an error occurs during the auto-completion request, the error details will be prefixed with this string. */
145
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
146
152
  }
147
153
 
148
154
  AutoCompleteField.defaultProps = {
@@ -181,6 +187,7 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
181
187
  resetQueryToValue,
182
188
  itemValueString,
183
189
  requestErrorPrefix = "",
190
+ hasBackDrop = false,
184
191
  ...otherProps
185
192
  } = props;
186
193
  const [selectedItem, setSelectedItem] = useState<T | undefined>(initialValue);
@@ -188,7 +195,8 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
188
195
  const [listLoading, setListLoading] = useState<boolean>(false);
189
196
 
190
197
  const [query, setQuery] = useState<string>("");
191
- const [hasFocus, setHasFocus] = useState<boolean>(false);
198
+ // If the input field has focus
199
+ const [inputHasFocus, setInputHasFocus] = useState<boolean>(false);
192
200
  const [highlightingEnabled, setHighlightingEnabled] = useState<boolean>(true);
193
201
  const [requestError, setRequestError] = useState<string | undefined>(undefined)
194
202
 
@@ -226,7 +234,7 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
226
234
  );
227
235
 
228
236
  useEffect(() => {
229
- if (!disabled && !readOnly && hasFocus) {
237
+ if (!disabled && !readOnly && inputHasFocus) {
230
238
  setListLoading(true);
231
239
  const timeout: number = window.setTimeout(async () => {
232
240
  fetchQueryResults(query);
@@ -239,18 +247,18 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
239
247
  return;
240
248
  },
241
249
  // eslint-disable-next-line react-hooks/exhaustive-deps
242
- [hasFocus, query]
250
+ [inputHasFocus, query]
243
251
  );
244
252
 
245
253
  const fieldWidthLimits = elementWidth(fieldRef);
246
254
 
247
255
  // We need to fire some actions when the auto-complete widget gets or loses focus
248
256
  const handleOnFocusIn = () => {
249
- setHasFocus(true);
257
+ setInputHasFocus(true);
250
258
  };
251
259
 
252
260
  const handleOnFocusOut = () => {
253
- setHasFocus(false);
261
+ setInputHasFocus(false);
254
262
  };
255
263
 
256
264
  // On popover close reset query to selected item
@@ -384,6 +392,8 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
384
392
  onClosed: onPopoverClose,
385
393
  ...otherProps.contextOverlayProps,
386
394
  ...preventOverlayOnReadonly,
395
+ // Needed to capture clicks outside of the popover, e.g. in order to close it.
396
+ hasBackdrop: hasBackDrop
387
397
  }
388
398
  if(selectedItem !== undefined) {
389
399
  // Makes sure that even when an empty string is selected, the placeholder won't be shown.
@@ -409,7 +419,10 @@ export function AutoCompleteField<T extends any, UPDATE_VALUE extends any>(props
409
419
  className={`${eccgui}-autocompletefield__input`}
410
420
  disabled={disabled}
411
421
  // Need to display error messages in list
412
- items={requestError ? [requestError as T] : filtered}
422
+ items={requestError ?
423
+ [requestError as T] :
424
+ filtered
425
+ }
413
426
  initialContent={onlyDropdownWithQuery ? null : undefined}
414
427
  inputValueRenderer={selectedItem !== undefined ? itemValueRenderer : () => ""}
415
428
  itemRenderer={requestError ? requestErrorRenderer : optionRenderer}
@@ -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 (
@@ -6,7 +6,7 @@ import {extractSearchWords, matchesAllWords} from "../../Typography/Highlighter"
6
6
  import {createNewItemRendererFactory} from "../autoCompleteFieldUtils";
7
7
 
8
8
  const AutoCompleteFieldStory: ComponentMeta<typeof AutoCompleteField> = {
9
- title: "Components/AutoCompleteField",
9
+ title: "Forms/AutoCompleteField",
10
10
  component: AutoCompleteField,
11
11
  argTypes: {
12
12
  },
@@ -1,125 +1,19 @@
1
1
  import React from "react";
2
2
  import { ComponentStory, ComponentMeta } from "@storybook/react";
3
-
4
- import Button from "./Button";
5
- import Icon from "../Icon/Icon";
6
- import Spacing from "../Separation/Spacing";
3
+ import { Button, Icon, Spacing} from "../../../index";
7
4
 
8
5
  export default {
9
6
  title: "Components/Button",
10
7
  component: Button,
11
8
  argTypes: {
12
- active: {
13
- description: "Button will display in an active state.",
14
- control: "boolean",
15
- table: {
16
- defaultValue: { summary: false },
17
- type: { summary: "boolean" },
18
- }
19
- },
20
- className: {
21
- description: "A space-delimited list of class names.",
22
- control: "text",
23
- table: {
24
- defaultValue: { summary: undefined },
25
- type: { summary: "string" },
26
- }
27
- },
28
- disabled: {
29
- description: "Whether this action is non-interactive and the button is not usable.",
30
- control: "boolean",
31
- table: {
32
- defaultValue: { summary: false },
33
- type: { summary: "boolean" },
34
- }
35
- },
36
- minimal: {
37
- description: "Whether this button should use minimal styles.",
38
- control: "boolean",
39
- table: {
40
- defaultValue: { summary: false },
41
- type: { summary: "boolean" },
42
- }
43
- },
44
- outlined: {
45
- description: "Whether this button should use outlined styles.",
46
- control: "boolean",
47
- table: {
48
- defaultValue: { summary: false },
49
- type: { summary: "boolean" },
50
- }
51
- },
52
- text: {
53
- description: "Action text. Can be any single React renderable. Can be used as alternative to include `children` elements.",
54
- control: "text",
55
- table: {
56
- defaultValue: { summary: undefined },
57
- type: { summary: "React.ReactNode" },
58
- }
59
- },
60
- type: {
61
- description: "HTML type attribute of button. Note that this prop has no effect on buttons with `href` property set.",
62
- control: "text",
63
- table: {
64
- defaultValue: { summary: "button" },
65
- type: { summary: "button | reset | submit" },
66
- }
67
- },
68
- loading: {
69
- description: "Button is displayed disabled and contains a spinner instead of its contents.",
70
- control: "boolean",
71
- table: {
72
- defaultValue: { summary: false },
73
- type: { summary: "boolean" },
74
- }
75
- },
76
- fill: {
77
- description: "Whether this button should expand to fill its container.",
78
- control: "boolean",
79
- table: {
80
- defaultValue: { summary: false },
81
- type: { summary: "boolean" },
82
- }
83
- },
84
- large: {
85
- description: "Whether this button should use large styles.",
86
- control: "boolean",
87
- table: {
88
- defaultValue: { summary: false },
89
- type: { summary: "boolean" },
90
- }
91
- },
92
- small: {
93
- description: "Whether this button should use small styles",
94
- control: "boolean",
95
- table: {
96
- defaultValue: { summary: false },
97
- type: { summary: "boolean" },
98
- }
99
- },
100
9
  icon: {
101
- description: "Left aligned icon, can be a canonical icon name or an `Icon` element.",
102
10
  control: { disable: true, },
103
- table: {
104
- defaultValue: { summary: undefined },
105
- type: { summary: "Icon | string" },
106
- }
107
11
  },
108
12
  rightIcon: {
109
- description: "Right aligned icon, can be a canonical icon name or an `Icon` element.",
110
13
  control: { disable: true, },
111
- table: {
112
- defaultValue: { summary: undefined },
113
- type: { summary: "Icon | string" },
114
- }
115
14
  },
116
15
  onClick: {
117
- description: "Click event handler.",
118
16
  action: "clicked",
119
- table: {
120
- defaultValue: { summary: undefined },
121
- type: { summary: "(event: MouseEvent<HTMLElement>) => void" },
122
- }
123
17
  },
124
18
  },
125
19
  } as ComponentMeta<typeof Button>;
@@ -13,7 +13,7 @@ import {ValidIconName} from "../Icon/canonicalIconNames";
13
13
 
14
14
  export type AnchorOrButtonProps = Omit<BlueprintButtonProps, "elementRef" | "icon" | "rightIcon"> | Omit<BlueprintAnchorButtonProps, "elementRef" | "icon" | "rightIcon">;
15
15
 
16
- export interface ButtonProps extends React.HTMLAttributes<HTMLElement> {
16
+ export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement | HTMLAnchorElement> {
17
17
  /**
18
18
  * Always use this when the button triggers an affirmative action, e.g. confirm a process.
19
19
  * The button is displayed with primary color scheme.
@@ -100,7 +100,7 @@ function Button({
100
100
  break;
101
101
  }
102
102
 
103
- let ButtonType = restProps.href ? BlueprintAnchorButton : BlueprintButton;
103
+ let ButtonType: any = restProps.href ? BlueprintAnchorButton : BlueprintButton;
104
104
 
105
105
  const button = (
106
106
  <ButtonType
@@ -8,8 +8,8 @@ import {
8
8
  CardTitle,
9
9
  CardOptions,
10
10
  CardActions,
11
- } from "../";
12
- import Divider from "../../Separation/Divider";
11
+ Divider,
12
+ } from "../../../../index";
13
13
 
14
14
  import { Default as CardHeaderExample } from "./CardHeader.stories";
15
15
  import { Default as CardContentExample } from "./CardContent.stories";
@@ -29,28 +29,9 @@ export default {
29
29
  argTypes: {
30
30
  elevation: {
31
31
  control: { type: "number", min: 0, max: 4 },
32
- description: "Intensity of the drop shadow beneath the card. At elevation 0, no drop shadow is applied.",
33
- table: {
34
- type: { summary: "number: 0 to 5" },
35
- defaultValue: { summary: 1 },
36
- }
37
- },
38
- interactive: {
39
- control: "boolean",
40
- description: "Card responds to user interactions, hovering over the card will increase the card's elevation and change the mouse cursor to a pointer. Is set automatically to `true` if an `onClick` handler is available.",
41
- table: {
42
- type: { summary: "boolean" },
43
- defaultValue: { summary: false },
44
- }
45
32
  },
46
33
  onClick: {
47
34
  ...helpersArgTypes.handlerOnClick,
48
- description: "Callback invoked when the card is clicked.",
49
- defaultValue: null,
50
- table: {
51
- type: { summary: "(e: MouseEvent<HTMLDivElement>) => void" },
52
- defaultValue: { summary: undefined },
53
- }
54
35
  },
55
36
  children: {
56
37
  control: "none",