@eclipse-scout/core 22.0.0-beta.1 → 22.0.0-beta.5

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 (45) hide show
  1. package/dist/eclipse-scout-core-theme-dark.css +71 -20
  2. package/dist/eclipse-scout-core-theme-dark.css.map +1 -1
  3. package/dist/eclipse-scout-core-theme.css +70 -19
  4. package/dist/eclipse-scout-core-theme.css.map +1 -1
  5. package/dist/eclipse-scout-core.js +295 -242
  6. package/dist/eclipse-scout-core.js.map +1 -1
  7. package/package.json +2 -2
  8. package/src/App.js +71 -9
  9. package/src/RemoteApp.js +1 -0
  10. package/src/desktop/Desktop.js +3 -3
  11. package/src/desktop/notification/DesktopNotification.js +33 -8
  12. package/src/desktop/outline/Outline.js +2 -2
  13. package/src/desktop/outline/Outline.less +14 -6
  14. package/src/desktop/outline/OutlineViewButton.js +1 -0
  15. package/src/filechooser/FileChooser.js +2 -1
  16. package/src/form/Form.js +11 -3
  17. package/src/glasspane/DeferredGlassPaneTarget.js +2 -2
  18. package/src/login/LoginBox.less +8 -1
  19. package/src/main.less +1 -1
  20. package/src/menu/ComboMenu.js +22 -17
  21. package/src/menu/ComboMenu.less +71 -26
  22. package/src/menu/ContextMenuPopup.js +3 -2
  23. package/src/menu/ContextMenuPopup.less +1 -1
  24. package/src/menu/EllipsisMenu.js +4 -0
  25. package/src/menu/Menu.js +24 -11
  26. package/src/menu/menubar/MenuBar.js +2 -19
  27. package/src/menu/menubar/MenuBarBox.js +3 -34
  28. package/src/menu/menubar/MenuBarLayout.js +6 -19
  29. package/src/menu/menubox/MenuBoxLayout.js +1 -1
  30. package/src/menu/menus.js +4 -10
  31. package/src/messagebox/MessageBox.js +3 -22
  32. package/src/modeselector/ModeSelectorLayout.js +11 -6
  33. package/src/notification/Notification.js +15 -14
  34. package/src/popup/Popup.js +3 -20
  35. package/src/session/BusyIndicator.js +2 -1
  36. package/src/session/Session.js +0 -62
  37. package/src/status/Status.js +2 -1
  38. package/src/style/colors-dark.less +3 -1
  39. package/src/style/colors.less +2 -0
  40. package/src/style/sizes.less +1 -0
  41. package/src/table/columns/Column.js +4 -0
  42. package/src/tree/Tree.js +2 -2
  43. package/src/widget/LoadingSupport.js +1 -1
  44. package/src/widget/Widget.js +28 -32
  45. package/src/widget/WidgetSupport.js +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eclipse-scout/core",
3
- "version": "22.0.0-beta.1",
3
+ "version": "22.0.0-beta.5",
4
4
  "description": "Eclipse Scout runtime",
5
5
  "author": "BSI Business Systems Integration AG",
6
6
  "homepage": "https://www.eclipse.org/scout",
@@ -26,7 +26,7 @@
26
26
  "src"
27
27
  ],
