@eccenca/gui-elements 24.1.0-rc.6 → 24.2.0-featuresupportprojectvariableautocompletioncmem5572.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 (126) hide show
  1. package/CHANGELOG.md +78 -18
  2. package/README.md +10 -7
  3. package/dist/cjs/cmem/markdown/Markdown.js +13 -11
  4. package/dist/cjs/cmem/markdown/Markdown.js.map +1 -1
  5. package/dist/cjs/cmem/markdown/highlightSearchWords.js +6 -1
  6. package/dist/cjs/cmem/markdown/highlightSearchWords.js.map +1 -1
  7. package/dist/cjs/common/Intent/index.js +4 -11
  8. package/dist/cjs/common/Intent/index.js.map +1 -1
  9. package/dist/cjs/components/AutoSuggestion/AutoSuggestion.js +37 -26
  10. package/dist/cjs/components/AutoSuggestion/AutoSuggestion.js.map +1 -1
  11. package/dist/cjs/components/AutoSuggestion/ExtendedCodeEditor.js +2 -2
  12. package/dist/cjs/components/AutoSuggestion/ExtendedCodeEditor.js.map +1 -1
  13. package/dist/cjs/components/Button/Button.js +2 -2
  14. package/dist/cjs/components/Button/Button.js.map +1 -1
  15. package/dist/cjs/components/ContextOverlay/ContextMenu.js +3 -2
  16. package/dist/cjs/components/ContextOverlay/ContextMenu.js.map +1 -1
  17. package/dist/cjs/components/ContextOverlay/ContextOverlay.js +46 -2
  18. package/dist/cjs/components/ContextOverlay/ContextOverlay.js.map +1 -1
  19. package/dist/cjs/components/Form/FieldItem.js +3 -2
  20. package/dist/cjs/components/Form/FieldItem.js.map +1 -1
  21. package/dist/cjs/components/Form/FieldSet.js +3 -2
  22. package/dist/cjs/components/Form/FieldSet.js.map +1 -1
  23. package/dist/cjs/components/Icon/canonicalIconNames.js +1 -0
  24. package/dist/cjs/components/Icon/canonicalIconNames.js.map +1 -1
  25. package/dist/cjs/components/MultiSelect/MultiSelect.js +18 -14
  26. package/dist/cjs/components/MultiSelect/MultiSelect.js.map +1 -1
  27. package/dist/cjs/components/Notification/Notification.js +7 -4
  28. package/dist/cjs/components/Notification/Notification.js.map +1 -1
  29. package/dist/cjs/components/OverviewItem/OverviewItemActions.js +9 -2
  30. package/dist/cjs/components/OverviewItem/OverviewItemActions.js.map +1 -1
  31. package/dist/cjs/components/Spinner/Spinner.js +7 -5
  32. package/dist/cjs/components/Spinner/Spinner.js.map +1 -1
  33. package/dist/cjs/components/Tooltip/Tooltip.js +75 -7
  34. package/dist/cjs/components/Tooltip/Tooltip.js.map +1 -1
  35. package/dist/cjs/extensions/react-flow/nodes/NodeContent.js +17 -13
  36. package/dist/cjs/extensions/react-flow/nodes/NodeContent.js.map +1 -1
  37. package/dist/esm/cmem/markdown/Markdown.js +13 -11
  38. package/dist/esm/cmem/markdown/Markdown.js.map +1 -1
  39. package/dist/esm/cmem/markdown/highlightSearchWords.js +6 -1
  40. package/dist/esm/cmem/markdown/highlightSearchWords.js.map +1 -1
  41. package/dist/esm/common/Intent/index.js +14 -10
  42. package/dist/esm/common/Intent/index.js.map +1 -1
  43. package/dist/esm/components/AutoSuggestion/AutoSuggestion.js +42 -33
  44. package/dist/esm/components/AutoSuggestion/AutoSuggestion.js.map +1 -1
  45. package/dist/esm/components/AutoSuggestion/ExtendedCodeEditor.js +2 -2
  46. package/dist/esm/components/AutoSuggestion/ExtendedCodeEditor.js.map +1 -1
  47. package/dist/esm/components/Button/Button.js +2 -2
  48. package/dist/esm/components/Button/Button.js.map +1 -1
  49. package/dist/esm/components/ContextOverlay/ContextMenu.js +3 -2
  50. package/dist/esm/components/ContextOverlay/ContextMenu.js.map +1 -1
  51. package/dist/esm/components/ContextOverlay/ContextOverlay.js +63 -3
  52. package/dist/esm/components/ContextOverlay/ContextOverlay.js.map +1 -1
  53. package/dist/esm/components/Form/FieldItem.js +3 -2
  54. package/dist/esm/components/Form/FieldItem.js.map +1 -1
  55. package/dist/esm/components/Form/FieldSet.js +3 -2
  56. package/dist/esm/components/Form/FieldSet.js.map +1 -1
  57. package/dist/esm/components/Icon/canonicalIconNames.js +1 -0
  58. package/dist/esm/components/Icon/canonicalIconNames.js.map +1 -1
  59. package/dist/esm/components/MultiSelect/MultiSelect.js +18 -14
  60. package/dist/esm/components/MultiSelect/MultiSelect.js.map +1 -1
  61. package/dist/esm/components/Notification/Notification.js +7 -4
  62. package/dist/esm/components/Notification/Notification.js.map +1 -1
  63. package/dist/esm/components/OverviewItem/OverviewItemActions.js +25 -2
  64. package/dist/esm/components/OverviewItem/OverviewItemActions.js.map +1 -1
  65. package/dist/esm/components/Spinner/Spinner.js +9 -5
  66. package/dist/esm/components/Spinner/Spinner.js.map +1 -1
  67. package/dist/esm/components/Tooltip/Tooltip.js +92 -8
  68. package/dist/esm/components/Tooltip/Tooltip.js.map +1 -1
  69. package/dist/esm/extensions/react-flow/nodes/NodeContent.js +17 -13
  70. package/dist/esm/extensions/react-flow/nodes/NodeContent.js.map +1 -1
  71. package/dist/types/cmem/markdown/Markdown.d.ts +8 -1
  72. package/dist/types/common/Intent/index.d.ts +10 -1
  73. package/dist/types/components/AutoSuggestion/AutoSuggestion.d.ts +5 -1
  74. package/dist/types/components/AutoSuggestion/ExtendedCodeEditor.d.ts +5 -1
  75. package/dist/types/components/Button/Button.d.ts +5 -1
  76. package/dist/types/components/ContextOverlay/ContextMenu.d.ts +9 -2
  77. package/dist/types/components/ContextOverlay/ContextOverlay.d.ts +6 -1
  78. package/dist/types/components/Form/FieldItem.d.ts +10 -1
  79. package/dist/types/components/Form/FieldSet.d.ts +10 -1
  80. package/dist/types/components/Icon/canonicalIconNames.d.ts +1 -0
  81. package/dist/types/components/MultiSelect/MultiSelect.d.ts +12 -4
  82. package/dist/types/components/Notification/Notification.d.ts +10 -1
  83. package/dist/types/components/OverviewItem/OverviewItemActions.d.ts +13 -1
  84. package/dist/types/components/ProgressBar/ProgressBar.d.ts +2 -2
  85. package/dist/types/components/Spinner/Spinner.d.ts +8 -3
  86. package/dist/types/components/Structure/TitleSubsection.d.ts +7 -0
  87. package/dist/types/components/Table/TableContainer.d.ts +2 -2
  88. package/dist/types/components/Table/TableExpandRow.d.ts +1 -1
  89. package/dist/types/components/Table/index.d.ts +1 -0
  90. package/dist/types/components/Tabs/Tab.d.ts +14 -0
  91. package/dist/types/components/Tooltip/Tooltip.d.ts +9 -1
  92. package/package.json +47 -48
  93. package/src/cmem/markdown/Markdown.tsx +25 -14
  94. package/src/cmem/markdown/highlightSearchWords.test.ts +8 -2
  95. package/src/cmem/markdown/highlightSearchWords.ts +6 -1
  96. package/src/common/Intent/index.ts +6 -6
  97. package/src/components/AutoSuggestion/AutoSuggestion.tsx +50 -32
  98. package/src/components/AutoSuggestion/ExtendedCodeEditor.tsx +8 -0
  99. package/src/components/Button/Button.stories.tsx +10 -6
  100. package/src/components/Button/Button.tsx +7 -2
  101. package/src/components/ContextOverlay/ContextMenu.stories.tsx +1 -1
  102. package/src/components/ContextOverlay/ContextMenu.tsx +26 -13
  103. package/src/components/ContextOverlay/ContextOverlay.tsx +83 -5
  104. package/src/components/Form/FieldItem.tsx +14 -3
  105. package/src/components/Form/FieldSet.tsx +13 -2
  106. package/src/components/Form/Stories/FieldItem.stories.tsx +4 -0
  107. package/src/components/Form/Stories/FieldSet.stories.tsx +4 -0
  108. package/src/components/Icon/canonicalIconNames.tsx +1 -0
  109. package/src/components/MultiSelect/MultiSelect.tsx +27 -15
  110. package/src/components/MultiSuggestField/MultiSuggestField.stories.tsx +6 -0
  111. package/src/components/Notification/Notification.stories.tsx +4 -0
  112. package/src/components/Notification/Notification.tsx +17 -4
  113. package/src/components/OverviewItem/OverviewItemActions.tsx +24 -1
  114. package/src/components/OverviewItem/stories/OverviewItemList.stories.tsx +2 -7
  115. package/src/components/OverviewItem/stories/OverviewItemListPerformance.tsx +174 -0
  116. package/src/components/OverviewItem/stories/OverviewItemPerformance.stories.tsx +19 -0
  117. package/src/components/Spinner/Spinner.tsx +13 -5
  118. package/src/components/Spinner/Stories/spinner.stories.tsx +6 -1
  119. package/src/components/Table/TableContainer.tsx +2 -2
  120. package/src/components/Table/TableExpandRow.tsx +1 -1
  121. package/src/components/Table/index.tsx +1 -0
  122. package/src/components/Tooltip/Tooltip.stories.tsx +3 -2
  123. package/src/components/Tooltip/Tooltip.tsx +121 -10
  124. package/src/extensions/react-flow/nodes/NodeContent.tsx +18 -14
  125. package/src/extensions/react-flow/nodes/_nodes.scss +1 -1
  126. package/src/extensions/react-flow/nodes/stories/NodeContent.stories.tsx +45 -9
