Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 104 additions & 1 deletion packages/main/cypress/specs/ToolbarItem.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import DatePicker from "../../src/DatePicker.js";
import Select from "../../src/Select.js";
import Option from "../../src/Option.js";
import CheckBox from "../../src/CheckBox.js";
import Title from "../../src/Title.js";
import add from "@ui5/webcomponents-icons/dist/add.js";

describe("Toolbar Item Properties", () => {
Expand Down Expand Up @@ -790,4 +791,106 @@ describe("ToolbarItem CSS Custom State", () => {
expect(item._internals.states.has("overflowed")).to.be.true;
});
});
});
});

describe("ToolbarItem shrink-content", () => {
it("Should keep the toolbar button visible when shrink-content is set on a wide NeverOverflow item", () => {
cy.mount(
<div style={{ width: "400px" }}>
<Toolbar align-content="Start">
<ToolbarItem overflow-priority="NeverOverflow" shrink-content>
<Title wrapping-type="None">Super Super SuperSuperSuperSuper SuperSuperSuperSuper SuperSuperSuperSuper Long Text</Title>
</ToolbarItem>
<ToolbarButton text="Button 1"></ToolbarButton>
</Toolbar>
</div>
);

// The button must be visible — the title shrinks to yield space
cy.get("[ui5-toolbar-button][text='Button 1']")
.should("be.visible");

// No overflow button needed — the title shrinks instead of pushing items out
cy.get("[ui5-toolbar]")
.shadow()
.find(".ui5-tb-overflow-btn")
.should("have.class", "ui5-tb-overflow-btn-hidden");
});

it("Should apply ui5-tb-self-overflow class to the item wrapper when shrink-content is set", () => {
cy.mount(
<div style={{ width: "400px" }}>
<Toolbar align-content="Start">
<ToolbarItem overflow-priority="NeverOverflow" shrink-content>
<Title wrapping-type="None">Super Super SuperSuperSuperSuper SuperSuperSuperSuper Long Text</Title>
</ToolbarItem>
<ToolbarButton text="Button 1"></ToolbarButton>
</Toolbar>
</div>
);

// hasOverflow must be true when shrink-content is set
cy.get("[ui5-toolbar-item]").then($item => {
const item = $item[0] as ToolbarItem;
expect(item.hasOverflow).to.be.true;
});

// The toolbar wrapper div must carry the ui5-tb-self-overflow CSS class
cy.get("[ui5-toolbar]").then($toolbar => {
const toolbar = $toolbar[0] as Toolbar;
const item = document.querySelector("[ui5-toolbar-item]") as ToolbarItem;
const wrapper = toolbar.shadowRoot!.querySelector(`#${item._individualSlot}`) as HTMLElement;
expect(wrapper.classList.contains("ui5-tb-self-overflow")).to.be.true;
});
});

it("Should NOT apply shrink behavior when shrink-content is not set", () => {
cy.mount(
<div style={{ width: "400px" }}>
<Toolbar align-content="Start">
<ToolbarItem overflow-priority="NeverOverflow">
<Title wrapping-type="None">Super Super SuperSuperSuperSuper SuperSuperSuperSuper Long Text</Title>
</ToolbarItem>
<ToolbarButton text="Button 1"></ToolbarButton>
</Toolbar>
</div>
);

// hasOverflow must be false when shrink-content is not set
cy.get("[ui5-toolbar-item]").then($item => {
const item = $item[0] as ToolbarItem;
expect(item.hasOverflow).to.be.false;
});
});

it("Should keep the button visible after the toolbar is resized", () => {
cy.mount(
<div id="shrink-resize-container" style={{ width: "300px" }}>
<Toolbar align-content="Start">
<ToolbarItem overflow-priority="NeverOverflow" shrink-content>
<Title wrapping-type="None">Super Super SuperSuperSuperSuper SuperSuperSuperSuper SuperSuperSuperSuper Long Text</Title>
</ToolbarItem>
<ToolbarButton text="Button 1"></ToolbarButton>
</Toolbar>
</div>
);

// Button is visible at narrow width
cy.get("[ui5-toolbar-button][text='Button 1']")
.should("be.visible");

// Widen the container
cy.get("#shrink-resize-container")
.invoke("css", "width", "800px");

// Button must still be visible after resize
cy.get("[ui5-toolbar-button][text='Button 1']")
.should("be.visible");

// Overflow button must remain hidden throughout
cy.get("[ui5-toolbar]")
.shadow()
.find(".ui5-tb-overflow-btn")
.should("have.class", "ui5-tb-overflow-btn-hidden");
});
});
9 changes: 7 additions & 2 deletions packages/main/src/Toolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ class Toolbar extends UI5Element {
addItemsAdditionalProperties(item: ToolbarItemBase) {
item.isOverflowed = this.overflowItems.indexOf(item) !== -1;
const itemWrapper = this.shadowRoot!.querySelector(`#${item._individualSlot}`) as HTMLElement;
if (item.hasOverflow && !item.isOverflowed && itemWrapper) {
if (item.hasOverflow && item.clampMaxWidth && !item.isOverflowed && itemWrapper) {
// We need to set the max-width to the self-overflow element in order ot prevent it from taking all the available space,
// since, unlike the other items, it is allowed to grow and shrink
// We need to set the max-width to none and its position to absolute to allow the item to grow and measure its width,
Expand Down Expand Up @@ -433,8 +433,13 @@ class Toolbar extends UI5Element {

this.items.forEach(item => {
const itemWidth = this.getItemWidth(item);
// shrinkContent items (clampMaxWidth=false) are intentionally included in totalWidth:
// getItemWidth returns 0 on first render (_isRendering=true) and the cached flex-distributed
// width on subsequent renders, both of which keep overflowSpace non-positive so distributeItems
// is not incorrectly triggered. They are excluded from minWidth only, as their rendered
// width is not a stable natural-width value.
totalWidth += itemWidth;
if (item.overflowPriority === ToolbarItemOverflowBehavior.NeverOverflow) {
if (item.overflowPriority === ToolbarItemOverflowBehavior.NeverOverflow && item.clampMaxWidth) {
minWidth += itemWidth;
}
this.ITEMS_WIDTH_MAP.set(item._id, itemWidth);
Expand Down
19 changes: 18 additions & 1 deletion packages/main/src/ToolbarItem.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import slot from "@ui5/webcomponents-base/dist/decorators/slot-strict.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import getActiveElement from "@ui5/webcomponents-base/dist/util/getActiveElement.js";
Expand Down Expand Up @@ -115,6 +116,18 @@ class ToolbarItem extends ToolbarItemBase {
})
item!: DefaultSlot<IToolbarItemContent>;

/**
* Defines whether the item should shrink to fit the available toolbar space
* instead of pushing other items out of view.
* Use this when the slotted content (e.g. a long title) should yield space
* to other toolbar items rather than overflow them.
* @default false
* @public
* @since 2.25.0
*/
@property({ type: Boolean })
shrinkContent = false;

// Method called by ui5-toolbar to inform about the existing toolbar wrapper
checkForWrapper() {
if (this._wrapperChecked) {
Expand Down Expand Up @@ -209,7 +222,11 @@ class ToolbarItem extends ToolbarItemBase {
}

get hasOverflow(): boolean {
return this.item[0]?.hasOverflow ?? false;
return this.item[0]?.hasOverflow || this.shrinkContent;
}

get clampMaxWidth(): boolean {
return !this.shrinkContent;
}

getFocusDomRef(): HTMLElement | undefined {
Expand Down
11 changes: 11 additions & 0 deletions packages/main/src/ToolbarItemBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,17 @@ class ToolbarItemBase extends UI5Element {
return false;
}

/**
* Returns whether the toolbar should apply a max-width cap to this item
* during the self-overflow measurement cycle.
* Override to return false for items that must remain free to grow and shrink
* (i.e. items that use flex-shrink to yield space rather than overflowing).
* @protected
*/
get clampMaxWidth(): boolean {
return true;
}

/**
* Returns if the item is separator.
* @protected
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import html from '!!raw-loader!./sample.html';
import js from '!!raw-loader!./main.js';
import react from '!!raw-loader!./sample.tsx';

<Editor html={html} js={js} react={react} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import "@ui5/webcomponents/dist/Toolbar.js";
import "@ui5/webcomponents/dist/ToolbarItem.js";
import "@ui5/webcomponents/dist/ToolbarButton.js";
import "@ui5/webcomponents/dist/Title.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!-- playground-fold -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sample</title>
</head>
<body style="background-color: var(--sapBackgroundColor); height: 250px;">
<!-- playground-fold-end -->

<ui5-toolbar align-content="Start">
<ui5-toolbar-item overflow-priority="NeverOverflow" shrink-content>
<ui5-title wrapping-type="None">Super Long Title That Should Shrink To Fit Available Space</ui5-title>
</ui5-toolbar-item>
<ui5-toolbar-button text="Edit"></ui5-toolbar-button>
<ui5-toolbar-button text="Delete"></ui5-toolbar-button>
</ui5-toolbar>

<!-- playground-fold -->
<script type="module" src="main.js"></script>
</body>
</html>
<!-- playground-fold-end -->
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import createReactComponent from "@ui5/webcomponents-base/dist/createReactComponent.js";
import ToolbarClass from "@ui5/webcomponents/dist/Toolbar.js";
import ToolbarItemClass from "@ui5/webcomponents/dist/ToolbarItem.js";
import ToolbarButtonClass from "@ui5/webcomponents/dist/ToolbarButton.js";
import TitleClass from "@ui5/webcomponents/dist/Title.js";

const Toolbar = createReactComponent(ToolbarClass);
const ToolbarItem = createReactComponent(ToolbarItemClass);
const ToolbarButton = createReactComponent(ToolbarButtonClass);
const Title = createReactComponent(TitleClass);

function App() {
return (
<>
<Toolbar alignContent="Start">
<ToolbarItem overflowPriority="NeverOverflow" shrinkContent>
<Title wrappingType="None">
Super Long Title That Should Shrink To Fit Available Space
</Title>
</ToolbarItem>
<ToolbarButton text="Edit" />
<ToolbarButton text="Delete" />
</Toolbar>
</>
);
}

export default App;
Loading