28
28
  "devDependencies": {
29
- "@eclipse-scout/cli": "22.0.0-beta.1",
29
+ "@eclipse-scout/cli": "22.0.0-beta.5",
30
30
  "@eclipse-scout/releng": "^22.0.0",
31
31
  "jasmine-core": "3.10.1",
32
32
  "jasmine-ajax": "4.0.0",
package/src/App.js CHANGED
@@ -38,6 +38,7 @@ export default class App {
38
38
  this.events = this._createEventSupport();
39
39
  this.initialized = false;
40
40
  this.sessions = [];
41
+ this._loadingTimeoutId = null;
41
42
 
42
43
  // register the listeners which were added to scout before the app is created
43
44
  listeners.forEach(function(listener) {
@@ -195,8 +196,11 @@ export default class App {
195
196
  */
196
197
  _init(options) {
197
198
  options = options || {};
198
- if (!this._checkBrowserCompatibility(options)) {
199
- return;
199
+ this.setLoading(true)
200
+ let compatibilityPromise = this._checkBrowserCompatibility(options);
201
+ if (compatibilityPromise) {
202
+ this.setLoading(false);
203
+ return compatibilityPromise.then(newOptions => this._init(newOptions));
200
204
  }
201
205
 
202
206
  this._initVersion(options);
@@ -224,23 +228,79 @@ export default class App {
224
228
  $.log.isInfoEnabled() && $.log.info('Detected browser ' + device.browser + ' version ' + device.browserVersion);
225
229
  if (!scout.nvl(options.checkBrowserCompatibility, true) || device.isSupportedBrowser()) {
226
230
  // No check requested or browser is supported
227
- return true;
231
+ return;
228
232
  }
229
233
 
234
+ let deferred = $.Deferred();
235
+ let newOptions = objects.valueCopy(options);
236
+ newOptions.checkBrowserCompatibility = false;
230
237
  $('.scout').each(function() {
231
- let $entryPoint = $(this),
232
- $box = $entryPoint.appendDiv(),
233
- newOptions = objects.valueCopy(options);
238
+ let $entryPoint = $(this);
239
+ let $box = $entryPoint.appendDiv();
234
240
 
235
- newOptions.checkBrowserCompatibility = false;
236
241
  $box.load('unsupported-browser.html', () => {
237
242
  $box.find('button').on('click', () => {
238
243
  $box.remove();
239
- app._init(newOptions);
244
+ deferred.resolve(newOptions);
240
245
  });
241
246
  });
242
247
  });
243
- return false;
248
+ return deferred.promise();
249
+ }
250
+
251
+ setLoading(loading) {
252
+ if (loading) {
253
+ this._loadingTimeoutId = setTimeout(() => {
254
+ // Don't start loading if a desktop is already rendered to prevent flickering when the loading will be set to false after app initialization finishes
255
+ if (!this.sessions.some(session => session.desktop && session.desktop.rendered)) {
256
+ this._renderLoading();
257
+ }
258
+ }, 200);
259
+ } else {
260
+ clearTimeout(this._loadingTimeoutId);
261
+ this._loadingTimeoutId = null;
262
+ this._removeLoading();
263
+ }
264
+ }
265
+
266
+ _renderLoading() {
267
+ let $body = $('body'),
268
+ $loadingRoot = $body.children('.application-loading-root');
269
+ if (!$loadingRoot.length) {
270
+ $loadingRoot = $body.appendDiv('application-loading-root')
271
+ .addClass('application-loading-root')
272
+ .fadeIn();
273
+ }
274
+ this._renderLoadingElement($loadingRoot, 'application-loading01');
275
+ this._renderLoadingElement($loadingRoot, 'application-loading02');
276
+ this._renderLoadingElement($loadingRoot, 'application-loading03');
277
+ }
278
+
279
+ _renderLoadingElement($loadingRoot, cssClass) {
280
+ if ($loadingRoot.children('.' + cssClass).length) {
281
+ return;
282
+ }
283
+ // noinspection JSValidateTypes
284
+ $loadingRoot.appendDiv(cssClass).hide()
285
+ .fadeIn();
286
+ }
287
+
288
+ _removeLoading() {
289
+ let $loadingRoot = $('body').children('.application-loading-root');
290
+ // the fadeout animation only contains a to-value and no from-value
291
+ // therefore set the current value to the elements style
292
+ $loadingRoot.css('opacity', $loadingRoot.css('opacity'));
293
+ // Add animation listener before adding the classes to ensure the listener will always be triggered even while debugging
294
+ $loadingRoot.oneAnimationEnd(() => $loadingRoot.remove());
295
+ if ($loadingRoot.css('opacity') == 1) {
296
+ $loadingRoot.addClass('fadeout and-more');
297
+ } else {
298
+ $loadingRoot.addClass('fadeout');
299
+ }
300
+ if (!Device.get().supportsCssAnimation()) {
301
+ // fallback for old browsers that do not support the animation-end event
302
+ $loadingRoot.remove();
303
+ }
244
304
  }
245
305
 
246
306
  _initVersion(options) {
@@ -380,6 +440,7 @@ export default class App {
380
440
 
381
441
  _initDone(options) {
382
442
  this.initialized = true;
443
+ this.setLoading(false);
383
444
  this.trigger('init', {
384
445
  options: options
385
446
  });
@@ -388,6 +449,7 @@ export default class App {
388
449
 
389
450
  _fail(options, error, ...args) {
390
451
  $.log.error('App initialization failed.');
452
+ this.setLoading(false);
391
453
 
392
454
  return this.errorHandler.handle(error, ...args)
393
455
  .then(errorInfo => {
package/src/RemoteApp.js CHANGED
@@ -50,6 +50,7 @@ export default class RemoteApp extends App {
50
50
 
51
51
  _fail(options, error, ...args) {
52
52
  $.log.error('App initialization failed', error);
53
+ this.setLoading(false);
53
54
  // Session.js already handled the error -> don't show a message here
54
55
  // Reject with original rejection arguments
55
56
  return $.rejectedPromise(error, ...args);
@@ -895,11 +895,11 @@ export default class Desktop extends Widget {
895
895
  }
896
896
 
897
897
  /**
898
- * Destroys every popup which is a descendant of the given widget.
898
+ * Removes every popup which is a descendant of the given widget.
899
899
  */
900
- destroyPopupsFor(widget) {
900
+ removePopupsFor(widget) {
901
901
  this.getPopupsFor(widget).forEach(popup => {
902
- popup.destroy();
902
+ popup.remove();
903
903
  });
904
904
  }
905
905
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
2
+ * Copyright (c) 2010-2022 BSI Business Systems Integration AG.
3
3
  * All rights reserved. This program and the accompanying materials
4
4
  * are made available under the terms of the Eclipse Public License v1.0
5
5
  * which accompanies this distribution, and is available at
@@ -8,7 +8,7 @@
8
8
  * Contributors:
9
9
  * BSI Business Systems Integration AG - initial API and implementation
10
10
  */
11
- import {Device, Notification as ScoutNotification, strings} from '../../index';
11
+ import {Device, Notification as ScoutNotification, scout, Status, strings} from '../../index';
12
12
 
13
13
  export default class DesktopNotification extends ScoutNotification {
14
14
 
@@ -20,7 +20,7 @@ export default class DesktopNotification extends ScoutNotification {
20
20
  this._removing = false;
21
21
  this.nativeOnly = false;
22
22
  this.nativeNotificationTitle = null;
23
- this.nativeNotificationIconId = null;
23
+ this.nativeNotificationStatus = null; // holds native message & native icon
24
24
  this.nativeNotificationVisibility = DesktopNotification.NativeNotificationVisibility.NONE;
25
25
  this.nativeNotification = null;
26
26
  this.nativeNotificationShown = false;
@@ -58,7 +58,13 @@ export default class DesktopNotification extends ScoutNotification {
58
58
  let defaults = this.session.desktop.nativeNotificationDefaults;
59
59
  if (defaults) {
60
60
  this.nativeNotificationTitle = model.nativeNotificationTitle !== undefined ? model.nativeNotificationTitle : defaults.title;
61
- this.nativeNotificationIconId = model.nativeNotificationIconId !== undefined ? model.nativeNotificationIconId : defaults.iconId;
61
+ if (this.nativeNotificationStatus) {
62
+ this.nativeNotificationStatus.iconId = this.nativeNotificationStatus.iconId !== undefined ? this.nativeNotificationStatus.iconId : defaults.iconId;
63
+ } else {
64
+ this.nativeNotificationStatus = new Status({
65
+ iconId: defaults.iconId
66
+ });
67
+ }
62
68
  this.nativeNotificationVisibility = scout.nvl(model.nativeNotificationVisibility !== undefined ? model.nativeNotificationVisibility : defaults.visibility, DesktopNotification.NativeNotificationVisibility.NONE);
63
69
  }
64
70
  this.resolveTextKeys(['nativeNotificationTitle']);
@@ -108,10 +114,24 @@ export default class DesktopNotification extends ScoutNotification {
108
114
  return;
109
115
  }
110
116
  let title = scout.nvl(this.nativeNotificationTitle, '');
111
- let body = scout.nvl(strings.nl2br(this.status.message), '');
117
+ let body = (this.nativeNotificationStatus || {}).message;
118
+ if (strings.empty(body)) {
119
+ body = (this.status || {}).message;
120
+ }
121
+ if (!body) {
122
+ body = '';
123
+ }
124
+ if (this.htmlEnabled) {
125
+ body = strings.plainText(body, {removeFontIcons: true});
126
+ }
127
+ let iconId = (this.nativeNotificationStatus || {}).iconId;
128
+ if (strings.empty(iconId)) {
129
+ // icon must not be null or empty. If no icon it must be undefined
130
+ iconId = undefined;
131
+ }
112
132
  this.nativeNotification = new Notification(title, {
113
133
  body: body,
114
- icon: this.nativeNotificationIconId
134
+ icon: iconId
115
135
  });
116
136
 
117
137
  this.nativeNotification.addEventListener('show', event => {
@@ -240,8 +260,13 @@ export default class DesktopNotification extends ScoutNotification {
240
260
  this.setProperty('nativeNotificationTitle', title);
241
261
  }
242
262
 
243
- setNativeNotificationIconId(iconId) {
244
- this.setProperty('nativeNotificationIconId', iconId);
263
+ setNativeNotificationStatus(status) {
264
+ this.setProperty('nativeNotificationStatus', status);
265
+ }
266
+
267
+ _setNativeNotificationStatus(status) {
268
+ status = Status.ensure(status);
269
+ this._setProperty('nativeNotificationStatus', status);
245
270
  }
246
271
 
247
272
  setNativeNotificationVisibility(visibility) {
@@ -1064,10 +1064,10 @@ export default class Outline extends Tree {
1064
1064
  this.setProperty('nodeMenuBarVisible', visible);
1065
1065
  }
1066
1066
 
1067
- glassPaneTargets() {
1067
+ glassPaneTargets(element) {
1068
1068
  // MessageBoxes are often created with Outlines as displayParent. The default implementation of this function
1069
1069
  // would not render any glass panes when the outline is collapsed, thus we need to override this behavior.
1070
- return this._glassPaneTargets();
1070
+ return this._glassPaneTargets(element);
1071
1071
  }
1072
1072
 
1073
1073
  _glassPaneTargets(element) {
@@ -9,12 +9,6 @@
9
9
  * BSI Business Systems Integration AG - initial API and implementation
10
10
  */
11
11
  .outline.tree {
12
-
13
- &.in-background > .tree-data > .tree-node.selected {
14
- background-color: @outline-in-background-selection-background-color;
15
- color: @outline-in-background-selection-color;
16
- }
17
-
18
12
  & > .tree-data {
19
13
  padding-top: @outline-data-padding-top;
20
14
 
@@ -91,6 +85,20 @@
91
85
  }
92
86
  }
93
87
  }
88
+
89
+ &.in-background > .tree-data {
90
+ & > .tree-node,
91
+ & > .animation-wrapper > .tree-node {
92
+ &.group {
93
+ background-color: @outline-in-background-group-background-color;
94
+ }
95
+
96
+ &.selected {
97
+ background-color: @outline-in-background-selection-background-color;
98
+ color: @outline-in-background-selection-color;
99
+ }
100
+ }
101
+ }
94
102
  }
95
103
 
96
104
  .outline-title {
@@ -14,6 +14,7 @@ export default class OutlineViewButton extends ViewButton {
14
14
 
15
15
  constructor() {
16
16
  super();
17
+ this.outline = null;
17
18
  this._addWidgetProperties('outline');
18
19
  this._addPreserveOnPropertyChangeProperties(['outline']);
19
20
  this._addCloneProperties(['outline']);
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
2
+ * Copyright (c) 2010-2022 BSI Business Systems Integration AG.
3
3
  * All rights reserved. This program and the accompanying materials
4
4
  * are made available under the terms of the Eclipse Public License v1.0
5
5
  * which accompanies this distribution, and is available at
@@ -21,6 +21,7 @@ export default class FileChooser extends Widget {
21
21
  this.boxButtons = null;
22
22
  this.uploadButton = null;
23
23
  this.cancelButton = null;
24
+ this.inheritAccessibility = false; // inherit not necessary. if the FileChooser can be opened, it must be editable. Opening a disabled chooser makes no sense.
24
25
  this._addWidgetProperties(['boxButtons', 'uploadButton', 'cancelButton']);
25
26
  }
26
27
 
package/src/form/Form.js CHANGED
@@ -294,7 +294,9 @@ export default class Form extends Widget {
294
294
  }
295
295
 
296
296
  /**
297
- * Method may be implemented to load the data. By default, the provided this.data is returned.
297
+ * Method may be implemented to load the data. <br>
298
+ * By default, a resolved promise containing the provided this.data is returned.
299
+ * @returns {Promise}
298
300
  */
299
301
  _load() {
300
302
  return $.resolvedPromise().then(() => {
@@ -319,6 +321,9 @@ export default class Form extends Widget {
319
321
  return $.resolvedPromise();
320
322
  }
321
323
 
324
+ /**
325
+ * @param {any} data
326
+ */
322
327
  setData(data) {
323
328
  this.setProperty('data', data);
324
329
  }
@@ -327,8 +332,11 @@ export default class Form extends Widget {
327
332
  // NOP
328
333
  }
329
334
 
335
+ /**
336
+ * @returns {any}
337
+ */
330
338
  exportData() {
331
- // NOP
339
+ return null;
332
340
  }
333
341
 
334
342
  /**
@@ -382,7 +390,7 @@ export default class Form extends Widget {
382
390
 
383
391
  /**
384
392
  * This function is called by the lifecycle, when the 'save' function is called.<p>
385
- * The data given to this function is the result of 'exportData' which was called in advance.
393
+ * The data given to this function is the result of {@link exportData} which was called in advance.
386
394
  *
387
395
  * @returns {Promise} promise which may contain a Status specifying if the save operation was successful. The promise may be empty which means the save operation was successful.
388
396
  */
@@ -14,8 +14,8 @@
14
14
  */
15
15
  export default class DeferredGlassPaneTarget {
16
16
  constructor() {
17
- this.$glassPaneTargets;
18
- this.glassPaneRenderer;
17
+ this.$glassPaneTargets = null;
18
+ this.glassPaneRenderer = null;
19
19
  }
20
20
 
21
21
  ready($glassPaneTargets) {
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
2
+ * Copyright (c) 2010-2022 BSI Business Systems Integration AG.
3
3
  * All rights reserved. This program and the accompanying materials
4
4
  * are made available under the terms of the Eclipse Public License v1.0
5
5
  * which accompanies this distribution, and is available at
@@ -84,3 +84,10 @@
84
84
  height: 18px;
85
85
  vertical-align: middle;
86
86
  }
87
+
88
+ .login-body,
89
+ .logout-body {
90
+ & > .box-background-elements.box-background-elements-fadeout {
91
+ #scout.animation(nop 0.05s);
92
+ }
93
+ }
package/src/main.less CHANGED
@@ -408,7 +408,7 @@ button::-moz-focus-inner {
408
408
  pointer-events: none;
409
409
 
410
410
  &.fadeout {
411
- #scout.animation(application-loading-fade-out @loading-fade-duration linear 1);
411
+ #scout.animation(application-loading-fade-out @loading-fade-duration linear 1 forwards);
412
412
  }
413
413
  }
414
414
 
@@ -8,21 +8,13 @@
8
8
  * Contributors:
9
9
  * BSI Business Systems Integration AG - initial API and implementation
10
10
  */
11
- import {HtmlComponent, Menu} from '../index';
11
+ import {HtmlComponent, Menu, widgets} from '../index';
12
12
 
13
13
  export default class ComboMenu extends Menu {
14
14
 
15
15
  constructor() {
16
16
  super();
17
- this.childSelected = false;
18
- this._childSelectedHandler = this._onChildSelected.bind(this);
19
- }
20
-
21
- _setChildActions(childActions) {
22
- this.childActions.forEach(child => child.off('propertyChange:selected', this._childSelectedHandler));
23
- super._setChildActions(childActions);
24
- this.childActions.forEach(child => child.on('propertyChange:selected', this._childSelectedHandler));
25
- this._updateChildSelected();
17
+ this._childVisibleChangeHandler = this._onChildVisibleChange.bind(this);
26
18
  }
27
19
 
28
20
  _render() {
@@ -36,10 +28,15 @@ export default class ComboMenu extends Menu {
36
28
 
37
29
  _renderProperties() {
38
30
  super._renderProperties();
39
- this._renderChildSelected();
40
31
  this._renderChildActions();
41
32
  }
42
33
 
34
+ _setChildActions(childActions) {
35
+ this.childActions.forEach(child => child.off('propertyChange:visible', this._childVisibleChangeHandler));
36
+ super._setChildActions(childActions);
37
+ this.childActions.forEach(child => child.on('propertyChange:visible', this._childVisibleChangeHandler));
38
+ }
39
+
43
40
  _renderChildActions() {
44
41
  super._renderChildActions();
45
42
 
@@ -47,6 +44,7 @@ export default class ComboMenu extends Menu {
47
44
  childAction.addCssClass('combo-menu-child');
48
45
  childAction.render();
49
46
  });
47
+ widgets.updateFirstLastMarker(this.childActions);
50
48
  }
51
49
 
52
50
  // @override
@@ -54,15 +52,22 @@ export default class ComboMenu extends Menu {
54
52
  return false;
55
53
  }
56
54
 
57
- _onChildSelected(event) {
58
- this._updateChildSelected();
55
+ _onChildVisibleChange(event) {
56
+ if (this.rendered) {
57
+ widgets.updateFirstLastMarker(this.childActions);
58
+ }
59
59
  }
60
60
 
61
- _updateChildSelected() {
62
- this.setProperty('childSelected', this.childActions.some(child => child.selected));
61
+ _doActionTogglesPopup() {
62
+ return false;
63
63
  }
64
64
 
65
- _renderChildSelected() {
66
- this.$container.toggleClass('child-selected', this.childSelected);
65
+ isToggleAction() {
66
+ return false;
67
+ }
68
+
69
+ isTabTarget() {
70
+ // To make children tabbable, combo menu must never be a tab target, even if its a default menu
71
+ return false;
67
72
  }
68
73
  }
@@ -18,25 +18,32 @@
18
18
  background-color: transparent;
19
19
  }
20
20
 
21
- & > .menu-item:not(:first-child) {
22
- margin-left: 3px;
23
-
24
- &::before {
25
- content: '';
26
- position: absolute;
27
- left: -1px;
28
- top: 7px;
29
- height: calc(100% - 14px);
30
- width: 1px;
31
- background-color: @border-color;
21
+ & > .menu-item {
22
+ &:not(.first) {
23
+ margin-left: 3px;
24
+
25
+ &::before {
26
+ content: '';
27
+ position: absolute;
28
+ left: -1px;
29
+ top: 7px;
30
+ height: calc(100% - 14px);
31
+ width: 1px;
32
+ background-color: @border-color;
33
+ }
32
34
  }
33
- }
34
35
 
35
- &:not(.disabled).child-selected,
36
- &:not(.disabled):hover {
37
- & > .menu-item:not(:first-child)::before {
36
+ &:focus::before {
38
37
  display: none;
39
38
  }
39
+
40
+ &:not(.disabled):hover,
41
+ &.selected {
42
+ &::before,
43
+ & ~ .menu-item::before {
44
+ display: none;
45
+ }
46
+ }
40
47
  }
41
48
  }
42
49
 
@@ -45,14 +52,19 @@
45
52
 
46
53
  & > .menu-item {
47
54
  border: 1px solid @button-border-color;
55
+ flex-grow: 1;
48
56
 
49
- &:not(:last-child) {
57
+ &.menu-icononly:not(.first.last) { // Rule must not be applied if combo menu contains only one item
58
+ flex-grow: 0;
59
+ }
60
+
61
+ &:not(.last) {
50
62
  border-top-right-radius: 0;
51
63
  border-bottom-right-radius: 0;
52
64
  border-right: 0;
53
65
  }
54
66
 
55
- &:not(:first-child) {
67
+ &:not(.first) {
56
68
  border-top-left-radius: 0;
57
69
  border-bottom-left-radius: 0;
58
70
  border-left: 0;
@@ -62,31 +74,64 @@
62
74
  .disabled& {
63
75
  border-color: @button-disabled-color;
64
76
  }
77
+
78
+ &:focus {
79
+ z-index: 1; // Allows box-shadow to draw over the right menu-item
80
+ }
65
81
  }
66
82
 
67
83
  &.default {
68
84
  & > .menu-item {
69
85
  .button.default();
70
86
 
87
+ &::before {
88
+ background-color: @default-combo-menu-separator-color;
89
+ }
90
+
71
91
  &.selected {
72
92
  background-color: @default-button-selected-background-color;
73
93
  border-color: @default-button-selected-background-color;
74
94
  }
95
+
96
+ &.disabled {
97
+ color: @disabled-inverted-color;
98
+
99
+ &:hover, &.active, &:active, &.selected {
100
+ background-color: @default-button-background-color;
101
+ border-color: @default-button-background-color;
102
+ }
103
+ }
75
104
  }
76
105
  }
77
106
  }
78
107
 
79
- .context-menu {
80
- & > .combo-menu {
81
- padding: 0;
108
+ .context-menu > .combo-menu {
109
+ padding: 0;
82
110
 
83
- & > .menu-item {
84
- color: @context-menu-item-color;
85
- padding-top: @context-menu-item-padding-y;
86
- padding-bottom: @context-menu-item-padding-y;
111
+ & > .menu-item {
112
+ color: @context-menu-item-color;
113
+ padding: @context-menu-item-padding-y @context-menu-item-padding-right @context-menu-item-padding-y @context-menu-item-padding-left;
114
+ flex-grow: 1;
115
+ justify-content: start;
116
+ border-radius: 0;
117
+
118
+ & > .font-icon {
119
+ color: @context-menu-item-icon-color;
120
+ }
121
+
122
+ &.menu-textandicon > .icon {
123
+ margin-right: @context-menu-item-icon-margin-right;
124
+ }
125
+
126
+ &.menu-icononly:not(.first.last) { // Rule must not be applied if combo menu contains only one item
127
+ flex-grow: 0;
128
+ }
129
+
130
+ &.disabled {
131
+ color: @menu-item-disabled-color;
87
132
 
88
- &:first-child {
89
- margin-left: @context-menu-item-padding-left - @menu-item-padding-x;
133
+ & > .font-icon {
134
+ color: @menu-item-disabled-color;
90
135
  }
91
136
  }
92
137
  }
@@ -367,8 +367,9 @@ export default class ContextMenuPopup extends Popup {
367
367
 
368
368
  // prevent loosing original parent
369
369
  let originalParent = menu.parent;
370
- if (this.cloneMenuItems && !menu.cloneOf) {
371
- // clone will recursively also clone all child actions.
370
+ // Clone menu items but only clone once unless it is for a different context menu (e.g. a context menu of a combo menu inside a context menu)
371
+ // Clone will recursively also clone all child actions.
372
+ if (this.cloneMenuItems && !menu.cloneOf || !this.has(menu)) {
372
373
  menu = menu.clone({
373
374
  parent: this,
374
375
  textPosition: Action.TextPosition.DEFAULT
@@ -47,7 +47,7 @@
47
47
  }
48
48
 
49
49
  &.menu-textandicon > .icon {
50
- margin-right: 14px;
50
+ margin-right: @context-menu-item-icon-margin-right;
51
51
  }
52
52
 
53
53
  &.selected {
@@ -57,4 +57,8 @@ export default class EllipsisMenu extends Menu {
57
57
  isTabTarget() {
58
58
  return super.isTabTarget() && !this.hidden;
59
59
  }
60
+
61
+ _childrenForEnabledComputed() {
62
+ return this.childActions;
63
+ }
60
64
  }