diff options
author | Claudio Guglielmo | 2021-05-17 16:45:11 +0000 |
---|---|---|
committer | Claudio Guglielmo | 2021-05-31 07:52:07 +0000 |
commit | 58187b8942ced371a223315b1b0ee2859e8a76f8 (patch) | |
tree | 2401e1324f760f2caef7aa445ede664a36b622b9 | |
parent | 1afdc9970b689b10ea20b2bb2f5898642e973f5f (diff) | |
download | org.eclipse.scout.rt-58187b8942ced371a223315b1b0ee2859e8a76f8.tar.gz org.eclipse.scout.rt-58187b8942ced371a223315b1b0ee2859e8a76f8.tar.xz org.eclipse.scout.rt-58187b8942ced371a223315b1b0ee2859e8a76f8.zip |
BrushUp: improve menu, context menu, menu bar, combo menu, button
Improve menubar and menus
- The menus now have a padding and hover effect.
- The menu bar cannot have a margin/padding anymore, the first and last
menu entries need to have the margin
- The rules for a menu are moved to Menu.less so that a menu can easier
be used outside of a menu bar. This also makes the rules less
complicated.
- Menus and buttons now use flex box to layout the content -> vertical
align helper and other hacks can be removed.
- The style for the font icons are removed, they were not needed but
made overriding more complex.
- Renamed .menubox to .menubar-box because it belongs to the
MenubarBox.js and not to MenuBox.js
- The selected state of menus and buttons is now correctly visualized.
Improve focus style
- The focus style for menus and buttons is updated and simplified.
- No pseudo element is necessary anymore.
Harmonize buttons and menus
- Rename class default-menu to default
- A menu button now uses the same style as a regular button, no
duplicated rules anymore
Simplify ButtonField
- The additional div is not necessary anymore because of the more simple
focus state.
- The additional rules are not necessary anymore
Add borderless style to Button.js
- The existing link style cannot use paddings because it would break
a lot of alignments. The new style looks like a menu in the menu bar.
Improve context menu
- The context menu now uses a modern style.
- The old code could not handle the rounded corners well -> refactored
context menu animations.
- The paddings are now animated.
- If an image is loaded during animation the size of the popup is now
adjusted.
Improve combo menu
- Added a separator to make it not look the same as a menu with
sub menus
- The combo menu now supports the button and default menu style
- A regular menu now shows the sub menu icon even if there is no text
and icon which makes it easier to use it in a combo menu
Improve form field menu
- Form fields without border (check box, label fields etc). now don't
increase the menu bar height anymore by adding an unnecessary margin.
- Form fields are now moved 10px to the left to remove the mandatory
indicator gap so that it is not necessary anymore to explicitly add
the class no_mandatory_indicator.
52 files changed, 898 insertions, 941 deletions
diff --git a/eclipse-scout-core/src/desktop/DesktopDense.less b/eclipse-scout-core/src/desktop/DesktopDense.less index 69c9d659ab..2adc5fe2dc 100644 --- a/eclipse-scout-core/src/desktop/DesktopDense.less +++ b/eclipse-scout-core/src/desktop/DesktopDense.less @@ -48,15 +48,14 @@ /* MenuBar.less */ - .menubar { - & > .menubox { + .menubar:not(.main-menubar) { + & > .menubar-box { & > .menu-item { padding-top: 3px; /* @menubar-item-padding-y; */ padding-bottom: 3px; /* @menubar-item-padding-y; */ /* By making sure menu items are always at least the same height as form fields, it is easier to align them with each other, especially when zoomed */ min-height: @logical-grid-row-height-dense; /* @logical-grid-row-height; */ - } } } diff --git a/eclipse-scout-core/src/desktop/bench/DesktopBench.js b/eclipse-scout-core/src/desktop/bench/DesktopBench.js index 96e6ce0316..86f1b21dcb 100644 --- a/eclipse-scout-core/src/desktop/bench/DesktopBench.js +++ b/eclipse-scout-core/src/desktop/bench/DesktopBench.js @@ -410,7 +410,8 @@ export default class DesktopBench extends Widget { } if (content) { if (content instanceof Table) { - content.menuBar.setCssClass('main-menubar'); + content.menuBar.addCssClass('main-menubar'); + content.menuBar.removeCssClass('bounded'); } content.displayViewId = 'C'; } diff --git a/eclipse-scout-core/src/desktop/bench/DesktopBench.less b/eclipse-scout-core/src/desktop/bench/DesktopBench.less index e0f1f2df8a..2e0967add9 100644 --- a/eclipse-scout-core/src/desktop/bench/DesktopBench.less +++ b/eclipse-scout-core/src/desktop/bench/DesktopBench.less @@ -112,11 +112,10 @@ } & > .menubar-container { - padding-left: 6px; - padding-right: @detail-table-header-menubar-padding-right; - & > .menubar > .menubox > .menu-item { + & > .menubar > .menubar-box > .menu-item { margin-right: 12px; + border-radius: @border-radius; } } } diff --git a/eclipse-scout-core/src/desktop/outline/Outline.js b/eclipse-scout-core/src/desktop/outline/Outline.js index 71b58cd2a7..e126d84be4 100644 --- a/eclipse-scout-core/src/desktop/outline/Outline.js +++ b/eclipse-scout-core/src/desktop/outline/Outline.js @@ -20,7 +20,7 @@ import { HtmlComponent, keyStrokeModifier, MenuBar, - menus as menus_1, + menus as menuUtil, MessageBoxController, NavigateButton, NavigateDownButton, @@ -899,7 +899,7 @@ export default class Outline extends Tree { // DetailContent can be null or it is the tableRowDetail. Don't show menus on OutlineOverview. if (selectedPage.detailTable) { detailTable = selectedPage.detailTable; - menuItems = menus_1.filter(detailTable.menus, ['Table.EmptySpace'], false, true); + menuItems = menuUtil.filter(detailTable.menus, ['Table.EmptySpace'], false, true); tableControls = detailTable.tableControls; detailTable.setMenuBarVisible(false); this._attachDetailMenusListener(detailTable); @@ -908,7 +908,7 @@ export default class Outline extends Tree { let parentPage = selectedPage.parentNode; if (parentPage && parentPage.detailTable) { detailTable = parentPage.detailTable; - menuItems = menuItems.concat(menus_1.filter(detailTable.menus, ['Table.SingleSelection'], false, true)); + menuItems = menuItems.concat(menuUtil.filter(detailTable.menus, ['Table.SingleSelection'], false, true)); detailTable.setMenuBarVisible(false); this._attachDetailMenusListener(detailTable); } @@ -1196,7 +1196,7 @@ export default class Outline extends Tree { this.updateKeyStrokes(menus, oldMenus); this._setProperty('menus', menus); if (this.titleMenuBar) { // _setMenus is called by parent class Tree.js, at this time titleMenuBar is not yet initialized - let menuItems = menus_1.filter(this.menus, ['Tree.Header']); + let menuItems = menuUtil.filter(this.menus, ['Tree.Header']); this.titleMenuBar.setMenuItems(menuItems); } } diff --git a/eclipse-scout-core/src/desktop/outline/Outline.less b/eclipse-scout-core/src/desktop/outline/Outline.less index 425ce09ff8..84ad37a171 100644 --- a/eclipse-scout-core/src/desktop/outline/Outline.less +++ b/eclipse-scout-core/src/desktop/outline/Outline.less @@ -85,7 +85,6 @@ cursor: pointer; color: @outline-title-color; font-size: 19px; - #scout.overflow-ellipsis-nowrap(); padding: 0 @outline-title-padding-right 0 @outline-title-padding-left; border-bottom: 1px solid @outline-title-border-color; font-weight: @font-weight-bold; @@ -103,6 +102,7 @@ & > .text { flex-grow: 1; + #scout.overflow-ellipsis-nowrap(); } & > .menubar { @@ -110,6 +110,11 @@ border: none; width: auto; flex-grow: 0; + margin-right: -10px; + + & > .menubar-box > .menu-item.last { + margin-right: 0; + } } } @@ -226,6 +231,10 @@ & > .outline-title { padding-left: @compact-outline-title-padding-x; padding-right: @compact-outline-title-padding-x; + + & > .menubar { + margin-right: -@menubar-item-icononly-padding-x; + } } & > .tree-data { @@ -247,11 +256,27 @@ right: 0; top: 0; width: auto; - padding: @compact-outline-node-padding-y - 7px @compact-outline-node-padding-x 0 0; + padding: @compact-outline-node-padding-y - 7px @compact-outline-node-padding-x - @menubar-item-icononly-padding-x 0 0; } & > .detail-menubar { padding: 0 @compact-outline-node-padding-x @compact-outline-node-padding-y @compact-outline-node-padding-x; + margin-left: -@menubar-item-icononly-padding-x; + + & > .menubar-box > .menu-item { + padding-left: @menubar-item-icononly-padding-x; + padding-right: @menubar-item-icononly-padding-x; + margin-left: @menubar-item-icononly-margin-x; + margin-right: @menubar-item-icononly-margin-x; + + &.first { + margin-left: 0; + } + + &.last { + margin-right: 0; + } + } } & > .form { diff --git a/eclipse-scout-core/src/desktop/outline/navigation/NavigateButton.js b/eclipse-scout-core/src/desktop/outline/navigation/NavigateButton.js index b2c472755f..15a897bd19 100644 --- a/eclipse-scout-core/src/desktop/outline/navigation/NavigateButton.js +++ b/eclipse-scout-core/src/desktop/outline/navigation/NavigateButton.js @@ -27,10 +27,6 @@ export default class NavigateButton extends Menu { this.node = null; this.outline = null; this.actionStyle = Action.ActionStyle.BUTTON; - /** - * Additional CSS class to be applied in _render method. - */ - this._additionalCssClass = ''; this._addCloneProperties(['node', 'outline', 'altKeyStrokeContext']); this.inheritAccessibility = false; } @@ -49,7 +45,6 @@ export default class NavigateButton extends Menu { this.updateEnabled(); super._render(); this.$container.addClass('navigate-button small'); - this.$container.addClass(this._additionalCssClass); this.altKeyStrokeContext.registerKeyStroke(this); } diff --git a/eclipse-scout-core/src/desktop/outline/navigation/NavigateButton.less b/eclipse-scout-core/src/desktop/outline/navigation/NavigateButton.less index 0931331a42..e2bc79cc7d 100644 --- a/eclipse-scout-core/src/desktop/outline/navigation/NavigateButton.less +++ b/eclipse-scout-core/src/desktop/outline/navigation/NavigateButton.less @@ -10,18 +10,22 @@ */ .navigate-button { + &.menu-button > .font-icon { + font-size: 18px; + } + &.up { color: @navigate-up-button-color; border-color: @navigate-up-button-border-color; + } +} - & > .font-icon { - color: @navigate-up-button-color; - } +.menubar-box > .menu-button.navigate-button { + &.up { + margin-right: 6px; } - .menubar > .menubox > &.menu-item.down.menu-button:not(.last) { - /* Right and left margin of the navigate buttons should be equal. - Setting this margin-right is especially necessary if on the right side of these buttons is another button. */ - margin-right: @bench-padding-x; + &.down.left-of-button { + margin-right: @bench-padding-x - @menubar-button-margin; } } diff --git a/eclipse-scout-core/src/desktop/outline/navigation/NavigateUpButton.js b/eclipse-scout-core/src/desktop/outline/navigation/NavigateUpButton.js index 99dd84ad33..761c1c43a8 100644 --- a/eclipse-scout-core/src/desktop/outline/navigation/NavigateUpButton.js +++ b/eclipse-scout-core/src/desktop/outline/navigation/NavigateUpButton.js @@ -17,7 +17,6 @@ export default class NavigateUpButton extends NavigateButton { super(); this._defaultIconId = icons.ANGLE_UP; this._defaultText = 'ui.Up'; - this._additionalCssClass = 'small-gap'; this.iconId = this._defaultIconId; this.keyStroke = 'backspace'; } diff --git a/eclipse-scout-core/src/desktop/toolbox/DesktopToolBox.less b/eclipse-scout-core/src/desktop/toolbox/DesktopToolBox.less index 89ae8c6711..09de08ae6e 100644 --- a/eclipse-scout-core/src/desktop/toolbox/DesktopToolBox.less +++ b/eclipse-scout-core/src/desktop/toolbox/DesktopToolBox.less @@ -25,6 +25,8 @@ padding: 0 14px; border-radius: @desktop-tool-box-item-border-radius; color: inherit; + display: inline-flex; + align-items: center; &.compact { padding-left: 10px; @@ -35,10 +37,6 @@ } } - &:not(.menu-button) > .icon { - top: 0; // TODO BrushUp remove when -1 hack is removed from menu.less - } - & > .font-icon { font-size: @desktop-tool-box-item-font-size; } @@ -53,7 +51,8 @@ color: inherit; } - &.selected { + &.selected.has-popup { + color: inherit; background-color: @desktop-tool-box-item-selected-background-color; } diff --git a/eclipse-scout-core/src/focus/FocusManager.js b/eclipse-scout-core/src/focus/FocusManager.js index 43b9271e5d..c62ea095aa 100644 --- a/eclipse-scout-core/src/focus/FocusManager.js +++ b/eclipse-scout-core/src/focus/FocusManager.js @@ -361,7 +361,7 @@ export default class FocusManager { firstElement = candidate; } - if (!firstDefaultButton && $candidate.is('.default-menu')) { + if (!firstDefaultButton && $candidate.is('.default')) { firstDefaultButton = candidate; } diff --git a/eclipse-scout-core/src/form/FormMenu.js b/eclipse-scout-core/src/form/FormMenu.js index d13b802310..1a2f4a6460 100644 --- a/eclipse-scout-core/src/form/FormMenu.js +++ b/eclipse-scout-core/src/form/FormMenu.js @@ -112,6 +112,13 @@ export default class FormMenu extends Menu { } } + _renderSelected() { + super._renderSelected(); + + // Form menu always has a popup (form could be set later, so super call cannot set the class correctly) + this.$container.addClass('has-popup'); + } + _canOpenPopup() { // A menu can be opened in the menu bar but also in a context menu, where it will be cloned. // The form itself won't be cloned, so there can always be only one rendered form. diff --git a/eclipse-scout-core/src/form/FormMenu.less b/eclipse-scout-core/src/form/FormMenu.less index ae637d53a8..73398e49c1 100644 --- a/eclipse-scout-core/src/form/FormMenu.less +++ b/eclipse-scout-core/src/form/FormMenu.less @@ -13,5 +13,4 @@ & > .form > .root-group-box > .main-menubar { background-color: @form-menu-popup-main-menubar-background-color; } - } diff --git a/eclipse-scout-core/src/form/fields/button/Button.js b/eclipse-scout-core/src/form/fields/button/Button.js index 7c8a2fe41e..8c824ac8be 100644 --- a/eclipse-scout-core/src/form/fields/button/Button.js +++ b/eclipse-scout-core/src/form/fields/button/Button.js @@ -50,7 +50,8 @@ export default class Button extends FormField { DEFAULT: 0, TOGGLE: 1, RADIO: 2, - LINK: 3 + LINK: 3, + BORDERLESS: 4 }; static SUBMENU_ICON = icons.ANGLE_DOWN_BOLD; @@ -122,19 +123,17 @@ export default class Button extends FormField { _render() { let $button; if (this.displayStyle === Button.DisplayStyle.LINK) { - // Render as link-button/ menu-item. - // This is a bit weird: the model defines a button, but in the UI it behaves like a menu-item. - // Probably it would be more reasonable to change the configuration (which would lead to additional - // effort required to change an existing application). - $button = this.$parent.makeDiv('link-button'); - // Separate $link element to have a smaller focus border - this.$link = $button.appendDiv('menu-item link'); - this.$buttonLabel = this.$link.appendSpan('button-label text'); + // Render as link-button + $button = this.$parent.makeDiv('link-button menu-item'); + this.$buttonLabel = $button.appendSpan('button-label text'); } else { // render as button $button = this.$parent.makeElement('<button>') .addClass('button'); - this.$buttonLabel = $button.appendSpan('button-label'); + if (this.displayStyle === Button.DisplayStyle.BORDERLESS) { + $button.addClass('borderless'); + } + this.$buttonLabel = $button.appendSpan('button-label text'); if (Device.get().supportsOnlyTouch()) { $button.setTabbable(false); @@ -156,7 +155,7 @@ export default class Button extends FormField { }, this); if (this.label || !this.iconId) { // no indicator when _only_ the icon is visible let icon = icons.parseIconId(Button.SUBMENU_ICON); - this.$submenuIcon = (this.$link || $button) + this.$submenuIcon = $button .appendSpan('submenu-icon') .text(icon.iconCharacter); } @@ -269,7 +268,6 @@ export default class Button extends FormField { _renderEnabled() { super._renderEnabled(); if (this.displayStyle === Button.DisplayStyle.LINK) { - this.$link.setEnabled(this.enabledComputed); this.$field.setTabbable(this.enabledComputed && !Device.get().supportsOnlyTouch()); } } @@ -303,7 +301,7 @@ export default class Button extends FormField { * Adds an image or font-based icon to the button by adding either an IMG or SPAN element to the button. */ _renderIconId() { - let $iconTarget = this.$link || this.$fieldContainer; + let $iconTarget = this.$fieldContainer; $iconTarget.icon(this.iconId); let $icon = $iconTarget.data('$icon'); if ($icon) { @@ -337,7 +335,7 @@ export default class Button extends FormField { } get$Icon() { - let $iconTarget = this.$link || this.$fieldContainer; + let $iconTarget = this.$fieldContainer; return $iconTarget.children('.icon'); } diff --git a/eclipse-scout-core/src/form/fields/button/Button.less b/eclipse-scout-core/src/form/fields/button/Button.less index 4d6b7f95a4..7953fd16ec 100644 --- a/eclipse-scout-core/src/form/fields/button/Button.less +++ b/eclipse-scout-core/src/form/fields/button/Button.less @@ -8,25 +8,6 @@ * Contributors: * BSI Business Systems Integration AG - initial API and implementation */ -.button-field > .button { - - /* The 18px min-height is required because in Chrome the height of the ::after element - * is not calculated properly when height is set to 100%. This has the effect that buttons - * change their size when a button changes its focus state. See ticket 194111. - */ - #scout.vertical-align-helper-after(18px); - - & > .button-label { - padding: @text-field-padding-y - @button-padding-y - @button-margin-top 0; - } - - &.selected { - border-style: inset; - border-right-color: @border-color; - border-bottom-color: @border-color; - } -} - .button-field > .field { margin-left: @mandatory-indicator-width; @@ -35,67 +16,22 @@ } } -.button-field > .field.link-button { - /* add a transparent border to align text with normal buttons which also have a border */ - border-top: 1px solid transparent; - border-bottom: 1px solid transparent; - - .logical-grid-layout > & { - /* make sure link is centered when button has a fixed height (which is the case with logical grid layout) */ - #scout.vertical-align-helper-before(); - white-space: nowrap; - } - - & > .link.menu-item { - /* Draw underline in child element, otherwise the position would not be correct in FF */ - text-decoration: none; - vertical-align: middle; - color: @link-color; - - & > .text { - text-decoration: underline; - overflow: hidden; - text-overflow: ellipsis; - } - - & > .text, - & > .submenu-icon, - & > .icon { - /* Reset IE hack for link buttons because it breaks ellipsis tooltip */ - pointer-events: auto; - - /* Make sure button is not clickable if it is covered by a glass pane */ - - .glasspane-parent&, - .glasspane-parent & { - pointer-events: none; - } - } - - &:hover { - color: @link-hover-color; - } +.button-field > .button { + // Ensure button height remains the same if fill vertical = false + min-height: @logical-grid-row-height; +} - &:active, &.active { - color: @link-active-color; - } +.button-field > .link-button { + padding-left: 0; + padding-right: 0; - &.disabled { - color: @disabled-color; - } + &:hover, + &:active, &.active { + background-color: transparent; } &:focus { - outline: none; - - & > .link.menu-item::after { - #scout.button-focus(); - } - } - - & > .link.menu-item > .icon.image-icon { - /* override hack in Menu.css */ - top: 0; + #scout.box-shadow-focus(); } } diff --git a/eclipse-scout-core/src/form/fields/groupbox/GroupBox.less b/eclipse-scout-core/src/form/fields/groupbox/GroupBox.less index 4a4eee7fd2..dc513f0f37 100644 --- a/eclipse-scout-core/src/form/fields/groupbox/GroupBox.less +++ b/eclipse-scout-core/src/form/fields/groupbox/GroupBox.less @@ -32,7 +32,6 @@ & > .menubar { margin-left: @mandatory-indicator-width; - margin-right: 0; background-color: inherit; #scout.menubar-background-color-inherit; } @@ -76,6 +75,13 @@ display: inline-block; border: none; background-color: transparent; + vertical-align: middle; + padding-left: 10px; + + & > .menubar-box > .menu-item { + margin-top: 0; + margin-bottom: 0; + } } } diff --git a/eclipse-scout-core/src/form/fields/tabbox/TabBox.less b/eclipse-scout-core/src/form/fields/tabbox/TabBox.less index ed894614f7..85466972fc 100644 --- a/eclipse-scout-core/src/form/fields/tabbox/TabBox.less +++ b/eclipse-scout-core/src/form/fields/tabbox/TabBox.less @@ -64,11 +64,6 @@ & > .tab-item > .title > .sub-label { display: block; } - - & > .overflow-tab-item.menu-item { - padding-top: 12px; - padding-bottom: 20px; - } } &.spread-even { @@ -90,8 +85,7 @@ & > .menubar { position: absolute; #scout.menubar-background-color-inherit(); - border-bottom: none; - padding-left: 12px; + border-bottom-color: transparent; } & > .status { @@ -221,28 +215,16 @@ } .overflow-tab-item.menu-item { - padding: @tab-item-title-padding-top @tab-item-padding-x - 8px @tab-item-title-padding-bottom; - /* Reduce padding a little and compensate with margin to make popup header a little smaller */ - margin: 0 8px; - font-weight: bold; - - /* This makes sure the overflow menu item is not larger than a tab item (especially if a larger font is used). - It also prevents the popup from jumping. */ - line-height: 1; + vertical-align: middle; + color: @tab-item-color; &::before { display: none; } - & > .text { - padding: 0; - - } - & > .submenu-icon { padding-left: 4px; - line-height: 15px; } &:focus { diff --git a/eclipse-scout-core/src/form/fields/wrappedform/WrappedFormField.less b/eclipse-scout-core/src/form/fields/wrappedform/WrappedFormField.less index c0aed49885..20edad4478 100644 --- a/eclipse-scout-core/src/form/fields/wrappedform/WrappedFormField.less +++ b/eclipse-scout-core/src/form/fields/wrappedform/WrappedFormField.less @@ -36,8 +36,16 @@ & > .root-group-box > .menubar { margin-left: @mandatory-indicator-width; - margin-right: 0; - padding-left: 0; #scout.menubar-background-color-inherit; + + & > .menubar-box > .menu-item { + &.first { + margin-left: 0; + } + + &.last { + margin-right: 0; + } + } } } diff --git a/eclipse-scout-core/src/index.js b/eclipse-scout-core/src/index.js index 697ce7c968..aaaa4c6259 100644 --- a/eclipse-scout-core/src/index.js +++ b/eclipse-scout-core/src/index.js @@ -240,8 +240,8 @@ export {default as MenuBarLayout} from './menu/menubar/MenuBarLayout'; export {default as MenuBarLeftKeyStroke} from './menu/menubar/MenuBarLeftKeyStroke'; export {default as MenuBarRightKeyStroke} from './menu/menubar/MenuBarRightKeyStroke'; export {default as MenuBarPopup} from './menu/menubar/MenuBarPopup'; -export {default as MenubarBox} from './menu/menubar/MenubarBox'; -export {default as MenubarBoxLayout} from './menu/menubar/MenubarBoxLayout'; +export {default as MenuBarBox} from './menu/menubar/MenuBarBox'; +export {default as MenuBarBoxLayout} from './menu/menubar/MenuBarBoxLayout'; export {default as Calendar} from './calendar/Calendar'; export {default as CalendarAdapter} from './calendar/CalendarAdapter'; export {default as CalendarComponent} from './calendar/CalendarComponent'; diff --git a/eclipse-scout-core/src/index.less b/eclipse-scout-core/src/index.less index b293201f68..e1f1ac2188 100644 --- a/eclipse-scout-core/src/index.less +++ b/eclipse-scout-core/src/index.less @@ -88,8 +88,9 @@ @import "image/Image"; @import "keystroke/keybox"; @import "layout/logicalgrid/LogicalGridLayout"; -@import "menu/ContextMenuPopup"; @import "menu/Menu"; +@import "menu/ContextMenuPopup"; +@import "menu/ComboMenu"; @import "menu/form/field/FormFieldMenu"; @import "menu/menubar/MenuBar"; @import "menu/menubar/MenuBarPopup"; diff --git a/eclipse-scout-core/src/main.less b/eclipse-scout-core/src/main.less index 3304c1996c..1f5aaed33b 100644 --- a/eclipse-scout-core/src/main.less +++ b/eclipse-scout-core/src/main.less @@ -319,27 +319,23 @@ button { .button { position: relative; + display: inline-flex; + align-items: center; + justify-content: center; color: @button-color; background-color: @button-background-color; border: 1px solid @button-border-color; border-radius: @button-border-radius; cursor: pointer; - white-space: nowrap; padding: @button-padding-y @button-padding-x; - /* Note: no vertical align helper here! this is very dependent on the content of the button */ - /* (only text or other DOM nodes), therefore it has to be added manually when desired. */ - & > .button-label, - & > .submenu-icon, - & > .icon { - vertical-align: middle; - display: inline-block; + &.borderless { + background-color: transparent; + border: 0; } - & > .button-label { - margin-top: @button-margin-top; - overflow: hidden; - text-overflow: ellipsis; + & > .text { + #scout.overflow-ellipsis-nowrap(); } & > .icon { @@ -362,13 +358,16 @@ button { } & > .font-icon { - color: @button-font-icon-color; font-size: @button-font-icon-size; } & > .submenu-icon { #scout.submenu-icon(); padding-left: 8px; + + .selected& { + #scout.submenu-icon-open(); + } } & > .button-label, @@ -380,39 +379,43 @@ button { &:hover { color: @button-hover-color; + background-color: @hover-background-color; } - &:focus { - outline: none; - overflow: visible; /* without this, IE would cut off the focus glow */ - - &::before { - #scout.button-focus(); - } - } - - &:active, - &.active { + &:active, &.active { color: @button-active-color; background-color: @button-active-background-color; + } - & > .font-icon { - color: @button-active-color; + &.selected { + color: @active-inverted-color; + background-color: @active-inverted-background-color; + border-color: @active-inverted-background-color; + + &:hover { + background-color: @active-inverted-hover-background-color; + border-color: @active-inverted-hover-background-color; + } + + &.active, &:active { + background-color: @active-inverted-active-background-color; + border-color: @active-inverted-active-background-color; } } - &.default:not(.disabled) { + &:focus { + #scout.button-focus(); + } + + &.default { background-color: @default-button-background-color; /* border is necessary to align the text with text from buttons with a real border */ border-color: @default-button-background-color; color: @default-button-color; - &:focus { - color: @default-button-color; - } - &:hover { background-color: @default-button-hover-background-color; + border-color: @default-button-hover-background-color; } &:active, &.active { @@ -420,20 +423,21 @@ button { border-color: @default-button-active-background-color; } - & > .font-icon { - font-weight: normal; - color: @icon-inverted-color; + &:focus { + #scout.button-focus(); } } - &:disabled { + &:disabled, + &.disabled { background-color: @button-disabled-background-color; border-color: @button-disabled-border-color; color: @button-disabled-color; cursor: default; - & > .font-icon { - color: @button-font-icon-disabled-color; + &:hover, &.active, &:active { + background-color: @button-disabled-background-color; + border-color: @button-disabled-border-color; } } diff --git a/eclipse-scout-core/src/menu/ComboMenu.js b/eclipse-scout-core/src/menu/ComboMenu.js index 93818a9605..5b57608c83 100644 --- a/eclipse-scout-core/src/menu/ComboMenu.js +++ b/eclipse-scout-core/src/menu/ComboMenu.js @@ -14,6 +14,15 @@ export default class ComboMenu extends Menu { constructor() { super(); + this.childSelected = false; + this._childSelectedHandler = this._onChildSelected.bind(this); + } + + _setChildActions(childActions) { + this.childActions.forEach(child => child.off('propertyChange:selected', this._childSelectedHandler)); + super._setChildActions(childActions); + this.childActions.forEach(child => child.on('propertyChange:selected', this._childSelectedHandler)); + this._updateChildSelected(); } _render() { @@ -23,6 +32,16 @@ export default class ComboMenu extends Menu { } this.$container.unfocusable(); this.htmlComp = HtmlComponent.install(this.$container, this.session); + } + + _renderProperties() { + super._renderProperties(); + this._renderChildSelected(); + this._renderChildActions(); + } + + _renderChildActions() { + super._renderChildActions(); this.childActions.forEach(childAction => { childAction.addCssClass('combo-menu-child'); @@ -34,4 +53,16 @@ export default class ComboMenu extends Menu { _togglesSubMenu() { return false; } + + _onChildSelected(event) { + this._updateChildSelected(); + } + + _updateChildSelected() { + this.setProperty('childSelected', this.childActions.some(child => child.selected)); + } + + _renderChildSelected() { + this.$container.toggleClass('child-selected', this.childSelected); + } } diff --git a/eclipse-scout-core/src/menu/ComboMenu.less b/eclipse-scout-core/src/menu/ComboMenu.less new file mode 100644 index 0000000000..e612164783 --- /dev/null +++ b/eclipse-scout-core/src/menu/ComboMenu.less @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2010-2021 BSI Business Systems Integration AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * BSI Business Systems Integration AG - initial API and implementation + */ + +.combo-menu { + padding: 0; + cursor: default; + align-items: stretch; + + &:active, &.active, &:hover { + background-color: transparent; + } + + & > .menu-item:not(:first-child) { + margin-left: 3px; + + &::before { + content: ''; + position: absolute; + left: -1px; + top: 7px; + height: calc(100% - 14px); + width: 1px; + background-color: @border-color; + } + } + + &:not(.disabled).child-selected, + &:not(.disabled):hover { + & > .menu-item:not(:first-child)::before { + display: none; + } + } +} + +.combo-menu.menu-button { + border: 0; + + & > .menu-item { + border: 1px solid @button-border-color; + + &:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right: 0; + } + + &:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left: 0; + margin-left: 0; + } + + .disabled& { + border-color: @button-disabled-color; + } + } + + &.default { + & > .menu-item { + .button.default(); + + &.selected { + background-color: @default-button-active-background-color; + border-color: @default-button-active-background-color; + } + } + } +} + +.context-menu { + & > .combo-menu { + padding: 0; + + & > .menu-item { + color: @context-menu-item-color; + padding-top: @context-menu-item-padding-y; + padding-bottom: @context-menu-item-padding-y; + + &:first-child { + margin-left: @context-menu-item-padding-left - @menu-item-padding-x; + } + } + } +}
\ No newline at end of file diff --git a/eclipse-scout-core/src/menu/ContextMenuPopup.js b/eclipse-scout-core/src/menu/ContextMenuPopup.js index 30599361fc..b886b17940 100644 --- a/eclipse-scout-core/src/menu/ContextMenuPopup.js +++ b/eclipse-scout-core/src/menu/ContextMenuPopup.js @@ -8,7 +8,7 @@ * Contributors: * BSI Business Systems Integration AG - initial API and implementation */ -import {Action, arrays, ContextMenuPopupLayout, HtmlComponent, MenuDestinations, menuNavigationKeyStrokes, Popup, RowLayout, scrollbars} from '../index'; +import {Action, arrays, ContextMenuPopupLayout, HtmlComponent, MenuDestinations, menuNavigationKeyStrokes, Popup, RowLayout, Rectangle, graphics} from '../index'; import $ from 'jquery'; export default class ContextMenuPopup extends Popup { @@ -20,8 +20,9 @@ export default class ContextMenuPopup extends Popup { this.animateRemoval = true; this.menuItems = []; this.cloneMenuItems = true; + this.bodyAnimating = false; this._toggleSubMenuQueue = []; - this.repositionEnabled = true; + this.animationDuration = 300; } _init(options) { @@ -65,6 +66,11 @@ export default class ContextMenuPopup extends Popup { this._renderMenuItems(); } + _remove() { + this._toggleSubMenuQueue = []; + super._remove(); + } + _renderBody() { this.$body = this.$container.appendDiv('context-menu'); // Complete the layout hierarchy between the popup and the menu items @@ -72,41 +78,33 @@ export default class ContextMenuPopup extends Popup { htmlBody.setLayout(this._createBodyLayout()); } - /** - * @param [options] - * @override - */ _installScrollbars(options) { super._installScrollbars({ axis: 'y' }); } - /** - * @override - */ - get$Scrollable() { - return this.$body; - } - - _bounds() { - return this.htmlComp.bounds().subtractFromDimension(this.htmlComp.insets()); - } - - removeSubMenuItems(parentMenu, animated) { + _checkRemoveSubMenuItemsPossible(parentMenu, animated) { if (!this.rendered && !this.rendering) { - return; + return false; } let openingAnimationRunning = this.isOpeningAnimationRunning(); - if (this.bodyAnimating || openingAnimationRunning) { + let resizeAnimationRunning = this.htmlComp.layout.resizeAnimationRunning; + if (this.bodyAnimating || openingAnimationRunning || resizeAnimationRunning) { // Let current animation finish and execute afterwards to prevent an unpredictable behavior and inconsistent state this._toggleSubMenuQueue.push(this.removeSubMenuItems.bind(this, parentMenu, animated)); if (openingAnimationRunning) { this.$container.oneAnimationEnd(() => this._processSubMenuQueue()); } - return; + return false; } + return true; + } + removeSubMenuItems(parentMenu, animated) { + if (!this._checkRemoveSubMenuItemsPossible(parentMenu, animated)) { + return false; + } this.$body = parentMenu.__originalParent.$subMenuBody; // move new body to back this.$body.insertBefore(parentMenu.$subMenuBody); @@ -115,75 +113,74 @@ export default class ContextMenuPopup extends Popup { parentMenu.__originalParent._doActionTogglesSubMenu(); } - let actualBounds = this._bounds(); + let popupBounds = this.htmlComp.bounds(); - this.revalidateLayout(); + this._adjustTextAlignment(); + HtmlComponent.get(this.$body).invalidateLayoutTree(); + this.validateLayoutTree(); this.position(); if (animated) { - this.bodyAnimating = true; - let duration = 300; - let position = parentMenu.$placeHolder.position(); - parentMenu.$subMenuBody.css({ - width: 'auto', - height: 'auto' - }); - let targetBounds = this._bounds(); - parentMenu.$subMenuBody.css('box-shadow', 'none'); - this.htmlComp.setBounds(actualBounds); - if (this.verticalAlignment !== Popup.Alignment.TOP) { - // set container to element - parentMenu.$subMenuBody.cssTop(); - } - - this._animateTopAndLeft(this.htmlComp.$comp, actualBounds, targetBounds, duration); + this._animateRemoveSubmenuItems(parentMenu, popupBounds); + } + } - // move new body to top of popup - parentMenu.$subMenuBody.cssHeightAnimated(actualBounds.height, parentMenu.$container.cssHeight(), { - duration: duration, + _animateRemoveSubmenuItems(parentMenu, popupBounds) { + let parentMenuPosition = parentMenu.$placeHolder.position(); + let popupInsets = this.htmlComp.insets(); + let endPopupBounds = this.htmlComp.bounds(); + let oldBodyBounds = HtmlComponent.get(parentMenu.$subMenuBody).bounds(); + let bodyBounds = HtmlComponent.get(this.$body).bounds(); + let startBodyBounds = new Rectangle(0, popupInsets.top, oldBodyBounds.width, oldBodyBounds.height); + let endBodyBounds = new Rectangle(0, popupInsets.top + parentMenuPosition.top, bodyBounds.width, parentMenu.$container.cssHeight()); + + this.bodyAnimating = true; + this.htmlComp.layout.disableAutoPosition(); + this._animateResizePopup(this.htmlComp.$comp, popupBounds, endPopupBounds); + this._animateTextOffset(parentMenu.$subMenuBody, parentMenu.$subMenuBody.data('text-offset')); + + // Collapse old body + parentMenu.$subMenuBody + .cssWidthAnimated(startBodyBounds.width, endBodyBounds.width, { + duration: this.animationDuration, + progress: this.revalidateLayout.bind(this), + complete: () => this._completeAnimateRemoveSubMenuItems(parentMenu), + queue: false + }) + .cssHeightAnimated(startBodyBounds.height, endBodyBounds.height, { + duration: this.animationDuration, + queue: false + }) + .cssTopAnimated(startBodyBounds.y, endBodyBounds.y, { + duration: this.animationDuration, queue: false }); - let endTopposition = position.top - this.$body.cssHeight(), - startTopposition = 0 - actualBounds.height; - - parentMenu.$subMenuBody.cssTopAnimated(startTopposition, endTopposition, { - duration: duration, - queue: false, - complete: () => { - this.bodyAnimating = false; - if (!this.rendered || !parentMenu.$container) { - return; - } - scrollbars.uninstall(parentMenu.$subMenuBody, this.session); - parentMenu.$placeHolder.replaceWith(parentMenu.$container); - parentMenu.$container.toggleClass('expanded', false); - this._updateFirstLastClass(); - this.updateNextToSelected('menu-item', parentMenu.$container); - - parentMenu.$subMenuBody.detach(); - this._installScrollbars(); - this.$body.css('box-shadow', ''); - // Do one final layout to fix any potentially wrong sizes (e.g. due to async image loading) - this._invalidateLayoutTreeAndRepositionPopup(); - this._processSubMenuQueue(); - } - }); - - this.$body.cssWidthAnimated(actualBounds.width, targetBounds.width, { - duration: duration, - start: this.revalidateLayout.bind(this, true), - progress: this.revalidateLayout.bind(this, false), + // Resize new body so that it doesn't increase the popup height and shows unnecessary scrollbars if new body will be bigger + // It also ensures correct text ellipsis during animation, position is already correct + this.$body + .cssWidthAnimated(oldBodyBounds.width, bodyBounds.width, { + duration: this.animationDuration, + queue: false + }) + .cssHeightAnimated(oldBodyBounds.height, bodyBounds.height, { + duration: this.animationDuration, queue: false }); + } - if (targetBounds.height !== actualBounds.height) { - this.$body.cssHeightAnimated(actualBounds.height, targetBounds.height, { - duration: duration, - queue: false - }); - } + _completeAnimateRemoveSubMenuItems(parentMenu) { + this.bodyAnimating = false; + if (!this.rendered || !parentMenu.$container) { + return; } + parentMenu.$placeHolder.replaceWith(parentMenu.$container); + parentMenu.$container.toggleClass('expanded', false); + this._updateFirstLastClass(); + this.updateNextToSelected('menu-item', parentMenu.$container); + + parentMenu.$subMenuBody.detach(); + this._processSubMenuQueue(); } _processSubMenuQueue() { @@ -193,33 +190,40 @@ export default class ContextMenuPopup extends Popup { } } - renderSubMenuItems(parentMenu, menus, animated, initialSubMenuRendering) { + _checkRenderSubMenuItemsPossible(parentMenu, menus, animated, initialSubMenuRendering) { if (!this.session.desktop.rendered && !initialSubMenuRendering) { this.initialSubMenusToRender = { parentMenu: parentMenu, menus: menus }; - return; + return false; } if (!this.rendered && !this.rendering) { - return; + return false; } let openingAnimationRunning = this.isOpeningAnimationRunning(); - if (this.bodyAnimating || openingAnimationRunning) { + let resizeAnimationRunning = this.htmlComp.layout.resizeAnimationRunning; + if (this.bodyAnimating || openingAnimationRunning || resizeAnimationRunning) { // Let current animation finish and execute afterwards to prevent an unpredictable behavior and inconsistent state this._toggleSubMenuQueue.push(this.renderSubMenuItems.bind(this, parentMenu, menus, animated, initialSubMenuRendering)); if (openingAnimationRunning) { this.$container.oneAnimationEnd(() => this._processSubMenuQueue()); } - return; + return false; } + return true; + } - let actualBounds = this._bounds(); - - parentMenu.__originalParent.$subMenuBody = this.$body; + renderSubMenuItems(parentMenu, menus, animated, initialSubMenuRendering) { + if (!this._checkRenderSubMenuItemsPossible(parentMenu, menus, animated, initialSubMenuRendering)) { + return false; + } - let $all = this.$body.find('.' + 'menu-item'); - $all.removeClass('next-to-selected'); + let popupBounds = this.htmlComp.bounds(); + let $oldBody = this.$body; + parentMenu.__originalParent.$subMenuBody = $oldBody; + let $menuItems = this.$body.find('.menu-item'); + $menuItems.removeClass('next-to-selected'); if (!parentMenu.$subMenuBody) { this._renderBody(); @@ -230,117 +234,114 @@ export default class ContextMenuPopup extends Popup { this.$body = parentMenu.$subMenuBody; } let $insertAfterElement = parentMenu.$container.prev(); - let position = parentMenu.$container.position(); + let parentMenuPosition = parentMenu.$container.position(); parentMenu.$placeHolder = parentMenu.$container.clone(); // HtmlComponent is necessary for the row layout (it would normally be installed by Menu.js, but $placeholder is just a jquery clone of parentMenu.$container and is not managed by a real widget) HtmlComponent.install(parentMenu.$placeHolder, this.session); if ($insertAfterElement.length) { parentMenu.$placeHolder.insertAfter($insertAfterElement); } else { - parentMenu.__originalParent.$subMenuBody.prepend(parentMenu.$placeHolder); + $oldBody.prepend(parentMenu.$placeHolder); } - this.$body.insertAfter(parentMenu.__originalParent.$subMenuBody); + this.$body.insertAfter($oldBody); this.$body.prepend(parentMenu.$container); parentMenu.$container.toggleClass('expanded'); + this._adjustTextAlignment(); - this.revalidateLayout(); + HtmlComponent.get(this.$body).invalidateLayoutTree(); + this.validateLayoutTree(); this.position(); - this.updateNextToSelected(); if (animated) { - this.bodyAnimating = true; - let duration = 300; - parentMenu.__originalParent.$subMenuBody.css({ - width: 'auto', - height: 'auto' - }); - let targetBounds = this._bounds(); - - this._animateTopAndLeft(this.htmlComp.$comp, actualBounds, targetBounds, duration); + this._animateRenderSubMenuItems(parentMenu, popupBounds, parentMenuPosition); + } else { + $oldBody.detach(); + this._updateFirstLastClass(); + } + } - this.$body.css('box-shadow', 'none'); - // set container to element - this.$body.cssWidthAnimated(actualBounds.width, targetBounds.width, { - duration: duration, - start: this.revalidateLayout.bind(this, true), - progress: this.revalidateLayout.bind(this, false), + _animateRenderSubMenuItems(parentMenu, popupBounds, parentMenuPosition) { + let $oldBody = parentMenu.__originalParent.$subMenuBody; + let endPopupBounds = this.htmlComp.bounds(); + let popupInsets = this.htmlComp.insets(); + let oldBodyBounds = graphics.bounds($oldBody); + let bodyBounds = HtmlComponent.get(this.$body).bounds(); + let startBodyBounds = new Rectangle(0, popupInsets.top + parentMenuPosition.top, oldBodyBounds.width, parentMenu.$container.cssHeight()); + let endBodyBounds = new Rectangle(0, popupInsets.top, bodyBounds.width, bodyBounds.height); + + this.bodyAnimating = true; + this.htmlComp.layout.disableAutoPosition(); + this._animateResizePopup(this.htmlComp.$comp, popupBounds, endPopupBounds); + this._animateTextOffset(this.$body, $oldBody.data('text-offset')); + + // Expand new body + this.$body + .cssWidthAnimated(startBodyBounds.width, endBodyBounds.width, { + duration: this.animationDuration, + progress: this.revalidateLayout.bind(this), + complete: () => this._completeAnimateRenderSubMenuItems($oldBody), queue: false - }); - - this.$body.cssHeightAnimated(parentMenu.$container.cssHeight(), targetBounds.height, { - duration: duration, + }) + .cssHeightAnimated(startBodyBounds.height, endBodyBounds.height, { + duration: this.animationDuration, + queue: false + }) + .cssTopAnimated(startBodyBounds.y, endBodyBounds.y, { + duration: this.animationDuration, queue: false }); - let endTopposition = 0 - targetBounds.height, - startTopposition = position.top - parentMenu.__originalParent.$subMenuBody.cssHeight(), - topMargin = 0; - - // move new body to top of popup. - this.$body.cssTopAnimated(startTopposition, endTopposition, { - duration: duration, - queue: false, - complete: () => { - this.bodyAnimating = false; - if (!this.rendered) { - return; - } - if (parentMenu.__originalParent.$subMenuBody) { - scrollbars.uninstall(parentMenu.__originalParent.$subMenuBody, this.session); - parentMenu.__originalParent.$subMenuBody.detach(); - this.$body.cssTop(topMargin); - this._installScrollbars(); - this._updateFirstLastClass(); - this.$body.css('box-shadow', ''); - } - // Do one final layout to fix any potentially wrong sizes (e.g. due to async image loading) - this._invalidateLayoutTreeAndRepositionPopup(); - this._processSubMenuQueue(); - } + // Resize old body so that it doesn't increase the popup height and shows unnecessary scrollbars if new body will be smaller + // It also ensures correct text ellipsis during animation, position is already correct + $oldBody + .cssWidthAnimated(oldBodyBounds.width, endBodyBounds.width, { + duration: this.animationDuration, + queue: false + }) + .cssHeightAnimated(oldBodyBounds.height, endBodyBounds.height, { + duration: this.animationDuration, + queue: false }); + } - if (actualBounds.height !== targetBounds.height) { - parentMenu.__originalParent.$subMenuBody.cssHeightAnimated(actualBounds.height, targetBounds.height, { - duration: duration, - queue: false - }); - this.$container.cssHeight(actualBounds.height, targetBounds.height, { - duration: duration, - queue: false - }); - } - if (this.verticalAlignment === Popup.Alignment.TOP) { - this.$container.cssTopAnimated(actualBounds.y, targetBounds.y, { - duration: duration, - queue: false - }).css('overflow', 'visible'); - } - } else { - if (!initialSubMenuRendering) { - scrollbars.uninstall(parentMenu.__originalParent.$subMenuBody, this.session); - } - parentMenu.__originalParent.$subMenuBody.detach(); - this._installScrollbars(); + _completeAnimateRenderSubMenuItems($oldBody) { + this.bodyAnimating = false; + if (!this.rendered) { + return; + } + if ($oldBody) { + $oldBody.detach(); + this.$body.cssTop(''); this._updateFirstLastClass(); } + this.htmlComp.layout.resetAutoPosition(); + this._processSubMenuQueue(); } - _animateTopAndLeft($comp, actualBounds, targetBounds, duration) { + _animateResizePopup($comp, popupBounds, targetBounds) { let options = { - duration: duration, + duration: this.animationDuration, queue: false }; $comp - .cssTopAnimated(actualBounds.y, targetBounds.y, options) - .cssLeftAnimated(actualBounds.x, targetBounds.x, options); + .cssTopAnimated(popupBounds.y, targetBounds.y, options) + .cssLeftAnimated(popupBounds.x, targetBounds.x, options) + .cssWidthAnimated(popupBounds.width, targetBounds.width, options) + .cssHeightAnimated(popupBounds.height, targetBounds.height, options); } - revalidateLayout(repositionEnabled) { - this.repositionEnabled = scout.nvl(repositionEnabled, true); - super.revalidateLayout(); - this.repositionEnabled = true; + _animateTextOffset($body, textOffset, targetOffset) { + targetOffset = scout.nvl(targetOffset, this.$body.data('text-offset')); + let $menuItems = this.$visibleMenuItems($body); + $menuItems.each((index, menuItem) => { + let $menuItem = $(menuItem); + let $text = $menuItem.children('.text'); + let padding = this._calcTextPaddingLeft($menuItem, textOffset); + let targetPadding = this._calcTextPaddingLeft($menuItem, targetOffset); + $text.cssAnimated({paddingLeft: padding}, {paddingLeft: targetPadding}, {duration: this.animationDuration}); + }); } _renderMenuItems(menus, initialSubMenuRendering) { @@ -382,19 +383,13 @@ export default class ContextMenuPopup extends Popup { menu.__originalParent = originalParent; } menu.render(this.$body); + menu.$container.removeClass('menu-button'); this._attachMenuListeners(menu); - - // Invalidate popup layout after images icons have been loaded, because the - // correct size might not be known yet. If the layout would not be revalidated, the popup - // size will be wrong (text is cut off after image has been loaded). - // The menu item actually does it by itself, but the popup needs to be repositioned too. - if (menu.icon) { - menu.icon.on('load error', this._invalidateLayoutTreeAndRepositionPopup.bind(this)); - } }, this); this._handleInitialSubMenus(initialSubMenuRendering); this._updateFirstLastClass(); + this._adjustTextAlignment(); } _attachCloneMenuListeners(menu) { @@ -456,8 +451,9 @@ export default class ContextMenuPopup extends Popup { return this.$body.children('.menu-item'); } - $visibleMenuItems() { - return this.$body.children('.menu-item:visible'); + $visibleMenuItems($body) { + $body = $body || this.$body; + return $body.children('.menu-item:visible'); } /** @@ -469,7 +465,7 @@ export default class ContextMenuPopup extends Popup { this.$body.children('.menu-item').each(function() { let $menuItem = $(this); - $menuItem.removeClass('context-menu-item-first context-menu-item-last'); + $menuItem.removeClass('first last'); if ($menuItem.isVisible()) { if (!$firstMenuItem) { @@ -479,10 +475,10 @@ export default class ContextMenuPopup extends Popup { } }); if ($firstMenuItem) { - $firstMenuItem.addClass('context-menu-item-first'); + $firstMenuItem.addClass('first'); } if ($lastMenuItem) { - $lastMenuItem.addClass('context-menu-item-last'); + $lastMenuItem.addClass('last'); } } @@ -530,13 +526,52 @@ export default class ContextMenuPopup extends Popup { }, this); } - _invalidateLayoutTreeAndRepositionPopup() { - this.invalidateLayoutTree(); - this.session.layoutValidator.schedulePostValidateFunction(() => { - if (!this.rendered) { // check needed because this is an async callback - return; + _adjustTextAlignment($body) { + $body = $body || this.$body; + let $menuItems = this.$visibleMenuItems($body); + let textOffset = this._calcTextOffset($menuItems); + $body.data('text-offset', textOffset); + this._updateTextOffset(textOffset, $menuItems); + } + + _calcTextOffset($menuItems) { + let textOffset = 0; + $menuItems = $menuItems || this.$visibleMenuItems(); + $menuItems.each((index, menuItem) => { + let $menuItem = $(menuItem); + let $icon = $menuItem.children('.icon'); + let iconWidth = 0; + + if ($icon.length > 0) { + iconWidth = $icon.outerWidth(true); + } + textOffset = Math.max(textOffset, iconWidth); + }); + return textOffset; + } + + _updateTextOffset(textOffset, $menuItems) { + // Update the padding of each text such that the sum of icon width and the padding + // are the same for all items. This ensures that the texts are all aligned. + $menuItems = $menuItems || this.$visibleMenuItems(); + $menuItems.each((index, menuItem) => { + let $menuItem = $(menuItem); + let $text = $menuItem.children('.text'); + $text.css('padding-left', this._calcTextPaddingLeft($menuItem, textOffset)); + let htmlComp = HtmlComponent.optGet($menuItem); + if (htmlComp) { + htmlComp.invalidateLayout(); } - this.position(); }); } + + _calcTextPaddingLeft($menuItem, textOffset) { + let $icon = $menuItem.children('.icon'); + let iconWidth = 0; + + if ($icon.length > 0) { + iconWidth = $icon.outerWidth(true); + } + return textOffset - iconWidth; + } } diff --git a/eclipse-scout-core/src/menu/ContextMenuPopup.less b/eclipse-scout-core/src/menu/ContextMenuPopup.less index e51c35093e..7f4044408f 100644 --- a/eclipse-scout-core/src/menu/ContextMenuPopup.less +++ b/eclipse-scout-core/src/menu/ContextMenuPopup.less @@ -10,6 +10,10 @@ */ .context-menu-popup { + background-color: @popup-background-color; + border-radius: @border-radius-medium; + overflow: hidden; + #scout.drop-shadow-large(); &.animate-open { .popup-animate-open(); @@ -21,16 +25,20 @@ } .context-menu { - position: relative; - border: 1px solid @popup-border-color; + position: absolute; background-color: @popup-background-color; - #scout.drop-shadow-large(); + margin: 6px 0; & > .menu-item { - border-top: solid 1px @border-color; - display: block; - width: 100%; - padding: 11px 15px; + display: flex; + padding: @context-menu-item-padding-y 24px @context-menu-item-padding-y @context-menu-item-padding-left; + color: @context-menu-item-color; + border-radius: 0; + font-weight: normal; + + & > .font-icon { + color: @context-menu-item-icon-color; + } & > .text { #scout.overflow-ellipsis(); @@ -39,43 +47,36 @@ } &.menu-textandicon > .icon { - margin-right: 10px; + margin-right: 14px; } &.selected { - border-top: 1px solid @item-selection-border-color; - border-color: @item-selection-border-color; background-color: @item-selection-background-color; - } - &.next-to-selected { - border-top-color: @item-selection-border-color; + & > .submenu-icon { + transform: none; + } } - &.context-menu-item-first { - border-top: 1px solid transparent; - } + &.expanded { + color: @active-color; - &.context-menu-item-last { - border-bottom: 1px solid transparent; + & > .submenu-icon { + #scout.submenu-icon-open(); + } } - &.selected.context-menu-item-first { - border-top: 1px solid @item-selection-border-color; - } - - &.selected.context-menu-item-last { - border-color: @item-selection-border-color; - } + &.disabled { + color: @menu-item-disabled-color; - &.expanded > .submenu-icon { - #scout.transform(rotateX(180deg)); - top: 0; + & > .font-icon { + color: @menu-item-disabled-color; + } } & > .key-box { bottom: auto; - top: 7px; + top: 5px; } } } diff --git a/eclipse-scout-core/src/menu/ContextMenuPopupLayout.js b/eclipse-scout-core/src/menu/ContextMenuPopupLayout.js index b4ec7edf29..c08d79e53e 100644 --- a/eclipse-scout-core/src/menu/ContextMenuPopupLayout.js +++ b/eclipse-scout-core/src/menu/ContextMenuPopupLayout.js @@ -8,8 +8,7 @@ * Contributors: * BSI Business Systems Integration AG - initial API and implementation */ -import {graphics, HtmlComponent, PopupLayout} from '../index'; -import $ from 'jquery'; +import {graphics, HtmlComponent, PopupLayout, scrollbars} from '../index'; export default class ContextMenuPopupLayout extends PopupLayout { @@ -17,24 +16,37 @@ export default class ContextMenuPopupLayout extends PopupLayout { super(popup); } - layout($container) { - let $menuItems = this.popup.$visibleMenuItems(); - this._adjustTextAlignment($menuItems); - this._resetMaxWidthFor($menuItems); - super.layout($container); - this._setMaxWidthFor($menuItems); - } - _setSize(prefSize) { - super._setSize(prefSize); - if (this.popup.bodyAnimating) { + scrollbars.update(this.popup.get$Scrollable()); return; } - let htmlPopup = this.popup.htmlComp; + super._setSize(prefSize); + scrollbars.update(this.popup.get$Scrollable()); let htmlBody = HtmlComponent.get(this.popup.$body); - let bodySize = prefSize.subtract(htmlPopup.insets()); - htmlBody.setSize(bodySize.subtract(htmlBody.margins())); + htmlBody.pack(); + } + + invalidate(htmlSource) { + // If a child triggers a layout invalidation, the popup needs to be resized. + // This will happen for sure if a child is an image which will be loaded during the animation. + // Ideally, the running animations would be stopped, the popup layouted, the animations adjusted to the new bounds and then continued. + // This is currently too complicated to implement, so instead we let the animations finish and resize the popup at the end (but before other resize animations start). + if (this.popup.bodyAnimating && htmlSource && htmlSource.isDescendantOf(this.popup.htmlComp)) { + this.popup._toggleSubMenuQueue.unshift(() => { + if (!this.popup.rendered) { + return; + } + let oldOffset = this.popup.$body.data('text-offset'); + this.popup._adjustTextAlignment(); + this.popup.animateResize = true; + this.resizeAnimationDuration = this.popup.animationDuration; + this.popup.revalidateLayoutTree(); + this.popup.animateResize = false; + this.popup._animateTextOffset(this.popup.$body, oldOffset); + this.popup.$container.promise().done(() => this.popup._processSubMenuQueue()); + }); + } } preferredLayoutSize($container, options) { @@ -47,7 +59,7 @@ export default class ContextMenuPopupLayout extends PopupLayout { let popupStyleBackup = this.popup.$container.attr('style'); let $siblingBodies = this.popup.$body.siblings('.context-menu'); $siblingBodies.addClass('hidden'); - this.popup.$container.css({ + this.popup.$body.css({ width: 'auto', height: 'auto' }); @@ -62,92 +74,4 @@ export default class ContextMenuPopupLayout extends PopupLayout { .add(htmlComp.insets()) .add(htmlBody.margins()); } - - _adjustTextAlignment($menuItems) { - // Calculate the text offset (= max icon width) - let textOffset = 0; - $menuItems.each((index, menuItem) => { - let $menuItem = $(menuItem); - let $icon = $menuItem.children('.icon'); - let iconWidth = 0; - - if ($icon.length > 0) { - iconWidth = $icon.outerWidth(true); - } - textOffset = Math.max(textOffset, iconWidth); - }); - - // Update the padding of each text such that the sum of icon width and the padding - // are the same for all items. This ensures that the texts are all aligned. - $menuItems.each((index, menuItem) => { - let $menuItem = $(menuItem); - let $icon = $menuItem.children('.icon'); - let $text = $menuItem.children('.text'); - let iconWidth = 0; - - if ($icon.length > 0) { - iconWidth = $icon.outerWidth(true); - } - $text.css('padding-left', textOffset - iconWidth); - let htmlComp = HtmlComponent.optGet($menuItem); - if (htmlComp) { - htmlComp.invalidateLayout(); - } - }); - } - - _resetMaxWidthFor($menuItems) { - $menuItems.each((pos, item) => { - let $menu = $(item), - menu = $menu.data('widget'); - - if (!menu) { - // After closing a submenu the link to the widget gets lost - return; - } - - if (menu.$text) { - menu.$text.css('max-width', ''); - } - }); - } - - _setMaxWidthFor($menuItems) { - $menuItems.each((pos, item) => { - let $menu = $(item), - menu = $menu.data('widget'); - - if (!menu) { - // After closing a submenu the link to the widget gets lost - return; - } - - if (menu.$text) { - // Submenu icon is on the right side of the text. - // If there is not enough space to show the whole menu item (icon, text and submenu icon), the text is truncated. - // Icon and submenu icon are always shown. - let textMaxWidth = this._calcTextMaxWidth(menu); - menu.$text.cssPxValue('max-width', textMaxWidth); - } - }); - } - - _calcTextMaxWidth(menu) { - let containerWidth = menu.$container.width(), - $icon = menu.get$Icon(), - $text = menu.$text, - $submenuIcon = menu.$submenuIcon, - textWidth = containerWidth + 1; // add 1px to make it work even if containerWidth is a float - - if ($text && $text.isVisible()) { - textWidth -= $text.cssMarginX(); - } - if ($icon && $icon.isVisible()) { - textWidth -= $icon.outerWidth(true); - } - if ($submenuIcon && $submenuIcon.isVisible()) { - textWidth -= $submenuIcon.outerWidth(true); - } - return textWidth; - } } diff --git a/eclipse-scout-core/src/menu/Menu.js b/eclipse-scout-core/src/menu/Menu.js index 519f5e4bd9..2fbfe82fe7 100644 --- a/eclipse-scout-core/src/menu/Menu.js +++ b/eclipse-scout-core/src/menu/Menu.js @@ -40,7 +40,7 @@ export default class Menu extends Action { this.menuFilter = null; this.$submenuIcon = null; this.$subMenuBody = null; - this._addCloneProperties(['defaultMenu', 'menuTypes', 'overflow', 'stackable', 'separator', 'shrinkable', 'parentMenu', 'menuFilter']); + this._addCloneProperties(['defaultMenu', 'menuTypes', 'overflow', 'stackable', 'separator', 'shrinkable', 'parentMenu', 'menuFilter', 'subMenuVisibility']); this._addWidgetProperties('childActions'); } @@ -158,6 +158,7 @@ export default class Menu extends Action { this._closeSubMenues(); } } + this.$container.toggleClass('has-popup', this._doActionTogglesSubMenu() || this._doActionTogglesPopup()); } _closeSubMenues() { @@ -253,18 +254,11 @@ export default class Menu extends Action { return this.childActions.length > 0; } - /** - * Only render child actions if the sub-menu popup is open. - */ _renderChildActions() { - if (objects.optProperty(this.popup, 'rendered')) { - let $popup = this.popup.$container; - this.childActions.forEach(menu => { - menu.render($popup); - }); + // Child action in a sub menu cannot be replaced dynamically, popup has to be closed first. + if (!this.rendering) { + this._renderSubMenuIcon(); } - - this._renderSubMenuIcon(); } setSubMenuVisibility(subMenuVisibility) { @@ -311,16 +305,16 @@ export default class Menu extends Action { this.invalidateLayoutTree(); } } - if (this.rendered) { + if (!this.rendering) { this._renderTextPosition(); + this._updateIconAndTextStyle(); } } _renderText(text) { super._renderText(text); this.$container.toggleClass('has-text', strings.hasText(this.text) && this.textVisible); - if (this.rendered) { - this._updateIconAndTextStyle(); + if (!this.rendering) { this._renderSubMenuIcon(); } this.invalidateLayoutTree(); @@ -342,8 +336,7 @@ export default class Menu extends Action { _renderIconId() { super._renderIconId(); this.$container.toggleClass('has-icon', !!this.iconId); - if (this.rendered) { - this._updateIconAndTextStyle(); + if (!this.rendering) { this._renderSubMenuIcon(); } this.invalidateLayoutTree(); @@ -462,8 +455,11 @@ export default class Menu extends Action { _updateIconAndTextStyle() { let hasText = this._hasText(); let hasTextAndIcon = !!(hasText && this.iconId); + let hasIcon = !!this.iconId; + let hasSubMenuIcon = !!this.$submenuIcon; + let hasOneIcon = (hasIcon && !hasSubMenuIcon) || (!hasIcon && hasSubMenuIcon); this.$container.toggleClass('menu-textandicon', hasTextAndIcon); - this.$container.toggleClass('menu-icononly', !hasText); + this.$container.toggleClass('menu-icononly', !hasText && hasOneIcon); } _closePopup() { @@ -679,7 +675,7 @@ export default class Menu extends Action { } _renderMenuStyle() { - this.$container.toggleClass('default-menu', this.menuStyle === Menu.MenuStyle.DEFAULT); + this.$container.toggleClass('default', this.menuStyle === Menu.MenuStyle.DEFAULT); } setDefaultMenu(defaultMenu) { diff --git a/eclipse-scout-core/src/menu/Menu.less b/eclipse-scout-core/src/menu/Menu.less index e62c70e4a7..b154e757d7 100644 --- a/eclipse-scout-core/src/menu/Menu.less +++ b/eclipse-scout-core/src/menu/Menu.less @@ -10,12 +10,17 @@ */ .menu-item { position: relative; - display: inline-block; - vertical-align: middle; - white-space: nowrap; - color: @text-color; + display: inline-flex; + align-items: center; + color: @menu-item-color; cursor: pointer; - #scout.vertical-align-helper-before(); + padding: @menu-item-padding-y @menu-item-padding-x; + border-radius: @border-radius; + + &.menu-icononly { + padding-left: @menubar-item-icononly-padding-x; + padding-right: @menubar-item-icononly-padding-x; + } & > .key-box { bottom: -3px; @@ -23,49 +28,57 @@ &:hover { color: @hover-color; + background-color: @hover-background-color; } &:active, &.active { - color: @active-color; + background-color: @active-background-color; + } + + &.selected { + color: @active-inverted-color; + background-color: @active-inverted-background-color; + + &:hover { + background-color: @active-inverted-hover-background-color; + } + + &.active, &:active { + background-color: @active-inverted-active-background-color; + } + + &.has-popup { + color: @menu-item-color; + background-color: @selected-background-color; + } + } + + #scout.box-shadow-focus-transition(); + + &:focus { + #scout.box-shadow-focus-inset(); } &.disabled { color: @menu-item-disabled-color; + background-color: transparent; cursor: default; + + &:hover, &.active, &:active { + background-color: transparent; + } } &.menu-textandicon > .icon { margin-right: 8px; } - & > .icon, - & > .text, - & > .submenu-icon, - & > .text > .submenu-icon { - position: relative; - vertical-align: middle; - display: inline-block; - line-height: normal; - height: auto; - - /* Make IE trigger the :active state if the text or icon is pressed */ - pointer-events: none; - } - & > .text { - /* Icon is bigger than the text -> increase text size to make menu items with and without font icons about equal height. */ - /* Otherwise menu items in context menus have a different size, and the popup of a menu with submenus may not be positioned correctly. */ - padding-top: @menu-item-text-padding-top; - padding-bottom: 1px; - } - - &:not(.menu-button) > .icon { - /* Move "a bit" towards the top, because the icon would not look centered with the text's baseline otherwise. */ - top: -1px; + #scout.overflow-ellipsis-nowrap(); } & > .font-icon { - font-size: 18px; + font-size: 16px; } & > .image-icon { @@ -92,31 +105,18 @@ & > .text > .submenu-icon { #scout.submenu-icon(); padding-left: 8px; - top: -1px; - } - - & > .font-icon { - font-size: 18px; - } - - &.menu-open { - color: @active-color; - } - - &.combo-menu-child.menu-icononly:not(:first-child) { - margin-left: 8px; - & > .submenu-icon { + .menu-icononly& { padding-left: 0; } + + .selected& { + #scout.submenu-icon-open(); + } } &.bottom-text.menu-textandicon { - text-align: center; - - &::before { - display: none; - } + flex-direction: column; & > .icon { display: block; @@ -124,22 +124,21 @@ padding-bottom: 3px; } - &:not(.menu-button) > .icon { - top: 0; // reset - } - & > .text { - display: block; + display: flex; + align-items: center; font-size: @font-size-extra-small; - padding-bottom: 0; // reset & > .submenu-icon { font-size: 12px; padding-left: 4px; - top: 0; // reset // Make sure the icon does not increase the height and therefore the whole menu line-height: 0; } } } } + +.menu-button { + .button(); +} diff --git a/eclipse-scout-core/src/menu/form/field/FormFieldMenu.less b/eclipse-scout-core/src/menu/form/field/FormFieldMenu.less index 627494d707..1b1c0b1422 100644 --- a/eclipse-scout-core/src/menu/form/field/FormFieldMenu.less +++ b/eclipse-scout-core/src/menu/form/field/FormFieldMenu.less @@ -9,10 +9,19 @@ * BSI Business Systems Integration AG - initial API and implementation */ .form-field-menu { + // Remove y padding but keep x padding to have the same gap between form field menus and regular menus. + padding-top: 0; + padding-bottom: 0; cursor: default; + color: @text-color; &:hover { color: @text-color; + background-color: transparent; + } + + &:active, &.active { + background-color: transparent; } &:not(.has-text):not(.has-icon) { @@ -26,8 +35,6 @@ position: relative; vertical-align: middle; - /* remove margin left since the menu-item already has a padding */ - & > label { margin-left: 8px; } @@ -38,6 +45,12 @@ } } + &:not(.has-icon) > .form-field:not(.no-mandatory-indicator) { + // Remove margin left since the menu-item already has a padding + // This reduces the gap between form field menus in the menu bar, but also improves the alignment in the context menu + margin-left: -@mandatory-indicator-width; + } + & > .check-box-field.disabled:not(.read-only) > .field > .label, & > .radiobutton-group > .radiobutton-group-body > .radio-button.disabled:not(.read-only) > .field > .label, & > .radio-button.disabled:not(.read-only) > .field > .label { @@ -46,11 +59,30 @@ } } -/* Inside context menu popup -> set preferred size */ -.context-menu > .menu-item.form-field-menu { - display: flex; - align-items: center; +.menubar-box > .form-field-menu { + & > .string-field, + & > .smart-field, + & > .number-field, + & > .date-field, + & > .tag-field, + & > .file-chooser-field { + margin-top: @menubar-field-menu-margin-y - @menubar-item-margin-y; + margin-bottom: @menubar-field-menu-margin-y - @menubar-item-margin-y; + } +} + +:not(.main-menubar):not(.bounded) > .menubar-box > .form-field-menu { + &.first { + padding-left: 0; + } + &.last { + padding-right: 0; + } +} + +/* Inside context menu popup -> set preferred size */ +.context-menu > .form-field-menu { & > .text, & > .icon { flex: none; diff --git a/eclipse-scout-core/src/menu/menubar/MenuBar.js b/eclipse-scout-core/src/menu/menubar/MenuBar.js index 3072d7b5a7..a90a646e80 100644 --- a/eclipse-scout-core/src/menu/menubar/MenuBar.js +++ b/eclipse-scout-core/src/menu/menubar/MenuBar.js @@ -70,12 +70,12 @@ export default class MenuBar extends Widget { this.menuFilter = (menus, destination, onlyVisible, enableDisableKeyStroke) => options.menuFilter(menus, MenuDestinations.MENU_BAR, onlyVisible, enableDisableKeyStroke); } - this.menuboxLeft = scout.create('MenubarBox', { + this.menuboxLeft = scout.create('MenuBarBox', { parent: this, cssClass: 'left', tooltipPosition: this._oppositePosition() }); - this.menuboxRight = scout.create('MenubarBox', { + this.menuboxRight = scout.create('MenuBarBox', { parent: this, cssClass: 'right', tooltipPosition: this._oppositePosition() diff --git a/eclipse-scout-core/src/menu/menubar/MenuBar.less b/eclipse-scout-core/src/menu/menubar/MenuBar.less index c17694f2ea..a27c56f6d8 100644 --- a/eclipse-scout-core/src/menu/menubar/MenuBar.less +++ b/eclipse-scout-core/src/menu/menubar/MenuBar.less @@ -13,66 +13,23 @@ #scout { .main-menubar-background-color-standard() { background-color: @main-menubar-background-color; - - & > .menu-button.disabled { - background-color: @menubar-button-disabled-background-color; - } } .menubar-background-color-inherit() { background-color: inherit; - - & > .menu-button.disabled { - /* use regular button color since menubar has no color */ - background-color: @button-disabled-background-color; - } } /* standard main-menubar */ .main-menubar-standard() { #scout.main-menubar-background-color-standard(); - padding-left: @bench-padding-x; - padding-right: @bench-padding-x; min-height: @main-menubar-height; margin-left: inherit; margin-right: inherit; } - /* a transparent main-menubar with a smaller bottom border */ + /* a transparent main-menubar */ .main-menubar-light() { #scout.menubar-background-color-inherit(); - padding-left: 0; - padding-right: 0; - margin-left: @bench-padding-x; - margin-right: @bench-padding-x; - } - - .menubar-focus() { - &:focus { - outline: none; - - &:not(.disabled) { - color: @hover-color; - - &:not(.menu-button) { - color: @focus-color; - - &::after { - color: @focus-color; - } - } - - /* Draw border for icon only items */ - - &.menu-icononly::after { - #scout.button-focus(); - } - - &:not(.menu-icononly):not(.menu-button) > .text { - text-decoration: underline; - } - } - } } } @@ -83,184 +40,76 @@ width: 100%; background-color: @menubar-background-color; border-bottom: 1px solid @border-color; - /* default style is 'top' */ - vertical-align: middle; white-space: nowrap; &.bottom { border-bottom: 0; border-top: 1px solid @border-color; } +} - & > .menubox { - display: inline-block; - height: 100%; - #scout.vertical-align-helper-before(); +.menubar-box { + display: inline-flex; + height: 100%; + align-items: center; - &.right { - float: right; - } + &.right { + float: right; + } - & > .menu-separator { - display: inline-block; - vertical-align: middle; - width: 1px; - margin-right: @menu-item-margin-right; - background-color: @border-color; - height: 15px; + & > .menu-separator { + width: 1px; + margin: 0 @menu-item-padding-x; + background-color: @border-color; + height: 15px; - &.overflown { - display: none; - } + &.overflown { + display: none; } + } - /* child menus of combo-menu must have the same padding, but not the all the other styles of > .menu-item */ - & > .combo-menu > .menu-item { - padding-top: @menubar-item-padding-y; - padding-bottom: @menubar-item-padding-y; - } - - & > .menu-item { - display: inline-block; - padding-top: @menubar-item-padding-y; - padding-bottom: @menubar-item-padding-y; - - /* By making sure menu items are always at least the same height as form fields, it is easier to align them with each other, especially when zoomed */ - min-height: @logical-grid-row-height; - - &.overflown { - display: none; - } - - #scout.menubar-focus(); - - &:not(.last) { - margin-right: @menu-item-margin-right; - } - - &.form-field-menu { - padding-top: @menubar-field-menu-margin-y; - padding-bottom: @menubar-field-menu-margin-y; - } - - /* ---------------------------------- */ - - &.menu-button { - text-align: center; - color: @menubar-button-color; - background-color: @menubar-button-background-color; - border: 1px solid @menubar-button-border-color; - border-radius: @button-border-radius; - min-width: 110px; - margin-top: @menubar-field-menu-margin-y; - margin-bottom: @menubar-field-menu-margin-y; - padding-left: @button-padding-x; - padding-right: @button-padding-x; - - &:not(.bottom-text) { - height: @logical-grid-row-height; - padding-top: @button-padding-y; - padding-bottom: @button-padding-y; - } - - /* TODO [7.0] cgu/BSH: Actually, we wanted to use .overflow-ellipsis-nowrap to support */ - /* ellipsis, but this breaks the focus, because the ::before inline element gets */ - /* cut off. We should find a better solution for this. */ - /*white-space: nowrap;*/ - - &:hover { - color: @button-hover-color; - - & > .font-icon { - color: @button-hover-color; - } - } - - &:active, &.active { - color: @button-active-color; - background-color: @menubar-button-active-background-color; - - & > .font-icon { - color: @button-active-color; - } - } - - &:focus::after { - #scout.button-focus(); - } - - & > .font-icon { - color: @menubar-button-font-icon-color; - } - - &.default-menu:not(.disabled) { - #scout.font-text-normal(@font-weight-bold); - background-color: @default-button-background-color; - /* border is necessary to align the text with text from buttons with a real border */ - border-color: transparent; - color: @default-button-color; - - &:focus { - color: @default-button-color; - } - - &:hover { - background-color: @default-button-hover-background-color; - } - - &:active, &.active { - background-color: @default-button-active-background-color; - border-color: @default-button-active-background-color; - } - - & > .font-icon { - font-weight: normal; - color: @icon-inverted-color; - } - } + & > .menu-item { + margin: @menubar-item-margin-y @menubar-item-margin-x; + /* By making sure menu items are always at least the same height as form fields, it is easier to align them with each other, especially when zoomed */ + min-height: @logical-grid-row-height; - &.disabled { - background-color: @menubar-button-disabled-background-color; - border-color: @menubar-button-disabled-border-color; - color: @menubar-button-disabled-color; - cursor: default; + &.menu-icononly { + margin-left: @menubar-item-icononly-margin-x; + margin-right: @menubar-item-icononly-margin-x; + min-width: @logical-grid-row-height; // Make it square + justify-content: center; + } - & > .font-icon { - color: @menubar-button-font-icon-disabled-color; - } - } + &.overflown { + display: none; + } - &.selected { - border-style: inset; - border-right-color: @border-color; - border-bottom-color: @border-color; - } + &.first { + margin-left: 0; + } - &:not(.last) { - margin-right: @menu-item-margin-right; - } + &.last { + margin-right: 0; + } + } - &.left-of-button { - margin-right: @menu-item-margin-right-between-buttons; - } + & > .menu-button { + min-width: 110px; + margin: @menubar-button-margin; - &.small-gap { - margin-right: 6px; - } - - &.small { - min-width: 53px; + &.small { + min-width: 53px; + } + } - & > .font-icon { - font-size: 18px; - } - } - } + /* If the menubar has a border left and right, the first and last items need to be adjusted */ + .bounded > & > .menu-item { + &.first { + margin-left: @menubar-item-margin-y; } - /* Menu items within a top-level combo-menu should have a focus-border too */ - & > .combo-menu > .menu-item { - #scout.menubar-focus(); + &.last { + margin-right: @menubar-item-margin-y; } } } @@ -268,47 +117,48 @@ .main-menubar { #scout.main-menubar-standard; - & > .menubox { + & > .menubar-box { - & > .menu-item, & > .combo-menu > .menu-item { - margin-top: 8px; - margin-bottom: 7px; + & > .menu-separator { + height: @logical-grid-row-height - 12px; + } - &.bottom-text.menu-textandicon { - margin-top: 0; - margin-bottom: 0; - } + & > .menu-item { + margin-left: @main-menubar-item-margin-x; + margin-right: @main-menubar-item-margin-x; - &.menu-button { - margin-top: 8px; - margin-bottom: 7px; + &.first { + margin-left: @main-menubar-first-menu-item-margin-left; } - &.form-field-menu { - padding-top: 0; - padding-bottom: 0; + &.last { + margin-right: @main-menubar-last-menu-item-margin-right; } - &.small > .font-icon { - font-size: 18px; - } + &.menu-icononly { + margin-left: @menubar-item-icononly-margin-x; + margin-right: @menubar-item-icononly-margin-x; - &.last { - margin-right: 0; + &.first { + margin-left: @main-menubar-first-menu-item-icononly-margin-left; + } + + &.last { + margin-right: @main-menubar-last-menu-item-icononly-margin-right; + } } } - & > .menu-separator { - height: @logical-grid-row-height - 12px; - } + & > .menu-button { + &.first, + &.first.menu-icononly { + margin-left: @bench-padding-x; + } - /* combo-menu itself should have no margins and no padding so its child menus - * can render a focus border like regular top-level menus. */ - & > .combo-menu.menu-item { - margin-top: 0; - margin-bottom: 0; - padding-top: 0; - padding-bottom: 0; + &.last, + &.last.menu-icononly { + margin-right: @bench-padding-x; + } } } } diff --git a/eclipse-scout-core/src/menu/menubar/MenubarBox.js b/eclipse-scout-core/src/menu/menubar/MenuBarBox.js index 8c6c16eead..d280673276 100644 --- a/eclipse-scout-core/src/menu/menubar/MenubarBox.js +++ b/eclipse-scout-core/src/menu/menubar/MenuBarBox.js @@ -8,9 +8,9 @@ * Contributors: * BSI Business Systems Integration AG - initial API and implementation */ -import {arrays, HtmlComponent, MenuBar, MenubarBoxLayout, Widget} from '../../index'; +import {arrays, HtmlComponent, MenuBar, MenuBarBoxLayout, Widget} from '../../index'; -export default class MenubarBox extends Widget { +export default class MenuBarBox extends Widget { constructor() { super(); @@ -26,10 +26,10 @@ export default class MenubarBox extends Widget { } _render() { - this.$container = this.$parent.appendDiv('menubox'); + this.$container = this.$parent.appendDiv('menubar-box'); this.htmlComp = HtmlComponent.install(this.$container, this.session); - this.htmlComp.setLayout(new MenubarBoxLayout(this)); + this.htmlComp.setLayout(new MenuBarBoxLayout(this)); } _renderProperties() { diff --git a/eclipse-scout-core/src/menu/menubar/MenubarBoxLayout.js b/eclipse-scout-core/src/menu/menubar/MenuBarBoxLayout.js index 9961a55a28..62ee03027f 100644 --- a/eclipse-scout-core/src/menu/menubar/MenubarBoxLayout.js +++ b/eclipse-scout-core/src/menu/menubar/MenuBarBoxLayout.js @@ -10,7 +10,7 @@ */ import {AbstractLayout, Dimension} from '../../index'; -export default class MenubarBoxLayout extends AbstractLayout { +export default class MenuBarBoxLayout extends AbstractLayout { constructor(menubox) { super(); diff --git a/eclipse-scout-core/src/menu/menubar/MenuBarLayout.js b/eclipse-scout-core/src/menu/menubar/MenuBarLayout.js index acff64e83b..49d9529f83 100644 --- a/eclipse-scout-core/src/menu/menubar/MenuBarLayout.js +++ b/eclipse-scout-core/src/menu/menubar/MenuBarLayout.js @@ -17,20 +17,20 @@ export default class MenuBarLayout extends AbstractLayout { this._menuBar = menuBar; this._overflowMenuItems = []; - this._visibleMenuItems = []; this._ellipsis = null; this.collapsed = false; } layout($container) { - let menuItems = this._menuBar.orderedMenuItems.left.concat(this._menuBar.orderedMenuItems.right), - htmlContainer = HtmlComponent.get($container), - ellipsis; + let menuItems = this._menuBar.orderedMenuItems.left.concat(this._menuBar.orderedMenuItems.right); + let visibleMenuItems = this.visibleMenuItems(); + let htmlContainer = HtmlComponent.get($container); - ellipsis = arrays.find(menuItems, menuItem => { + let ellipsis = arrays.find(menuItems, menuItem => { return menuItem.ellipsis; }); + this._setFirstLastMenuMarker(visibleMenuItems); // is required to determine available size correctly this.preferredLayoutSize($container, { widthHint: htmlContainer.availableSize().width, undo: false @@ -40,7 +40,7 @@ export default class MenuBarLayout extends AbstractLayout { if (ellipsis && this._overflowMenuItems.length > 0) { ellipsis.setHidden(false); } - this._visibleMenuItems.forEach(menuItem => { + visibleMenuItems.forEach(menuItem => { menuItem._setOverflown(false); }, this); @@ -62,11 +62,11 @@ export default class MenuBarLayout extends AbstractLayout { } // trigger menu items layout - this._visibleMenuItems.forEach(menuItem => { + visibleMenuItems.forEach(menuItem => { menuItem.validateLayout(); }); - this._visibleMenuItems.forEach(menuItem => { + visibleMenuItems.forEach(menuItem => { // Make sure open popups are at the correct position after layouting if (menuItem.popup) { menuItem.popup.position(); @@ -79,9 +79,7 @@ export default class MenuBarLayout extends AbstractLayout { if (!this._menuBar.isVisible()) { return new Dimension(0, 0); } - let visibleMenuItems = this._menuBar.orderedMenuItems.all.filter(menuItem => { - return menuItem.visible; - }, this); + let visibleMenuItems = this.visibleMenuItems(); let overflowMenuItems = visibleMenuItems.filter(menuItem => { let overflown = menuItem.overflown; menuItem._setOverflown(false); @@ -119,7 +117,6 @@ export default class MenuBarLayout extends AbstractLayout { this.shrink(shrinkedMenuItems); } - this._visibleMenuItems = visibleMenuItems; return prefSize.add(htmlComp.insets()); } @@ -255,6 +252,12 @@ export default class MenuBarLayout extends AbstractLayout { }, this); } + visibleMenuItems() { + return this._menuBar.orderedMenuItems.all.filter(menuItem => { + return menuItem.visible; + }, this); + } + /* --- STATIC HELPERS ------------------------------------------------------------- */ /** diff --git a/eclipse-scout-core/src/planner/Planner.js b/eclipse-scout-core/src/planner/Planner.js index 1f199014f4..6239392def 100644 --- a/eclipse-scout-core/src/planner/Planner.js +++ b/eclipse-scout-core/src/planner/Planner.js @@ -124,7 +124,8 @@ export default class Planner extends Widget { this.menuBar = scout.create('MenuBar', { parent: this, position: MenuBar.Position.BOTTOM, - menuOrder: new PlannerMenuItemsOrder(this.session, 'Planner') + menuOrder: new PlannerMenuItemsOrder(this.session, 'Planner'), + cssClass: 'bounded' }); for (let i = 0; i < this.resources.length; i++) { this._initResource(this.resources[i]); diff --git a/eclipse-scout-core/src/planner/Planner.less b/eclipse-scout-core/src/planner/Planner.less index 38471d4a30..497bf282c4 100644 --- a/eclipse-scout-core/src/planner/Planner.less +++ b/eclipse-scout-core/src/planner/Planner.less @@ -24,11 +24,6 @@ position: absolute; overflow: hidden; - & > .menubar { - padding-left: @resource-padding-x; - padding-right: @resource-padding-x; - } - & > .menubar.bottom { position: absolute; bottom: 0; diff --git a/eclipse-scout-core/src/planner/PlannerHeader.less b/eclipse-scout-core/src/planner/PlannerHeader.less index de871e9df2..3de6f78b80 100644 --- a/eclipse-scout-core/src/planner/PlannerHeader.less +++ b/eclipse-scout-core/src/planner/PlannerHeader.less @@ -127,9 +127,9 @@ } &.disabled { - background-color: @menubar-button-disabled-background-color; - border-color: @menubar-button-disabled-border-color; - color: @menubar-button-disabled-color; + background-color: @button-disabled-background-color; + border-color: @button-disabled-border-color; + color: @button-disabled-color; cursor: default; } } diff --git a/eclipse-scout-core/src/popup/Popup.js b/eclipse-scout-core/src/popup/Popup.js index 5a3c80fb7e..fe4a4b35b4 100644 --- a/eclipse-scout-core/src/popup/Popup.js +++ b/eclipse-scout-core/src/popup/Popup.js @@ -207,12 +207,13 @@ export default class Popup extends Widget { return; } // Give the browser time to layout properly before starting the animation to make sure it will be smooth. - this.$container.addClass('invisible'); + // The before-animate-open class will make the popup invisible (cannot use the invisible class because it is already used by _validateVisibility) + this.$container.addClass('before-animate-open'); setTimeout(() => { if (!this.rendered || this.removing) { return; } - this.$container.removeClass('invisible'); + this.$container.removeClass('before-animate-open'); this.validateFocus(); // Need to be done after popup is visible again because focus cannot be set on invisible elements. this.$container.addClassForAnimation('animate-open'); }); diff --git a/eclipse-scout-core/src/popup/Popup.less b/eclipse-scout-core/src/popup/Popup.less index 0e531b77f9..86968e73a3 100644 --- a/eclipse-scout-core/src/popup/Popup.less +++ b/eclipse-scout-core/src/popup/Popup.less @@ -20,6 +20,10 @@ min-width: @tooltip-arrow-size; min-height: @tooltip-arrow-size; } + + &.before-animate-open { + .invisible(); + } } .popup-arrow { diff --git a/eclipse-scout-core/src/popup/PopupLayout.js b/eclipse-scout-core/src/popup/PopupLayout.js index f30155911a..d2d050aa90 100644 --- a/eclipse-scout-core/src/popup/PopupLayout.js +++ b/eclipse-scout-core/src/popup/PopupLayout.js @@ -18,6 +18,9 @@ export default class PopupLayout extends AbstractLayout { this.doubleCalcPrefSize = true; // enables popups with a height which depends on the width (= popups with wrapping content) this.autoPosition = true; this.autoSize = true; + this.resizeAnimationRunning = false; + this.resizeAnimationDuration = null; // default + this._autoPositionOrig = null; } layout($container) { @@ -69,6 +72,7 @@ export default class PopupLayout extends AbstractLayout { // Bounds did not change -> do nothing return; } + this.resizeAnimationRunning = true; htmlComp.$comp .stop(true) .cssHeight(currentBounds.height) @@ -81,13 +85,15 @@ export default class PopupLayout extends AbstractLayout { left: prefPosition.left, top: prefPosition.top }, { - complete: function() { + duration: this.resizeAnimationDuration, + complete: () => { + this.resizeAnimationRunning = false; if (!this.popup.rendered) { return; } // Ensure the arrow is at the correct position after the animation this._position(); - }.bind(this) + } }); } @@ -129,10 +135,8 @@ export default class PopupLayout extends AbstractLayout { * @returns {Dimension} */ _calcMaxSize() { - if (this.popup.repositionEnabled) { - // Position the popup at the desired location before doing any calculations to consider the preferred bounds - this._position(false); - } + // Position the popup at the desired location before doing any calculations to consider the preferred bounds + this._position(false); let maxWidth, maxHeight, htmlComp = this.popup.htmlComp, @@ -222,10 +226,8 @@ export default class PopupLayout extends AbstractLayout { * @returns {Insets} */ _calcMaxSizeAroundAnchor() { - if (this.popup.repositionEnabled) { - // Position the popup at the desired location before doing any calculations because positioning adds CSS classes which might change margins - this._position(false); - } + // Position the popup at the desired location before doing any calculations because positioning adds CSS classes which might change margins + this._position(false); let maxWidthLeft, maxWidthRight, maxHeightDown, maxHeightUp, htmlComp = this.popup.htmlComp, @@ -256,4 +258,16 @@ export default class PopupLayout extends AbstractLayout { return new Insets(maxHeightUp, maxWidthRight, maxHeightDown, maxWidthLeft); } + + disableAutoPosition() { + if (this._autoPositionOrig === null) { + this._autoPositionOrig = this.autoPosition; + this.autoPosition = false; + } + } + + resetAutoPosition() { + this.autoPosition = this._autoPositionOrig; + this._autoPositionOrig = null; + } } diff --git a/eclipse-scout-core/src/style/colors.less b/eclipse-scout-core/src/style/colors.less index 8c4286fb84..2b88d25133 100644 --- a/eclipse-scout-core/src/style/colors.less +++ b/eclipse-scout-core/src/style/colors.less @@ -76,7 +76,10 @@ @active-color: @palette-blue-6; @active-inverted-background-color: @palette-blue-5; +@active-inverted-hover-background-color: @palette-blue-6; +@active-inverted-active-background-color: darken(@active-inverted-hover-background-color, 10%); @active-inverted-color: @palette-white; +@active-background-color: fade(@palette-black, 14%); @application-loading-background-color: @palette-white; @application-loading01-color: fade(@palette-blue-5, 90%); @application-loading02-color: @palette-cyan-4; @@ -99,6 +102,7 @@ @error-color: @palette-red-3; @error-glow-color: @palette-red-3; @focus-border-color: @palette-blue-6; +@focus-box-shadow-color: darken(@palette-blue-1, 10%); @focus-color: @palette-blue-6; @focus-glow-color: fade(@focus-border-color, 50%); @focus-border-color-inverted: @active-inverted-color; @@ -122,7 +126,7 @@ @loading-indicator-knight-rider-color: @palette-blue-3; @panel-background-color: @palette-gray-2; @read-only-color: @text-color; -@selected-background-color: fade(@palette-black, 14%); +@selected-background-color: @active-background-color; @selected-intense-background-color: fade(@palette-black, 30%); @sub-title-color: @palette-gray-7; @text-color: @palette-gray-10; @@ -148,15 +152,13 @@ @boxbutton-border-color: @border-color; @browser-field-background-color: @palette-white; /* always white to not change the look of the website */ @button-active-color: @active-color; -@button-active-background-color: @palette-gray-3; +@button-active-background-color: @active-background-color; @button-background-color: @control-background-color; @button-border-color: @button-color; @button-color: @palette-blue-6; -@button-disabled-background-color: @control-disabled-background-color; +@button-disabled-background-color: transparent; @button-disabled-border-color: @border-color; @button-disabled-color: @disabled-color; -@button-font-icon-color: @button-color; -@button-font-icon-disabled-color: @button-disabled-border-color; @button-hover-color: @button-color; @busyindicator-active-color: @palette-blue-6; @busyindicator-border-color: @border-color; @@ -194,6 +196,8 @@ @command-button-active-color: @active-inverted-color; @command-button-active-border-color: @active-inverted-background-color; @command-button-active-background-color: @active-inverted-background-color; +@context-menu-item-color: @text-color; +@context-menu-item-icon-color: @link-color; @date-picker-background-color: @control-popup-background-color; @date-picker-header-background-color: @panel-background-color; @date-picker-day-hover-border-color: @item-selection-border-color; @@ -213,10 +217,10 @@ @date-picker-weekend-color: @palette-blue-6; @date-picker-day-preselected-border-color: @item-selection-border-color; @date-picker-now-preselected-border-color: @highlight-color; -@default-button-active-background-color: @palette-blue-6; -@default-button-background-color: @palette-blue-4; -@default-button-color: @palette-white; -@default-button-hover-background-color: @default-button-background-color; +@default-button-active-background-color: @active-inverted-active-background-color; +@default-button-background-color: @active-inverted-background-color; +@default-button-color: @active-inverted-color; +@default-button-hover-background-color: @active-inverted-hover-background-color; @desktop-bench-background-color: @background-color; @desktop-bench-tab-area-background-color: @palette-gray-3; @desktop-navigation-handle-active-background-color: @collapse-handle-active-background-color; @@ -260,24 +264,15 @@ @link-hover-color: lighten(@link-color, 10%); @main-menubar-background-color: @menubar-background-color; @menubar-background-color: @palette-white; -@menubar-button-active-background-color: @button-active-background-color; -@menubar-button-background-color: @button-background-color; -@menubar-button-border-color: @button-border-color; -@menubar-button-color: @button-color; -@menubar-button-disabled-background-color: @button-disabled-background-color; -@menubar-button-disabled-border-color: @palette-gray-5; -@menubar-button-disabled-color: @button-disabled-color; -@menubar-button-font-icon-color: @menubar-button-color; -@menubar-button-font-icon-disabled-color: @menubar-button-disabled-border-color; -@menubar-item-color: @text-color; +@menu-item-color: @link-color; @menu-item-disabled-color: @disabled-color; @messagebox-background-color: @popup-background-color; @mode-hover-text-color: @hover-color; @mode-selected-background-color: @active-inverted-background-color; @mode-selected-text-color: @active-inverted-color; @mode-alternative-selected-color: @mode-selected-background-color; -@navigate-up-button-border-color: @menubar-button-border-color; -@navigate-up-button-color: @menubar-button-color; +@navigate-up-button-border-color: @button-border-color; +@navigate-up-button-color: @button-color; @desktop-navigation-background-color: @desktop-header-background-color; @desktop-navigation-body-background-color: @palette-white; @desktop-navigation-color: @palette-gray-10; diff --git a/eclipse-scout-core/src/style/mixins.less b/eclipse-scout-core/src/style/mixins.less index 06351efe43..9e37194208 100644 --- a/eclipse-scout-core/src/style/mixins.less +++ b/eclipse-scout-core/src/style/mixins.less @@ -40,12 +40,6 @@ #scout.glow(@focus-glow-color); } - .focus-border-inverted(@bordersize: 1px) { - outline: none; - border: @bordersize solid @focus-border-color-inverted; - #scout.glow(@focus-glow-color-inverted); - } - .no-focus-border() { outline: none; border: 0; @@ -380,9 +374,22 @@ } .button-focus() { - #scout.overlay(-3px, -3px, 6px, 6px); - #scout.focus-border(); - border-radius: @button-border-radius; + #scout.box-shadow-focus(); + border-color: mix(@button-border-color, @focus-box-shadow-color, 25%); + } + + .box-shadow-focus() { + outline: none; + box-shadow: 0 0 0 @focus-box-shadow-size @focus-box-shadow-color; + } + + .box-shadow-focus-inset() { + outline: none; + box-shadow: inset 0 0 0 @focus-box-shadow-size @focus-box-shadow-color; + } + + .box-shadow-focus-transition() { + transition: box-shadow 0.3s ease; } .triangle-top-left(@size, @color) { @@ -437,6 +444,10 @@ font-size: 16px; } + .submenu-icon-open() { + #scout.transform(rotateX(180deg) translateY(-1px)); + } + /* Invisible pseudo element that enables vertical-align if container has height set */ .vertical-align-helper(@minHeight: 0) { content: ''; diff --git a/eclipse-scout-core/src/style/sizes.less b/eclipse-scout-core/src/style/sizes.less index fc29d67e32..c63cd3e166 100644 --- a/eclipse-scout-core/src/style/sizes.less +++ b/eclipse-scout-core/src/style/sizes.less @@ -34,6 +34,7 @@ @field-label-width: 140px; @field-status-width: 20px; @field-status-margin-left: 10px; +@focus-box-shadow-size: 3px; @text-field-padding-x: 10px; @text-field-padding-y: 7px; @text-field-padding-top-compensation: 1px; @@ -89,6 +90,7 @@ @busyindicator-large-size: 100px; @busyindicator-large-border-width: 2px; @button-border-radius: @border-radius; +@button-focus-border-radius: 6px; @button-padding-x: 7px; @button-padding-y: 2px; @button-margin-top: 1px; /* Necessary to align icon with text */ @@ -109,6 +111,8 @@ @compact-outline-node-padding-x: 16px; @compact-outline-node-padding-y: @outline-breadcrumb-node-padding-y; @compact-outline-title-padding-x: @compact-outline-node-padding-x; +@context-menu-item-padding-y: 8px; +@context-menu-item-padding-left: 16px; @dashboard-tile-label-padding-bottom: 5px; @dashboard-tile-label-large-padding-bottom: 10px; @date-picker-header-height: @logical-grid-row-height; @@ -164,15 +168,23 @@ @group-box-title-padding-top: 12px; @group-box-title-with-sub-label-padding-bottom: 6px; @login-box-font-size: 16px; -@menu-item-height: 39px; -@menu-item-margin-right-between-buttons: 10px; -@menu-item-margin-right: 20px; -@menu-item-max-image-icon-height: 18px; -@menu-item-min-image-icon-width: 18px; -@menu-item-text-padding-top: 1px; @main-menubar-height: @desktop-header-height; +@main-menubar-item-margin-x: 5px; +@main-menubar-first-menu-item-margin-left: @bench-padding-x - @menu-item-padding-x; +@main-menubar-last-menu-item-margin-right: @main-menubar-first-menu-item-margin-left; +@main-menubar-first-menu-item-icononly-margin-left: @bench-padding-x - @menubar-item-icononly-padding-x; +@main-menubar-last-menu-item-icononly-margin-right: @main-menubar-first-menu-item-icononly-margin-left; +@menubar-button-margin: @menubar-field-menu-margin-y; @menubar-field-menu-margin-y: 4px; -@menubar-item-padding-y: 6px; +@menubar-item-icononly-padding-x: 8px; +@menubar-item-icononly-margin-x: 2px; +@menubar-item-margin-x: 2px; +@menubar-item-margin-y: 2px; +@menu-item-height: 39px; +@menu-item-min-image-icon-width: 18px; +@menu-item-max-image-icon-height: 18px; +@menu-item-padding-x: 10px; +@menu-item-padding-y: 6px; @messagebox-label-padding: 20px; @messagebox-max-width: 330px; @mobile-popup-title-margin-right: 30px; diff --git a/eclipse-scout-core/src/table/Table.js b/eclipse-scout-core/src/table/Table.js index 8b0594770b..cbba2db559 100644 --- a/eclipse-scout-core/src/table/Table.js +++ b/eclipse-scout-core/src/table/Table.js @@ -4325,7 +4325,8 @@ export default class Table extends Widget { parent: this, position: MenuBar.Position.BOTTOM, menuOrder: new MenuItemsOrder(this.session, 'Table'), - menuFilter: this._filterMenusHandler + menuFilter: this._filterMenusHandler, + cssClass: 'bounded' }); } diff --git a/eclipse-scout-core/src/table/Table.less b/eclipse-scout-core/src/table/Table.less index d434b09f2b..37433bd10a 100644 --- a/eclipse-scout-core/src/table/Table.less +++ b/eclipse-scout-core/src/table/Table.less @@ -25,11 +25,6 @@ } } - & > .menubar:not(.main-menubar) { - padding-left: @table-menubar-padding; - padding-right: @table-menubar-padding; - } - &.checkable { & > .table-data > .table-row { cursor: pointer; diff --git a/eclipse-scout-core/src/table/TableHeader.less b/eclipse-scout-core/src/table/TableHeader.less index fe7af94937..62f7539749 100644 --- a/eclipse-scout-core/src/table/TableHeader.less +++ b/eclipse-scout-core/src/table/TableHeader.less @@ -29,8 +29,6 @@ /* make menubar a little smaller than header (but still center it). Otherwise browsers may draw the menu bar over the bottom border of the table header when the page is zoomed */ top: 1px; height: calc(~'100% - 2px'); - padding-right: 6px; - padding-left: 6px; /* Do not use transparent color here, because otherwise header items would be visible behind */ background-color: inherit; z-index: 1; @@ -39,6 +37,10 @@ height: 100%; background-color: transparent; border: none; + + & > .menubar-box > .menu-item { + border-radius: 0; + } } } diff --git a/eclipse-scout-core/src/tile/fields/tablefield/TileTableField.less b/eclipse-scout-core/src/tile/fields/tablefield/TileTableField.less index 95856bc6b7..44acca8074 100644 --- a/eclipse-scout-core/src/tile/fields/tablefield/TileTableField.less +++ b/eclipse-scout-core/src/tile/fields/tablefield/TileTableField.less @@ -135,7 +135,7 @@ } } - & > .menubar > .menubox > .menu-item { + & > .menubar > .menubar-box > .menu-item { color: @tile-default-inverted-color; } } @@ -172,7 +172,7 @@ } } - & > .menubar > .menubox > .menu-item { + & > .menubar > .menubar-box > .menu-item { color: @tile-alternative-inverted-color; } } diff --git a/eclipse-scout-core/src/tree/Tree.js b/eclipse-scout-core/src/tree/Tree.js index c47a0afafd..e797c02255 100644 --- a/eclipse-scout-core/src/tree/Tree.js +++ b/eclipse-scout-core/src/tree/Tree.js @@ -162,7 +162,8 @@ export default class Tree extends Widget { parent: this, position: MenuBar.Position.BOTTOM, menuOrder: new MenuItemsOrder(this.session, 'Tree'), - menuFilter: this._filterMenusHandler + menuFilter: this._filterMenusHandler, + cssClass: 'bounded' }); this._updateItemPath(true); this._setDisplayStyle(this.displayStyle); diff --git a/eclipse-scout-core/src/tree/Tree.less b/eclipse-scout-core/src/tree/Tree.less index d2d14a9766..b43babe035 100644 --- a/eclipse-scout-core/src/tree/Tree.less +++ b/eclipse-scout-core/src/tree/Tree.less @@ -26,11 +26,6 @@ border-top-color: @item-selection-nonfocus-background-color; } } - - & > .menubar { - padding-left: 8px; - padding-right: 8px; - } } .tree-data { diff --git a/eclipse-scout-core/src/widget/Widget.js b/eclipse-scout-core/src/widget/Widget.js index 7540df058a..737afaf8ed 100644 --- a/eclipse-scout-core/src/widget/Widget.js +++ b/eclipse-scout-core/src/widget/Widget.js @@ -2099,6 +2099,9 @@ export default class Widget { return null; } + /** + * @param {object} [options] + */ _installScrollbars(options) { let $scrollable = this.get$Scrollable(); if (!$scrollable) { diff --git a/eclipse-scout-core/test/menu/ContextMenuPopupSpec.js b/eclipse-scout-core/test/menu/ContextMenuPopupSpec.js index 75122949c6..f757317378 100644 --- a/eclipse-scout-core/test/menu/ContextMenuPopupSpec.js +++ b/eclipse-scout-core/test/menu/ContextMenuPopupSpec.js @@ -194,8 +194,8 @@ describe('ContextMenuPopup', () => { let menu0Clone = findClone(popup, menu0); let menu2Clone = findClone(popup, menu2); - expect(menu0Clone.$container).toHaveClass('context-menu-item-first'); - expect(menu2Clone.$container).toHaveClass('context-menu-item-last'); + expect(menu0Clone.$container).toHaveClass('first'); + expect(menu2Clone.$container).toHaveClass('last'); popup.remove(); }); @@ -212,9 +212,9 @@ describe('ContextMenuPopup', () => { let menu0Clone = findClone(popup, menu0); let menu1Clone = findClone(popup, menu1); let menu2Clone = findClone(popup, menu2); - expect(menu0Clone.$container).toHaveClass('context-menu-item-first'); - expect(menu1Clone.$container).toHaveClass('context-menu-item-last'); - expect(menu2Clone.$container).not.toHaveClass('context-menu-item-last'); + expect(menu0Clone.$container).toHaveClass('first'); + expect(menu1Clone.$container).toHaveClass('last'); + expect(menu2Clone.$container).not.toHaveClass('last'); popup.remove(); }); diff --git a/eclipse-scout-core/test/menu/MenuBarSpec.js b/eclipse-scout-core/test/menu/MenuBarSpec.js index 83f91fee44..5fc66fd3bf 100644 --- a/eclipse-scout-core/test/menu/MenuBarSpec.js +++ b/eclipse-scout-core/test/menu/MenuBarSpec.js @@ -397,8 +397,8 @@ describe('MenuBar', () => { expect(menuBar.menuItems[0]).toBe(menu1); expect(menuBar.menuItems[1]).toBe(menu2); - expect(menu1.$container).not.toHaveClass('default-menu'); - expect(menu2.$container).toHaveClass('default-menu'); + expect(menu1.$container).not.toHaveClass('default'); + expect(menu2.$container).toHaveClass('default'); }); it('marks ButtonAdapterMenu that reacts to ENTER keystroke as default menu', () => { @@ -426,7 +426,7 @@ describe('MenuBar', () => { expect(menuBar.menuItems.length).toBe(1); expect(menuBar.menuItems[0]).toBe(adapterMenu); - expect(adapterMenu.$container).toHaveClass('default-menu'); + expect(adapterMenu.$container).toHaveClass('default'); }); it('marks first visible and enabled menu that has the "defaultMenu" flag set as default menu', () => { @@ -469,12 +469,12 @@ describe('MenuBar', () => { expect(menuBar.menuItems[4]).toBe(menu5); expect(menuBar.menuItems[5]).toBe(menu6); - expect(menu1.$container).not.toHaveClass('default-menu'); - expect(menu2.$container).not.toHaveClass('default-menu'); - expect(menu3.$container).not.toHaveClass('default-menu'); - expect(menu4.$container).toHaveClass('default-menu'); - expect(menu5.$container).not.toHaveClass('default-menu'); - expect(menu6.$container).not.toHaveClass('default-menu'); + expect(menu1.$container).not.toHaveClass('default'); + expect(menu2.$container).not.toHaveClass('default'); + expect(menu3.$container).not.toHaveClass('default'); + expect(menu4.$container).toHaveClass('default'); + expect(menu5.$container).not.toHaveClass('default'); + expect(menu6.$container).not.toHaveClass('default'); expect(menu4).toBe(menuBar.defaultMenu); }); @@ -496,18 +496,18 @@ describe('MenuBar', () => { menuBar.setMenuItems(menusItems); menuBar.render(); expect(menu1.rendered).toBe(true); - expect(menu1.$container).not.toHaveClass('default-menu'); + expect(menu1.$container).not.toHaveClass('default'); expect(menu2.rendered).toBe(true); expect(menuBar.defaultMenu).toBe(menu2); - expect(menu2.$container).toHaveClass('default-menu'); + expect(menu2.$container).toHaveClass('default'); menu2.setProperty('enabled', false); expect(menuBar.defaultMenu).toBe(null); - expect(menu2.$container).not.toHaveClass('default-menu'); + expect(menu2.$container).not.toHaveClass('default'); menu2.setProperty('enabled', true); expect(menuBar.defaultMenu).toBe(menu2); - expect(menu2.$container).toHaveClass('default-menu'); + expect(menu2.$container).toHaveClass('default'); }); it('updates state if keyStroke or defaultMenu property of menu changes', () => { @@ -530,35 +530,35 @@ describe('MenuBar', () => { expect(menu1.rendered).toBe(true); expect(menu2.rendered).toBe(true); expect(menuBar.defaultMenu).toBe(menu2); - expect(menu1.$container).not.toHaveClass('default-menu'); - expect(menu2.$container).toHaveClass('default-menu'); + expect(menu1.$container).not.toHaveClass('default'); + expect(menu2.$container).toHaveClass('default'); menu2.setProperty('keyStroke', null); expect(menuBar.defaultMenu).toBe(null); - expect(menu1.$container).not.toHaveClass('default-menu'); - expect(menu2.$container).not.toHaveClass('default-menu'); + expect(menu1.$container).not.toHaveClass('default'); + expect(menu2.$container).not.toHaveClass('default'); menu1.setProperty('keyStroke', 'enter'); expect(menuBar.defaultMenu).toBe(menu1); - expect(menu1.$container).toHaveClass('default-menu'); - expect(menu2.$container).not.toHaveClass('default-menu'); + expect(menu1.$container).toHaveClass('default'); + expect(menu2.$container).not.toHaveClass('default'); menu2.setProperty('defaultMenu', true); expect(menuBar.defaultMenu).toBe(menu2); - expect(menu1.$container).not.toHaveClass('default-menu'); - expect(menu2.$container).toHaveClass('default-menu'); + expect(menu1.$container).not.toHaveClass('default'); + expect(menu2.$container).toHaveClass('default'); menu1.setProperty('defaultMenu', false); menu2.setProperty('defaultMenu', false); expect(menuBar.defaultMenu).toBe(null); - expect(menu1.$container).not.toHaveClass('default-menu'); - expect(menu2.$container).not.toHaveClass('default-menu'); + expect(menu1.$container).not.toHaveClass('default'); + expect(menu2.$container).not.toHaveClass('default'); menu1.setProperty('defaultMenu', undefined); menu2.setProperty('defaultMenu', undefined); expect(menuBar.defaultMenu).toBe(menu1); - expect(menu1.$container).toHaveClass('default-menu'); - expect(menu2.$container).not.toHaveClass('default-menu'); + expect(menu1.$container).toHaveClass('default'); + expect(menu2.$container).not.toHaveClass('default'); }); it('considers rendered state of default menu', () => { @@ -579,16 +579,16 @@ describe('MenuBar', () => { menuBar.setMenuItems(menuItems); menuBar.render(); expect(menu1.rendered).toBe(true); - expect(menu1.$container).not.toHaveClass('default-menu'); + expect(menu1.$container).not.toHaveClass('default'); expect(menu2.rendered).toBe(true); expect(menuBar.defaultMenu).toBe(menu2); - expect(menu2.$container).toHaveClass('default-menu'); + expect(menu2.$container).toHaveClass('default'); // Move default menu into ellipsis and call updateDefaultMenu explicitly to recalculate state menus.moveMenuIntoEllipsis(menu2, ellipsisMenu); menuBar.updateDefaultMenu(); expect(menu1.rendered).toBe(true); - expect(menu1.$container).not.toHaveClass('default-menu'); + expect(menu1.$container).not.toHaveClass('default'); expect(menu2.rendered).toBe(false); expect(menuBar.defaultMenu).toBe(menu2); diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/button/IButton.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/button/IButton.java index 3bff572fd0..6d8547bed1 100644 --- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/button/IButton.java +++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/button/IButton.java @@ -57,6 +57,7 @@ public interface IButton extends IFormField { int DISPLAY_STYLE_TOGGLE = 1; int DISPLAY_STYLE_RADIO = 2; int DISPLAY_STYLE_LINK = 3; + int DISPLAY_STYLE_BORDERLESS = 4; void doClick(); |