@eclipse-scout/core 22.0.33 → 22.0.37

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 (41) hide show
  1. package/dist/eclipse-scout-core-990e036967fa14669bd8.min.js +2 -0
  2. package/dist/eclipse-scout-core-990e036967fa14669bd8.min.js.map +1 -0
  3. package/dist/eclipse-scout-core-theme-dark-17a1d77d53510e113a3c.min.css +1 -0
  4. package/dist/eclipse-scout-core-theme-dark.css +28 -4
  5. package/dist/eclipse-scout-core-theme-dark.css.map +1 -1
  6. package/dist/eclipse-scout-core-theme-dfed415a6b7fb17f3f4c.min.css +1 -0
  7. package/dist/eclipse-scout-core-theme.css +28 -4
  8. package/dist/eclipse-scout-core-theme.css.map +1 -1
  9. package/dist/eclipse-scout-core.js +8311 -26955
  10. package/dist/eclipse-scout-core.js.map +1 -1
  11. package/dist/file-list +4 -4
  12. package/dist/texts.json +7 -0
  13. package/package.json +2 -2
  14. package/src/App.js +31 -23
  15. package/src/ErrorHandler.js +3 -2
  16. package/src/RemoteApp.js +6 -13
  17. package/src/datepicker/DatePicker.js +5 -9
  18. package/src/desktop/outline/Outline.js +3 -2
  19. package/src/form/fields/datefield/DateField.js +26 -8
  20. package/src/form/fields/datefield/DateFieldAdapter.js +18 -8
  21. package/src/form/js/JsFormAdapter.js +2 -0
  22. package/src/keystroke/KeyStroke.js +23 -18
  23. package/src/lookup/StaticLookupCall.js +28 -3
  24. package/src/main.less +1 -0
  25. package/src/popup/Popup.js +94 -15
  26. package/src/popup/Popup.less +6 -2
  27. package/src/session/Session.js +10 -5
  28. package/src/style/sizes.less +2 -0
  29. package/src/table/TableFooter.js +4 -4
  30. package/src/table/TableFooter.less +2 -2
  31. package/src/table/controls/TableControl.js +8 -3
  32. package/src/table/keystrokes/AbstractTableNavigationKeyStroke.js +1 -1
  33. package/src/tile/TileGrid.js +21 -9
  34. package/src/timepicker/TimePicker.js +0 -33
  35. package/src/tree/Tree.js +46 -5
  36. package/src/tree/Tree.less +3 -1
  37. package/src/tree/TreeNode.js +4 -3
  38. package/dist/eclipse-scout-core-b11dde8e4ac1367f5a06.min.js +0 -2
  39. package/dist/eclipse-scout-core-b11dde8e4ac1367f5a06.min.js.map +0 -1
  40. package/dist/eclipse-scout-core-theme-dark-0d9d468e5722f73f5075.min.css +0 -1
  41. package/dist/eclipse-scout-core-theme-db0af3fa95956b820bfc.min.css +0 -1
@@ -3,7 +3,7 @@
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
6
- * http://www.eclipse.org/legal/epl-v10.html
6
+ * https://www.eclipse.org/legal/epl-v10.html
7
7
  *
8
8
  * Contributors:
9
9
  * BSI Business Systems Integration AG - initial API and implementation
