@elyra/canvas 12.42.0 → 12.43.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 (128) hide show
  1. package/dist/{canvas-constants-13b58448.js → canvas-constants-acb99f64.js} +2 -2
  2. package/dist/{canvas-constants-13b58448.js.map → canvas-constants-acb99f64.js.map} +1 -1
  3. package/dist/{canvas-controller-cb1d7420.js → canvas-controller-6726b9ac.js} +2 -2
  4. package/dist/{canvas-controller-a53943e4.js.map → canvas-controller-6726b9ac.js.map} +1 -1
  5. package/dist/{canvas-controller-a53943e4.js → canvas-controller-6c6bc68f.js} +2 -2
  6. package/dist/{canvas-controller-cb1d7420.js.map → canvas-controller-6c6bc68f.js.map} +1 -1
  7. package/dist/common-canvas-9374ef35.js +2 -0
  8. package/dist/common-canvas-9374ef35.js.map +1 -0
  9. package/dist/common-canvas-a6435bdb.js +2 -0
  10. package/dist/common-canvas-a6435bdb.js.map +1 -0
  11. package/dist/common-canvas.es.js +1 -1
  12. package/dist/common-canvas.es.js.map +1 -1
  13. package/dist/common-canvas.js +1 -1
  14. package/dist/common-canvas.js.map +1 -1
  15. package/dist/common-properties-08707efe.js +2 -0
  16. package/dist/common-properties-08707efe.js.map +1 -0
  17. package/dist/common-properties-acd55e94.js +2 -0
  18. package/dist/common-properties-acd55e94.js.map +1 -0
  19. package/dist/context-menu-wrapper-271eb2df.js +2 -0
  20. package/dist/context-menu-wrapper-271eb2df.js.map +1 -0
  21. package/dist/context-menu-wrapper-e4a7ab4d.js +2 -0
  22. package/dist/context-menu-wrapper-e4a7ab4d.js.map +1 -0
  23. package/dist/{datarecord-metadata-v3-schema-98ec66e9.js → datarecord-metadata-v3-schema-03427296.js} +2 -2
  24. package/dist/{datarecord-metadata-v3-schema-98ec66e9.js.map → datarecord-metadata-v3-schema-03427296.js.map} +1 -1
  25. package/dist/{flexible-table-7c7de0f9.js → flexible-table-107ca2fd.js} +1 -1
  26. package/dist/{flexible-table-7c7de0f9.js.map → flexible-table-107ca2fd.js.map} +1 -1
  27. package/dist/{flexible-table-35e9922a.js → flexible-table-5cc1ad6b.js} +1 -1
  28. package/dist/{flexible-table-35e9922a.js.map → flexible-table-5cc1ad6b.js.map} +1 -1
  29. package/dist/{icon-9edff40c.js → icon-2caf035c.js} +2 -2
  30. package/dist/{icon-9edff40c.js.map → icon-2caf035c.js.map} +1 -1
  31. package/dist/{index-94fec521.js → index-5dac3da8.js} +2 -2
  32. package/dist/{index-94fec521.js.map → index-5dac3da8.js.map} +1 -1
  33. package/dist/{index-e2f8a935.js → index-fee06179.js} +2 -2
  34. package/dist/{index-e2f8a935.js.map → index-fee06179.js.map} +1 -1
  35. package/dist/lib/canvas-controller.es.js +1 -1
  36. package/dist/lib/canvas-controller.js +1 -1
  37. package/dist/lib/canvas.es.js +1 -1
  38. package/dist/lib/canvas.js +1 -1
  39. package/dist/lib/context-menu.es.js +1 -1
  40. package/dist/lib/context-menu.js +1 -1
  41. package/dist/lib/properties/clem.es.js +2 -0
  42. package/dist/lib/properties/clem.es.js.map +1 -0
  43. package/dist/lib/properties/clem.js +2 -0
  44. package/dist/lib/properties/clem.js.map +1 -0
  45. package/dist/lib/properties/field-picker.es.js +1 -1
  46. package/dist/lib/properties/field-picker.js +1 -1
  47. package/dist/lib/properties/flexible-table.es.js +1 -1
  48. package/dist/lib/properties/flexible-table.js +1 -1
  49. package/dist/lib/properties/getPythonHints.es.js +2 -0
  50. package/dist/lib/properties/getPythonHints.es.js.map +1 -0
  51. package/dist/lib/properties/getPythonHints.js +2 -0
  52. package/dist/lib/properties/getPythonHints.js.map +1 -0
  53. package/dist/lib/properties.es.js +1 -1
  54. package/dist/lib/properties.js +1 -1
  55. package/dist/lib/tooltip.es.js +1 -1
  56. package/dist/lib/tooltip.es.js.map +1 -1
  57. package/dist/lib/tooltip.js +1 -1
  58. package/dist/lib/tooltip.js.map +1 -1
  59. package/dist/styles/common-canvas.min.css +1 -1
  60. package/dist/styles/common-canvas.min.css.map +1 -1
  61. package/dist/toolbar-ccc1d600.js +2 -0
  62. package/dist/toolbar-ccc1d600.js.map +1 -0
  63. package/dist/toolbar-e4445bf8.js +2 -0
  64. package/dist/toolbar-e4445bf8.js.map +1 -0
  65. package/package.json +3 -3
  66. package/rollup.config.js +2 -0
  67. package/src/color-picker/color-picker.jsx +5 -1
  68. package/src/common-canvas/canvas-controller.js +38 -0
  69. package/src/common-canvas/cc-central-items.jsx +0 -4
  70. package/src/common-canvas/cc-toolbar.jsx +26 -9
  71. package/src/common-canvas/common-canvas.scss +3 -3
  72. package/src/common-canvas/svg-canvas-d3.scss +3 -2
  73. package/src/common-canvas/svg-canvas-renderer.js +44 -21
  74. package/src/common-properties/components/field-picker/field-picker.jsx +4 -0
  75. package/src/common-properties/controls/checkbox/checkbox.scss +0 -1
  76. package/src/common-properties/controls/expression/expression.jsx +3 -5
  77. package/src/common-properties/controls/expression/languages/python-hint.js +18 -30
  78. package/src/common-properties/controls/expression/languages/r-hint.js +16 -8
  79. package/src/common-properties/index.js +4 -2
  80. package/src/index.js +2 -2
  81. package/src/notification-panel/notification-panel.jsx +82 -56
  82. package/src/notification-panel/notification-panel.scss +42 -40
  83. package/src/object-model/object-model.js +19 -5
  84. package/src/object-model/redux/reducers/canvasinfo.js +7 -11
  85. package/src/object-model/redux/reducers/canvastoolbar.js +5 -6
  86. package/src/palette/palette-dialog-topbar.jsx +27 -38
  87. package/src/palette/palette-flyout-content-category.jsx +25 -6
  88. package/src/palette/palette.scss +8 -40
  89. package/src/toolbar/index.js +18 -0
  90. package/src/toolbar/toolbar-action-item.jsx +40 -10
  91. package/src/toolbar/toolbar-button-item.jsx +46 -19
  92. package/src/toolbar/toolbar-divider-item.jsx +1 -1
  93. package/src/toolbar/toolbar-overflow-item.jsx +4 -2
  94. package/src/toolbar/toolbar-sub-menu-item.jsx +6 -5
  95. package/src/toolbar/toolbar-sub-menu.jsx +4 -6
  96. package/src/toolbar/toolbar-sub-panel.jsx +31 -18
  97. package/src/toolbar/toolbar-sub-utils.js +21 -12
  98. package/src/toolbar/toolbar.jsx +79 -25
  99. package/src/toolbar/toolbar.scss +47 -47
  100. package/src/tooltip/tooltip.jsx +56 -10
  101. package/stats.html +1 -1
  102. package/assets/images/palette/close_32.svg +0 -1
  103. package/assets/images/palette/palette_close.svg +0 -4
  104. package/assets/images/palette/palette_grid_deselected.svg +0 -2
  105. package/assets/images/palette/palette_grid_hover.svg +0 -2
  106. package/assets/images/palette/palette_grid_selected.svg +0 -2
  107. package/assets/images/palette/palette_list_deselected.svg +0 -1
  108. package/assets/images/palette/palette_list_hover.svg +0 -1
  109. package/assets/images/palette/palette_list_selected.svg +0 -1
  110. package/assets/images/palette/palette_open.svg +0 -4
  111. package/assets/images/zoom_to_fit.svg +0 -8
  112. package/dist/common-canvas-42027a3f.js +0 -2
  113. package/dist/common-canvas-42027a3f.js.map +0 -1
  114. package/dist/common-canvas-f758ff42.js +0 -2
  115. package/dist/common-canvas-f758ff42.js.map +0 -1
  116. package/dist/common-properties-2e1b7ec7.js +0 -2
  117. package/dist/common-properties-2e1b7ec7.js.map +0 -1
  118. package/dist/common-properties-5e8870e3.js +0 -2
  119. package/dist/common-properties-5e8870e3.js.map +0 -1
  120. package/dist/context-menu-wrapper-49f9a1af.js +0 -2
  121. package/dist/context-menu-wrapper-49f9a1af.js.map +0 -1
  122. package/dist/context-menu-wrapper-5d6a399f.js +0 -2
  123. package/dist/context-menu-wrapper-5d6a399f.js.map +0 -1
  124. package/dist/toolbar-6acda0a2.js +0 -2
  125. package/dist/toolbar-6acda0a2.js.map +0 -1
  126. package/dist/toolbar-d5647da2.js +0 -2
  127. package/dist/toolbar-d5647da2.js.map +0 -1
  128. package/src/palette/palette-dialog-topbar-three-way-icon.jsx +0 -82
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2017-2023 Elyra Authors
2
+ * Copyright 2017-2024 Elyra Authors
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
@@ -21,9 +21,6 @@ import Tooltip from "../tooltip/tooltip.jsx";
21
21
  import ArrangeHorizontally from "./../../assets/images/arrange_horizontally.svg";
