@elyra/canvas 12.41.0 → 12.42.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 (150) hide show
  1. package/dist/{_baseIteratee-05ccf6a8.js → _baseIteratee-148093b7.js} +3 -3
  2. package/dist/{_baseIteratee-05ccf6a8.js.map → _baseIteratee-148093b7.js.map} +1 -1
  3. package/dist/{canvas-constants-079172c0.js → canvas-constants-13b58448.js} +2 -2
  4. package/dist/{canvas-constants-079172c0.js.map → canvas-constants-13b58448.js.map} +1 -1
  5. package/dist/canvas-controller-a53943e4.js +2 -0
  6. package/dist/canvas-controller-a53943e4.js.map +1 -0
  7. package/dist/canvas-controller-cb1d7420.js +2 -0
  8. package/dist/canvas-controller-cb1d7420.js.map +1 -0
  9. package/dist/common-canvas-42027a3f.js +2 -0
  10. package/dist/common-canvas-42027a3f.js.map +1 -0
  11. package/dist/common-canvas-f758ff42.js +2 -0
  12. package/dist/common-canvas-f758ff42.js.map +1 -0
  13. package/dist/common-canvas.es.js +1 -1
  14. package/dist/common-canvas.es.js.map +1 -1
  15. package/dist/common-canvas.js +1 -1
  16. package/dist/common-canvas.js.map +1 -1
  17. package/dist/common-properties-2e1b7ec7.js +2 -0
  18. package/dist/common-properties-2e1b7ec7.js.map +1 -0
  19. package/dist/common-properties-5e8870e3.js +2 -0
  20. package/dist/common-properties-5e8870e3.js.map +1 -0
  21. package/dist/context-menu-wrapper-49f9a1af.js +2 -0
  22. package/dist/context-menu-wrapper-49f9a1af.js.map +1 -0
  23. package/dist/context-menu-wrapper-5d6a399f.js +2 -0
  24. package/dist/context-menu-wrapper-5d6a399f.js.map +1 -0
  25. package/dist/{datarecord-metadata-v3-schema-59505bc5.js → datarecord-metadata-v3-schema-98ec66e9.js} +2 -2
  26. package/dist/{datarecord-metadata-v3-schema-59505bc5.js.map → datarecord-metadata-v3-schema-98ec66e9.js.map} +1 -1
  27. package/dist/{flexible-table-43e2d052.js → flexible-table-35e9922a.js} +2 -2
  28. package/dist/{flexible-table-43e2d052.js.map → flexible-table-35e9922a.js.map} +1 -1
  29. package/dist/{flexible-table-63ffd573.js → flexible-table-7c7de0f9.js} +1 -1
  30. package/dist/{flexible-table-63ffd573.js.map → flexible-table-7c7de0f9.js.map} +1 -1
  31. package/dist/{icon-0390f1fe.js → icon-9edff40c.js} +2 -2
  32. package/dist/{icon-0390f1fe.js.map → icon-9edff40c.js.map} +1 -1
  33. package/dist/{index-57503b50.js → index-94fec521.js} +2 -2
  34. package/dist/{index-57503b50.js.map → index-94fec521.js.map} +1 -1
  35. package/dist/{index-1cd54914.js → index-e2f8a935.js} +2 -2
  36. package/dist/{index-1cd54914.js.map → index-e2f8a935.js.map} +1 -1
  37. package/dist/{isArrayLikeObject-36898fcb.js → isArrayLikeObject-7a30aa4b.js} +2 -2
  38. package/dist/{isArrayLikeObject-36898fcb.js.map → isArrayLikeObject-7a30aa4b.js.map} +1 -1
  39. package/dist/lib/canvas-controller.es.js +1 -1
  40. package/dist/lib/canvas-controller.js +1 -1
  41. package/dist/lib/canvas.es.js +1 -1
  42. package/dist/lib/canvas.js +1 -1
  43. package/dist/lib/context-menu.es.js +1 -1
  44. package/dist/lib/context-menu.js +1 -1
  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.es.js +1 -1
  50. package/dist/lib/properties.js +1 -1
  51. package/dist/lib/tooltip.es.js +1 -1
  52. package/dist/lib/tooltip.es.js.map +1 -1
  53. package/dist/lib/tooltip.js +1 -1
  54. package/dist/lib/tooltip.js.map +1 -1
  55. package/dist/styles/common-canvas.min.css +1 -1
  56. package/dist/styles/common-canvas.min.css.map +1 -1
  57. package/dist/toolbar-6acda0a2.js +2 -0
  58. package/dist/toolbar-6acda0a2.js.map +1 -0
  59. package/dist/toolbar-d5647da2.js +2 -0
  60. package/dist/toolbar-d5647da2.js.map +1 -0
  61. package/package.json +12 -4
  62. package/src/color-picker/color-picker.jsx +92 -17
  63. package/src/command-actions/arrangeLayoutAction.js +7 -6
  64. package/src/command-actions/attachNodeToLinksAction.js +4 -4
  65. package/src/command-actions/collapseSuperNodeInPlaceAction.js +5 -5
  66. package/src/command-actions/colorSelectedObjectsAction.js +4 -4
  67. package/src/command-actions/commonPropertiesAction.js +1 -1
  68. package/src/command-actions/convertSuperNodeExternalToLocalAction.js +4 -4
  69. package/src/command-actions/convertSuperNodeLocalToExternalAction.js +4 -4
  70. package/src/command-actions/createAutoNodeAction.js +14 -5
  71. package/src/command-actions/createCommentAction.js +4 -10
  72. package/src/command-actions/createCommentLinkAction.js +4 -4
  73. package/src/command-actions/createNodeAction.js +13 -4
  74. package/src/command-actions/createNodeAttachLinksAction.js +4 -4
  75. package/src/command-actions/createNodeLinkAction.js +13 -4
  76. package/src/command-actions/createNodeLinkDetachedAction.js +4 -4
  77. package/src/command-actions/createNodeOnLinkAction.js +4 -4
  78. package/src/command-actions/createSuperNodeAction.js +7 -7
  79. package/src/command-actions/deconstructSuperNodeAction.js +5 -5
  80. package/src/command-actions/deleteLinkAction.js +4 -4
  81. package/src/command-actions/deleteObjectsAction.js +15 -6
  82. package/src/command-actions/disconnectObjectsAction.js +13 -4
  83. package/src/command-actions/displayPreviousPipelineAction.js +4 -4
  84. package/src/command-actions/displaySubPipelineAction.js +4 -4
  85. package/src/command-actions/editCommentAction.js +4 -4
  86. package/src/command-actions/editDecorationLabelAction.js +4 -4
  87. package/src/command-actions/expandSuperNodeInPlaceAction.js +5 -5
  88. package/src/command-actions/insertNodeIntoLinkAction.js +4 -4
  89. package/src/command-actions/moveObjectsAction.js +4 -4
  90. package/src/command-actions/pasteAction.js +16 -7
  91. package/src/command-actions/saveToPaletteAction.js +4 -4
  92. package/src/command-actions/setLinksStyleAction.js +4 -4
  93. package/src/command-actions/setNodeLabelAction.js +4 -4
  94. package/src/command-actions/setObjectsStyleAction.js +4 -4
  95. package/src/command-actions/sizeAndPositionObjectsAction.js +4 -4
  96. package/src/command-actions/updateLinkAction.js +4 -4
  97. package/src/common-canvas/canvas-controller-menu-utils.js +1 -1
  98. package/src/common-canvas/canvas-controller.js +78 -62
  99. package/src/common-canvas/cc-central-items.jsx +1 -1
  100. package/src/common-canvas/cc-context-toolbar.jsx +9 -13
  101. package/src/common-canvas/cc-toolbar.jsx +2 -0
  102. package/src/common-canvas/svg-canvas-renderer.js +6 -2
  103. package/src/common-canvas/svg-canvas-utils-drag-det-link.js +8 -1
  104. package/src/common-canvas/svg-canvas-utils-drag-new-link.js +1 -1
  105. package/src/common-properties/components/table-buttons/table-buttons.scss +0 -1
  106. package/src/common-properties/controls/expression/expression-builder/expression-builder.jsx +32 -26
  107. package/src/common-properties/controls/expression/expression.jsx +146 -117
  108. package/src/common-properties/controls/expression/expression.scss +43 -45
  109. package/src/common-properties/controls/expression/languages/CLEM-hint.js +86 -159
  110. package/src/common-properties/controls/expression/languages/python-hint.js +53 -104
  111. package/src/common-properties/controls/expression/languages/r-hint.js +55 -130
  112. package/src/common-properties/properties-controller.js +5 -0
  113. package/src/context-menu/common-context-menu.jsx +4 -1
  114. package/src/index.js +12 -2
  115. package/src/object-model/redux/canvas-store.js +4 -3
  116. package/src/toolbar/toolbar-action-item.jsx +90 -314
  117. package/src/toolbar/toolbar-button-item.jsx +354 -0
  118. package/src/toolbar/toolbar-divider-item.jsx +3 -4
  119. package/src/toolbar/toolbar-overflow-item.jsx +82 -36
  120. package/src/toolbar/toolbar-sub-menu-item.jsx +235 -0
  121. package/src/toolbar/toolbar-sub-menu.jsx +254 -0
  122. package/src/toolbar/toolbar-sub-panel.jsx +81 -0
  123. package/src/toolbar/toolbar-sub-utils.js +77 -0
  124. package/src/toolbar/toolbar.jsx +330 -146
  125. package/src/toolbar/toolbar.scss +22 -15
  126. package/src/tooltip/tooltip.jsx +9 -2
  127. package/stats.html +1 -1
  128. package/dist/canvas-controller-1e71b405.js +0 -2
  129. package/dist/canvas-controller-1e71b405.js.map +0 -1
  130. package/dist/canvas-controller-4bed5320.js +0 -2
  131. package/dist/canvas-controller-4bed5320.js.map +0 -1
  132. package/dist/common-canvas-097c5169.js +0 -2
  133. package/dist/common-canvas-097c5169.js.map +0 -1
  134. package/dist/common-canvas-e13c0858.js +0 -2
  135. package/dist/common-canvas-e13c0858.js.map +0 -1
  136. package/dist/common-properties-706cef87.js +0 -2
  137. package/dist/common-properties-706cef87.js.map +0 -1
  138. package/dist/common-properties-9bd69b61.js +0 -2
  139. package/dist/common-properties-9bd69b61.js.map +0 -1
  140. package/dist/context-menu-wrapper-3a7fdec8.js +0 -2
  141. package/dist/context-menu-wrapper-3a7fdec8.js.map +0 -1
  142. package/dist/context-menu-wrapper-fc85d853.js +0 -2
  143. package/dist/context-menu-wrapper-fc85d853.js.map +0 -1
  144. package/dist/toolbar-918ab52e.js +0 -2
  145. package/dist/toolbar-918ab52e.js.map +0 -1
  146. package/dist/toolbar-fdb750f9.js +0 -2
  147. package/dist/toolbar-fdb750f9.js.map +0 -1
  148. package/src/toolbar/toolbar-action-sub-area.jsx +0 -126
  149. package/src/toolbar/toolbar-overflow-menu.jsx +0 -77
  150. package/src/toolbar/toolbar-utils.js +0 -33