@@ -29,6 +29,7 @@ export default class Popup extends Widget {
29
29
  this.windowPaddingX = 10;
30
30
  this.windowPaddingY = 5;
31
31
  this.withGlassPane = false;
32
+ this._withGlassPane = null;
32
33
  this.withFocusContext = true;
33
34
  this.initialFocus = () => FocusRule.AUTO;
34
35
  this.focusableContainer = false;
@@ -67,12 +68,19 @@ export default class Popup extends Widget {
67
68
  // This is necessary because the mousedown listener is attached to the capture phase and therefore executed before any other.
68
69
  // If anchor was clicked, popup would already be closed and then opened again -> popup could never be closed by clicking the anchor
69
70
  this.closeOnAnchorMouseDown = true;
71
+ this._closeOnAnchorMouseDown = null;
70
72
 
71
73
  // Defines whether the popup should be closed on a mouse click outside of the popup
72
74
  this.closeOnMouseDownOutside = true;
75
+ this._closeOnMouseDownOutside = null;
73
76
 
74
77
  // Defines whether the popup should be closed whenever another popup opens.
75
78
  this.closeOnOtherPopupOpen = true;
79
+ this._closeOnOtherPopupOpen = null;
80
+
81
+ // Defines whether the popup should behave like a modal form. If true, the properties closeOnAnchorMouseDown, closeOnMouseDownOutside
82
+ // and closeOnOtherPopupOpen ore overruled and set to false. The property withGlassPane is overruled too and set to true.
83
+ this.modal = false;
76
84
 
77
85
  this._openLater = false;
78
86
 
@@ -145,10 +153,8 @@ export default class Popup extends Widget {
145
153
  if (options.location) {
146
154
  this.anchorBounds = new Rectangle(options.location.x, options.location.y, 0, 0);
147
155
  }
148
- if (this.withGlassPane) {
149
- this._glassPaneRenderer = new GlassPaneRenderer(this);
150
- }
151
156
  this._setAnchor(this.anchor);
157
+ this._setModal(this.modal);
152
158
  }
153
159
 
154
160
  /**
@@ -299,6 +305,7 @@ export default class Popup extends Widget {
299
305
  this._renderWithArrow();
300
306
  this._renderWithFocusContext();
301
307
  this._renderWithGlassPane();
308
+ this._renderModal();
302
309
  }
303
310
 
304
311
  _postRender() {
@@ -383,9 +390,89 @@ export default class Popup extends Widget {
383
390
  this.session.focusManager.installFocusContext(this.$container, FocusRule.PREPARE);
384
391
  }
385
392
 
393
+ setModal(modal) {
394
+ this.setProperty('modal', modal);
395
+ }
396
+
397
+ _setModal(modal) {
398
+ this._setProperty('modal', modal);
399
+ if (modal) {
400
+ widgets.preserveAndSetProperty(() => this.setProperty('withGlassPane', true), () => this.withGlassPane, this, '_withGlassPane');
401
+ widgets.preserveAndSetProperty(() => this.setProperty('closeOnAnchorMouseDown', false), () => this.closeOnAnchorMouseDown, this, '_closeOnAnchorMouseDown');
402
+ widgets.preserveAndSetProperty(() => this.setProperty('closeOnMouseDownOutside', false), () => this.closeOnMouseDownOutside, this, '_closeOnMouseDownOutside');
403
+ widgets.preserveAndSetProperty(() => this.setProperty('closeOnOtherPopupOpen', false), () => this.closeOnOtherPopupOpen, this, '_closeOnOtherPopupOpen');
404
+ } else {
405
+ widgets.resetProperty(v => this.setWithGlassPane(v), this, '_withGlassPane');
406
+ widgets.resetProperty(v => this.setCloseOnAnchorMouseDown(v), this, '_closeOnAnchorMouseDown');
407
+ widgets.resetProperty(v => this.setCloseOnMouseDownOutside(v), this, '_closeOnMouseDownOutside');
408
+ widgets.resetProperty(v => this.setCloseOnOtherPopupOpen(v), this, '_closeOnOtherPopupOpen');
409
+ }
410
+ }
411
+
412
+ _renderModal() {
413
+ this.$container.toggleClass('modal', this.modal);
414
+ }
415
+
416
+ setWithGlassPane(withGlassPane) {
417
+ if (!this.modal) {
418
+ this.setProperty('withGlassPane', withGlassPane);
419
+ } else {
420
+ this._withGlassPane = withGlassPane;
421
+ }
422
+ }
423
+
386
424
  _renderWithGlassPane() {
387
- if (this._glassPaneRenderer) {
425
+ if (this.withGlassPane && !this._glassPaneRenderer) {
426
+ this._glassPaneRenderer = new GlassPaneRenderer(this);
388
427
  this._glassPaneRenderer.renderGlassPanes();
428
+ } else if (!this.withGlassPane && this._glassPaneRenderer) {
429
+ this._glassPaneRenderer.removeGlassPanes();
430
+ this._glassPaneRenderer = null;
431
+ }
432
+ }
433
+
434
+ setCloseOnMouseDownOutside(closeOnMouseDownOutside) {
435
+ if (!this.modal) {
436
+ this.setProperty('closeOnMouseDownOutside', closeOnMouseDownOutside);
437
+ } else {
438
+ this._closeOnMouseDownOutside = closeOnMouseDownOutside;
439
+ }
440
+ }
441
+
442
+ _renderCloseOnMouseDownOutside() {
443
+ // The listener needs to be executed in the capturing phase -> prevents that _onDocumentMouseDown will be executed right after the popup gets opened using mouse down, otherwise the popup would be closed immediately
444
+ if (this.closeOnMouseDownOutside && !this._documentMouseDownHandler) {
445
+ this._documentMouseDownHandler = this._onDocumentMouseDown.bind(this);
446
+ this.$container.document(true).addEventListener('mousedown', this._documentMouseDownHandler, true); // true=the event handler is executed in the capturing phase
447
+ } else if (!this.closeOnMouseDownOutside && this._documentMouseDownHandler) {
448
+ this.$container.document(true).removeEventListener('mousedown', this._documentMouseDownHandler, true);
449
+ this._documentMouseDownHandler = null;
450
+ }
451
+ }
452
+
453
+ setCloseOnAnchorMouseDown(closeOnAnchorMouseDown) {
454
+ if (!this.modal) {
455
+ this.setProperty('closeOnAnchorMouseDown', closeOnAnchorMouseDown);
456
+ } else {
457
+ this._closeOnAnchorMouseDown = closeOnAnchorMouseDown;
458
+ }
459
+ }
460
+
461
+ setCloseOnOtherPopupOpen(closeOnOtherPopupOpen) {
462
+ if (!this.modal) {
463
+ this.setProperty('closeOnOtherPopupOpen', closeOnOtherPopupOpen);
464
+ } else {
465
+ this._closeOnOtherPopupOpen = closeOnOtherPopupOpen;
466
+ }
467
+ }
468
+
469
+ _renderCloseOnOtherPopupOpen() {
470
+ if (this.closeOnOtherPopupOpen && !this._popupOpenHandler) {
471
+ this._popupOpenHandler = this._onPopupOpen.bind(this);
472
+ this.session.desktop.on('popupOpen', this._popupOpenHandler);
473
+ } else if (!this.closeOnOtherPopupOpen && this._popupOpenHandler) {
474
+ this.session.desktop.off('popupOpen', this._popupOpenHandler);
475
+ this._popupOpenHandler = null;
389
476
  }
390
477
  }
391
478
 
@@ -482,17 +569,9 @@ export default class Popup extends Widget {
482
569
  */
483
570
  _attachCloseHandlers() {
484
571
  // Install mouse close handler
485
- // The listener needs to be executed in the capturing phase -> prevents that _onDocumentMouseDown will be executed right after the popup gets opened using mouse down, otherwise the popup would be closed immediately
486
- if (this.closeOnMouseDownOutside) {
487
- this._documentMouseDownHandler = this._onDocumentMouseDown.bind(this);
488
- this.$container.document(true).addEventListener('mousedown', this._documentMouseDownHandler, true); // true=the event handler is executed in the capturing phase
489
- }
490
-
572
+ this._renderCloseOnMouseDownOutside();
491
573
  // Install popup open close handler
492
- if (this.closeOnOtherPopupOpen) {
493
- this._popupOpenHandler = this._onPopupOpen.bind(this);
494
- this.session.desktop.on('popupOpen', this._popupOpenHandler);
495
- }
574
+ this._renderCloseOnOtherPopupOpen();
496
575
  }
497
576
 
498
577
  _attachAnchorHandlers() {
@@ -1,9 +1,9 @@
1
1
  /*
2
- * Copyright (c) 2014-2018 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
6
- * http://www.eclipse.org/legal/epl-v10.html
6
+ * https://www.eclipse.org/legal/epl-v10.html
7
7
  *
8
8
  * Contributors:
9
9
  * BSI Business Systems Integration AG - initial API and implementation
@@ -24,6 +24,10 @@
24
24
  &.before-animate-open {
25
25
  .invisible();
26
26
  }
27
+
28
+ &.modal.animate-modality-highlight {
29
+ #scout.animation-shake();
30
+ }
27
31
  }
28
32
 
29
33
  .popup-arrow {
@@ -1069,12 +1069,13 @@ export default class Session {
1069
1069
  * do nothing. Can be used to prevent double messages for the same error.
1070
1070
  */
1071
1071
  showFatalMessage(options, errorCode) {
1072
- if (errorCode) {
1073
- if (this._fatalMessagesOnScreen[errorCode]) {
1074
- return;
1075
- }
1076
- this._fatalMessagesOnScreen[errorCode] = true;
1072
+ if (!errorCode) {
1073
+ errorCode = App.get().errorHandler.getJsErrorCode();
1074
+ }
1075
+ if (this._fatalMessagesOnScreen[errorCode]) {
1076
+ return;
1077
1077
  }
1078
+ this._fatalMessagesOnScreen[errorCode] = true;
1078
1079
 
1079
1080
  options = options || {};
1080
1081
  let model = {
@@ -1107,6 +1108,10 @@ export default class Session {
1107
1108
  messageBox.render($entryPoint);
1108
1109
  }
1109
1110
 
1111
+ isFatalMessageShown() {
1112
+ return Object.keys(this._fatalMessagesOnScreen).length > 0;
1113
+ }
1114
+
1110
1115
  uploadFiles(target, files, uploadProperties, maxTotalSize, allowedTypes) {
1111
1116
  let formData = new FormData(),
1112
1117
  acceptedFiles = [];
@@ -351,6 +351,7 @@
351
351
  @tooltip-padding-x: 12px;
352
352
  @tooltip-padding-y: @context-menu-item-padding-y;
353
353
  @tree-node-icon-width: 16px;
354
+ @tree-node-icon-padding-right: 9px;
354
355
  @tree-node-bitmap-icon-size: @tree-node-icon-width;
355
356
  @tree-node-bitmap-icon-margin-top: -2px;
356
357
  @tree-node-checkbox-size: 20px;
@@ -364,6 +365,7 @@
364
365
  @tree-node-padding-left: 28px;
365
366
  @tree-node-padding-right: 7px;
366
367
  @tree-node-padding-y: 7px;
368
+ @tree-node-padding-level-diff-parent-has-icon: @tree-node-icon-width + @tree-node-icon-padding-right;
367
369
  @view-tab-key-box-bottom: 9px;
368
370
  @view-tab-icon-font-size: 20px;
369
371
  @view-tab-selected-width: 56px;
@@ -1,9 +1,9 @@
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
6
- * http://www.eclipse.org/legal/epl-v10.html
6
+ * https://www.eclipse.org/legal/epl-v10.html
7
7
  *
8
8
  * Contributors:
9
9
  * BSI Business Systems Integration AG - initial API and implementation
@@ -583,13 +583,13 @@ export default class TableFooter extends Widget {
583
583
  if (footer.animating) {
584
584
  // Layout may be called when container stays open but changes its size using an animation.
585
585
  // At that time the controlContainer has not yet the final size, therefore measuring is not possible, but not necessary anyway.
586
- controlContainerHeight = control.height;
586
+ controlContainerHeight = scout.nvl(control && control.height, controlContainerHeight);
587
587
  } else {
588
588
  // Measure the real height
589
589
  controlContainerHeight = graphics.size(footer.$controlContainer).height;
590
590
  // Expand control height? (but only if not resizing)
591
591
  if (!footer.resizing && growControl) {
592
- controlContainerHeight = Math.max(control.height, controlContainerHeight);
592
+ controlContainerHeight = Math.max(control && control.height, controlContainerHeight);
593
593
  }
594
594
  }
595
595
  }
@@ -24,7 +24,7 @@
24
24
  left: 0;
25
25
  height: 16px;
26
26
  cursor: row-resize;
27
- z-index: 1;
27
+ z-index: 2;
28
28
  border-top: @table-control-resize-border-width solid @table-control-resize-border-color;
29
29
  }
30
30
 
@@ -44,7 +44,7 @@
44
44
  }
45
45
 
46
46
  display: none;
47
- z-index: 1;
47
+ z-index: 2; // Must not be smaller than z-index of scrollbar, see Table.less
48
48
  /* Reset nowrap, forms may have fields which need wrapping (e.g. label field) */
49
49
  white-space: normal;
50
50
 
@@ -1,9 +1,9 @@
1
1
  /*
2
- * Copyright (c) 2014-2018 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
6
- * http://www.eclipse.org/legal/epl-v10.html
6
+ * https://www.eclipse.org/legal/epl-v10.html
7
7
  *
8
8
  * Contributors:
9
9
  * BSI Business Systems Integration AG - initial API and implementation
@@ -99,13 +99,18 @@ export default class TableControl extends Action {
99
99
  /**
100
100
  * Renders the content if not already rendered.<br>
101
101
  * Opens the container if the container is not already open.<br>
102
- * Does nothing if the content is not available yet to -> don't open container if content is not rendered yet to prevent blank container or laggy opening.
102
+ * Does nothing if the content is not available yet to -> don't open container if content is not rendered yet to prevent blank container or laggy opening.<br>
103
+ * Does nothing if the control is not selected.
103
104
  */
104
105
  renderContent() {
105
106
  if (!this.contentRendered && !this.isContentAvailable()) {
106
107
  return;
107
108
  }
108
109
 
110
+ if (!this.selected) {
111
+ return;
112
+ }
113
+
109
114
  if (!this.tableFooter.open) {
110
115
  this.tableFooter.openControlContainer(this);
111
116
  }
@@ -197,6 +197,6 @@ export default class AbstractTableNavigationKeyStroke extends KeyStroke {
197
197
  }
198
198
 
199
199
  _isEnabled() {
200
- return !this.field.tileMode;
200
+ return !this.field.tileMode && super._isEnabled();
201
201
  }
202
202
  }
@@ -51,6 +51,7 @@ export default class TileGrid extends Widget {
51
51
  this.virtual = false;
52
52
  this.virtualScrolling = null;
53
53
  this.withPlaceholders = false;
54
+ this.placeholderProducer = null;
54
55
 
55
56
  this.$filterFieldContainer = null;
56
57
  this.textFilterEnabled = false;
@@ -610,6 +611,10 @@ export default class TileGrid extends Widget {
610
611
  this.invalidateLayoutTree();
611
612
  }
612
613
 
614
+ setPlaceholderProducer(placeholderProducer) {
615
+ this.setProperty('placeholderProducer', placeholderProducer);
616
+ }
617
+
613
618
  fillUpWithPlaceholders() {
614
619
  if (!this.withPlaceholders) {
615
620
  this._deleteAllPlaceholders();
@@ -623,9 +628,7 @@ export default class TileGrid extends Widget {
623
628
  if (!this.withPlaceholders) {
624
629
  return this.tiles;
625
630
  }
626
- return this.tiles.filter(tile => {
627
- return !(tile instanceof PlaceholderTile);
628
- });
631
+ return this.tiles.filter(tile => !(tile instanceof PlaceholderTile));
629
632
  }
630
633
 
631
634
  _createPlaceholders() {
@@ -635,7 +638,8 @@ export default class TileGrid extends Widget {
635
638
  placeholders = [];
636
639
 
637
640
  if (tiles.length > 0) {
638
- lastX = tiles[tiles.length - 1].gridData.x;
641
+ let tile = tiles[tiles.length - 1];
642
+ lastX = tile.gridData.x + tile.gridData.w - 1;
639
643
  } else {
640
644
  // If there are no tiles, create one row with placeholders
641
645
  lastX = -1;
@@ -655,9 +659,17 @@ export default class TileGrid extends Widget {
655
659
  }
656
660
 
657
661
  _createPlaceholder() {
658
- return scout.create('PlaceholderTile', {
659
- parent: this
660
- });
662
+ let placeholder = (this.placeholderProducer && this.placeholderProducer()) || {};
663
+ if (placeholder instanceof PlaceholderTile) {
664
+ return placeholder;
665
+ }
666
+ if (objects.isPlainObject(placeholder)) {
667
+ return scout.create($.extend(true, {}, {
668
+ objectType: 'PlaceholderTile',
669
+ parent: this
670
+ }, placeholder));
671
+ }
672
+ throw new Error('Placeholder producer returned unexpected result.');
661
673
  }
662
674
 
663
675
  _deleteObsoletePlaceholders() {
@@ -1000,7 +1012,7 @@ export default class TileGrid extends Widget {
1000
1012
  }
1001
1013
 
1002
1014
  _applyFilters(tiles, fullReset) {
1003
- return this.filterSupport.applyFilters(tiles, fullReset);
1015
+ return this.filterSupport.applyFilters(tiles.filter(tile => !(tile instanceof PlaceholderTile)), fullReset);
1004
1016
  }
1005
1017
 
1006
1018
  /**
@@ -1010,7 +1022,7 @@ export default class TileGrid extends Widget {
1010
1022
  return new FilterSupport({
1011
1023
  widget: this,
1012
1024
  $container: () => this.$filterFieldContainer,
1013
- getElementsForFiltering: () => this.tiles,
1025
+ getElementsForFiltering: this.tilesWithoutPlaceholders.bind(this),
1014
1026
  createTextFilter: this._createTextFilter.bind(this),
1015
1027
  updateTextFilterText: this._updateTextFilterText.bind(this)
1016
1028
  });
@@ -204,39 +204,6 @@ export default class TimePicker extends Widget {
204
204
 
205
205
  }
206
206
 
207
- _findNextAllowedDate(years, months, days) {
208
- let i, date,
209
- sum = years + months + days,
210
- dir = sum > 0 ? 1 : -1,
211
- now = this.selectedDate || dates.trunc(new Date());
212
-
213
- // if we shift by year or month, shift the 'now' date and then use that date as starting point
214
- // to find the next allowed date.
215
- if (years !== 0) {
216
- now = dates.shift(now, years, 0, 0);
217
- } else if (months !== 0) {
218
- now = dates.shift(now, 0, months, 0);
219
- }
220
-
221
- if (dir === 1) { // find next allowed date, starting from currently selected date
222
- for (i = 0; i < this.allowedDates.length; i++) {
223
- date = this.allowedDates[i];
224
- if (dates.compare(now, date) < 0) {
225
- return date;
226
- }
227
- }
228
- } else if (dir === -1) { // find previous allowed date, starting from currently selected date
229
- for (i = this.allowedDates.length - 1; i >= 0; i--) {
230
- date = this.allowedDates[i];
231
- if (dates.compare(now, date) > 0) {
232
- return date;
233
- }
234
- }
235
- }
236
-
237
- return null;
238
- }
239
-
240
207
  _onNavigationMouseDown(event) {
241
208
  let $target = $(event.currentTarget);
242
209
  let diff = $target.data('shift');
package/src/tree/Tree.js CHANGED
@@ -37,6 +37,7 @@ export default class Tree extends Widget {
37
37
  this.nodesMap = {}; // all nodes by id
38
38
  this.nodePaddingLevelCheckable = 23; /* padding for one tree-level if the tree is checkable */
39
39
  this.nodePaddingLevelNotCheckable = 18; /* padding for one tree-level if the tree is not checkable. this includes outline trees! */
40
+ this.nodePaddingLevelDiffParentHasIcon = null; /* is read from CSS */
40
41
  this.nodePaddingLeft = null; /* is read from CSS */
41
42
  this.nodeCheckBoxPaddingLeft = 29;
42
43
  this.nodeControlPaddingLeft = null; /* is read from CSS */
@@ -70,6 +71,7 @@ export default class Tree extends Widget {
70
71
 
71
72
  // contains all parents of a selected node, the selected node and the first level children
72
73
  this._inSelectionPathList = {};
74
+ this._changeNodeTaskScheduled = false;
73
75
  this.viewRangeRendered = new Range(0, 0);
74
76
  this.viewRangeSize = 20;
75
77
 
@@ -890,7 +892,7 @@ export default class Tree extends Widget {
890
892
 
891
893
  _renderNode(node) {
892
894
  let paddingLeft = this._computeNodePaddingLeft(node);
893
- node.render(this.$container, paddingLeft, this.checkable, this.enabledComputed);
895
+ node.render(this.$container, paddingLeft);
894
896
  return node.$node;
895
897
  }
896
898
 
@@ -953,7 +955,7 @@ export default class Tree extends Widget {
953
955
  let $control = $node.children('.tree-node-control');
954
956
  let $checkbox = $node.children('.tree-node-checkbox');
955
957
 
956
- node._updateControl($control, this);
958
+ node._updateControl($control);
957
959
  if (this.checkable) {
958
960
  if ($checkbox.length === 0) {
959
961
  node._renderCheckbox();
@@ -1312,7 +1314,7 @@ export default class Tree extends Widget {
1312
1314
  let node = $node.data('node');
1313
1315
  let paddingLeft = this._computeNodePaddingLeft(node);
1314
1316
  $node.cssPaddingLeft(objects.isNullOrUndefined(paddingLeft) ? '' : paddingLeft);
1315
- node._updateControl($node.children('.tree-node-control'), this);
1317
+ node._updateControl($node.children('.tree-node-control'));
1316
1318
  });
1317
1319
  }
1318
1320
 
@@ -2087,18 +2089,41 @@ export default class Tree extends Widget {
2087
2089
  if (this.isBreadcrumbStyleActive()) {
2088
2090
  return this.nodePaddingLeft;
2089
2091
  }
2090
- let padding = node.level * this.nodePaddingLevel + this.nodePaddingLeft;
2092
+ let padding = this.nodePaddingLeft + this._computeNodePaddingLeftForLevel(node);
2091
2093
  if (this.checkable) {
2092
2094
  padding += this.nodeCheckBoxPaddingLeft;
2093
2095
  }
2094
2096
  return padding;
2095
2097
  }
2096
2098
 
2099
+ _computeNodeControlPaddingLeft(node) {
2100
+ return this.nodeControlPaddingLeft + this._computeNodePaddingLeftForLevel(node);
2101
+ }
2102
+
2103
+ _computeNodePaddingLeftForLevel(node) {
2104
+ if (this.checkable || !this.nodePaddingLevelDiffParentHasIcon) {
2105
+ return node.level * this.nodePaddingLevel;
2106
+ }
2107
+ let padding = 0;
2108
+ let parentNode = node.parentNode;
2109
+ while (parentNode) {
2110
+ padding += this.nodePaddingLevel;
2111
+ // Increase the padding if the parent node has an icon to make the hierarchy more clear
2112
+ // This is not necessary if the child nodes have icons as well, the padding even looks too big, as it is the case for checkable trees.
2113
+ // We only check the first child node for an icon because that one has the biggest impact on the hierarchy visualization. It also increases performance a little.
2114
+ if (parentNode.iconId && !parentNode.childNodes[0].iconId) {
2115
+ padding += this.nodePaddingLevelDiffParentHasIcon;
2116
+ }
2117
+ parentNode = parentNode.parentNode;
2118
+ }
2119
+ return padding;
2120
+ }
2121
+
2097
2122
  /**
2098
2123
  * Reads the paddings from CSS and stores them in nodePaddingLeft and nodeControlPaddingLeft
2099
2124
  */
2100
2125
  _computeNodePaddings() {
2101
- if (this.nodePaddingLeft !== null && this.nodeControlPaddingLeft !== null) {
2126
+ if (this.nodePaddingLeft !== null && this.nodeControlPaddingLeft !== null && this.nodePaddingLevelDiffParentHasIcon !== null) {
2102
2127
  return;
2103
2128
  }
2104
2129
  let $dummyNode = this.$data.appendDiv('tree-node');
@@ -2109,6 +2134,9 @@ export default class Tree extends Widget {
2109
2134
  if (this.nodeControlPaddingLeft === null) {
2110
2135
  this.nodeControlPaddingLeft = $dummyNodeControl.cssPaddingLeft();
2111
2136
  }
2137
+ if (this.nodePaddingLevelDiffParentHasIcon === null) {
2138
+ this.nodePaddingLevelDiffParentHasIcon = this.$container.cssPxValue('--node-padding-level-diff-parent-has-icon');
2139
+ }
2112
2140
  $dummyNode.remove();
2113
2141
  }
2114
2142
 
@@ -3169,6 +3197,19 @@ export default class Tree extends Widget {
3169
3197
  this.applyFiltersForNode(node);
3170
3198
  if (this.rendered) {
3171
3199
  node._decorate();
3200
+ // The padding size of a node depends on whether the node or the parent node has an icon, see _computeNodePaddingLeftForLevel
3201
+ // Unfortunately, we cannot easily detect whether the icon has changed or not.
3202
+ // However, the padding calculation only needs to be done if the node that toggles the icon is visible and expanded or has an expanded parent.
3203
+ let paddingDirty = !!this.nodePaddingLevelDiffParentHasIcon && !!this.visibleNodesMap[node.id] && (node.expanded || !!node.parentNode);
3204
+ if (paddingDirty && !this._changeNodeTaskScheduled) {
3205
+ // Because the change node event is not batch capable, performance would slow down if many change node events are processed
3206
+ // To mitigate this, the updating is done later
3207
+ queueMicrotask(() => {
3208
+ this._updateNodePaddingsLeft();
3209
+ this._changeNodeTaskScheduled = false;
3210
+ });
3211
+ this._changeNodeTaskScheduled = true;
3212
+ }
3172
3213
  }
3173
3214
  this.trigger('nodeChanged', {
3174
3215
  node: node
@@ -10,6 +10,8 @@
10
10
  */
11
11
  .tree {
12
12
  position: relative;
13
+ // The value of the css variable is read by Tree.js and added to the level padding if the parent node has an icon
14
+ --node-padding-level-diff-parent-has-icon: @tree-node-padding-level-diff-parent-has-icon;
13
15
 
14
16
  &:focus,
15
17
  &.focused {
@@ -122,7 +124,7 @@
122
124
 
123
125
  & > .icon {
124
126
  vertical-align: top;
125
- padding-right: 9px;
127
+ padding-right: @tree-node-icon-padding-right;
126
128
  display: inline-block;
127
129
  text-align: center;
128
130
  min-width: @tree-node-icon-width;
@@ -253,12 +253,13 @@ export default class TreeNode {
253
253
 
254
254
  _renderControl() {
255
255
  let $control = this.$node.prependDiv('tree-node-control');
256
- this._updateControl($control, this.getTree());
256
+ this._updateControl($control);
257
257
  }
258
258
 
259
- _updateControl($control, tree) {
259
+ _updateControl($control) {
260
+ let tree = this.getTree();
260
261
  $control.toggleClass('checkable', tree.checkable);
261
- $control.cssPaddingLeft(tree.nodeControlPaddingLeft + this.level * tree.nodePaddingLevel);
262
+ $control.cssPaddingLeft(tree._computeNodeControlPaddingLeft(this));
262
263
  $control.setVisible(!this.leaf);
263
264
  }
264
265