22
22
  import ArrangeVertically from "./../../assets/images/arrange_vertically.svg";
23
23
  import ToggleNotificationPanel from "./../../assets/images/notification_counter_icon.svg";
24
- import PaletteClose from "./../../assets/images/palette/palette_close.svg";
25
- import PaletteOpen from "./../../assets/images/palette/palette_open.svg";
26
- import ZoomToFit from "./../../assets/images/zoom_to_fit.svg";
27
24
 
28
25
  import { Button } from "carbon-components-react";
29
26
  import SVG from "react-inlinesvg";
@@ -31,7 +28,8 @@ import classNames from "classnames";
31
28
  import { StopFilledAlt16, Play16, Undo16, Redo16, Chat16, ChatOff16, Result16,
32
29
  Cut16, Copy16, Paste16, Edit16, ColorPalette16, Maximize16, Minimize16,
33
30
  Launch16, AddComment16, TrashCan16, ZoomIn16, ZoomOut16,
34
- ChevronRight16, ChevronDown16, ChevronUp16 } from "@carbon/icons-react";
31
+ ChevronRight16, ChevronDown16, ChevronUp16,
32
+ CenterToFit16, OpenPanelFilledLeft16 } from "@carbon/icons-react";
35
33
  import { TOOLBAR_STOP, TOOLBAR_RUN, TOOLBAR_UNDO, TOOLBAR_REDO,
36
34
  TOOLBAR_CUT, TOOLBAR_COPY, TOOLBAR_PASTE, TOOLBAR_CLIPBOARD,
37
35
  TOOLBAR_CREATE_COMMENT, TOOLBAR_CREATE_AUTO_COMMENT, TOOLBAR_COLOR_BACKGROUND,
@@ -54,6 +52,14 @@ class ToolbarButtonItem extends React.Component {
54
52
  componentDidUpdate() {
55
53
  if (this.props.isFocusInToolbar &&
56
54
  this.props.buttonFocusAction === this.props.actionObj.action) {
55
+ // If a Jsx object was provided, the class of the component should have
56
+ // been set to toolbar-jsx-obj.
57
+ const jsxItem = this.buttonRef.current.querySelector(".toolbar-jsx-obj");
58
+ if (jsxItem) {
59
+ jsxItem.focus();
60
+ return;
61
+ }
62
+
57
63
  this.buttonRef.current.focus();
58
64
  }
59
65
  }
@@ -105,17 +111,18 @@ class ToolbarButtonItem extends React.Component {
105
111
  return <ZoomIn16 disabled={disabled} />;
106
112
  case (TOOLBAR_ZOOM_OUT):
107
113
  return <ZoomOut16 disabled={disabled} />;
108
-
109
114
  case (TOOLBAR_ZOOM_FIT):
110
- return <SVG src={ZoomToFit} disabled={disabled} />;
115
+ return <CenterToFit16 disabled={disabled} />;
116
+ case (TOOLBAR_OPEN_PALETTE):
117
+ return <OpenPanelFilledLeft16 disabled={disabled} />;
118
+ case (TOOLBAR_CLOSE_PALETTE):
119
+ return <OpenPanelFilledLeft16 disabled={disabled} />;
120
+
121
+ // Non-carbon icons
111
122
  case (TOOLBAR_ARRANGE_HORIZONALLY):
112
123
  return <SVG src={ArrangeHorizontally} disabled={disabled} />;
113
124
  case (TOOLBAR_ARRANGE_VERTICALLY):
114
125
  return <SVG src={ArrangeVertically} disabled={disabled} />;
115
- case (TOOLBAR_OPEN_PALETTE):
116
- return <SVG src={PaletteOpen} disabled={disabled} />;
117
- case (TOOLBAR_CLOSE_PALETTE):
118
- return <SVG src={PaletteClose} disabled={disabled} />;
119
126
  case (TOOLBAR_TOGGLE_NOTIFICATION_PANEL):
120
127
  return <SVG src={ToggleNotificationPanel} disabled={disabled} />;
121
128
 
@@ -169,7 +176,7 @@ class ToolbarButtonItem extends React.Component {
169
176
  return null;
170
177
  }
171
178
 
172
- generateButton(actionObj) {
179
+ generateRegularItem(actionObj) {
173
180
  let labelBefore = null;
174
181
  let labelAfter = null;
175
182
 
@@ -264,6 +271,26 @@ class ToolbarButtonItem extends React.Component {
264
271
  );
265
272
  }
266
273
 
274
+ // Creates a <div> containing the JSX in the actionObj.jsx field, wrapped in a tooltip
275
+ // <div>, for display as an action item in the toolbar. The jsx field can be just
276
+ // regular JSX OR a function that returns JSX. If the application has provided a
277
+ // function we call it, passing in the tabIndex that the component in the JSX should
278
+ // use, based on whether it is focused or not.
279
+ generateJsxItem(actionObj) {
280
+ let content = null;
281
+ if (typeof actionObj.jsx === "function") {
282
+ const tabIndex = this.props.buttonFocusAction === actionObj.action ? 0 : -1;
283
+ content = actionObj.jsx(tabIndex);
284
+ } else {
285
+ content = actionObj.jsx;
286
+ }
287
+ const jsx = this.wrapInTooltip(content);
288
+ const div = (<div ref={this.buttonRef}>{jsx}</div>);
289
+
290
+ return div;
291
+ }
292
+
293
+
267
294
  wrapInTooltip(content) {
268
295
  if (!this.props.isInMenu && (this.showLabelAsTip(this.props.actionObj) || this.props.actionObj.tooltip)) {
269
296
  const tip = this.props.actionObj.tooltip ? this.props.actionObj.tooltip : this.props.actionObj.label;
@@ -296,13 +323,10 @@ class ToolbarButtonItem extends React.Component {
296
323
 
297
324
  render() {
298
325
  const actionObj = this.props.actionObj;
299
- let divContent = null;
300
326
 
301
- if (actionObj.jsx) {
302
- divContent = this.wrapInTooltip(actionObj.jsx);
303
- } else {
304
- divContent = this.generateButton(actionObj);
305
- }
327
+ const divContent = actionObj.jsx
328
+ ? this.generateJsxItem(actionObj)
329
+ : this.generateRegularItem(actionObj);
306
330
 
307
331
  return divContent;
308
332
  }
@@ -333,7 +357,10 @@ ToolbarButtonItem.propTypes = {
333
357
  subMenu: PropTypes.array,
334
358
  subPanel: PropTypes.any,
335
359
  subPanelData: PropTypes.object,
336
- jsx: PropTypes.object,
360
+ jsx: PropTypes.oneOfType([
361
+ PropTypes.object,
362
+ PropTypes.func
363
+ ]),
337
364
  tooltip: PropTypes.oneOfType([
338
365
  PropTypes.string,
339
366
  PropTypes.object,
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2017-2023 Elyra Authors
2
+ * Copyright 2017-2024 Elyra Authors
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2017-2023 Elyra Authors
2
+ * Copyright 2017-2024 Elyra Authors
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
@@ -89,6 +89,7 @@ class ToolbarOverflowItem extends React.Component {
89
89
 
90
90
  } else {
91
91
  document.addEventListener("click", this.clickOutside, false);
92
+ this.props.closeAnyOpenSubArea();
92
93
  this.props.setOverflowIndex(this.props.index);
93
94
  this.openSubMenu();
94
95
  this.props.setToolbarFocusAction(this.props.action);
@@ -168,7 +169,8 @@ ToolbarOverflowItem.propTypes = {
168
169
  containingDivId: PropTypes.string,
169
170
  toolbarFocusAction: PropTypes.string,
170
171
  setToolbarFocusAction: PropTypes.func,
171
- isFocusInToolbar: PropTypes.bool
172
+ isFocusInToolbar: PropTypes.bool,
173
+ closeAnyOpenSubArea: PropTypes.func
172
174
  };
173
175
 
174
176
  export default ToolbarOverflowItem;
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2017-2023 Elyra Authors
2
+ * Copyright 2017-2024 Elyra Authors
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
@@ -62,9 +62,9 @@ class ToolbarSubMenuItem extends React.Component {
62
62
  clickOutside(evt) {
63
63
  if (this.state.subAreaDisplayed) {
64
64
  const items = document.getElementsByClassName(this.generateActionName());
65
- const isOver = items && items.length > 0 ? items[0].contains(evt.target) : false;
65
+ const isOver = items?.length > 0 ? items[0].contains(evt.target) : false;
66
66
 
67
- if (!isOver) {
67
+ if (!isOver && !this.props.actionObj.leaveSubAreaOpenOnClickOutside) {
68
68
  this.closeSubArea();
69
69
  }
70
70
  }
@@ -93,14 +93,14 @@ class ToolbarSubMenuItem extends React.Component {
93
93
  } else {
94
94
  evt.stopPropagation();
95
95
  if (this.props.isInCascadeMenu) {
96
- this.props.closeParentSubArea();
96
+ this.props.closeParentSubArea(true); // true = close only if closeSubAreaOnClick is checked
97
97
  this.props.setSubMenuFocus();
98
98
 
99
99
  } else if (this.props.isInOverflowMenu) {
100
100
  this.props.setSubMenuFocus(this.props.actionObj.action);
101
101
 
102
102
  } else {
103
- this.props.closeParentSubArea();
103
+ this.props.closeParentSubArea(true); // true = close only if closeSubAreaOnClick is checked
104
104
  this.props.setToolbarFocusAction(); // Resets the toolbar focus action
105
105
  }
106
106
  this.props.toolbarActionHandler(this.props.actionObj.action, evt);
@@ -210,6 +210,7 @@ ToolbarSubMenuItem.propTypes = {
210
210
  isSelected: PropTypes.bool,
211
211
  kind: PropTypes.string,
212
212
  closeSubAreaOnClick: PropTypes.bool,
213
+ leaveSubAreaOpenOnClickOutside: PropTypes.bool,
213
214
  subMenu: PropTypes.array,
214
215
  subPanel: PropTypes.any,
215
216
  subPanelData: PropTypes.object,
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2023 Elyra Authors
2
+ * Copyright 2024 Elyra Authors
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
@@ -35,8 +35,6 @@ class ToolbarSubMenu extends React.Component {
35
35
  focusAction: "subarea"
36
36
  };
37
37
 
38
- this.areaRef = React.createRef();
39
-
40
38
  this.onKeyDown = this.onKeyDown.bind(this);
41
39
  this.setFocusAction = this.setFocusAction.bind(this);
42
40
  this.setSubMenuFocus = this.setSubMenuFocus.bind(this);
@@ -143,7 +141,7 @@ class ToolbarSubMenu extends React.Component {
143
141
  const focusableActions = [];
144
142
 
145
143
  for (let i = 0; i < this.props.subMenuActions.length; i++) {
146
- if (this.props.subMenuActions[i].enable) {
144
+ if (this.props.subMenuActions[i].enable || this.props.subMenuActions[i].jsx) {
147
145
  focusableActions.push(this.props.subMenuActions[i]);
148
146
  }
149
147
  }
@@ -224,7 +222,7 @@ class ToolbarSubMenu extends React.Component {
224
222
  this.subMenuItems = this.generateSubMenuItems();
225
223
 
226
224
  return (
227
- <div ref={this.areaRef} style={style} className={"toolbar-popover-list submenu"}
225
+ <div ref={(ref) => (this.areaRef = ref)} style={style} className={"toolbar-popover-list submenu"}
228
226
  tabIndex={-1} onKeyDown={this.onKeyDown}
229
227
  >
230
228
  {this.subMenuItems}
@@ -242,7 +240,7 @@ ToolbarSubMenu.propTypes = {
242
240
  closeSubArea: PropTypes.func,
243
241
  setToolbarFocusAction: PropTypes.func,
244
242
  setSubMenuFocus: PropTypes.func,
245
- actionItemRect: PropTypes.object.isRequired,
243
+ actionItemRect: PropTypes.object,
246
244
  expandDirection: PropTypes.string.isRequired,
247
245
  containingDivId: PropTypes.string,
248
246
  parentSelector: PropTypes.string,
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2023 Elyra Authors
2
+ * Copyright 2024 Elyra Authors
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
@@ -20,46 +20,59 @@ import PropTypes from "prop-types";
20
20
  import { adjustSubAreaPosition, generateSubAreaStyle } from "./toolbar-sub-utils.js";
21
21
 
22
22
  const ESC_KEY = 27;
23
+ const LEFT_ARROW_KEY = 37;
24
+ const RIGHT_ARROW_KEY = 39;
25
+
23
26
 
24
27
  class ToolbarSubPanel extends React.Component {
25
28
  constructor(props) {
26
29
  super(props);
27
30
 
28
- this.areaRef = React.createRef();
29
-
30
- this.onClick = this.onClick.bind(this);
31
31
  this.onKeyDown = this.onKeyDown.bind(this);
32
+ this.closeSubPanel = this.closeSubPanel.bind(this);
32
33
  }
33
34
 
34
35
  componentDidMount() {
35
- if (this.props.containingDivId) {
36
- adjustSubAreaPosition(this.areaRef,
37
- this.props.containingDivId, this.props.expandDirection, this.props.actionItemRect);
38
- }
36
+ adjustSubAreaPosition(this.areaRef,
37
+ this.props.containingDivId, this.props.expandDirection, this.props.actionItemRect);
39
38
  }
40
39
 
41
- onClick() {
42
- this.props.closeSubArea();
40
+ componentDidUpdate() {
41
+ adjustSubAreaPosition(this.areaRef,
42
+ this.props.containingDivId, this.props.expandDirection, this.props.actionItemRect);
43
43
  }
44
44
 
45
45
  onKeyDown(evt) {
46
46
  if (evt.keyCode === ESC_KEY) {
47
47
  this.props.closeSubArea();
48
48
  evt.stopPropagation();
49
+
50
+ } else if (evt.keyCode === LEFT_ARROW_KEY || evt.keyCode === RIGHT_ARROW_KEY) {
51
+ evt.stopPropagation();
49
52
  }
50
53
  }
51
54
 
55
+ // If the user clicks the panel background, by default focus would go
56
+ // through to the toolbar and focus would be lost from this sub-panel.
57
+ // This method prevents any focus event going through to the toolbar.
58
+ onFocus(evt) {
59
+ evt.stopPropagation();
60
+ evt.preventDefault();
61
+ }
62
+
63
+ closeSubPanel(evt) {
64
+ this.props.closeSubArea(); // Don't pass a paremeter otherwise it will check closeSubAreaOnClick.
65
+ }
66
+
52
67
  render() {
53
68
  const style = generateSubAreaStyle(this.props.expandDirection, this.props.actionItemRect);
54
69
 
55
70
  if (this.props.subPanel) {
56
- const subPanel = typeof this.props.subPanel === "object"
57
- ? this.props.subPanel
58
- : (<this.props.subPanel closeSubPanel={this.props.closeSubArea} subPanelData={this.props.subPanelData} />);
59
-
60
71
  return (
61
- <div ref={this.areaRef} style={style} className={"toolbar-popover-list subpanel"} onClick={this.onClick} onKeyDown={this.onKeyDown}>
62
- {subPanel}
72
+ <div ref={(ref) => (this.areaRef = ref)} style={style} className={"toolbar-popover-list subpanel"} tabIndex={-1}
73
+ onKeyDown={this.onKeyDown} onFocus={this.onFocus}
74
+ >
75
+ <this.props.subPanel closeSubPanel={this.closeSubPanel} subPanelData={this.props.subPanelData} />
63
76
  </div>
64
77
  );
65
78
  }
@@ -69,11 +82,11 @@ class ToolbarSubPanel extends React.Component {
69
82
  }
70
83
 
71
84
  ToolbarSubPanel.propTypes = {
72
- subPanel: PropTypes.any,
85
+ subPanel: PropTypes.any.isRequired,
73
86
  subPanelData: PropTypes.object,
74
87
  closeSubArea: PropTypes.func,
75
88
  setToolbarFocusAction: PropTypes.func,
76
- actionItemRect: PropTypes.object.isRequired,
89
+ actionItemRect: PropTypes.object,
77
90
  expandDirection: PropTypes.string.isRequired,
78
91
  containingDivId: PropTypes.string
79
92
  };
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2023 Elyra Authors
2
+ * Copyright 2024 Elyra Authors
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
@@ -15,20 +15,25 @@
15
15
  */
16
16
 
17
17
  // These utility functions are used by both toolbar-sub-menu.jsx AND
18
- // toolbar-sub-panel.jsx to position the areaEf (menu or panel) relative
19
- // to the parent actionItemRect, passed in, in the direction indicated by
20
- // the expandDirection parameter and constrained within the <div>
21
- // specified by the containingDivId parameter.
18
+ // toolbar-sub-panel.jsx to position the sub-area (sub-menu or sub-panel)
19
+ // relative to the parent actionItemRect, passed in, in the direction
20
+ // indicated by the expandDirection parameter and constrained within
21
+ // the <div> specified by the containingDivId parameter.
22
22
 
23
23
 
24
24
  // Adjust the position of the sub-area to make sure it doesn't extend
25
25
  // outside the containing divs boundary. We need to do this after the subarea
26
26
  // has been mounted so we can query its size and position.
27
27
  export function adjustSubAreaPosition(areaRef, containingDivId, expandDirection, actionItemRect) {
28
+ if (!areaRef || !actionItemRect || !containingDivId) {
29
+ return;
30
+ }
28
31
  const containingDiv = document.getElementById(containingDivId);
29
- const containingDivRect = containingDiv.getBoundingClientRect();
32
+ const containingDivRect = containingDiv
33
+ ? containingDiv.getBoundingClientRect()
34
+ : { top: -1000, bottom: 1000, left: -1000, right: 1000 }; // To enable Jest tests.
30
35
 
31
- const thisAreaRect = areaRef.current.getBoundingClientRect();
36
+ const thisAreaRect = areaRef.getBoundingClientRect();
32
37
 
33
38
  const outsideBottom = thisAreaRect.bottom - containingDivRect.bottom;
34
39
  const outsideRight = thisAreaRect.right - containingDivRect.right;
@@ -40,28 +45,32 @@ export function adjustSubAreaPosition(areaRef, containingDivId, expandDirection,
40
45
  ? actionItemRect.top - thisAreaRect.height
41
46
  : actionItemRect.bottom - outsideBottom;
42
47
 
43
- areaRef.current.style.top = newTop + "px";
48
+ areaRef.style.top = newTop + "px";
44
49
  }
45
50
 
46
51
  if (outsideRight > 0) {
47
- const newLeft = actionItemRect.left - outsideRight - 2;
48
- areaRef.current.style.left = newLeft + "px";
52
+ const newLeft = actionItemRect.left - outsideRight;
53
+ areaRef.style.left = newLeft + "px";
49
54
  }
50
55
 
51
56
  } else {
52
57
  if (outsideBottom > 0) {
53
58
  const newTop = thisAreaRect.top - outsideBottom - 2;
54
- areaRef.current.style.top = newTop + "px";
59
+ areaRef.style.top = newTop + "px";
55
60
  }
56
61
 
57
62
  if (outsideRight > 0) {
58
63
  const newLeft = actionItemRect.left - thisAreaRect.width;
59
- areaRef.current.style.left = newLeft + "px";
64
+ areaRef.style.left = newLeft + "px";
60
65
  }
61
66
  }
62
67
  }
63
68
 
64
69
  export function generateSubAreaStyle(expandDirection, actionItemRect) {
70
+ if (!actionItemRect) {
71
+ return null;
72
+ }
73
+
65
74
  if (expandDirection === "vertical") {
66
75
  return {
67
76
  top: actionItemRect.bottom + 1,
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2017-2023 Elyra Authors
2
+ * Copyright 2017-2024 Elyra Authors
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
@@ -69,6 +69,7 @@ class Toolbar extends React.Component {
69
69
  this.generateToolbarItems = this.generateToolbarItems.bind(this);
70
70
  this.setFocusAction = this.setFocusAction.bind(this);
71
71
  this.setFocusOnItem = this.setFocusOnItem.bind(this);
72
+ this.closeAnyOpenSubArea = this.closeAnyOpenSubArea.bind(this);
72
73
  }
73
74
 
74
75
  // If, after updating, we are left in a situation where this.state.focusAction
@@ -76,13 +77,12 @@ class Toolbar extends React.Component {
76
77
  // item. This might happen when an item with focus is activated and the action it
77
78
  // performs causes itself to become disabled. For example, if the delete item is
78
79
  // activated the selected objects are deleted and since no objects are now selected
79
- // the delete item (which has focus) will become disabled.
80
+ // the delete item (which has focus) will become disabled. It may also happen if the
81
+ // toolbar config is updated and the current focusAction item is removed.
80
82
  componentDidUpdate() {
81
- if (this.isFocusInToolbar) {
82
- const index = this.getFocusableItemRefs().findIndex((item) => this.getRefAction(item) === this.state.focusAction);
83
- if (index === -1) {
84
- this.setFocusOnFirstItem();
85
- }
83
+ const index = this.getFocusableItemRefs().findIndex((item) => this.getRefAction(item) === this.state.focusAction);
84
+ if (index === -1) {
85
+ this.setFocusOnFirstItem();
86
86
  }
87
87
  }
88
88
 
@@ -106,11 +106,15 @@ class Toolbar extends React.Component {
106
106
  }
107
107
  }
108
108
 
109
- // When focus leaves the toolbar make sure we record it so we don't
110
- // accidentally set focus on a toolbar item when re-rendering with
111
- // the focus elsewhere.
112
- onBlur() {
113
- this.isFocusInToolbar = false;
109
+ // When focus leaves the toolbar make sure we record it. This prevents
110
+ // us accidentally setting focus on a toolbar item when the toolbar
111
+ // re-renders with the focus elsewhere.
112
+ onBlur(evt) {
113
+ // If the focus is being moved to an object outside the toolbar div
114
+ // we set isFocusInToolbar to false.
115
+ if (!evt.relatedTarget?.closest(".toolbar-div")) {
116
+ this.isFocusInToolbar = false;
117
+ }
114
118
  }
115
119
 
116
120
  // This is called when the user presses a key with focus on one of the
@@ -136,17 +140,48 @@ class Toolbar extends React.Component {
136
140
  }
137
141
 
138
142
  // When the toolbar resizes, check each toolbar item to see if it has
139
- // a sub-menu open and, if it does, close it.
143
+ // an open sub-area and, if that item is not a focusable item, close
144
+ // the sub-area. The item may no longer be focusable it is it was wrapped
145
+ // into the overflow menu. Also, check to see if the current focus action
146
+ // item is focusable and, if not, set focus on the first focusable item.
140
147
  onToolbarResize() {
141
- this.leftItemRefs.forEach((ref) => this.closeSubMenuOnRef(ref));
142
- this.rightItemRefs.forEach((ref) => this.closeSubMenuOnRef(ref));
143
- this.overflowItemRefs.forEach((ref) => this.closeOverflowMenuOnRef(ref));
148
+ const focusableItemRefs = this.getFocusableItemRefs();
149
+ // Note: isFocusActionFocusable needs to be calculated here before any
150
+ // update to the toolbar caused by the code in the subsequent if ststement.
151
+ const isFocusActionFocusable = this.isFocusActionFocusable(this.state.focusAction, focusableItemRefs);
152
+ const refWithOpenSubArea = this.getRefWithOpenSubArea();
153
+
154
+ if (refWithOpenSubArea) {
155
+ const action = refWithOpenSubArea.current.getAction();
156
+ const isFocusActionWithOpenSubAreaFocusable = this.isFocusActionFocusable(action, focusableItemRefs);
157
+
158
+ if (!isFocusActionWithOpenSubAreaFocusable) {
159
+ refWithOpenSubArea.current.closeSubArea();
160
+
161
+ } else {
162
+ // This forces a refresh that will cause the position of any
163
+ // open sub-area to be recaulculated based on the new toolbar width.
164
+ this.setFocusAction(this.state.focusAction);
165
+ }
166
+ }
144
167
 
145
- if (this.isFocusInToolbar) {
168
+ // If the focus action item is not focusable (maybe because it has been
169
+ // moved into the overflow menu) then set focus on the first focusable item.
170
+ if (!isFocusActionFocusable) {
146
171
  this.setFocusOnFirstItem();
147
172
  }
148
173
  }
149
174
 
175
+ // Returns the ref to any item that currently has an open sub-area or null
176
+ // if no item has an open sub-area.
177
+ getRefWithOpenSubArea() {
178
+ let subAreaOpenRef = this.leftItemRefs.find((ref) => ref.current.isSubAreaDisplayed());
179
+ if (!subAreaOpenRef) {
180
+ subAreaOpenRef = this.rightItemRefs.find((ref) => ref.current.isSubAreaDisplayed());
181
+ }
182
+ return subAreaOpenRef;
183
+ }
184
+
150
185
  // Either sets the focus on the item for the action passed in or, if
151
186
  // no action is passed in, set the focus on the current focusAction.
152
187
  // Setting the current focusAction is used to return focus back to an
@@ -192,18 +227,18 @@ class Toolbar extends React.Component {
192
227
 
193
228
  getPreviousItemRef(focusableItemRefs) {
194
229
  const index = focusableItemRefs.findIndex((item) => this.getRefAction(item) === this.state.focusAction);
195
- if (index > 0) {
196
- return focusableItemRefs[index - 1];
230
+ if (index === 0) {
231
+ return focusableItemRefs[focusableItemRefs.length - 1];
197
232
  }
198
- return null;
233
+ return focusableItemRefs[index - 1];
199
234
  }
200
235
 
201
236
  getNextItemRef(focusableItemRefs) {
202
237
  const index = focusableItemRefs.findIndex((item) => this.getRefAction(item) === this.state.focusAction);
203
- if (index < focusableItemRefs.length - 1) {
204
- return focusableItemRefs[index + 1];
238
+ if (index === focusableItemRefs.length - 1) {
239
+ return focusableItemRefs[0];
205
240
  }
206
- return null;
241
+ return focusableItemRefs[index + 1];
207
242
  }
208
243
 
209
244
  getRefAction(ref) {
@@ -313,6 +348,14 @@ class Toolbar extends React.Component {
313
348
  return index;
314
349
  }
315
350
 
351
+ // Returns true of the current focus action item is one of the focusable
352
+ // items. (It may not be if it has been placed in the overflow menu).
353
+ isFocusActionFocusable(focusAction, focusableItemRefs) {
354
+ const indexFocusAction = focusableItemRefs.findIndex((ref) =>
355
+ ref.current.props.actionObj?.action === focusAction);
356
+ return indexFocusAction > -1;
357
+ }
358
+
316
359
  // Returns a reference to the first item that is not on the
317
360
  // top (visible) row of the toolbar.
318
361
  findFirstRightItemRefNotOnTopRow() {
@@ -386,6 +429,7 @@ class Toolbar extends React.Component {
386
429
  toolbarFocusAction={this.state.focusAction}
387
430
  setToolbarFocusAction={this.setFocusOnItem}
388
431
  isFocusInToolbar={this.isFocusInToolbar}
432
+ closeAnyOpenSubArea={this.closeAnyOpenSubArea}
389
433
  size={this.props.size}
390
434
  />
391
435
  );
@@ -421,6 +465,7 @@ class Toolbar extends React.Component {
421
465
  toolbarFocusAction={this.state.focusAction}
422
466
  setToolbarFocusAction={this.setFocusOnItem}
423
467
  isFocusInToolbar={this.isFocusInToolbar}
468
+ closeAnyOpenSubArea={this.closeAnyOpenSubArea}
424
469
  />
425
470
  );
426
471
 
@@ -440,8 +485,17 @@ class Toolbar extends React.Component {
440
485
  return subMenuActions;
441
486
  }
442
487
 
443
- closeSubMenuOnRef(ref) {
444
- if (ref.current.state.subAreaDisplayed) {
488
+ closeAnyOpenSubArea() {
489
+ this.leftItemRefs.forEach((ref) => this.closeSubAreaOnRef(ref));
490
+ this.rightItemRefs.forEach((ref) => this.closeSubAreaOnRef(ref));
491
+ this.overflowItemRefs.forEach((ref) => this.closeOverflowMenuOnRef(ref));
492
+ }
493
+
494
+ closeSubAreaOnRef(ref) {
495
+ if (ref.current.props.actionObj.setExtIsSubAreaDisplayed) {
496
+ ref.current.props.actionObj.setExtIsSubAreaDisplayed(false);
497
+
498
+ } else if (ref.current.state.subAreaDisplayed) {
445
499
  ref.current.closeSubArea();
446
500
  }
447
501
  }