@@ -22,31 +22,110 @@ import ToolbarActionItem from "./toolbar-action-item.jsx";
22
22
  import ToolbarOverflowItem from "./toolbar-overflow-item.jsx";
23
23
  import ToolbarDividerItem from "./toolbar-divider-item.jsx";
24
24
 
25
+ const ESC_KEY = 27;
26
+ const LEFT_ARROW_KEY = 37;
27
+ const RIGHT_ARROW_KEY = 39;
28
+
25
29
  class Toolbar extends React.Component {
26
30
  constructor(props) {
27
31
  super(props);
28
32
 
33
+ // this.state.focusAction keeps track of which item has focus.
34
+ // This is used to ensure the focus goes to the same item that was
35
+ // previously focused when focus was lost (blurred) from the toolbar
36
+ // Index values (leftOverflowIndex and rightOverflowIndex) are used
37
+ // to keep track of how the left and right bar arrays
38
+ // should be split to be able to create the overflow menu.
39
+ this.state = {
40
+ focusAction: "toolbar",
41
+ leftOverflowIndex: null,
42
+ rightOverflowIndex: null,
43
+ };
44
+
45
+ // Keeps track of whether the focus is on the toolbar or not. We should
46
+ // not call focus() on any item in the toolbar if this.isFocusInToolbar
47
+ // is false, otherwise focus will be moved incorrectly to the toolbar
48
+ // and away from its current location.
49
+ this.isFocusInToolbar = false;
50
+
51
+ // Arrays to hold the left and right bar configurations
29
52
  this.leftBar = [];
30
53
  this.rightBar = [];
31
54
 
32
- this.resizeHandler = null;
55
+ // Arrays to store references to React objects in toolbar for the
56
+ // the left bar, right bar and current set of overflow items.
57
+ this.leftItemRefs = [];
58
+ this.rightItemRefs = [];
59
+ this.overflowItemRefs = [];
60
+
61
+ // Reference for the toolbar <div>
62
+ this.toolbarRef = React.createRef();
63
+
33
64
  this.onFocus = this.onFocus.bind(this);
65
+ this.onBlur = this.onBlur.bind(this);
66
+ this.onKeyDown = this.onKeyDown.bind(this);
34
67
  this.onToolbarResize = this.onToolbarResize.bind(this);
35
- this.generateExtensionMenuItems = this.generateExtensionMenuItems.bind(this);
68
+ this.setOverflowIndex = this.setOverflowIndex.bind(this);
36
69
  this.generateToolbarItems = this.generateToolbarItems.bind(this);
37
- this.setResizeHandler = this.setResizeHandler.bind(this);
70
+ this.setFocusAction = this.setFocusAction.bind(this);
71
+ this.setFocusOnItem = this.setFocusOnItem.bind(this);
72
+ }
73
+
74
+ // If, after updating, we are left in a situation where this.state.focusAction
75
+ // is for an item that is NOT focusable, then set the focus on the first focusable
76
+ // item. This might happen when an item with focus is activated and the action it
77
+ // performs causes itself to become disabled. For example, if the delete item is
78
+ // activated the selected objects are deleted and since no objects are now selected
79
+ // the delete item (which has focus) will become disabled.
80
+ 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
+ }
86
+ }
87
+ }
88
+
89
+ // When the toolbar is initially focused, this.state.focusAction
90
+ // will be set to the default of "toolbar". In that case we set the
91
+ // focus on the first focusable toolbar item.
92
+ onFocus(evt) {
93
+ this.isFocusInToolbar = true;
94
+
95
+ // If focus occurs because of a click on the toolbar body
96
+ // (not on a button) and no button has focus move focus to
97
+ // the first item otherwise just keep focus the same.
98
+ if (evt.target?.classList?.contains("toolbar-div")) {
99
+ if (this.state.focusAction === "toolbar") {
100
+ this.setFocusOnFirstItem();
101
+
102
+
103
+ } else {
104
+ this.setFocusOnItem(); // Reset focus on current focusAction.
105
+ }
106
+ }
107
+ }
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;
38
114
  }