@@ -1,15 +1,21 @@
1
1
  import React from "react";
2
2
  import ReactMarkdown from "react-markdown";
3
- import { PluggableList } from "react-markdown/lib/react-markdown";
3
+ /**
4
+ * Recreate the old type to provide support until next major
5
+ */
6
+ import { PluggableList as PluggableListDeprecated } from "react-markdown-deprecated/lib/react-markdown";
4
7
  import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
5
8
  // @ts-ignore: No declaration file for module (TODO: should be @ts-expect-error but GUI elements is used inside project with `noImplicitAny=false`)
6
9
  import remarkTypograf from "@mavrin/remark-typograf";
10
+ import rehypeExternalLinks from "rehype-external-links";
7
11
  import rehypeRaw from "rehype-raw";
8
12
  import { remarkDefinitionList } from "remark-definition-list";
9
13
  import remarkGfm from "remark-gfm";
14
+ import { PluggableList as PluggableListUnified } from "unified";
10
15
 
11
16
  import { CLASSPREFIX as eccgui } from "../../configuration/constants";
12
17
  import { HtmlContentBlock, HtmlContentBlockProps, TestableComponent } from "../../index";
18
+ type PluggableList = PluggableListUnified | PluggableListDeprecated;
13
19
 
14
20
  export interface MarkdownProps extends TestableComponent {
15
21
  children: string;
@@ -35,6 +41,7 @@ export interface MarkdownProps extends TestableComponent {
35
41
  /**
36
42
  * Additional reHype plugins to execute.
37
43
  * @see https://github.com/remarkjs/react-markdown#architecture
44
+ * @deprecated (v25) this property won't support `PluggableList` from "react-markdown/lib/react-markdown" with the next major version, only the one from `unified` will be supported then.
38
45
  */
39
46
  reHypePlugins?: PluggableList;
40
47
  /**
@@ -54,9 +61,9 @@ const configDefault = {
54
61
  @see https://github.com/remarkjs/react-markdown#api
55
62
  */
56
63
  // @see https://github.com/remarkjs/remark/blob/main/doc/plugins.md#list-of-plugins
57
- remarkPlugins: [remarkGfm, remarkTypograf, remarkDefinitionList] as PluggableList,
64
+ remarkPlugins: [remarkGfm, remarkTypograf, remarkDefinitionList] as PluggableListUnified,
58
65
  // @see https://github.com/rehypejs/rehype/blob/main/doc/plugins.md#list-of-plugins
59
- rehypePlugins: [] as PluggableList,
66
+ rehypePlugins: [] as PluggableListUnified,
60
67
  allowedElements: [
61
68
  // default markdown
62
69
  "a",
@@ -110,6 +117,13 @@ export const Markdown = ({
110
117
  htmlContentBlockProps,
111
118
  ...otherProps
112
119
  }: MarkdownProps) => {
120
+ const configHtmlExternalLinks = {
121
+ rel: ["nofollow"],
122
+ target: linkTargetName,
123
+ };
124
+
125
+ configDefault.rehypePlugins = configDefault.rehypePlugins.concat([[rehypeExternalLinks, configHtmlExternalLinks]]);
126
+
113
127
  const configHtml = allowHtml
114
128
  ? {
115
129
  rehypePlugins: [...configDefault.rehypePlugins].concat([rehypeRaw]),
@@ -132,14 +146,10 @@ export const Markdown = ({
132
146
  ...configDefault,
133
147
  ...configHtml,
134
148
  ...configTextOnly,
135
- linkTarget: linkTargetName
136
- ? (href: string, _children: any, _title: string) => {
137
- const linkTarget = href.charAt(0) !== "#" ? linkTargetName : "";
138
- return linkTarget as React.HTMLAttributeAnchorTarget;
139
- }
140
- : undefined,
141
149
  components: {
150
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
142
151
  code(props: any) {
152
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
143
153
  const { children, className, node, inline, ...rest } = props;
144
154
  const match = /language-(\w+)/.exec(className || "");
145
155
  return match ? (
@@ -159,14 +169,15 @@ export const Markdown = ({
159
169
  );
160
170
  },
161
171
  },
172
+ allowedElements,
162
173
  };
163
- allowedElements && (reactMarkdownProperties.allowedElements = allowedElements);
164
- reHypePlugins &&
165
- reHypePlugins.forEach(
166
- (plugin) => (reactMarkdownProperties.rehypePlugins = [...reactMarkdownProperties.rehypePlugins, plugin])
174
+
175
+ if (reHypePlugins) {
176
+ reactMarkdownProperties.rehypePlugins = reactMarkdownProperties.rehypePlugins.concat(
177
+ reHypePlugins as PluggableListUnified
167
178
  );
179
+ }
168
180
 
169
- // @ts-ignore because against the lib spec it does not allow a function for linkTarget.
170
181
  const markdownDisplay = <ReactMarkdown {...reactMarkdownProperties} />;
171
182
  return inheritBlock && !(otherProps["data-test-id"] || htmlContentBlockProps) ? (
172
183
  markdownDisplay
@@ -9,7 +9,12 @@ describe("Highlight search words reHype plugin", () => {
9
9
  const highlightSearchWordsPlugin = markdownUtils.highlightSearchWordsPluginFactory(searchQuery);
10
10
  const highlightSearchWordTransformer = highlightSearchWordsPlugin();
11
11
  const textNode = (text: string): Text => ({ type: "text", value: text });
12
- const markNode = (text: string): Element => ({ type: "element", tagName: "mark", children: [textNode(text)] });
12
+ const markNode = (text: string): Element => ({
13
+ type: "element",
14
+ tagName: "mark",
15
+ properties: {},
16
+ children: [textNode(text)],
17
+ });
13
18
  const result = highlightSearchWordTransformer(
14
19
  {
15
20
  type: "root",
@@ -17,12 +22,13 @@ describe("Highlight search words reHype plugin", () => {
17
22
  {
18
23
  type: "element",
19
24
  tagName: "p",
25
+ properties: {},
20
26
  children: [textNode("Text with abc query words xyz.")],
21
27
  },
22
28
  ],
23
29
  },
24
30
  new VFile(),
25
- // eslint-disable-next-line @typescript-eslint/no-empty-function
31
+
26
32
  () => {}
27
33
  );
28
34
  const rootChildren = (result as Root).children;
@@ -25,7 +25,12 @@ const highlightSearchWordsPluginFactory = (searchQuery: string | undefined) => {
25
25
  let matchArray = multiWordRegex.exec(text);
26
26
  while (matchArray !== null) {
27
27
  result.push(createTextNode(text.slice(offset, matchArray.index)));
28
- result.push({ type: "element", tagName: "mark", children: [createTextNode(matchArray[0])] });
28
+ result.push({
29
+ type: "element",
30
+ tagName: "mark",
31
+ properties: {},
32
+ children: [createTextNode(matchArray[0])],
33
+ });
29
34
  offset = multiWordRegex.lastIndex;
30
35
  matchArray = multiWordRegex.exec(text);
31
36
  }
@@ -1,16 +1,16 @@
1
1
  import { CLASSPREFIX as eccgui } from "../../configuration/constants";
2
+ import { Intent as BlueprintIntent } from "@blueprintjs/core";
2
3
 
3
- export type IntentTypes = "none" | "neutral" | "primary" | "accent" | "info" | "success" | "warning" | "danger";
4
+ export type IntentBlueprint = BlueprintIntent;
5
+ export const DefinitionsBlueprint = BlueprintIntent;
6
+
7
+ export type IntentTypes = IntentBlueprint | "neutral" | "accent" | "info";
4
8
 
5
9
  export const Definitions: { [key: string]: IntentTypes } = {
6
- PRIMARY: "primary",
10
+ ...DefinitionsBlueprint,
7
11
  ACCENT: "accent",
8
12
  NEUTRAL: "neutral",
9
- NONE: "none",
10
- SUCCESS: "success",
11
13
  INFO: "info",
12
- WARNING: "warning",
13
- DANGER: "danger",
14
14
  };
15
15
 
16
16
  export const intentClassName = (intent: IntentTypes) => {
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  import React, { useEffect, useMemo, useState } from "react";
2
3
  import { Classes as BlueprintClassNames } from "@blueprintjs/core";
3
4
  import { EditorView, Rect } from "@codemirror/view";
@@ -165,6 +166,10 @@ export interface AutoSuggestionProps {
165
166
  /** If this is enabled the value of the editor is replaced with the initialValue if it changes.
166
167
  * FIXME: This property is a workaround for some "controlled" usages of the component via the initialValue property. */
167
168
  reInitOnInitialValueChange?: boolean;
169
+ /** Optional height of the component */
170
+ height?: number | string;
171
+ /** Set read-only mode. Default: false */
172
+ readOnly?: boolean;
168
173
  }
169
174
 
170
175
  // Meta data regarding a request
@@ -197,6 +202,8 @@ const AutoSuggestion = ({
197
202
  mode,
198
203
  multiline = false,
199
204
  reInitOnInitialValueChange = false,
205
+ height,
206
+ readOnly,
200
207
  }: AutoSuggestionProps) => {
201
208
  const value = React.useRef<string>(initialValue);
202
209
  const cursorPosition = React.useRef(0);
@@ -207,7 +214,7 @@ const AutoSuggestion = ({
207
214
  const suggestionRequestData = React.useRef<RequestMetaData>({ requestId: undefined });
208
215
  const [pathValidationPending, setPathValidationPending] = React.useState(false);
209
216
  const validationRequestData = React.useRef<RequestMetaData>({ requestId: undefined });
210
- const [, setErrorMarkers] = React.useState<any[]>([]);
217
+ const errorMarkers = React.useRef<any[]>([]);
211
218
  const [validationResponse, setValidationResponse] = useState<CodeAutocompleteFieldValidationResult | undefined>(
212
219
  undefined
213
220
  );
@@ -219,8 +226,8 @@ const AutoSuggestion = ({
219
226
  CodeAutocompleteFieldSuggestionWithReplacementInfo | undefined
220
227
  >(undefined);
221
228
  const [cm, setCM] = React.useState<EditorView>();
222
- const currentCm = React.useRef<EditorView>()
223
- currentCm.current = cm
229
+ const currentCm = React.useRef<EditorView>();
230
+ currentCm.current = cm;
224
231
  const isFocused = React.useRef(false);
225
232
  const autoSuggestionDivRef = React.useRef<HTMLDivElement>(null);
226
233
  /** Mutable editor state, since this needs to be current in scope of the SingleLineEditorComponent. */
@@ -242,16 +249,16 @@ const AutoSuggestion = ({
242
249
  changes: { from: 0, to: currentCm.current.state?.doc.length, insert: initialValue },
243
250
  });
244
251
  // Validate initial value change
245
- checkValuePathValidity(initialValue)
252
+ checkValuePathValidity(initialValue);
246
253
  }
247
254
  }, [initialValue, reInitOnInitialValueChange]);
248
255
 
249
256
  React.useEffect(() => {
250
- if(currentCm.current) {
257
+ if (currentCm.current) {
251
258
  // Validate initial value
252
- checkValuePathValidity(initialValue)
259
+ checkValuePathValidity(initialValue);
253
260
  }
254
- }, [currentCm.current!!])
261
+ }, [!!currentCm.current]);
255
262
 
256
263
  const setCurrentIndex = (newIndex: number) => {
257
264
  editorState.index = newIndex;
@@ -263,10 +270,9 @@ const AutoSuggestion = ({
263
270
  editorState.cm = cm;
264
271
  }, [cm, editorState]);
265
272
 
266
- const dispatch = // eslint-disable-next-line @typescript-eslint/no-empty-function
267
- (
268
- typeof editorState?.cm?.dispatch === "function" ? editorState?.cm?.dispatch : () => {}
269
- ) as EditorView["dispatch"];
273
+ const dispatch = (
274
+ typeof editorState?.cm?.dispatch === "function" ? editorState?.cm?.dispatch : () => {}
275
+ ) as EditorView["dispatch"];
270
276
 
271
277
  React.useEffect(() => {
272
278
  editorState.dropdownShown = shouldShowDropdown;
@@ -288,8 +294,9 @@ const AutoSuggestion = ({
288
294
  return () => removeMarkFromText({ view: cm, from, to });
289
295
  }
290
296
  } else {
291
- //remove redundant markers
292
- cm && removeMarkFromText({ view: cm, from: 0, to: cm.state?.doc.length });
297
+ if (cm) {
298
+ removeMarkFromText({ view: cm, from: 0, to: cm.state?.doc.length });
299
+ }
293
300
  }
294
301
  return;
295
302
  }, [highlightedElement, selectedTextRanges, cm]);
@@ -298,9 +305,17 @@ const AutoSuggestion = ({
298
305
  React.useEffect(() => {
299
306
  const parseError = validationResponse?.parseError;
300
307
  if (cm) {
308
+ const clearCurrentErrorMarker = () => {
309
+ if (errorMarkers.current.length) {
310
+ const [from, to] = errorMarkers.current;
311
+ removeMarkFromText({ view: cm, from, to });
312
+ errorMarkers.current = [];
313
+ }
314
+ };
301
315
  if (parseError) {
302
316
  const { message, start, end } = parseError;
303
317
  const { toOffset, fromOffset } = getOffsetRange(cm, start, end);
318
+ clearCurrentErrorMarker();
304
319
  const { from, to } = markText({
305
320
  view: cm,
306
321
  from: fromOffset,
@@ -308,22 +323,14 @@ const AutoSuggestion = ({
308
323
  className: `${eccgui}-autosuggestion__text--highlighted-error`,
309
324
  title: message,
310
325
  });
311
-
312
- setErrorMarkers((previousMarkers) => {
313
- previousMarkers.forEach((m) => removeMarkFromText({ view: cm, from: m.from, to: m.to }));
314
- return [from, to];
315
- });
326
+ errorMarkers.current = [from, to];
316
327
  } else {
317
- // Valid, clear all error markers
318
- setErrorMarkers((previous) => {
319
- previous.forEach((m) => removeMarkFromText({ view: cm, from: m.from, to: m.to }));
320
- return [];
321
- });
328
+ clearCurrentErrorMarker();
322
329
  }
323
330
  }
324
331
 
325
332
  const isValid = validationResponse?.valid === undefined || validationResponse.valid;
326
- onInputChecked && onInputChecked(isValid);
333
+ onInputChecked?.(isValid);
327
334
  }, [validationResponse?.valid, validationResponse?.parseError, cm, onInputChecked]);
328
335
 
329
336
  /** generate suggestions and also populate the replacement indexes dict */
@@ -390,6 +397,7 @@ const AutoSuggestion = ({
390
397
  try {
391
398
  const result: CodeAutocompleteFieldValidationResult | undefined = await checkInput(inputString);
392
399
  setValidationResponse(result);
400
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
393
401
  } catch (e) {
394
402
  setValidationResponse(undefined);
395
403
  // TODO: Error handling
@@ -425,6 +433,7 @@ const AutoSuggestion = ({
425
433
  setSuggestionResponse(result);
426
434
  }
427
435
  }
436
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
428
437
  } catch (e) {
429
438
  setSuggestionResponse(undefined);
430
439
  // TODO: Error handling
@@ -445,12 +454,14 @@ const AutoSuggestion = ({
445
454
  [asyncHandleEditorInputChange, autoCompletionRequestDelay]
446
455
  );
447
456
 
448
- const handleChange = (val: string) => {
449
- value.current = val;
450
- checkValuePathValidity.cancel();
451
- checkValuePathValidity(value.current);
452
- onChange(val);
453
- };
457
+ const handleChange = React.useMemo(() => {
458
+ return (val: string) => {
459
+ value.current = val;
460
+ checkValuePathValidity.cancel();
461
+ checkValuePathValidity(value.current);
462
+ onChange(val);
463
+ };
464
+ }, [onChange, checkValuePathValidity]);
454
465
 
455
466
  const handleCursorChange = (cursor: number, coords: Rect, scrollinfo: HTMLElement, view: EditorView) => {
456
467
  //cursor here is offset from line 1, autosuggestion works with cursor per-line.
@@ -534,8 +545,13 @@ const AutoSuggestion = ({
534
545
  };
535
546
 
536
547
  const handleInputFocus = (focusState: boolean) => {
537
- onFocusChange && onFocusChange(focusState);
538
- focusState ? setShouldShowDropdown(true) : closeDropDown();
548
+ onFocusChange?.(focusState);
549
+ if (focusState) {
550
+ setShouldShowDropdown(true);
551
+ } else {
552
+ closeDropDown();
553
+ }
554
+
539
555
  if (!isFocused.current && focusState) {
540
556
  // Just got focus
541
557
  // Clear suggestions and repeat suggestion request, something else might have changed while this component was not focused
@@ -643,6 +659,8 @@ const AutoSuggestion = ({
643
659
  showScrollBar={showScrollBar}
644
660
  multiline={multiline}
645
661
  onMouseDown={handleInputMouseDown}
662
+ height={height}
663
+ readOnly={readOnly}
646
664
  />
647
665
  );
648
666
  }, [
@@ -61,6 +61,10 @@ export interface ExtendedCodeEditorProps {
61
61
  | "additionalExtensions"
62
62
  | "outerDivAttributes"
63
63
  >;
64
+ /** Optional height of the component */
65
+ height?: number | string;
66
+ /** Set read-only mode. Default: false */
67
+ readOnly?: boolean;
64
68
  }
65
69
 
66
70
  export type IEditorProps = ExtendedCodeEditorProps;
@@ -80,6 +84,8 @@ export const ExtendedCodeEditor = ({
80
84
  onCursorChange,
81
85
  onSelection,
82
86
  codeEditorProps,
87
+ height,
88
+ readOnly,
83
89
  }: ExtendedCodeEditorProps) => {
84
90
  const initialContent = React.useRef(multiline ? initialValue : initialValue.replace(/[\r\n]/g, " "));
85
91
  const multilineExtensions = multiline
@@ -104,6 +110,8 @@ export const ExtendedCodeEditor = ({
104
110
  mode={mode}
105
111
  name=""
106
112
  enableTab={enableTab}
113
+ height={height}
114
+ readOnly={readOnly}
107
115
  additionalExtensions={[...multilineExtensions]}
108
116
  outerDivAttributes={{
109
117
  className: `${eccgui}-${
@@ -18,6 +18,10 @@ export default {
18
18
  onClick: {
19
19
  action: "clicked",
20
20
  },
21
+ intent: {
22
+ ...helpersArgTypes.exampleIntent,
23
+ options: ["UNDEFINED", "primary", "success", "warning", "danger"],
24
+ },
21
25
  },
22
26
  } as Meta<typeof Button>;
23
27
 
@@ -67,17 +71,17 @@ const TemplateSemantic: StoryFn<typeof Button> = (args) => (
67
71
  export const ButtonSemantics = TemplateSemantic.bind({});
68
72
  ButtonSemantics.args = FullExample.args;
69
73
 
70
- const TemplateState: StoryFn<typeof Button> = (args) => (
74
+ const TemplateIntent: StoryFn<typeof Button> = (args) => (
71
75
  <OverlaysProvider>
72
- <Button {...args} text="Success" hasStateSuccess />
76
+ <Button {...args} text="Success" intent="success" />
73
77
  <Spacing vertical />
74
- <Button {...args} text="Warning" hasStateWarning />
78
+ <Button {...args} text="Warning" intent="warning" />
75
79
  <Spacing vertical />
76
- <Button {...args} text="Danger" hasStateDanger />
80
+ <Button {...args} text="Danger" intent="danger" />
77
81
  </OverlaysProvider>
78
82
  );
79
- export const ButtonStates = TemplateState.bind({});
80
- ButtonStates.args = FullExample.args;
83
+ export const ButtonIntent = TemplateIntent.bind({});
84
+ ButtonIntent.args = FullExample.args;
81
85
 
82
86
  const TemplateContent: StoryFn<typeof Button> = (args) => (
83
87
  <OverlaysProvider>
@@ -32,18 +32,22 @@ interface AdditionalButtonProps {
32
32
  elevated?: boolean;
33
33
  /**
34
34
  * The button is displayed with primary color scheme.
35
+ * @deprecated (v25) use `intent="primary"` instead.
35
36
  */
36
37
  hasStatePrimary?: boolean;
37
38
  /**
38
39
  * The button is displayed with success (some type of green) color scheme.
40
+ * @deprecated (v25) use `intent="success"` instead.
39
41
  */
40
42
  hasStateSuccess?: boolean;
41
43
  /**
42
44
  * The button is displayed with warning (some type of orange) color scheme.
45
+ * @deprecated (v25) use `intent="warning"` instead.
43
46
  */
44
47
  hasStateWarning?: boolean;
45
48
  /**
46
49
  * The button is displayed with danger (some type of red) color scheme.
50
+ * @deprecated (v25) use `intent="danger"` instead.
47
51
  */
48
52
  hasStateDanger?: boolean;
49
53
  /**
@@ -100,6 +104,7 @@ export const Button = ({
100
104
  tooltipProps,
101
105
  badge,
102
106
  badgeProps = { size: "small", position: "top-right", maxLength: 2 },
107
+ intent,
103
108
  ...restProps
104
109
  }: ButtonProps) => {
105
110
  let intention;
@@ -120,13 +125,13 @@ export const Button = ({
120
125
  break;
121
126
  }
122
127
 
123
- const ButtonType: any = restProps.href ? BlueprintAnchorButton : BlueprintButton;
128
+ const ButtonType = restProps.href ? BlueprintAnchorButton : BlueprintButton;
124
129
 
125
130
  const button = (
126
131
  <ButtonType
127
132
  {...restProps}
128
133
  className={`${eccgui}-button ` + className}
129
- intent={intention}
134
+ intent={(intent || intention) as BlueprintIntent}
130
135
  icon={typeof icon === "string" ? <Icon name={icon} /> : icon}
131
136
  rightIcon={typeof rightIcon === "string" ? <Icon name={rightIcon} /> : rightIcon}
132
137
  >
@@ -10,7 +10,7 @@ export default {
10
10
  subcomponents: { MenuItem },
11
11
  argTypes: {
12
12
  children: {
13
- control: "none",
13
+ control: false,
14
14
  },
15
15
  },
16
16
  } as Meta<typeof ContextMenu>;
@@ -40,8 +40,15 @@ export interface ContextMenuProps extends TestableComponent {
40
40
  * Props to spread to `ContextOverlay` that is used to display the dropdown.
41
41
  */
42
42
  contextOverlayProps?: Partial<Omit<ContextOverlayProps, "content" | "children" | "className">>;
43
- /** Disables the button to open the menu. */
43
+ /**
44
+ * Disables the button to open the menu.
45
+ */
44
46
  disabled?: boolean;
47
+ /**
48
+ * We use the target as placeholder before the real `<ContextMenu /` is rendered on first hover or focus event.
49
+ * In case of problems set this property to `true`.
50
+ */
51
+ preventPlaceholder?: boolean;
45
52
  }
46
53
 
47
54
  /**
@@ -58,27 +65,33 @@ export const ContextMenu = ({
58
65
  /* FIXME: The Tooltip component can interfere with the opened menu, since it is implemented via portal and may cover the menu,
59
66
  so by default we use the title attribute instead of Tooltip. */
60
67
  tooltipAsTitle = true,
68
+ preventPlaceholder = false,
61
69
  ...restProps
62
70
  }: ContextMenuProps) => {
71
+ const toggleButton =
72
+ typeof togglerElement === "string" ? (
73
+ <IconButton
74
+ tooltipAsTitle={tooltipAsTitle}
75
+ name={[togglerElement]}
76
+ text={togglerText}
77
+ large={togglerLarge}
78
+ disabled={!!disabled}
79
+ data-test-id={restProps["data-test-id"]}
80
+ />
81
+ ) : (
82
+ (togglerElement as ReactElement)
83
+ );
84
+
63
85
  return (
64
86
  <ContextOverlay
65
87
  {...restProps}
66
88
  {...contextOverlayProps}
67
89
  className={`${eccgui}-contextmenu ` + className}
68
90
  content={<Menu>{children}</Menu>}
91
+ disabled={!!disabled}
92
+ usePlaceholder={!preventPlaceholder}
69
93
  >
70
- {typeof togglerElement === "string" ? (
71
- <IconButton
72
- tooltipAsTitle={tooltipAsTitle}
73
- name={[togglerElement]}
74
- text={togglerText}
75
- large={togglerLarge}
76
- disabled={!!disabled}
77
- data-test-id={restProps["data-test-id"]}
78
- />
79
- ) : (
80
- (togglerElement as ReactElement)
81
- )}
94
+ {toggleButton}
82
95
  </ContextOverlay>
83
96
  );
84
97
  };
@@ -1,5 +1,10 @@
1
1
  import React from "react";
2
- import { Popover as BlueprintPopover, PopoverProps as BlueprintPopoverProps } from "@blueprintjs/core";
2
+ import {
3
+ Classes as BlueprintClasses,
4
+ Popover as BlueprintPopover,
5
+ PopoverProps as BlueprintPopoverProps,
6
+ Utils as BlueprintUtils,
7
+ } from "@blueprintjs/core";
3
8
 
4
9
  import { CLASSPREFIX as eccgui } from "../../configuration/constants";
5
10
 
@@ -13,6 +18,11 @@ export interface ContextOverlayProps extends Omit<BlueprintPopoverProps, "positi
13
18
  * Use it when you need to display modal dialogs out of the context overlay.
14
19
  */
15
20
  preventTopPosition?: boolean;
21
+ /**
22
+ * Use the overlay target as placeholder before the real `<ContextOverlay /` is rendered on first hover or focus event.
23
+ * Currently experimental.
24
+ */
25
+ usePlaceholder?: boolean;
16
26
  }
17
27
 
18
28
  /**
@@ -24,18 +34,86 @@ export const ContextOverlay = ({
24
34
  portalClassName,
25
35
  preventTopPosition,
26
36
  className = "",
27
- ...restProps
37
+ usePlaceholder = false,
38
+ ...otherPopoverProps
28
39
  }: ContextOverlayProps) => {
40
+ const placeholderRef = React.useRef(null);
41
+ const eventMemory = React.useRef<undefined | "afterhover" | "afterfocus">(undefined);
42
+ const [placeholder, setPlaceholder] = React.useState<boolean>(
43
+ // use placeholder only for "simple" overlays without special states
44
+ !otherPopoverProps.disabled &&
45
+ !otherPopoverProps.defaultIsOpen &&
46
+ !otherPopoverProps.isOpen &&
47
+ otherPopoverProps.renderTarget === undefined &&
48
+ usePlaceholder
49
+ );
50
+
51
+ React.useEffect(() => {
52
+ if (placeholderRef.current) {
53
+ const swap = (ev: MouseEvent | globalThis.FocusEvent) => {
54
+ eventMemory.current = ev.type === "focusin" ? "afterfocus" : "afterhover";
55
+ setPlaceholder(false);
56
+ };
57
+ (placeholderRef.current as HTMLElement).addEventListener("mouseenter", swap);
58
+ (placeholderRef.current as HTMLElement).addEventListener("focusin", swap);
59
+ return () => {
60
+ if (placeholderRef.current) {
61
+ (placeholderRef.current as HTMLElement).removeEventListener("mouseenter", swap);
62
+ (placeholderRef.current as HTMLElement).removeEventListener("focusin", swap);
63
+ }
64
+ };
65
+ }
66
+ return () => {};
67
+ }, [!!placeholderRef.current]);
68
+
69
+ const refocus = React.useCallback((node) => {
70
+ if (eventMemory.current === "afterfocus" && node) {
71
+ const target = node.targetRef.current.children[0];
72
+ if (target) {
73
+ target.focus();
74
+ }
75
+ }
76
+ }, []);
77
+
78
+ const targetClassName = `${eccgui}-contextoverlay` + (className ? ` ${className}` : "");
79
+
80
+ const displayPlaceholder = () => {
81
+ const PlaceholderElement = otherPopoverProps?.targetTagName ?? (otherPopoverProps?.fill ? "div" : "span");
82
+ const childTarget = BlueprintUtils.ensureElement(React.Children.toArray(children)[0]);
83
+ if (!childTarget) {
84
+ return null;
85
+ }
86
+ return React.createElement(
87
+ PlaceholderElement,
88
+ {
89
+ ...otherPopoverProps?.targetProps,
90
+ className: `${BlueprintClasses.POPOVER_TARGET} ${targetClassName}`,
91
+ ref: placeholderRef,
92
+ },
93
+ React.cloneElement(childTarget, {
94
+ ...childTarget.props,
95
+ className:
96
+ childTarget.props.className ?? "" + (otherPopoverProps.fill ? ` ${BlueprintClasses.FILL}` : ""),
97
+ tabIndex:
98
+ childTarget.props.tabIndex ??
99
+ (!otherPopoverProps?.disabled && otherPopoverProps?.openOnTargetFocus ? 0 : undefined),
100
+ })
101
+ );
102
+ };
103
+
29
104
  const portalClassNameFinal =
30
105
  (preventTopPosition ? `${eccgui}-contextoverlay__portal--lowertop` : "") +
31
106
  (portalClassName ? ` ${portalClassName}` : "");
32
107
 
33
- return (
108
+ return placeholder ? (
109
+ displayPlaceholder()
110
+ ) : (
34
111
  <BlueprintPopover
35
112
  placement="bottom"
36
- {...restProps}
37
- className={`${eccgui}-contextoverlay` + (className ? ` ${className}` : "")}
113
+ {...otherPopoverProps}
114
+ className={targetClassName}
38
115
  portalClassName={portalClassNameFinal.trim() ?? undefined}
116
+ ref={refocus}
39
117
  >
40
118
  {children}
41
119
  </BlueprintPopover>