39
115
 
40
- // When the toolbar is initially opened the tabindex for each element may not
41
- // be set correctly because of the time it takes to initially render the DOM.
42
- // Typically, this means the tabindex is not set correctly on whichever
43
- // overflow menu icon is displayed. Therefore, as the user moves the focus
44
- // to the first element in the toolbar (whose tabindex IS typically OK) we
45
- // set the tabindex for all elements again, this then sets the overflow
46
- // icon's tabindex correctly.
47
- onFocus() {
48
- this.setLeftBarItemsTabIndex();
49
- this.setRightBarItemsTabIndex();
116
+ // This is called when the user presses a key with focus on one of the
117
+ // toolbar items. We set the focusAction appropriately based on which
118
+ // key is pressed.
119
+ onKeyDown(evt) {
120
+ if (evt.keyCode === ESC_KEY) {
121
+ this.setFocusOnItem(); // Reset focus on current focusAction.
122
+
123
+ } else if (evt.keyCode === LEFT_ARROW_KEY) {
124
+ this.setFocusOnPreviousItem();
125
+
126
+ } else if (evt.keyCode === RIGHT_ARROW_KEY) {
127
+ this.setFocusOnNextItem();
128
+ }
50
129
  }
51
130
 
52
131
  // Prevents the inline-block elements of the left bar being scrolled to
@@ -56,166 +135,257 @@ class Toolbar extends React.Component {
56
135
  evt.preventDefault();
57
136
  }
58
137
 
59
- // Close the overflow menu, if it is open, when the toolbar is resized in
60
- // case a new menu needs to be displayed with the new toolbar width.
138
+ // When the toolbar resizes, check each toolbar item to see if it has
139
+ // a sub-menu open and, if it does, close it.
61
140
  onToolbarResize() {
62
- if (this.resizeHandler) {
63
- this.resizeHandler();
64
- }
141
+ this.leftItemRefs.forEach((ref) => this.closeSubMenuOnRef(ref));
142
+ this.rightItemRefs.forEach((ref) => this.closeSubMenuOnRef(ref));
143
+ this.overflowItemRefs.forEach((ref) => this.closeOverflowMenuOnRef(ref));
65
144
 
66
- this.setLeftBarItemsTabIndex();
67
- this.setRightBarItemsTabIndex();
145
+ if (this.isFocusInToolbar) {
146
+ this.setFocusOnFirstItem();
147
+ }
68
148
  }
69
149
 
70
- // Allows the overflow item or action item to set a function that will be
71
- // called when the toolbar is resized. This function causes the menu that
72
- // is currently open to be closed.
73
- setResizeHandler(resizeHandler) {
74
- this.resizeHandler = resizeHandler;
150
+ // Either sets the focus on the item for the action passed in or, if
151
+ // no action is passed in, set the focus on the current focusAction.
152
+ // Setting the current focusAction is used to return focus back to an
153
+ // item after focus has been moved elsewhere, such as onto a sub-menu
154
+ // or out of the toolbar completely.
155
+ setFocusOnItem(action) {
156
+ const actionToSet = action || this.state.focusAction;
157
+ const focusableItemRefs = this.getFocusableItemRefs();
158
+ if (focusableItemRefs.length > 0) {
159
+ this.setFocusAction(actionToSet);
160
+ }
75
161
  }
76
162
 
77
- // Sets the tabindex on all left bar items so tabbing works correctly. This
78
- // falls into two parts: 1. Set the tabindex for all overflow items to -1
79
- // except the overflow item that is displayed (if there is one). 2. Set the
80
- // tabindex of all hidden regular toolbar items to -1 and to 0 for all
81
- // displayed regular toolbar items.
82
- // Note: We detect the y coordinate of the 'top row' by using the top of
83
- // the first overflow icon. This is because the toolbar might be compressed
84
- // to the extent that the first overflow icon is the only item on the left
85
- // of the toolbar.
86
- setLeftBarItemsTabIndex() {
87
- const bar = this.getBar("left");
88
- if (!bar) {
89
- return;
163
+ setFocusOnFirstItem() {
164
+ const focusableItemRefs = this.getFocusableItemRefs();
165
+ if (focusableItemRefs.length > 0) {
166
+ const firstFocusAction = this.getRefAction(focusableItemRefs[0]);
167
+ this.setFocusAction(firstFocusAction);
90
168
  }
169
+ }
91
170
 
92
- const items = bar.querySelectorAll("[data-toolbar-item=true]") || [];
93
- const topRow = this.getTopOfFirstOverflowItem(bar);
94
- let lastTopRowElement = -1;
171
+ setFocusOnPreviousItem() {
172
+ const focusableItemRefs = this.getFocusableItemRefs();
173
+ const previousRef = this.getPreviousItemRef(focusableItemRefs);
174
+ if (previousRef) {
175
+ const previousFocusAction = this.getRefAction(previousRef);
176
+ this.setFocusAction(previousFocusAction);
177
+ }
178
+ }
95
179
 
96
- for (let i = 0; i < items.length; i++) {
97
- const itemRect = items[i].getBoundingClientRect();
180
+ setFocusOnNextItem() {
181
+ const focusableItemRefs = this.getFocusableItemRefs();
182
+ const nextRef = this.getNextItemRef(focusableItemRefs);
183
+ if (nextRef) {
184
+ const nextFocusAction = this.getRefAction(nextRef);
185
+ this.setFocusAction(nextFocusAction);
186
+ }
187
+ }
98
188
 
99
- this.setOverflowItemButtonTabIndex(i, -1, bar);
189
+ setFocusAction(focusAction) {
190
+ this.setState({ focusAction });
191
+ }
100
192
 
101
- if (itemRect.top === topRow) {
102
- lastTopRowElement = i;
103
- this.setToolbarItemButtonTabIndex(items[i], 0);
104
- } else {
105
- this.setToolbarItemButtonTabIndex(items[i], -1);
106
- }
193
+ getPreviousItemRef(focusableItemRefs) {
194
+ const index = focusableItemRefs.findIndex((item) => this.getRefAction(item) === this.state.focusAction);
195
+ if (index > 0) {
196
+ return focusableItemRefs[index - 1];
107
197
  }
198
+ return null;
199
+ }
108
200
 
109
- if (lastTopRowElement < items.length) {
110
- this.setOverflowItemButtonTabIndex(lastTopRowElement + 1, 0, bar);
201
+ getNextItemRef(focusableItemRefs) {
202
+ const index = focusableItemRefs.findIndex((item) => this.getRefAction(item) === this.state.focusAction);
203
+ if (index < focusableItemRefs.length - 1) {
204
+ return focusableItemRefs[index + 1];
111
205
  }
206
+ return null;
112
207
  }
113
208
 
114
- // Sets the tabindex on all right bar items so tabbing works correctly. This
115
- // involves setting the tabindex of all hidden regular toolbar items to -1
116
- // and to 0 for all displayed regular toolbar items.
117
- setRightBarItemsTabIndex() {
118
- const items = this.getRightBarItems();
119
- let topRow = 0;
209
+ getRefAction(ref) {
210
+ return ref.current.getAction();
211
+ }
120
212
 
121
- for (let i = 0; i < items.length; i++) {
122
- const itemRect = items[i].getBoundingClientRect();
213
+ // Returns an array of references to focusable (that is enabled)
214
+ // toolbar items that are on the top (visible) row of the toolbar.
215
+ getFocusableItemRefs() {
216
+ return this.getLeftBarFocusableItemRefs().concat(this.getRightBarFocusableItemRefs());
217
+ }
123
218
 
124
- if (i === 0) {
125
- topRow = itemRect.top;
126
- }
219
+ // Returns an array of references to left bar items that are
220
+ // on the top (visible) row of the toolbar and are focusable.
221
+ // That is, not disabled. In addition, there may also be a
222
+ // reference to an overflow item if one is visible on the
223
+ // top (visible) row of the toolbar.
224
+ getLeftBarFocusableItemRefs() {
225
+ const focusableItemRefs = [];
127
226
 
128
- if (itemRect.top === topRow) {
129
- this.setToolbarItemButtonTabIndex(items[i], 0);
130
- } else {
131
- this.setToolbarItemButtonTabIndex(items[i], -1);
227
+ if (this.leftItemRefs.length === 0) {
228
+ return focusableItemRefs;
229
+ }
230
+
231
+ const topRowY = this.findToolbarTopYCoordinate();
232
+ let overflowItemRef = null;
233
+
234
+ for (let i = 0; i < this.leftItemRefs.length; i++) {
235
+ const itemRect = this.leftItemRefs[i].current.getBoundingRect();
236
+
237
+ if (itemRect.top === topRowY) {
238
+ if (this.leftItemRefs[i].current.isEnabled()) {
239
+ focusableItemRefs.push(this.leftItemRefs[i]);
240
+ }
241
+
242
+ } else if (!overflowItemRef) {
243
+ const leftRefAction = this.getRefAction(this.leftItemRefs[i]);
244
+ const overflowAction = this.getOverflowAction(leftRefAction);
245
+ overflowItemRef = this.overflowItemRefs.find((oRef) => oRef.current.getAction() === overflowAction);
246
+ if (overflowItemRef) {
247
+ focusableItemRefs.push(overflowItemRef);
248
+ }
132
249
  }
133
250
  }
134
- }
135
251
 
136
- getBar(side) {
137
- const id = this.props.instanceId;
138
- const part = document.querySelector(`.toolbar-div[instanceid='${id}'] > .toolbar-${side}-bar`) || [];
139
- return part;
252
+ return focusableItemRefs;
140
253
  }
141
254
 
142
- getRightBarItems() {
143
- const bar = this.getBar("right");
144
- if (!bar) {
145
- return [];
255
+ // Returns an array of references to right bar items that are
256
+ // on the top (visible) row of the toolbar and are focusable.
257
+ // That is, not disabled.
258
+ getRightBarFocusableItemRefs() {
259
+ const focusableItemRefs = [];
260
+
261
+ if (this.rightItemRefs === 0) {
262
+ return focusableItemRefs;
146
263
  }
147
- return bar.querySelectorAll("[data-toolbar-item=true]") || [];
148
- }
149
264
 
150
- getTopOfFirstOverflowItem(bar) {
151
- const firstOverflowItem = this.getOverflowItem(0, bar);
152
- if (firstOverflowItem) {
153
- const rect = firstOverflowItem.getBoundingClientRect();
154
- return rect.top;
265
+ const topRowY = this.findToolbarTopYCoordinate();
266
+
267
+ for (let i = 0; i < this.rightItemRefs.length; i++) {
268
+ if (this.rightItemRefs[i].current.isEnabled()) {
269
+ const refRect = this.rightItemRefs[i].current.getBoundingRect();
270
+
271
+ if (refRect.top === topRowY) {
272
+ focusableItemRefs.push(this.rightItemRefs[i]);
273
+ }
274
+ }
155
275
  }
156
- return 0;
276
+ return focusableItemRefs.reverse();
157
277
  }
158
278
 
159
- getOverflowItem(index, bar) {
160
- const overflowClassName = "toolbar-index-" + index;
161
- return bar.getElementsByClassName(overflowClassName)[0];
279
+ // Items that appear in the overflow menu need unique action names because
280
+ // the original action item to which they are related will still exist, but
281
+ // hidden, in the toolbar.
282
+ getOverflowAction(action) {
283
+ return "overflow_" + action;
162
284
  }
163
285
 
164
- setToolbarItemButtonTabIndex(item, tabIndex) {
165
- const button = item.querySelector("button");
166
- if (button) {
167
- button.setAttribute("tabindex", tabIndex);
286
+ // Sets two index values: one for the left bar and one for the right that
287
+ // indicate which elements in each array should be put in the overflow menu.
288
+ // That is, those elements that do not appear on the top (visible) row of the
289
+ // toolbar.
290
+ setOverflowIndex(leftIndex) {
291
+ if (leftIndex === null) {
292
+ this.setState({
293
+ leftOverflowIndex: null,
294
+ rightOverflowIndex: null
295
+ });
296
+ } else {
297
+ this.setState({
298
+ leftOverflowIndex: leftIndex,
299
+ rightOverflowIndex: this.getRightOverflowIndex()
300
+ });
168
301
  }
169
302
  }
170
303
 
171
- setOverflowItemButtonTabIndex(index, tabIndex, bar) {
172
- const overflowItem = this.getOverflowItem(index, bar);
173
- if (overflowItem) {
174
- const overflowButton = overflowItem.querySelector("button");
175
- if (overflowButton) {
176
- overflowButton.setAttribute("tabindex", tabIndex);
304
+ // Returns the index of the first item in the right bar that is
305
+ // not on the top (visible) row of the toolbar.
306
+ getRightOverflowIndex() {
307
+ const ref = this.findFirstRightItemRefNotOnTopRow();
308
+
309
+ const index = ref === null
310
+ ? this.rightBar.length - 1
311
+ : this.rightBar.findIndex((ri) => ri.action === this.getRefAction(ref));
312
+
313
+ return index;
314
+ }
315
+
316
+ // Returns a reference to the first item that is not on the
317
+ // top (visible) row of the toolbar.
318
+ findFirstRightItemRefNotOnTopRow() {
319
+ const topRowY = this.findToolbarTopYCoordinate();
320
+
321
+ let rightItemRef = null;
322
+
323
+ for (let i = 0; i < this.rightItemRefs.length; i++) {
324
+ const itemRect = this.rightItemRefs[i].current.getBoundingRect();
325
+ if (itemRect.top !== topRowY && rightItemRef === null) {
326
+ rightItemRef = this.rightItemRefs[i];
177
327
  }
178
328
  }
329
+ return rightItemRef;
179
330
  }
180
331
 
181
- generateToolbarItems(actionDefinitions, overflow, withSpacer) {
332
+ // Returns the Y coordinate of the top of the toolbar. This is
333
+ // used to detecg which toolbar items are on the top (visible)
334
+ // row and which are wrapped onto other rows.
335
+ findToolbarTopYCoordinate() {
336
+ const rect = this.toolbarRef.current.getBoundingClientRect();
337
+ return rect.top;
338
+ }
339
+
340
+ // Generates an array of toolbar items from the toolbarActions array passed in. When
341
+ // withOverflowItem is true, which it is for the left bar, we also add an overflow item,
342
+ // inside an overflow item container, for each left toolbar action. As the canvas is made
343
+ // narrower the regular action items wrap onto a second (hidden) row of the toolbar and
344
+ // the overflow item, associated with the last wrapped action item, is revealed.
345
+ generateToolbarItems(toolbarActions, withOverflowItem, refs) {
182
346
  const newItems = [];
183
347
 
184
- for (let i = 0; i < actionDefinitions.length; i++) {
185
- const actionObj = actionDefinitions[i];
348
+ for (let i = 0; i < toolbarActions.length; i++) {
349
+ const actionObj = toolbarActions[i];
186
350
  if (actionObj) {
187
- if (withSpacer && !actionObj.divider) {
188
- newItems.push(this.generateOverflowIcon(i));
351
+ if (!actionObj.divider && withOverflowItem) {
352
+ newItems.push(this.generateOverflowItem(i, actionObj.action));
189
353
  }
190
- newItems.push(this.generateToolbarItem(actionObj, i, overflow));
354
+ newItems.push(this.generateToolbarItem(actionObj, i, refs));
191
355
  }
192
356
  }
193
357
  return newItems;
194
358
  }
195
359
 
196
- generateToolbarItem(actionObj, i, overflow) {
360
+ // Returns JSX for a toolbar item based on the actionObj passed in.
361
+ generateToolbarItem(actionObj, i, refs) {
197
362
  let jsx = null;
363
+
198
364
  if (actionObj) {
199
365
  if (actionObj.divider) {
200
366
  jsx = (
201
367
  <ToolbarDividerItem
202
368
  key={"toolbar-item-key-" + i}
203
- overflow={overflow}
369
+ isInMenu={false}
204
370
  />
205
371
  );
206
372
  } else {
373
+ const ref = React.createRef();
374
+ if (refs) {
375
+ refs.push(ref);
376
+ }
207
377
  jsx = (
208
378
  <ToolbarActionItem
379
+ ref={ref}
209
380
  key={"toolbar-item-key-" + i}
210
381
  actionObj={actionObj}
211
382
  tooltipDirection={this.props.tooltipDirection}
212
383
  toolbarActionHandler={this.props.toolbarActionHandler}
213
- generateToolbarItems={this.generateToolbarItems}
214
- setResizeHandler={this.setResizeHandler}
215
- overflow={overflow}
216
384
  instanceId={this.props.instanceId}
217
385
  containingDivId={this.props.containingDivId}
218
- onFocus={this.onFocus}
386
+ toolbarFocusAction={this.state.focusAction}
387
+ setToolbarFocusAction={this.setFocusOnItem}
388
+ isFocusInToolbar={this.isFocusInToolbar}
219
389
  size={this.props.size}
220
390
  />
221
391
  );
@@ -224,57 +394,62 @@ class Toolbar extends React.Component {
224
394
  return jsx;
225
395
  }
226
396
 
227
- generateOverflowIcon(index) {
397
+ // Returns JSX for an overflow toolbar item based on the index and action passed in.
398
+ generateOverflowItem(index, action) {
228
399
  const label = this.props.additionalText ? this.props.additionalText.overflowMenuLabel : "";
400
+ const overflowAction = this.getOverflowAction(action);
401
+ const subMenuActions = index === this.state.leftOverflowIndex ? this.createSubMenuActions() : [];
402
+
403
+ // Create a ref for the overflow item to add to array of references to
404
+ // all overflow items.
405
+ const ref = React.createRef();
406
+ this.overflowItemRefs.push(ref);
407
+
229
408
  const jsx = (
230
409
  <ToolbarOverflowItem
410
+ ref={ref}
231
411
  key={"toolbar-overflow-item-key-" + index}
232
412
  index={index}
233
- generateExtensionMenuItems={this.generateExtensionMenuItems}
234
- setResizeHandler={this.setResizeHandler}
235
- containingDivId={this.props.containingDivId}
236
- onFocus={this.onFocus}
413
+ action={overflowAction}
237
414
  label={label}
238
415
  size={this.props.size}
416
+ subMenuActions={subMenuActions}
417
+ setOverflowIndex={this.setOverflowIndex}
418
+ toolbarActionHandler={this.props.toolbarActionHandler}
419
+ instanceId={this.props.instanceId}
420
+ containingDivId={this.props.containingDivId}
421
+ toolbarFocusAction={this.state.focusAction}
422
+ setToolbarFocusAction={this.setFocusOnItem}
423
+ isFocusInToolbar={this.isFocusInToolbar}
239
424
  />
240
425
  );
241
426
 
242
427
  return jsx;
243
428
  }
244
429
 
245
- // Generates an array of action definition elements that correspond to the
246
- // hidden DOM items on the left and right of the toolbar. For any left bar
247
- // items we can use the leftIndex passed in to split the leftBar defintion
248
- // array, however for the right side we need to loop through the DOM items
249
- // and discover which is hidden and which is displayed.
250
- generateExtensionMenuItems(leftIndex) {
251
- const rightItems = this.generateRightOverflowItems();
252
- rightItems.reverse();
253
-
254
- const overflowMenuBarItems = this.leftBar.slice(leftIndex).concat(rightItems);
255
- const extensionItems = this.generateToolbarItems(overflowMenuBarItems, true, false);
256
- return extensionItems;
430
+ // Returns an array of overflow menu actions that should be displayed in
431
+ // the overflow menu for the overflow item indicated by the index passed in.
432
+ // This uses this.state.leftOverflowIndex and this.state.rightOverflowIndex which are
433
+ // set when the user clicks on a particular overflow item in the toolbar.
434
+ createSubMenuActions() {
435
+ let subMenuActions = [];
436
+ const l = this.leftBar.slice(this.state.leftOverflowIndex);
437
+ const r = this.rightBar.slice(this.state.rightOverflowIndex).reverse();
438
+ subMenuActions = l.concat(r);
439
+
440
+ return subMenuActions;
257
441
  }
258
442
 
259
- // Generates an array of right side defintion items that correspond to
260
- // right side DOM items that are hidden.
261
- generateRightOverflowItems() {
262
- const newDefItems = [];
263
- const items = this.getRightBarItems();
264
- let topRow = 0;
265
-
266
- for (let i = 0; i < items.length; i++) {
267
- const rect = items[i].getBoundingClientRect();
268
-
269
- if (i === 0) {
270
- topRow = rect.top;
271
- }
443
+ closeSubMenuOnRef(ref) {
444
+ if (ref.current.state.subAreaDisplayed) {
445
+ ref.current.closeSubArea();
446
+ }
447
+ }
272
448
 
273
- if (rect.top !== topRow) {
274
- newDefItems.push(this.rightBar[i]);
275
- }
449
+ closeOverflowMenuOnRef(ref) {
450
+ if (ref.current.state.showExtendedMenu) {
451
+ ref.current.closeSubMenu();
276
452
  }
277
- return newDefItems;
278
453
  }
279
454
 
280
455
  render() {
@@ -282,13 +457,22 @@ class Toolbar extends React.Component {
282
457
  this.rightBar = this.props.config.rightBar || [];
283
458
  this.rightBar = [...this.rightBar].reverse() || [];
284
459
 
285
- const leftItems = this.generateToolbarItems(this.leftBar, false, true);
286
- const rightItems = this.generateToolbarItems(this.rightBar, false, false);
460
+ // Arrays to store references to React objects in toolbar.
461
+ this.leftItemRefs = [];
462
+ this.rightItemRefs = [];
463
+ this.overflowItemRefs = [];
464
+
465
+ const leftItems = this.generateToolbarItems(this.leftBar, true, this.leftItemRefs);
466
+ const rightItems = this.generateToolbarItems(this.rightBar, false, this.rightItemRefs);
287
467
 
288
468
  const toolbarSizeClass = this.props.size === "sm" ? "toolbar-div toolbar-size-small" : "toolbar-div";
469
+ const tabIndex = this.state.focusAction === "toolbar" ? 0 : -1;
470
+
289
471
  const canvasToolbar = (
290
472
  <ReactResizeDetector handleWidth onResize={this.onToolbarResize}>
291
- <div className={toolbarSizeClass} instanceid={this.props.instanceId}>
473
+ <div ref={this.toolbarRef} className={toolbarSizeClass} instanceid={this.props.instanceId}
474
+ tabIndex={tabIndex} onFocus={this.onFocus} onBlur={this.onBlur} onKeyDown={this.onKeyDown}
475
+ >
292
476
  <div className="toolbar-left-bar" onScroll={this.onScroll}>
293
477
  {leftItems}
294
478
  </div>