From 592944256019990060ab21816dee308679849171 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Mon, 24 Jun 2024 14:20:29 +0300 Subject: [PATCH 01/27] feat(ui5-flexible-column-layout): expose public layoutsConfiguration property --- packages/fiori/src/FlexibleColumnLayout.ts | 184 +++++++++++++++++---- packages/fiori/src/fcl-utils/FCLLayout.ts | 12 +- packages/fiori/test/pages/FCLCustom.html | 2 +- 3 files changed, 164 insertions(+), 34 deletions(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index be2f56581a2b..706d123de74b 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -26,7 +26,7 @@ import type { PassiveEventListenerObject } from "@ui5/webcomponents-base/dist/ty import FCLLayout from "./types/FCLLayout.js"; import type { LayoutConfiguration } from "./fcl-utils/FCLLayout.js"; import { - getLayoutsByMedia, + getDefaultLayoutsByMedia, } from "./fcl-utils/FCLLayout.js"; // Texts @@ -81,6 +81,11 @@ type FlexibleColumnLayoutLayoutChangeEventDetail = { resized: boolean, }; +type FlexibleColumnLayoutLayoutConfigurationChangeEventDetail = { + layout: `${FCLLayout}`, + columnLayout: FlexibleColumnLayoutColumnLayout, +}; + type FCLAccessibilityRoles = Extract, "none" | "complementary" | "contentinfo" | "main" | "region"> type FCLAccessibilityAttributes = { startColumn?: { @@ -105,15 +110,6 @@ type FCLAccessibilityAttributes = { }, } -type UserDefinedColumnLayouts = { - "tablet": { - [layoutName in FCLLayout]?: FlexibleColumnLayoutColumnLayout; - }, - "desktop": { - [layoutName in FCLLayout]?: FlexibleColumnLayoutColumnLayout; - }, -} - /** * @class * @@ -216,6 +212,25 @@ type UserDefinedColumnLayouts = { resized: { type: Boolean }, }, }) + +/** + * Fired when the `layoutConfiguration` changes via user interaction by dragging the separators. + * @param {FCLLayout} layout The current layout + * @param {array} columnLayout The effective column layout, f.e [67%, 33%, 0] + * @public + */ +@event("layout-configuration-change", { + detail: { + /** + * @public + */ + layout: { type: FCLLayout }, + /** + * @public + */ + columnLayout: { type: Array }, + }, +}) class FlexibleColumnLayout extends UI5Element { /** * Defines the columns layout and their proportion. @@ -294,11 +309,44 @@ class FlexibleColumnLayout extends UI5Element { _visibleColumns = 1; /** - * Allows the user to replace the whole layouts configuration - * @private + * Allows to customize the proportions of the column widts per screen size and layout. + * If no custom proportion provided for a specific layout, the default will be used. + * + * The `layoutsConfiguration` object has the following structure: + * ```json + * { "desktop": { + * "TwoColumnsStartExpanded": { + * "layout": ["70%", "30%", "0px"] // 0 or "0%" is also valid + * }, + * "ThreeColumnsMidExpanded": { + * "layout": ["30%", "40%", "30%"] + * }, + * .. + * }, + * "tablet": { + * "TwoColumnsStartExpanded": { + * "layout": ["70%", "30%", "0px"] + * }, + * "ThreeColumnsMidExpanded": { + * "layout": [0, "70%", "30%"] + * }, + * .. + * } + * `` + * **Notes:** + * + * - The proportions should be given in percentages. For example ["30%", "40%", "30%"], ["70%", "30%", 0], etc. + * - The proportions should add up to 100%. + * - Hidden columns are marked as "0px", e.g. ["0px", "70%", "30%"]. Specifying 0 or "0%" for hidden columns is also valid. + * - If the proportions do not match the layout (e.g. if provided proportions ["70%", "30%", "0px"] for "OneColumn" layout), then the default proportions will be used instead. + * - If the user drags the columns separator to resize the columns, the `layoutsConfiguration` object will be updated with the user-specified proportions for the given layout. + * - No custom configuration available for the phone screen size, as the default of 100% column width is always used there. Any custom configuration for the phone screen size will be ignored. + * @public + * @since 2.0.0 + * @default {} */ @property({ type: Object }) - _layoutsConfiguration?: LayoutConfiguration; + layoutsConfiguration: LayoutConfiguration = {}; /** * Defines the content in the start column. @@ -327,10 +375,6 @@ class FlexibleColumnLayout extends UI5Element { _onSeparatorMoveEnd: (e: TouchEvent | MouseEvent) => void; static i18nBundle: I18nBundle; _prevLayout: `${FCLLayout}` | null; - _userDefinedColumnLayouts: UserDefinedColumnLayouts = { - tablet: {}, - desktop: {}, - }; _ontouchstart: PassiveEventListenerObject; separatorMovementSession?: SeparatorMovementSession | null; @@ -474,11 +518,24 @@ class FlexibleColumnLayout extends UI5Element { } nextColumnLayout(layout: `${FCLLayout}`) { - let userDefinedLayout; - if (this.media !== MEDIA.PHONE) { - userDefinedLayout = this._userDefinedColumnLayouts[this.media][layout]; + return this.getCustomColumnLayout(layout) || this.getDefaultColumnLayout(layout); + } + + getCustomColumnLayout(layout: `${FCLLayout}`) { + if (this.mediaAllowsCustomConfiguration()) { + const customLayout = this.layoutsConfiguration[this.media]?.[layout]?.layout; + if (customLayout && this.isValidColumnLayout(customLayout)) { + return customLayout; + } } - return userDefinedLayout || this._effectiveLayoutsByMedia[this.media][layout].layout; + } + + getDefaultColumnLayout(layout: `${FCLLayout}`) { + return getDefaultLayoutsByMedia()[this.media][layout].layout; + } + + mediaAllowsCustomConfiguration() { + return this.media !== MEDIA.PHONE; } calcVisibleColumns(colLayout: FlexibleColumnLayoutColumnLayout) { @@ -497,6 +554,13 @@ class FlexibleColumnLayout extends UI5Element { }); } + fireLayoutConfigurationChange() { + this.fireEvent("layout-configuration-change", { + layout: this.layout, + columnLayout: this._columnLayout!, + }); + } + onSeparatorPress(e: TouchEvent | MouseEvent) { const pressedSeparator = (e.target as HTMLElement).closest(".ui5-fcl-separator") as HTMLElement; if (pressedSeparator.classList.contains("ui5-fcl-separator-start") && !this.showStartSeparatorGrip) { @@ -549,7 +613,7 @@ class FlexibleColumnLayout extends UI5Element { return; } const newLayout = this.separatorMovementSession.tmpFCLLayout; - const newColumnLayout = this._columnLayout!; + const newColumnLayout = this._columnLayout! as string[]; this.saveUserDefinedColumnLayout(newLayout, newColumnLayout); this.exitSeparatorMovementSession(); @@ -577,14 +641,18 @@ class FlexibleColumnLayout extends UI5Element { this.separatorMovementSession = null; } - saveUserDefinedColumnLayout(newLayout: FCLLayout, newColumnLayout: FlexibleColumnLayoutColumnLayout) { - const media = this.media as MEDIA.TABLET | MEDIA.DESKTOP; - - this._userDefinedColumnLayouts[media][newLayout] = newColumnLayout; + saveUserDefinedColumnLayout(newLayout: FCLLayout, newColumnLayout: string[]) { + const media = this.media as MEDIA.TABLET | MEDIA.DESKTOP, + oldColumnLayout = this.getCustomColumnLayout(newLayout); + this.layoutsConfiguration[media] ??= {}; + this.layoutsConfiguration[media]![newLayout] ??= { layout: newColumnLayout }; if (this.layout !== newLayout) { this.layout = newLayout; this.fireLayoutChange(true, false); } + if (oldColumnLayout !== newColumnLayout) { + this.fireLayoutConfigurationChange(); + } } private isSeparatorAheadOfCursor(cursorX: number, separatorX: number, isForwardMove: boolean) { @@ -788,6 +856,62 @@ class FlexibleColumnLayout extends UI5Element { return `${(pxWidth / this._availableWidthForColumns) * 100}%`; } + isValidColumnLayout(columnLayout: (string | 0)[]) { + const pxWidths = columnLayout.map(x => this.convertColumnWidthToPixels(x)), + totalWidth = pxWidths.reduce((i, sum) => i + sum); + + if (Math.round(totalWidth) !== this._availableWidthForColumns) { + return false; + } + + const hasColumnBelowMinWidth = pxWidths.some(pxWidth => { + return (pxWidth > 0) && (pxWidth < COLUMN_MIN_WIDTH); + }); + if (hasColumnBelowMinWidth) { + return false; + } + + return this.verifyColumnWidthsMatchLayout(pxWidths); + } + + verifyColumnWidthsMatchLayout(pxWidths: number[]) { + const columnWidths = { + start: pxWidths[0], + mid: pxWidths[1], + end: pxWidths[2], + }, + startWidth = columnWidths.start, + startPercentWidth = parseInt(this.convertToRelativeColumnWidth(startWidth)); + + switch (this.layout) { + case FCLLayout.TwoColumnsStartExpanded: { + return columnWidths.start >= columnWidths.mid; + } + case FCLLayout.TwoColumnsMidExpanded: { + return columnWidths.mid > columnWidths.start; + } + case FCLLayout.ThreeColumnsEndExpanded: { + return (columnWidths.end > columnWidths.mid) && (startPercentWidth < 33); + } + case FCLLayout.ThreeColumnsStartExpandedEndHidden: { + return (columnWidths.start >= columnWidths.mid) && columnWidths.end === 0; + } + case FCLLayout.ThreeColumnsMidExpanded: { + return (columnWidths.mid >= columnWidths.end) + && ((this.media === MEDIA.DESKTOP && startPercentWidth < 33) // desktop + || (this.media === MEDIA.TABLET && startPercentWidth === 0)); // tablet + } + case FCLLayout.ThreeColumnsMidExpandedEndHidden: { + return (columnWidths.mid > columnWidths.start) + && columnWidths.end === 0 + && ((this.media === MEDIA.DESKTOP && startPercentWidth >= 33) + || (this.media === MEDIA.TABLET && startWidth >= COLUMN_MIN_WIDTH)); + } + } + + return false; + } + getNextLayoutOnSeparatorMovement(separator: HTMLElement, isStartToEndDirection: boolean, fclLayoutBeforeMove: FCLLayout, columnLayoutAfterMove: FlexibleColumnLayoutColumnLayout) { const isStartSeparator = separator === this.startSeparatorDOM, separatorName = isStartSeparator ? "start" : "end", @@ -1063,7 +1187,7 @@ class FlexibleColumnLayout extends UI5Element { } get effectiveSeparatorsInfo() { - return this._effectiveLayoutsByMedia[this.media][this.effectiveLayout].separators; + return getDefaultLayoutsByMedia()[this.media][this.effectiveLayout].separators; } get effectiveLayout() { @@ -1172,10 +1296,6 @@ class FlexibleColumnLayout extends UI5Element { return this.accessibilityAttributes.endSeparator?.role || "separator"; } - get _effectiveLayoutsByMedia() { - return this._layoutsConfiguration || getLayoutsByMedia(); - } - get _accAttributes() { return { columns: { @@ -1203,6 +1323,8 @@ export default FlexibleColumnLayout; export type { MEDIA, FlexibleColumnLayoutLayoutChangeEventDetail, + FlexibleColumnLayoutLayoutConfigurationChangeEventDetail, FCLAccessibilityAttributes, FlexibleColumnLayoutColumnLayout, + LayoutConfiguration, }; diff --git a/packages/fiori/src/fcl-utils/FCLLayout.ts b/packages/fiori/src/fcl-utils/FCLLayout.ts index edeb472a245b..f08c009b28cc 100644 --- a/packages/fiori/src/fcl-utils/FCLLayout.ts +++ b/packages/fiori/src/fcl-utils/FCLLayout.ts @@ -2,6 +2,14 @@ import type { MEDIA } from "../FlexibleColumnLayout.js"; import type FCLLayout from "../types/FCLLayout.js"; type LayoutConfiguration = { + [device in MEDIA]?: { + [layoutName in FCLLayout]?: { + layout: Array, + } + }; +} + +type ExtendedLayoutConfiguration = { [device in MEDIA]: { [layoutName in FCLLayout]: { layout: Array; @@ -13,7 +21,7 @@ type LayoutConfiguration = { }; }; -const getLayoutsByMedia = (): LayoutConfiguration => { +const getDefaultLayoutsByMedia = (): ExtendedLayoutConfiguration => { return { desktop: { "OneColumn": { @@ -214,7 +222,7 @@ const getLayoutsByMedia = (): LayoutConfiguration => { }; export { - getLayoutsByMedia, + getDefaultLayoutsByMedia, }; export type { diff --git a/packages/fiori/test/pages/FCLCustom.html b/packages/fiori/test/pages/FCLCustom.html index 02f144db6286..1610170ac85e 100644 --- a/packages/fiori/test/pages/FCLCustom.html +++ b/packages/fiori/test/pages/FCLCustom.html @@ -67,7 +67,7 @@ diff --git a/packages/fiori/test/specs/FCL.spec.js b/packages/fiori/test/specs/FCL.spec.js index f79ab7ed79c8..71a1078c47ff 100644 --- a/packages/fiori/test/specs/FCL.spec.js +++ b/packages/fiori/test/specs/FCL.spec.js @@ -495,7 +495,27 @@ describe("Layouts configuration", () => { // assert: the column widths in DOM are set as configured assert.strictEqual(await startColumn.getAttribute("style"), "width: 75%;", "the custom width is set as configured for start column"); assert.strictEqual(await midColumn.getAttribute("style"), "width: 25%;", "the custom width is set as configured for mid column"); - // TODO check no event fired + }); + + it("preserves column min-width when configuration set programatically", async () => { + const fcl = await browser.$("#fcl1"), + startColumn = await fcl.shadow$(".ui5-fcl-column--start"), + layoutConfig = { + "desktop": { + "TwoColumnsMidExpanded": { + layout: ["1%", "99%", "0%"] // specify unacceptably small width for first column + } + } + }, + smallestColumnWidth = 312; + + // set initial state + await fcl.setProperty("layoutsConfiguration", layoutConfig); + await fcl.setProperty("layout", "TwoColumnsMidExpanded"); + assert.strictEqual(await fcl.getProperty("layout"), "TwoColumnsMidExpanded", "new layout set"); + + // assert: the column widths in DOM are set as configured + assert.strictEqual(await startColumn.getSize("width"), smallestColumnWidth, "column min width is preserved"); }); it("updates the configuration upon interactive resize of columns within same layout", async () => { @@ -504,7 +524,8 @@ describe("Layouts configuration", () => { startColumn = await fcl.shadow$(".ui5-fcl-column--start"), midColumn = await fcl.shadow$(".ui5-fcl-column--middle"); - // assert initial state + // set initial state + await fcl.setProperty("layout", "TwoColumnsStartExpanded"); assert.strictEqual(await fcl.getProperty("layout"), "TwoColumnsStartExpanded", "initial state is as expected"); // act: drag to resize the columns within the same layout diff --git a/packages/website/docs/_components_pages/fiori/FlexibleColumnLayout.mdx b/packages/website/docs/_components_pages/fiori/FlexibleColumnLayout.mdx index b90c756cac12..384236dd1f0c 100644 --- a/packages/website/docs/_components_pages/fiori/FlexibleColumnLayout.mdx +++ b/packages/website/docs/_components_pages/fiori/FlexibleColumnLayout.mdx @@ -1,8 +1,16 @@ import Basic from "../../_samples/fiori/FlexibleColumnLayout/Basic/Basic.md"; +import LayoutsConfiguration from "../../_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/LayoutsConfiguration.md"; <%COMPONENT_OVERVIEW%> ## Basic Sample -<%COMPONENT_METADATA%> \ No newline at end of file +<%COMPONENT_METADATA%> + +## More Samples + +### LayoutsConfiguration +The FlexibleColumnLayout supports customization of the column sizes within the limits of the current layout. + + \ No newline at end of file From cb3cc8b4b1324192451bcdbd8d48d2cafaa8444b Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Mon, 22 Jul 2024 18:07:23 +0300 Subject: [PATCH 06/27] feat(ui5-flexible-column-layout): immediate update of layout configuration --- packages/fiori/src/FlexibleColumnLayout.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index 2182dc5c1e60..a85da7c90aa7 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -354,6 +354,7 @@ class FlexibleColumnLayout extends UI5Element { _onSeparatorMoveEnd: (e: TouchEvent | MouseEvent) => void; static i18nBundle: I18nBundle; _prevLayout: `${FCLLayout}` | null; + _prevLayoutsConfiguration: LayoutConfiguration | null; _ontouchstart: PassiveEventListenerObject; separatorMovementSession?: SeparatorMovementSession | null; @@ -361,6 +362,7 @@ class FlexibleColumnLayout extends UI5Element { super(); this._prevLayout = null; + this._prevLayoutsConfiguration = null; this.initialRendering = true; this._handleResize = this.handleResize.bind(this); this._onSeparatorMove = this.onSeparatorMove.bind(this); @@ -398,6 +400,7 @@ class FlexibleColumnLayout extends UI5Element { return; } + this.syncLayoutsConfiguration(); this.syncLayout(); } @@ -438,6 +441,15 @@ class FlexibleColumnLayout extends UI5Element { } } + syncLayoutsConfiguration() { + if (this._prevLayoutsConfiguration !== this.layoutsConfiguration) { + this._prevLayoutsConfiguration = this.layoutsConfiguration; + if (this.nextColumnLayout(this.layout).join() !== this._columnLayout?.join() && !this.separatorMovementSession) { + this.updateLayout(); + } + } + } + toggleColumns() { this.toggleColumn("start"); this.toggleColumn("mid"); From 853e84bfde694e6bf3ecf1010ee08a4212ea864c Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Tue, 30 Jul 2024 17:01:04 +0300 Subject: [PATCH 07/27] feat(ui5-flexible-column-layout): add sample --- .../LayoutsConfiguration.md | 5 + .../LayoutsConfiguration/main.css | 11 +++ .../LayoutsConfiguration/main.js | 54 +++++++++++ .../LayoutsConfiguration/sample.html | 95 +++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/LayoutsConfiguration.md create mode 100644 packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.css create mode 100644 packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js create mode 100644 packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/sample.html diff --git a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/LayoutsConfiguration.md b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/LayoutsConfiguration.md new file mode 100644 index 000000000000..0ac936771661 --- /dev/null +++ b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/LayoutsConfiguration.md @@ -0,0 +1,5 @@ +import html from '!!raw-loader!./sample.html'; +import js from '!!raw-loader!./main.js'; +import css from '!!raw-loader!./main.css'; + + diff --git a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.css b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.css new file mode 100644 index 000000000000..751822b57fea --- /dev/null +++ b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.css @@ -0,0 +1,11 @@ +.fcl { + height: 600px; +} + +.col { + height: 100%; +} + +.colHeader { + padding: 1rem; +} diff --git a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js new file mode 100644 index 000000000000..189f1f33913e --- /dev/null +++ b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js @@ -0,0 +1,54 @@ +import "@ui5/webcomponents/dist/Select.js"; +import "@ui5/webcomponents/dist/Option.js"; +import "@ui5/webcomponents/dist/Label.js"; +import "@ui5/webcomponents/dist/List.js"; +import "@ui5/webcomponents/dist/ListItemStandard.js"; +import "@ui5/webcomponents/dist/Title.js"; +import "@ui5/webcomponents-fiori/dist/FlexibleColumnLayout.js"; + +fcl.layoutsConfiguration = { + desktop: { + "TwoColumnsStartExpanded": { + layout: ["80%", "20%", 0], + }, + "TwoColumnsMidExpanded": { + layout: ["20%", "80%", 0], + }, + "ThreeColumnsMidExpanded": { + layout: ["25%", "45%", "30%"], + }, + "ThreeColumnsEndExpanded": { + layout: ["15%", "15%", "70%"], + }, + "ThreeColumnsStartExpandedEndHidden": { + layout: ["70%", "30%", 0], + }, + "ThreeColumnsMidExpandedEndHidden": { + layout: ["20%", "80%", 0], + }, + }, + tablet: { + "TwoColumnsStartExpanded": { + layout: ["60%", "40%", 0], + }, + "TwoColumnsMidExpanded": { + layout: ["40%", "60%", 0], + }, + "ThreeColumnsMidExpanded": { + layout: [0, "60%", "40%"], + }, + "ThreeColumnsEndExpanded": { + layout: [0, "40%", "60%"], + }, + "ThreeColumnsStartExpandedEndHidden": { + layout: ["60%", "40%", 0], + }, + "ThreeColumnsMidExpandedEndHidden": { + layout: ["40%", "60%", 0], + }, + }, +}; + +selectLayout.addEventListener("ui5-change", (e) => { + fcl.layout = e.detail.selectedOption.textContent; +}); \ No newline at end of file diff --git a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/sample.html b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/sample.html new file mode 100644 index 000000000000..0a47c51839d5 --- /dev/null +++ b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/sample.html @@ -0,0 +1,95 @@ + + + + + + + + Sample + + + + + + + Select layout + + TwoColumnsStartExpanded + TwoColumnsMidExpanded + ThreeColumnsMidExpanded + ThreeColumnsEndExpanded + ThreeColumnsStartExpandedEndHidden + ThreeColumnsMidExpandedEndHidden + + +
+
+ Column 1 +
+ + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + +
+
+
+ Column 2 +
+ + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + +
+
+
+ Column 3 +
+ + Hello worild! + Hello worild! + Hello worild! + +
+
+ + + + + + From 74621e9864d8e9355ecb278d3e0526a5f67c18f0 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Mon, 18 Aug 2025 12:28:16 +0300 Subject: [PATCH 08/27] feat(ui5-flexible-column-layout): improve type declarations --- packages/fiori/src/FlexibleColumnLayout.ts | 26 ++++++++++++++++------ packages/fiori/src/fcl-utils/FCLLayout.ts | 13 ++--------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index 5f33c332424a..b67302d0abd8 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -26,7 +26,6 @@ import { } from "@ui5/webcomponents-base/dist/Keys.js"; import type { PassiveEventListenerObject, AriaLandmarkRole } from "@ui5/webcomponents-base"; import FCLLayout from "./types/FCLLayout.js"; -import type { LayoutConfiguration } from "./fcl-utils/FCLLayout.js"; import { getDefaultLayoutsByMedia, getNextLayoutByArrowPress, @@ -77,7 +76,20 @@ type SeparatorMovementSession = { tmpFCLLayout: FCLLayout, // the layout that corresponds to the latest separator position }; -type FlexibleColumnLayoutColumnLayout = Array; +type FlexibleColumnLayoutColumnLayout = Array; + +type LayoutConfiguration = { + "tablet"?: { + [layoutName in FCLLayout]?: { + layout: FlexibleColumnLayoutColumnLayout, + } + }, + "desktop"?: { + [layoutName in FCLLayout]?: { + layout: FlexibleColumnLayoutColumnLayout, + } + }, +} type FlexibleColumnLayoutLayoutChangeEventDetail = { layout: `${FCLLayout}`, @@ -513,7 +525,7 @@ class FlexibleColumnLayout extends UI5Element { } getCustomColumnLayout(layout: `${FCLLayout}`) { - if (this.mediaAllowsCustomConfiguration()) { + if (this.mediaAllowsCustomConfiguration(this.media)) { const customLayout = this.layoutsConfiguration[this.media]?.[layout]?.layout; if (customLayout) { const normalizedWidths = this.normalizeColumnWidths(customLayout); // satisfy min-width constraint @@ -525,7 +537,7 @@ class FlexibleColumnLayout extends UI5Element { } setCustomColumnLayout(layout: `${FCLLayout}`, columnLayout: string[]) { - if (this.mediaAllowsCustomConfiguration()) { + if (this.mediaAllowsCustomConfiguration(this.media)) { this.layoutsConfiguration[this.media] ??= {}; this.layoutsConfiguration[this.media]![layout] ??= { layout: columnLayout }; this.layoutsConfiguration[this.media]![layout]!.layout = columnLayout; @@ -536,8 +548,8 @@ class FlexibleColumnLayout extends UI5Element { return getDefaultLayoutsByMedia()[this.media][layout].layout; } - mediaAllowsCustomConfiguration() { - return this.media !== MEDIA.PHONE; + mediaAllowsCustomConfiguration(media: MEDIA) { + return media !== MEDIA.PHONE; } calcVisibleColumns(colLayout: FlexibleColumnLayoutColumnLayout) { @@ -559,7 +571,7 @@ class FlexibleColumnLayout extends UI5Element { fireLayoutConfigurationChange() { const columnLayout = [...this._columnLayout!] as string[]; // do not leak reference to the private _columnLayout array to prevent apps modifying its content - this.fireEvent("layout-configuration-change", { + this.fireDecoratorEvent("layout-configuration-change", { layout: this.layout, columnLayout, }); diff --git a/packages/fiori/src/fcl-utils/FCLLayout.ts b/packages/fiori/src/fcl-utils/FCLLayout.ts index 8e8e8ebf20b9..da491fc3a99d 100644 --- a/packages/fiori/src/fcl-utils/FCLLayout.ts +++ b/packages/fiori/src/fcl-utils/FCLLayout.ts @@ -1,15 +1,7 @@ import type { MEDIA } from "../FlexibleColumnLayout.js"; import type FCLLayout from "../types/FCLLayout.js"; -type LayoutConfiguration = { - [device in MEDIA]?: { - [layoutName in FCLLayout]?: { - layout: Array, - } - }; -} - -type ExtendedLayoutConfiguration = { +type DefaultLayoutConfiguration = { [device in MEDIA]: { [layoutName in FCLLayout]: { layout: Array; @@ -23,7 +15,7 @@ type ExtendedLayoutConfiguration = { }; }; -const getDefaultLayoutsByMedia = (): ExtendedLayoutConfiguration => { +const getDefaultLayoutsByMedia = (): DefaultLayoutConfiguration => { return { desktop: { "OneColumn": { @@ -300,6 +292,5 @@ export { }; export type { - LayoutConfiguration, FCLLayout, }; From 897be5935694384ff428402754060380cd4ab7ed Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Mon, 18 Aug 2025 13:40:16 +0300 Subject: [PATCH 09/27] feat(ui5-flexible-column-layout): improve documentation --- packages/fiori/src/FlexibleColumnLayout.ts | 9 ++++++--- .../FlexibleColumnLayout/LayoutsConfiguration/main.js | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index b67302d0abd8..0a4720c2ad2f 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -103,6 +103,7 @@ type FlexibleColumnLayoutLayoutChangeEventDetail = { type FlexibleColumnLayoutLayoutConfigurationChangeEventDetail = { layout: `${FCLLayout}`, + media: `${MEDIA}`, columnLayout: FlexibleColumnLayoutColumnLayout, }; @@ -208,7 +209,8 @@ type FCLAccessibilityAttributes = { /** * Fired when the `layoutConfiguration` changes via user interaction by dragging the separators. * @param {FCLLayout} layout The current layout - * @param {array} columnLayout The effective column layout, f.e [67%, 33%, 0] + * @param {MEDIA} media The current media type + * @param {array} columnLayout The effective column layout, f.e ["67%", "33%", "0px"] * @public */ @event("layout-configuration-change", { @@ -313,8 +315,8 @@ class FlexibleColumnLayout extends UI5Element { * - The proportions should add up to 100%. * - Hidden columns are marked as "0px", e.g. ["0px", "70%", "30%"]. Specifying 0 or "0%" for hidden columns is also valid. * - If the proportions do not match the layout (e.g. if provided proportions ["70%", "30%", "0px"] for "OneColumn" layout), then the default proportions will be used instead. - * - If the user drags the columns separator to resize the columns, the `layoutsConfiguration` object will be updated with the user-specified proportions for the given layout. - * - No custom configuration available for the phone screen size, as the default of 100% column width is always used there. Any custom configuration for the phone screen size will be ignored. + * - Whenever the user drags the columns separator to resize the columns, the `layoutsConfiguration` object will be updated with the user-specified proportions for the given layout (and the `layout-configuration-change` event will be fired). + * - No custom configuration available for the phone screen size, as the default of 100% column width is always used there. * @public * @since 2.0.0 * @default {} @@ -573,6 +575,7 @@ class FlexibleColumnLayout extends UI5Element { const columnLayout = [...this._columnLayout!] as string[]; // do not leak reference to the private _columnLayout array to prevent apps modifying its content this.fireDecoratorEvent("layout-configuration-change", { layout: this.layout, + media: this.media, columnLayout, }); } diff --git a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js index 189f1f33913e..b3d880d178e5 100644 --- a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js +++ b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js @@ -51,4 +51,9 @@ fcl.layoutsConfiguration = { selectLayout.addEventListener("ui5-change", (e) => { fcl.layout = e.detail.selectedOption.textContent; +}); + +fcl.addEventListener("layout-configuration-change", (e) => { + console.log("Layout configuration change:", e.detail); + console.log("Layout configuration object:", fcl.layoutsConfiguration); }); \ No newline at end of file From 3beb58b0c07f4f14eacab595fff317ca9b23ff6c Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Mon, 18 Aug 2025 17:36:23 +0300 Subject: [PATCH 10/27] feat(ui5-flexible-column-layout): tmp config to allow sharing a sample --- packages/website/docusaurus.config.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/website/docusaurus.config.ts b/packages/website/docusaurus.config.ts index f79883b68c15..c477113e798a 100644 --- a/packages/website/docusaurus.config.ts +++ b/packages/website/docusaurus.config.ts @@ -25,7 +25,7 @@ const getBaseURL = () => { const BASE_URL = getBaseURL(); const getFullURL = () => { - return DEVELOPMENT_ENVIRONMENT ? `${BASE_URL}` : `https://sap.github.io${BASE_URL}` + return DEVELOPMENT_ENVIRONMENT ? `${BASE_URL}` : `https://kineticjs.github.io${BASE_URL}` } // ["v1", "nightly", "current"] @@ -41,7 +41,7 @@ const config: Config = { favicon: 'img/favicon.ico', // Set the production url of your site here - url: 'https://sap.github.io', + url: 'https://kineticjs.github.io', // Set the // pathname under which your site is served // For GitHub pages deployment, it is often '//' baseUrl: BASE_URL, @@ -168,7 +168,7 @@ const config: Config = { copyright: `© Copyright ${new Date().getFullYear()}, SAP SE and UI5 Web Components Contributors`, logo: { alt: 'SAP Logo', - src: 'https://sap.github.io/ui5-webcomponents/img/footer/sap-1920-1440.svg', + src: 'https://kineticjs.github.io/ui5-webcomponents/img/footer/sap-1920-1440.svg', width: 160, height: 51, }, @@ -204,7 +204,7 @@ const config: Config = { // // Youtube // - // + // // GitHub // // From d9b4dd306680b95672935880c86520ab8f33c616 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Tue, 2 Sep 2025 09:48:50 +0300 Subject: [PATCH 11/27] feat(ui5-flexible-column-layout): tests and some refactoring --- packages/fiori/cypress/specs/FCL.cy.tsx | 294 ++++++++++++++++++++- packages/fiori/src/FlexibleColumnLayout.ts | 224 ++++++++++------ 2 files changed, 429 insertions(+), 89 deletions(-) diff --git a/packages/fiori/cypress/specs/FCL.cy.tsx b/packages/fiori/cypress/specs/FCL.cy.tsx index b440858e4f29..f946a1133a51 100644 --- a/packages/fiori/cypress/specs/FCL.cy.tsx +++ b/packages/fiori/cypress/specs/FCL.cy.tsx @@ -209,6 +209,25 @@ describe("FlexibleColumnLayout Behavior", () => { cy.get("@fcl") .should("have.attr", "_visible-columns", "3"); + // Assert default column widths + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-column--start") + .should("have.attr", "style") + .and("include", "width: 25%"); + + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-column--middle") + .should("have.attr", "style") + .and("include", "width: 50%"); + + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-column--end") + .should("have.attr", "style") + .and("include", "width: 25%"); + cy.get("@layoutChangeStub") .should("not.have.been.called"); }); @@ -233,6 +252,25 @@ describe("FlexibleColumnLayout Behavior", () => { cy.get("@fcl") .should("have.attr", "_visible-columns", "2"); + // Assert default column widths for tablet size + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-column--start") + .should("have.attr", "style") + .and("include", "width: 0px"); + + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-column--middle") + .should("have.attr", "style") + .and("include", "width: 67%"); + + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-column--end") + .should("have.attr", "style") + .and("include", "width: 33%"); + cy.get("@layoutChangeStub") .should("have.been.calledOnce"); }); @@ -1116,4 +1154,258 @@ describe("Accessibility with Animation Disabled", () => { .find(".ui5-fcl-column--middle") .should("have.attr", "aria-hidden", "true"); }); -}); \ No newline at end of file +}); + +describe("Layouts configuration", () => { + const COLUMN_MIN_WIDTH = 248; + + it("initial configuration", () => { + + cy.mount( + +
some content
+
some content
+
some content
+
+ ); + + cy.get("[ui5-flexible-column-layout]") + .as("fcl") + .then($fcl => { + $fcl.get(0).addEventListener("layout-configuration-change", cy.stub().as("layoutConfigChangeStub")); + }); + + // Assert resulting column widths + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-column--start") + .should("have.attr", "style") + .and("include", "width: 67%"); + + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-column--middle") + .should("have.attr", "style") + .and("include", "width: 33%"); + + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-column--end") + .should("have.attr", "style") + .and("include", "width: 0px"); + + // Assert layoutsConfiguration is initially an empty object + cy.get("@fcl") + .invoke("prop", "layoutsConfiguration") + .should("deep.equal", {}); + + cy.get("@layoutConfigChangeStub") + .should("not.have.been.called"); + }); + + it("allows set configuration programatically", () => { + + cy.mount( + +
some content
+
some content
+
some content
+
+ ); + + cy.get("[ui5-flexible-column-layout]") + .as("fcl") + .then($fcl => { + $fcl.get(0).addEventListener("layout-configuration-change", cy.stub().as("layoutConfigChangeStub")); + }); + + // Set layoutsConfiguration programmatically + cy.get("@fcl") + .invoke("prop", "layoutsConfiguration", { + "desktop": { + "TwoColumnsStartExpanded": { + layout: ["75%", "25%", "0%"] + } + } + }); + + // Assert resulting column widths + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-column--start") + .should("have.attr", "style") + .and("include", "width: 75%"); + + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-column--middle") + .should("have.attr", "style") + .and("include", "width: 25%"); + + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-column--end") + .should("have.attr", "style") + .and("include", "width: 0px"); + + cy.get("@layoutConfigChangeStub") + .should("not.have.been.called"); + }); + + it("fires layout-configuration-change event when dragging separator within same layout", () => { + cy.mount( + +
Start
+
Mid
+
End
+
+ ); + + cy.get("[ui5-flexible-column-layout]").then(($fcl) => { + const fcl = $fcl[0]; + fcl.addEventListener("layout-configuration-change", cy.stub().as("layoutConfigurationChanged")); + }); + + cy.get("[ui5-flexible-column-layout]").as("fcl"); + + // resize the columns within the same layout-type + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-separator-start") + .should("be.visible") + .realMouseDown() + .realMouseMove(10, 0) + .realMouseUp(); + + cy.get("@layoutConfigurationChanged").should("have.been.calledOnce"); + cy.get("@fcl").should("have.prop", "layout", "TwoColumnsStartExpanded"); + + // Check that layoutsConfiguration property has the expected structure + cy.get("@fcl").invoke("prop", "layoutsConfiguration").then((layoutsConfig: any) => { + expect(layoutsConfig).to.have.property("desktop"); + expect(layoutsConfig.desktop).to.have.property("TwoColumnsStartExpanded"); + expect(layoutsConfig.desktop.TwoColumnsStartExpanded).to.have.property("layout"); + expect(layoutsConfig.desktop.TwoColumnsStartExpanded.layout).to.be.an("array"); + expect(layoutsConfig.desktop.TwoColumnsStartExpanded.layout).to.have.length(3); + expect(layoutsConfig.desktop.TwoColumnsStartExpanded.layout).to.satisfy((arr: any[]) => + arr.every(item => typeof item === "string") + ); + + // Check the exact values of the layout array + const layoutArray = layoutsConfig.desktop.TwoColumnsStartExpanded.layout; + + // 1) Calling parseInt on each of them should return a number + const parsedNumbers = layoutArray.map((item: string) => parseInt(item, 10)); + expect(parsedNumbers).to.satisfy((nums: number[]) => + nums.every(num => !isNaN(num)) + ); + + // 2) The last number should be 0 + expect(parsedNumbers[2]).to.equal(0); + + // 3) The first number should be greater than the second number + expect(parsedNumbers[0]).to.be.greaterThan(parsedNumbers[1]); + }); + }); + + it("fires layout-configuration-change event when dragging separator to update the layout", () => { + cy.mount( + +
Start
+
Mid
+
End
+
+ ); + + cy.get("[ui5-flexible-column-layout]").then(($fcl) => { + const fcl = $fcl[0]; + fcl.addEventListener("layout-configuration-change", cy.stub().as("layoutConfigurationChanged")); + }); + + cy.get("[ui5-flexible-column-layout]").as("fcl"); + + // resize the columns to a new layout-type + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-separator-start") + .should("be.visible") + .realMouseDown() + .realMouseMove(-400, 0) + .realMouseUp(); + + cy.get("@layoutConfigurationChanged").should("have.been.calledOnce"); + cy.get("@fcl").should("have.prop", "layout", "TwoColumnsMidExpanded"); + + // Check that layoutsConfiguration property has the expected structure + cy.get("@fcl").invoke("prop", "layoutsConfiguration").then((layoutsConfig: any) => { + expect(layoutsConfig).to.have.property("desktop"); + expect(layoutsConfig.desktop).to.have.property("TwoColumnsMidExpanded"); + expect(layoutsConfig.desktop.TwoColumnsMidExpanded).to.have.property("layout"); + expect(layoutsConfig.desktop.TwoColumnsMidExpanded.layout).to.be.an("array"); + expect(layoutsConfig.desktop.TwoColumnsMidExpanded.layout).to.have.length(3); + expect(layoutsConfig.desktop.TwoColumnsMidExpanded.layout).to.satisfy((arr: any[]) => + arr.every(item => typeof item === "string") + ); + + // Check the exact values of the layout array + const layoutArray = layoutsConfig.desktop.TwoColumnsMidExpanded.layout; + + // 1) Calling parseInt on each of them should return a number + const parsedNumbers = layoutArray.map((item: string) => parseInt(item, 10)); + expect(parsedNumbers).to.satisfy((nums: number[]) => + nums.every(num => !isNaN(num)) + ); + + // 2) The last number should be 0 + expect(parsedNumbers[2]).to.equal(0); + + // 3) The first number should be smaller than the second number + expect(parsedNumbers[0]).to.be.lessThan(parsedNumbers[1]); + }); + }); + + it("applies min width constraints", () => { + + cy.mount( + +
some content
+
some content
+
some content
+
+ ); + + cy.get("[ui5-flexible-column-layout]") + .as("fcl") + .then($fcl => { + $fcl.get(0).addEventListener("layout-configuration-change", cy.stub().as("layoutConfigChangeStub")); + }); + + // Set layoutsConfiguration programmatically + cy.get("@fcl") + .invoke("prop", "layoutsConfiguration", { + "desktop": { + "ThreeColumnsMidExpanded": { + layout: ["10%", "80%", "10%"] + } + } + }); + + // Assert resulting column widths respect minimum width constraint + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-column--start") + .should("have.prop", "offsetWidth", COLUMN_MIN_WIDTH); + + cy.get("@fcl") + .shadow() + .find(".ui5-fcl-column--end") + .should("have.prop", "offsetWidth", COLUMN_MIN_WIDTH); + + cy.get("@layoutConfigChangeStub") + .should("not.have.been.called"); + }); + +}); + diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index 0a4720c2ad2f..60b67406b27b 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -309,7 +309,7 @@ class FlexibleColumnLayout extends UI5Element { * Allows to customize the proportions of the column widts per screen size and layout. * If no custom proportion provided for a specific layout, the default will be used. * - * **Notes:** + * **Notes:** * * - The proportions should be given in percentages. For example ["30%", "40%", "30%"], ["70%", "30%", 0], etc. * - The proportions should add up to 100%. @@ -526,23 +526,26 @@ class FlexibleColumnLayout extends UI5Element { return this.getCustomColumnLayout(layout) || this.getDefaultColumnLayout(layout); } + /** + * Gets custom column layout configuration if available and valid. + * Ensures all visible columns meet minimum width requirements. + * @param layout The FCL layout to get configuration for + * @returns Normalized column layout or undefined if invalid/unavailable + */ getCustomColumnLayout(layout: `${FCLLayout}`) { - if (this.mediaAllowsCustomConfiguration(this.media)) { - const customLayout = this.layoutsConfiguration[this.media]?.[layout]?.layout; - if (customLayout) { - const normalizedWidths = this.normalizeColumnWidths(customLayout); // satisfy min-width constraint - if (this.isValidColumnLayout(normalizedWidths)) { // satisfy layout-specific contraints - return normalizedWidths; - } - } + // Only allow custom configuration for tablet and desktop (not phone) + if (!this.mediaAllowsCustomConfiguration(this.media)) { + return undefined; } - } - setCustomColumnLayout(layout: `${FCLLayout}`, columnLayout: string[]) { - if (this.mediaAllowsCustomConfiguration(this.media)) { - this.layoutsConfiguration[this.media] ??= {}; - this.layoutsConfiguration[this.media]![layout] ??= { layout: columnLayout }; - this.layoutsConfiguration[this.media]![layout]!.layout = columnLayout; + const customLayout = this.layoutsConfiguration[this.media]?.[layout]?.layout; + if (!customLayout) { + return undefined; + } + + const constraintCompliantLayout = this.applyMinimumWidthConstraints(customLayout); + if (this.isValidColumnLayout(constraintCompliantLayout)) { // satisfy layout-specific contraints + return constraintCompliantLayout; } } @@ -554,6 +557,14 @@ class FlexibleColumnLayout extends UI5Element { return media !== MEDIA.PHONE; } + updateLayoutsConfiguration(layout: `${FCLLayout}`, columnLayout: string[]) { + if (this.mediaAllowsCustomConfiguration(this.media)) { + this.layoutsConfiguration[this.media] ??= {}; + this.layoutsConfiguration[this.media]![layout] ??= { layout: columnLayout }; + this.layoutsConfiguration[this.media]![layout]!.layout = columnLayout; + } + } + calcVisibleColumns(colLayout: FlexibleColumnLayoutColumnLayout) { return colLayout.filter(colWidth => !this._isColumnHidden(colWidth)).length; } @@ -673,7 +684,7 @@ class FlexibleColumnLayout extends UI5Element { this.fireLayoutChange(true, false); } if (oldColumnLayout?.join() !== newColumnLayout.join()) { // compare arrays' content - this.setCustomColumnLayout(newLayout, newColumnLayout); + this.updateLayoutsConfiguration(newLayout, newColumnLayout); this.fireLayoutConfigurationChange(); } } @@ -767,13 +778,13 @@ class FlexibleColumnLayout extends UI5Element { _onArrowKeydown(e: KeyboardEvent) { if (isEnter(e) || isSpace(e)) { - e.preventDefault(); - const focusedElement = e.target as HTMLElement; - if (focusedElement === this.startArrowDOM) { + e.preventDefault(); + const focusedElement = e.target as HTMLElement; + if (focusedElement === this.startArrowDOM) { this.switchLayoutOnArrowPress(); - } + } } - } + } async _onSeparatorKeydown(e: KeyboardEvent) { const separator = e.target as HTMLElement; @@ -904,89 +915,126 @@ class FlexibleColumnLayout extends UI5Element { return this.verifyColumnWidthsMatchLayout(pxWidths); } - normalizeColumnWidths(columnLayout: (string | 0)[]) { - // convert to pixel numbers - const pxWidths = columnLayout.map(w => this.convertColumnWidthToPixels(w)); + /** + * Applies minimum width constraints to column layout configuration. + * Ensures all visible columns meet the minimum width requirement by transferring + * space from the wider columns to undersized columns. + * @param columnLayout Original column layout (percentages or pixels) + * @returns Constraint-compliant column layout in same format as input + */ + applyMinimumWidthConstraints(columnLayout: (string | 0)[]) { + return this.withPixelConversion(columnLayout, (pxWidths) => { + return this.adjustColumnsToMinimumWidth(pxWidths); + }); + } - this.adjustWidthsToMinWidth(pxWidths); + /** + * Adjusts column widths to ensure minimum width constraints. + * Takes width from the widest column to bring undersized columns up to minimum. + * @param pxWidths Array of column widths in pixels (modified in place) + */ + adjustColumnsToMinimumWidth(pxWidths: number[]) { + const adjustedWidths = [...pxWidths]; - // back to percent widths - return pxWidths.map(w => this.convertToRelativeColumnWidth(w)); - } + let totalDeficit = 0; + for (let i = 0; i < adjustedWidths.length; i++) { + const width = adjustedWidths[i]; + const isBelowMinimum = Math.ceil(width) < COLUMN_MIN_WIDTH; // ceil to avoid floating point precision issues - adjustWidthsToMinWidth(pxWidths: number[]) { - const indicesOfColumnsToAdjust = this.getIndicesOfColumnsBelowMinWidth(pxWidths); + if (!this._isColumnHidden(width) && isBelowMinimum) { + totalDeficit += COLUMN_MIN_WIDTH - width; + adjustedWidths[i] = COLUMN_MIN_WIDTH; + } + } + + if (totalDeficit === 0) { + return adjustedWidths; // no adjustments were needed + } - if (!indicesOfColumnsToAdjust.length) { - return; + // Create proportions for redistribution of the deficit based on available space above COLUMN_MIN_WIDTH + const columnProportions = this.getColumnProportionsAboveMinWidth(pxWidths); + + // Redistribute the deficit proportionally among columns that can contribute + for (let i = 0; i < adjustedWidths.length; i++) { + const isVisible = adjustedWidths[i] > 0; + if (isVisible && columnProportions[i] > 0) { + adjustedWidths[i] -= totalDeficit * columnProportions[i]; + } } + + return adjustedWidths; + } - const indexOfWidestColumn = this.getIndexOfWidestColumn(pxWidths); - let deficit = 0; + + getColumnProportionsAboveMinWidth(columnPxWidths: number[]) { + const redistributableWidths = columnPxWidths.map(width => + // Only widths that are above minimum AND were originally non-zero can contribute + (width > COLUMN_MIN_WIDTH && width > 0) ? width - COLUMN_MIN_WIDTH : 0 + ); - indicesOfColumnsToAdjust.forEach(indexOfColumnBelowMinWith => { - deficit = COLUMN_MIN_WIDTH - pxWidths[indexOfColumnBelowMinWith]; + const sumOfRedistributableWidths = redistributableWidths.reduce((sum, width) => sum + width, 0); - // enlarge the column by transfering from the widest column - pxWidths[indexOfColumnBelowMinWith] += deficit; - pxWidths[indexOfWidestColumn] -= deficit; - }); + if (sumOfRedistributableWidths === 0) { + return redistributableWidths; + } + + return redistributableWidths.map(width => width / sumOfRedistributableWidths); } - getIndicesOfColumnsBelowMinWidth(columnLayout: number[]) { - const indices: number[] = []; - columnLayout.forEach((pxWidth, i) => { - // ceil before comparing to avoid floating point precision issues - if ((pxWidth > 0) && (Math.ceil(pxWidth) < COLUMN_MIN_WIDTH)) { - indices.push(i); - } - }); - return indices; - } + /** + * Helper that handles pixel conversion for column width operations. + * Converts input to pixels, applies the operation, then converts back to relative widths. + * @param columnLayout Column layout in mixed formats + * @param operation Function that operates on pixel widths + * @returns Column layout in percentage format + */ + private withPixelConversion( + columnLayout: (string | 0)[], + operation: (pxWidths: number[]) => number[] + ) { + // Convert to pixels for calculations + const pxWidths = columnLayout.map(width => this.convertColumnWidthToPixels(width)); - getIndexOfWidestColumn(columnLayout: number[]) { - let maxIndex = 0; - columnLayout.forEach((pxWidth, i) => { - if (pxWidth > columnLayout[maxIndex]) { - maxIndex = i; - } - }); - return maxIndex; + // Apply the operation + const adjustedPxWidths = operation(pxWidths); + + // Convert back to percentage-based widths + return adjustedPxWidths.map(width => this.convertToRelativeColumnWidth(width)); } verifyColumnWidthsMatchLayout(pxWidths: number[]) { const columnWidths = { - start: pxWidths[0], - mid: pxWidths[1], - end: pxWidths[2], - }, + start: pxWidths[0], + mid: pxWidths[1], + end: pxWidths[2], + }, startWidth = columnWidths.start, startPercentWidth = parseInt(this.convertToRelativeColumnWidth(startWidth)); switch (this.layout) { - case FCLLayout.TwoColumnsStartExpanded: { - return columnWidths.start >= columnWidths.mid; - } - case FCLLayout.TwoColumnsMidExpanded: { - return columnWidths.mid > columnWidths.start; - } - case FCLLayout.ThreeColumnsEndExpanded: { - return (columnWidths.end > columnWidths.mid) && (startPercentWidth < 33); - } - case FCLLayout.ThreeColumnsStartExpandedEndHidden: { - return (columnWidths.start >= columnWidths.mid) && columnWidths.end === 0; - } - case FCLLayout.ThreeColumnsMidExpanded: { - return (columnWidths.mid >= columnWidths.end) - && ((this.media === MEDIA.DESKTOP && startPercentWidth < 33) // desktop - || (this.media === MEDIA.TABLET && startPercentWidth === 0)); // tablet - } - case FCLLayout.ThreeColumnsMidExpandedEndHidden: { - return (columnWidths.mid > columnWidths.start) - && columnWidths.end === 0 - && ((this.media === MEDIA.DESKTOP && startPercentWidth >= 33) - || (this.media === MEDIA.TABLET && startWidth >= COLUMN_MIN_WIDTH)); - } + case FCLLayout.TwoColumnsStartExpanded: { + return columnWidths.start >= columnWidths.mid; + } + case FCLLayout.TwoColumnsMidExpanded: { + return columnWidths.mid > columnWidths.start; + } + case FCLLayout.ThreeColumnsEndExpanded: { + return (columnWidths.end > columnWidths.mid) && (startPercentWidth < 33); + } + case FCLLayout.ThreeColumnsStartExpandedEndHidden: { + return (columnWidths.start >= columnWidths.mid) && columnWidths.end === 0; + } + case FCLLayout.ThreeColumnsMidExpanded: { + return (columnWidths.mid >= columnWidths.end) + && ((this.media === MEDIA.DESKTOP && startPercentWidth < 33) // desktop + || (this.media === MEDIA.TABLET && startPercentWidth === 0)); // tablet + } + case FCLLayout.ThreeColumnsMidExpandedEndHidden: { + return (columnWidths.mid > columnWidths.start) + && columnWidths.end === 0 + && ((this.media === MEDIA.DESKTOP && startPercentWidth >= 33) + || (this.media === MEDIA.TABLET && startWidth >= COLUMN_MIN_WIDTH)); + } } return false; @@ -995,10 +1043,10 @@ class FlexibleColumnLayout extends UI5Element { getNextLayoutOnSeparatorMovement(separator: HTMLElement, isStartToEndDirection: boolean, fclLayoutBeforeMove: FCLLayout, columnLayoutAfterMove: FlexibleColumnLayoutColumnLayout) { const isStartSeparator = separator === this.startSeparatorDOM, separatorName = isStartSeparator ? "start" : "end", - moved = (options: {separator: "start" | "end", from: FCLLayout, forward: boolean}) => { + moved = (options: { separator: "start" | "end", from: FCLLayout, forward: boolean }) => { return options.from === fclLayoutBeforeMove - && options.separator === separatorName - && options.forward === isStartToEndDirection; + && options.separator === separatorName + && options.forward === isStartToEndDirection; }, newColumnLayout = columnLayoutAfterMove.map(x => this.convertColumnWidthToPixels(x)), newColumnWidths = { From 06f648941adc4e9065d40888df1eb678cd6d0869 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Tue, 2 Sep 2025 10:10:18 +0300 Subject: [PATCH 12/27] feat(ui5-flexible-column-layout): fix eslint errors --- packages/fiori/src/FlexibleColumnLayout.ts | 71 +++++++++++----------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index 60b67406b27b..ca1c9d3cba57 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -923,7 +923,7 @@ class FlexibleColumnLayout extends UI5Element { * @returns Constraint-compliant column layout in same format as input */ applyMinimumWidthConstraints(columnLayout: (string | 0)[]) { - return this.withPixelConversion(columnLayout, (pxWidths) => { + return this.withPixelConversion(columnLayout, pxWidths => { return this.adjustColumnsToMinimumWidth(pxWidths); }); } @@ -946,7 +946,7 @@ class FlexibleColumnLayout extends UI5Element { adjustedWidths[i] = COLUMN_MIN_WIDTH; } } - + if (totalDeficit === 0) { return adjustedWidths; // no adjustments were needed } @@ -961,11 +961,10 @@ class FlexibleColumnLayout extends UI5Element { adjustedWidths[i] -= totalDeficit * columnProportions[i]; } } - + return adjustedWidths; } - getColumnProportionsAboveMinWidth(columnPxWidths: number[]) { const redistributableWidths = columnPxWidths.map(width => // Only widths that are above minimum AND were originally non-zero can contribute @@ -977,7 +976,7 @@ class FlexibleColumnLayout extends UI5Element { if (sumOfRedistributableWidths === 0) { return redistributableWidths; } - + return redistributableWidths.map(width => width / sumOfRedistributableWidths); } @@ -990,7 +989,7 @@ class FlexibleColumnLayout extends UI5Element { */ private withPixelConversion( columnLayout: (string | 0)[], - operation: (pxWidths: number[]) => number[] + operation: (pxWidths: number[]) => number[], ) { // Convert to pixels for calculations const pxWidths = columnLayout.map(width => this.convertColumnWidthToPixels(width)); @@ -1004,37 +1003,37 @@ class FlexibleColumnLayout extends UI5Element { verifyColumnWidthsMatchLayout(pxWidths: number[]) { const columnWidths = { - start: pxWidths[0], - mid: pxWidths[1], - end: pxWidths[2], - }, + start: pxWidths[0], + mid: pxWidths[1], + end: pxWidths[2], + }, startWidth = columnWidths.start, startPercentWidth = parseInt(this.convertToRelativeColumnWidth(startWidth)); switch (this.layout) { - case FCLLayout.TwoColumnsStartExpanded: { - return columnWidths.start >= columnWidths.mid; - } - case FCLLayout.TwoColumnsMidExpanded: { - return columnWidths.mid > columnWidths.start; - } - case FCLLayout.ThreeColumnsEndExpanded: { - return (columnWidths.end > columnWidths.mid) && (startPercentWidth < 33); - } - case FCLLayout.ThreeColumnsStartExpandedEndHidden: { - return (columnWidths.start >= columnWidths.mid) && columnWidths.end === 0; - } - case FCLLayout.ThreeColumnsMidExpanded: { - return (columnWidths.mid >= columnWidths.end) - && ((this.media === MEDIA.DESKTOP && startPercentWidth < 33) // desktop - || (this.media === MEDIA.TABLET && startPercentWidth === 0)); // tablet - } - case FCLLayout.ThreeColumnsMidExpandedEndHidden: { - return (columnWidths.mid > columnWidths.start) - && columnWidths.end === 0 - && ((this.media === MEDIA.DESKTOP && startPercentWidth >= 33) - || (this.media === MEDIA.TABLET && startWidth >= COLUMN_MIN_WIDTH)); - } + case FCLLayout.TwoColumnsStartExpanded: { + return columnWidths.start >= columnWidths.mid; + } + case FCLLayout.TwoColumnsMidExpanded: { + return columnWidths.mid > columnWidths.start; + } + case FCLLayout.ThreeColumnsEndExpanded: { + return (columnWidths.end > columnWidths.mid) && (startPercentWidth < 33); + } + case FCLLayout.ThreeColumnsStartExpandedEndHidden: { + return (columnWidths.start >= columnWidths.mid) && columnWidths.end === 0; + } + case FCLLayout.ThreeColumnsMidExpanded: { + return (columnWidths.mid >= columnWidths.end) + && ((this.media === MEDIA.DESKTOP && startPercentWidth < 33) // desktop + || (this.media === MEDIA.TABLET && startPercentWidth === 0)); // tablet + } + case FCLLayout.ThreeColumnsMidExpandedEndHidden: { + return (columnWidths.mid > columnWidths.start) + && columnWidths.end === 0 + && ((this.media === MEDIA.DESKTOP && startPercentWidth >= 33) + || (this.media === MEDIA.TABLET && startWidth >= COLUMN_MIN_WIDTH)); + } } return false; @@ -1043,10 +1042,10 @@ class FlexibleColumnLayout extends UI5Element { getNextLayoutOnSeparatorMovement(separator: HTMLElement, isStartToEndDirection: boolean, fclLayoutBeforeMove: FCLLayout, columnLayoutAfterMove: FlexibleColumnLayoutColumnLayout) { const isStartSeparator = separator === this.startSeparatorDOM, separatorName = isStartSeparator ? "start" : "end", - moved = (options: { separator: "start" | "end", from: FCLLayout, forward: boolean }) => { + moved = (options: {separator: "start" | "end", from: FCLLayout, forward: boolean}) => { return options.from === fclLayoutBeforeMove - && options.separator === separatorName - && options.forward === isStartToEndDirection; + && options.separator === separatorName + && options.forward === isStartToEndDirection; }, newColumnLayout = columnLayoutAfterMove.map(x => this.convertColumnWidthToPixels(x)), newColumnWidths = { From 1b11805f7e7368debc36da004610b59db80233e7 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Tue, 2 Sep 2025 10:45:38 +0300 Subject: [PATCH 13/27] feat(ui5-flexible-column-layout): fix eslint errors --- packages/fiori/src/FlexibleColumnLayout.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index ca1c9d3cba57..05d9811046f9 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -966,18 +966,20 @@ class FlexibleColumnLayout extends UI5Element { } getColumnProportionsAboveMinWidth(columnPxWidths: number[]) { - const redistributableWidths = columnPxWidths.map(width => - // Only widths that are above minimum AND were originally non-zero can contribute - (width > COLUMN_MIN_WIDTH && width > 0) ? width - COLUMN_MIN_WIDTH : 0 - ); + const widthsAboveMinWidth = columnPxWidths.map(width => { + if (width > COLUMN_MIN_WIDTH) { + return width - COLUMN_MIN_WIDTH; + } + return 0; + }); - const sumOfRedistributableWidths = redistributableWidths.reduce((sum, width) => sum + width, 0); + const total = widthsAboveMinWidth.reduce((sum, width) => sum + width, 0); - if (sumOfRedistributableWidths === 0) { - return redistributableWidths; + if (total === 0) { + return widthsAboveMinWidth; } - return redistributableWidths.map(width => width / sumOfRedistributableWidths); + return widthsAboveMinWidth.map(width => width / total); } /** From 6e6325a189394250962981d6b2da3b61239a8cae Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Tue, 2 Sep 2025 11:25:53 +0300 Subject: [PATCH 14/27] feat(ui5-flexible-column-layout): minor corrections --- packages/fiori/src/FlexibleColumnLayout.ts | 187 +++++++++++---------- 1 file changed, 94 insertions(+), 93 deletions(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index 05d9811046f9..fa748a7f0c62 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -543,6 +543,7 @@ class FlexibleColumnLayout extends UI5Element { return undefined; } + // apply minimum column-width constraints relative to the current fcl total width const constraintCompliantLayout = this.applyMinimumWidthConstraints(customLayout); if (this.isValidColumnLayout(constraintCompliantLayout)) { // satisfy layout-specific contraints return constraintCompliantLayout; @@ -557,12 +558,92 @@ class FlexibleColumnLayout extends UI5Element { return media !== MEDIA.PHONE; } - updateLayoutsConfiguration(layout: `${FCLLayout}`, columnLayout: string[]) { - if (this.mediaAllowsCustomConfiguration(this.media)) { - this.layoutsConfiguration[this.media] ??= {}; - this.layoutsConfiguration[this.media]![layout] ??= { layout: columnLayout }; - this.layoutsConfiguration[this.media]![layout]!.layout = columnLayout; + /** + * Applies minimum width constraints to column layout configuration. + * Ensures all visible columns meet the minimum width requirement by transferring + * space from the wider columns to the undersized columns. + * @param columnLayout Original column layout (percentages or pixels) + * @returns Constraint-compliant column layout in same format as input + */ + applyMinimumWidthConstraints(columnLayout: (string | 0)[]) { + return this.doWithPixelConversion(columnLayout, pxWidths => { + return this.adjustColumnsToMinimumWidth(pxWidths); + }); + } + + /** + * Adjusts column widths to ensure minimum width constraints. + * Takes width from the widest column to bring undersized columns up to minimum. + * @param pxWidths Array of column widths in pixels (modified in place) + */ + adjustColumnsToMinimumWidth(pxWidths: number[]) { + const adjustedWidths = [...pxWidths]; + + let totalDeficit = 0; + for (let i = 0; i < adjustedWidths.length; i++) { + const width = adjustedWidths[i]; + const isBelowMinimum = Math.ceil(width) < COLUMN_MIN_WIDTH; // ceil to avoid floating point precision issues + + if (!this._isColumnHidden(width) && isBelowMinimum) { + totalDeficit += COLUMN_MIN_WIDTH - width; + adjustedWidths[i] = COLUMN_MIN_WIDTH; + } + } + + if (totalDeficit === 0) { + return adjustedWidths; // no adjustments were needed + } + + // Create proportions for redistribution of the deficit based on available space above COLUMN_MIN_WIDTH + const columnProportions = this.getColumnProportionsAboveMinWidth(pxWidths); + + // Redistribute the deficit proportionally among columns that can contribute + for (let i = 0; i < adjustedWidths.length; i++) { + const isVisible = adjustedWidths[i] > 0; + if (isVisible && columnProportions[i] > 0) { + adjustedWidths[i] -= totalDeficit * columnProportions[i]; + } } + + return adjustedWidths; + } + + getColumnProportionsAboveMinWidth(columnPxWidths: number[]) { + const widthsAboveMinWidth = columnPxWidths.map(width => { + if (width > COLUMN_MIN_WIDTH) { + return width - COLUMN_MIN_WIDTH; + } + return 0; + }); + + const total = widthsAboveMinWidth.reduce((sum, width) => sum + width, 0); + + if (total === 0) { + return widthsAboveMinWidth; + } + + return widthsAboveMinWidth.map(width => width / total); + } + + /** + * Helper that handles pixel conversion for column width operations. + * Converts input to pixels, applies the operation, then converts back to relative widths. + * @param columnLayout Column layout in mixed formats + * @param operation Function that operates on pixel widths + * @returns Column layout in percentage format + */ + private doWithPixelConversion( + columnLayout: (string | 0)[], + operation: (pxWidths: number[]) => number[], + ) { + // Convert to pixels for calculations + const pxWidths = columnLayout.map(width => this.convertColumnWidthToPixels(width)); + + // Apply the operation + const adjustedPxWidths = operation(pxWidths); + + // Convert back to percentage-based widths + return adjustedPxWidths.map(width => this.convertToRelativeColumnWidth(width)); } calcVisibleColumns(colLayout: FlexibleColumnLayoutColumnLayout) { @@ -689,6 +770,14 @@ class FlexibleColumnLayout extends UI5Element { } } + updateLayoutsConfiguration(layout: `${FCLLayout}`, columnLayout: string[]) { + if (this.mediaAllowsCustomConfiguration(this.media)) { + this.layoutsConfiguration[this.media] ??= {}; + this.layoutsConfiguration[this.media]![layout] ??= { layout: columnLayout }; + this.layoutsConfiguration[this.media]![layout]!.layout = columnLayout; + } + } + private isSeparatorAheadOfCursor(cursorX: number, separatorX: number, isForwardMove: boolean) { if (isForwardMove) { return separatorX > cursorX; @@ -915,94 +1004,6 @@ class FlexibleColumnLayout extends UI5Element { return this.verifyColumnWidthsMatchLayout(pxWidths); } - /** - * Applies minimum width constraints to column layout configuration. - * Ensures all visible columns meet the minimum width requirement by transferring - * space from the wider columns to undersized columns. - * @param columnLayout Original column layout (percentages or pixels) - * @returns Constraint-compliant column layout in same format as input - */ - applyMinimumWidthConstraints(columnLayout: (string | 0)[]) { - return this.withPixelConversion(columnLayout, pxWidths => { - return this.adjustColumnsToMinimumWidth(pxWidths); - }); - } - - /** - * Adjusts column widths to ensure minimum width constraints. - * Takes width from the widest column to bring undersized columns up to minimum. - * @param pxWidths Array of column widths in pixels (modified in place) - */ - adjustColumnsToMinimumWidth(pxWidths: number[]) { - const adjustedWidths = [...pxWidths]; - - let totalDeficit = 0; - for (let i = 0; i < adjustedWidths.length; i++) { - const width = adjustedWidths[i]; - const isBelowMinimum = Math.ceil(width) < COLUMN_MIN_WIDTH; // ceil to avoid floating point precision issues - - if (!this._isColumnHidden(width) && isBelowMinimum) { - totalDeficit += COLUMN_MIN_WIDTH - width; - adjustedWidths[i] = COLUMN_MIN_WIDTH; - } - } - - if (totalDeficit === 0) { - return adjustedWidths; // no adjustments were needed - } - - // Create proportions for redistribution of the deficit based on available space above COLUMN_MIN_WIDTH - const columnProportions = this.getColumnProportionsAboveMinWidth(pxWidths); - - // Redistribute the deficit proportionally among columns that can contribute - for (let i = 0; i < adjustedWidths.length; i++) { - const isVisible = adjustedWidths[i] > 0; - if (isVisible && columnProportions[i] > 0) { - adjustedWidths[i] -= totalDeficit * columnProportions[i]; - } - } - - return adjustedWidths; - } - - getColumnProportionsAboveMinWidth(columnPxWidths: number[]) { - const widthsAboveMinWidth = columnPxWidths.map(width => { - if (width > COLUMN_MIN_WIDTH) { - return width - COLUMN_MIN_WIDTH; - } - return 0; - }); - - const total = widthsAboveMinWidth.reduce((sum, width) => sum + width, 0); - - if (total === 0) { - return widthsAboveMinWidth; - } - - return widthsAboveMinWidth.map(width => width / total); - } - - /** - * Helper that handles pixel conversion for column width operations. - * Converts input to pixels, applies the operation, then converts back to relative widths. - * @param columnLayout Column layout in mixed formats - * @param operation Function that operates on pixel widths - * @returns Column layout in percentage format - */ - private withPixelConversion( - columnLayout: (string | 0)[], - operation: (pxWidths: number[]) => number[], - ) { - // Convert to pixels for calculations - const pxWidths = columnLayout.map(width => this.convertColumnWidthToPixels(width)); - - // Apply the operation - const adjustedPxWidths = operation(pxWidths); - - // Convert back to percentage-based widths - return adjustedPxWidths.map(width => this.convertToRelativeColumnWidth(width)); - } - verifyColumnWidthsMatchLayout(pxWidths: number[]) { const columnWidths = { start: pxWidths[0], From b8f7bf55eb37f60017746b72fb6811b066de5981 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Tue, 2 Sep 2025 13:55:30 +0300 Subject: [PATCH 15/27] feat(ui5-flexible-column-layout): improve sample --- packages/fiori/src/FlexibleColumnLayout.ts | 104 +++++++------- .../LayoutsConfiguration/main.css | 19 +++ .../LayoutsConfiguration/main.js | 27 +++- .../LayoutsConfiguration/sample.html | 132 +++++++++--------- packages/website/docusaurus.config.ts | 8 +- 5 files changed, 167 insertions(+), 123 deletions(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index fa748a7f0c62..4f4545eb3f6e 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -543,7 +543,7 @@ class FlexibleColumnLayout extends UI5Element { return undefined; } - // apply minimum column-width constraints relative to the current fcl total width + // ensure visible columns are above min-width given the current fcl total width const constraintCompliantLayout = this.applyMinimumWidthConstraints(customLayout); if (this.isValidColumnLayout(constraintCompliantLayout)) { // satisfy layout-specific contraints return constraintCompliantLayout; @@ -573,7 +573,7 @@ class FlexibleColumnLayout extends UI5Element { /** * Adjusts column widths to ensure minimum width constraints. - * Takes width from the widest column to bring undersized columns up to minimum. + * Takes width from the widest columns to bring undersized columns up to minimum. * @param pxWidths Array of column widths in pixels (modified in place) */ adjustColumnsToMinimumWidth(pxWidths: number[]) { @@ -632,7 +632,7 @@ class FlexibleColumnLayout extends UI5Element { * @param operation Function that operates on pixel widths * @returns Column layout in percentage format */ - private doWithPixelConversion( + doWithPixelConversion( columnLayout: (string | 0)[], operation: (pxWidths: number[]) => number[], ) { @@ -646,6 +646,55 @@ class FlexibleColumnLayout extends UI5Element { return adjustedPxWidths.map(width => this.convertToRelativeColumnWidth(width)); } + isValidColumnLayout(columnLayout: (string | 0)[]) { + const pxWidths = columnLayout?.map(w => this.convertColumnWidthToPixels(w)); + const totalWidth = pxWidths.reduce((i, sum) => i + sum); + + if (Math.round(totalWidth) !== Math.round(this._availableWidthForColumns)) { + return false; + } + + return this.verifyColumnWidthsMatchLayout(pxWidths); + } + + verifyColumnWidthsMatchLayout(pxWidths: number[]) { + const columnWidths = { + start: pxWidths[0], + mid: pxWidths[1], + end: pxWidths[2], + }, + startWidth = columnWidths.start, + startPercentWidth = parseInt(this.convertToRelativeColumnWidth(startWidth)); + + switch (this.layout) { + case FCLLayout.TwoColumnsStartExpanded: { + return columnWidths.start >= columnWidths.mid; + } + case FCLLayout.TwoColumnsMidExpanded: { + return columnWidths.mid > columnWidths.start; + } + case FCLLayout.ThreeColumnsEndExpanded: { + return (columnWidths.end > columnWidths.mid) && (startPercentWidth < 33); + } + case FCLLayout.ThreeColumnsStartExpandedEndHidden: { + return (columnWidths.start >= columnWidths.mid) && columnWidths.end === 0; + } + case FCLLayout.ThreeColumnsMidExpanded: { + return (columnWidths.mid >= columnWidths.end) + && ((this.media === MEDIA.DESKTOP && startPercentWidth < 33) // desktop + || (this.media === MEDIA.TABLET && startPercentWidth === 0)); // tablet + } + case FCLLayout.ThreeColumnsMidExpandedEndHidden: { + return (columnWidths.mid > columnWidths.start) + && columnWidths.end === 0 + && ((this.media === MEDIA.DESKTOP && startPercentWidth >= 33) + || (this.media === MEDIA.TABLET && startWidth >= COLUMN_MIN_WIDTH)); + } + } + + return false; + } + calcVisibleColumns(colLayout: FlexibleColumnLayoutColumnLayout) { return colLayout.filter(colWidth => !this._isColumnHidden(colWidth)).length; } @@ -993,55 +1042,6 @@ class FlexibleColumnLayout extends UI5Element { return `${(pxWidth / this._availableWidthForColumns) * 100}%`; } - isValidColumnLayout(columnLayout: (string | 0)[]) { - const pxWidths = columnLayout?.map(w => this.convertColumnWidthToPixels(w)); - const totalWidth = pxWidths.reduce((i, sum) => i + sum); - - if (Math.round(totalWidth) !== Math.round(this._availableWidthForColumns)) { - return false; - } - - return this.verifyColumnWidthsMatchLayout(pxWidths); - } - - verifyColumnWidthsMatchLayout(pxWidths: number[]) { - const columnWidths = { - start: pxWidths[0], - mid: pxWidths[1], - end: pxWidths[2], - }, - startWidth = columnWidths.start, - startPercentWidth = parseInt(this.convertToRelativeColumnWidth(startWidth)); - - switch (this.layout) { - case FCLLayout.TwoColumnsStartExpanded: { - return columnWidths.start >= columnWidths.mid; - } - case FCLLayout.TwoColumnsMidExpanded: { - return columnWidths.mid > columnWidths.start; - } - case FCLLayout.ThreeColumnsEndExpanded: { - return (columnWidths.end > columnWidths.mid) && (startPercentWidth < 33); - } - case FCLLayout.ThreeColumnsStartExpandedEndHidden: { - return (columnWidths.start >= columnWidths.mid) && columnWidths.end === 0; - } - case FCLLayout.ThreeColumnsMidExpanded: { - return (columnWidths.mid >= columnWidths.end) - && ((this.media === MEDIA.DESKTOP && startPercentWidth < 33) // desktop - || (this.media === MEDIA.TABLET && startPercentWidth === 0)); // tablet - } - case FCLLayout.ThreeColumnsMidExpandedEndHidden: { - return (columnWidths.mid > columnWidths.start) - && columnWidths.end === 0 - && ((this.media === MEDIA.DESKTOP && startPercentWidth >= 33) - || (this.media === MEDIA.TABLET && startWidth >= COLUMN_MIN_WIDTH)); - } - } - - return false; - } - getNextLayoutOnSeparatorMovement(separator: HTMLElement, isStartToEndDirection: boolean, fclLayoutBeforeMove: FCLLayout, columnLayoutAfterMove: FlexibleColumnLayoutColumnLayout) { const isStartSeparator = separator === this.startSeparatorDOM, separatorName = isStartSeparator ? "start" : "end", diff --git a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.css b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.css index 751822b57fea..9afc1388f843 100644 --- a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.css +++ b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.css @@ -1,3 +1,18 @@ +.layout-grid { + display: grid; + grid-template-columns: auto 1fr; + grid-template-rows: auto auto; + gap: 0.5rem 1rem; + align-items: center; + margin-bottom: 1rem; +} + +.layout-grid ui5-label:first-child, +.layout-grid ui5-label:nth-child(3) { + text-align: right; + justify-self: end; +} + .fcl { height: 600px; } @@ -9,3 +24,7 @@ .colHeader { padding: 1rem; } + +.configurationInfo { + color: var(--sapPositiveColor); +} diff --git a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js index b3d880d178e5..cfd96fde5ce4 100644 --- a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js +++ b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js @@ -4,6 +4,7 @@ import "@ui5/webcomponents/dist/Label.js"; import "@ui5/webcomponents/dist/List.js"; import "@ui5/webcomponents/dist/ListItemStandard.js"; import "@ui5/webcomponents/dist/Title.js"; +import "@ui5/webcomponents/dist/Text.js"; import "@ui5/webcomponents-fiori/dist/FlexibleColumnLayout.js"; fcl.layoutsConfiguration = { @@ -49,11 +50,31 @@ fcl.layoutsConfiguration = { }, }; +function displayCustomLayoutConfigurationInfo() { + const configurationPerMedia = fcl.layoutsConfiguration[fcl.media]; + const layoutConfiguration = configurationPerMedia ? configurationPerMedia[fcl.layout] : undefined; + if (layoutConfiguration) { + configurationInfo.innerText = `[${layoutConfiguration.layout.join(", ")}]`; + } else { + configurationInfo.innerText = `none`; + } +} + selectLayout.addEventListener("ui5-change", (e) => { fcl.layout = e.detail.selectedOption.textContent; + displayCustomLayoutConfigurationInfo(); }); fcl.addEventListener("layout-configuration-change", (e) => { - console.log("Layout configuration change:", e.detail); - console.log("Layout configuration object:", fcl.layoutsConfiguration); -}); \ No newline at end of file + displayCustomLayoutConfigurationInfo(); +}); + +fcl.addEventListener("layout-change", (e) => { + selectLayout.value = e.detail.layout; +}); + +window.addEventListener("resize", () => { + displayCustomLayoutConfigurationInfo(); +}); + +displayCustomLayoutConfigurationInfo(); \ No newline at end of file diff --git a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/sample.html b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/sample.html index 0a47c51839d5..53758d7aac22 100644 --- a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/sample.html +++ b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/sample.html @@ -12,79 +12,83 @@ - Select layout - - TwoColumnsStartExpanded - TwoColumnsMidExpanded - ThreeColumnsMidExpanded - ThreeColumnsEndExpanded - ThreeColumnsStartExpandedEndHidden - ThreeColumnsMidExpandedEndHidden - +
+ Current layout + + TwoColumnsStartExpanded + TwoColumnsMidExpanded + ThreeColumnsMidExpanded + ThreeColumnsEndExpanded + ThreeColumnsStartExpandedEndHidden + ThreeColumnsMidExpandedEndHidden + + Custom configuration for current layout + +
- Column 1 -
- - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - + Column 1 +
+ + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! +
- Column 2 -
- - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - + Column 2 +
+ + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! + Hello worild! +
- Column 3 -
- - Hello worild! - Hello worild! - Hello worild! - + Column 3 +
+ + Hello worild! + Hello worild! + Hello worild! +
diff --git a/packages/website/docusaurus.config.ts b/packages/website/docusaurus.config.ts index c477113e798a..f79883b68c15 100644 --- a/packages/website/docusaurus.config.ts +++ b/packages/website/docusaurus.config.ts @@ -25,7 +25,7 @@ const getBaseURL = () => { const BASE_URL = getBaseURL(); const getFullURL = () => { - return DEVELOPMENT_ENVIRONMENT ? `${BASE_URL}` : `https://kineticjs.github.io${BASE_URL}` + return DEVELOPMENT_ENVIRONMENT ? `${BASE_URL}` : `https://sap.github.io${BASE_URL}` } // ["v1", "nightly", "current"] @@ -41,7 +41,7 @@ const config: Config = { favicon: 'img/favicon.ico', // Set the production url of your site here - url: 'https://kineticjs.github.io', + url: 'https://sap.github.io', // Set the // pathname under which your site is served // For GitHub pages deployment, it is often '//' baseUrl: BASE_URL, @@ -168,7 +168,7 @@ const config: Config = { copyright: `© Copyright ${new Date().getFullYear()}, SAP SE and UI5 Web Components Contributors`, logo: { alt: 'SAP Logo', - src: 'https://kineticjs.github.io/ui5-webcomponents/img/footer/sap-1920-1440.svg', + src: 'https://sap.github.io/ui5-webcomponents/img/footer/sap-1920-1440.svg', width: 160, height: 51, }, @@ -204,7 +204,7 @@ const config: Config = { // // Youtube // - // + // // GitHub // // From 3a5773280220fabc1b5f85a3778fb5b2509c5777 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Tue, 2 Sep 2025 17:40:42 +0300 Subject: [PATCH 16/27] feat(ui5-flexible-column-layout): improve sample --- .../LayoutsConfiguration/main.css | 26 +++- .../LayoutsConfiguration/main.js | 147 ++++++++++++++++-- .../LayoutsConfiguration/sample.html | 88 +++++------ 3 files changed, 196 insertions(+), 65 deletions(-) diff --git a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.css b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.css index 9afc1388f843..80eb9664461d 100644 --- a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.css +++ b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.css @@ -23,8 +23,32 @@ .colHeader { padding: 1rem; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--sapList_BorderColor); +} + +.colSubHeader { + display: flex; + gap: 0.5rem; } .configurationInfo { - color: var(--sapPositiveColor); + color: var(--sapInformativeColor); +} + +.product-details { + padding: 1rem; +} + +.product-info { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.product-info ui5-label { + font-weight: bold; + margin-top: 0.5rem; } diff --git a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js index cfd96fde5ce4..c7b3b96dd070 100644 --- a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js +++ b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/main.js @@ -5,7 +5,10 @@ import "@ui5/webcomponents/dist/List.js"; import "@ui5/webcomponents/dist/ListItemStandard.js"; import "@ui5/webcomponents/dist/Title.js"; import "@ui5/webcomponents/dist/Text.js"; +import "@ui5/webcomponents/dist/Button.js"; import "@ui5/webcomponents-fiori/dist/FlexibleColumnLayout.js"; +import "@ui5/webcomponents-icons/dist/decline.js"; +import "@ui5/webcomponents-icons/dist/slim-arrow-right.js"; fcl.layoutsConfiguration = { desktop: { @@ -16,7 +19,7 @@ fcl.layoutsConfiguration = { layout: ["20%", "80%", 0], }, "ThreeColumnsMidExpanded": { - layout: ["25%", "45%", "30%"], + layout: ["20%", "50%", "30%"], }, "ThreeColumnsEndExpanded": { layout: ["15%", "15%", "70%"], @@ -50,6 +53,19 @@ fcl.layoutsConfiguration = { }, }; +fcl.addEventListener("layout-configuration-change", (e) => { + displayCustomLayoutConfigurationInfo(); +}); + +fcl.addEventListener("layout-change", (e) => { + selectLayout.value = e.detail.layout; +}); + +selectLayout.addEventListener("ui5-change", (e) => { + fcl.layout = e.detail.selectedOption.textContent; + displayCustomLayoutConfigurationInfo(); +}); + function displayCustomLayoutConfigurationInfo() { const configurationPerMedia = fcl.layoutsConfiguration[fcl.media]; const layoutConfiguration = configurationPerMedia ? configurationPerMedia[fcl.layout] : undefined; @@ -60,21 +76,130 @@ function displayCustomLayoutConfigurationInfo() { } } -selectLayout.addEventListener("ui5-change", (e) => { - fcl.layout = e.detail.selectedOption.textContent; - displayCustomLayoutConfigurationInfo(); -}); +// Sample data for navigation +const categoryData = { + electronics: [ + { id: "laptop", name: "Laptop", description: "High-performance laptop with 16GB RAM and SSD storage. Perfect for work and gaming." }, + { id: "smartphone", name: "Smartphone", description: "Latest smartphone with advanced camera system and long battery life." }, + { id: "tablet", name: "Tablet", description: "Lightweight tablet with high-resolution display, ideal for reading and media consumption." } + ], + clothing: [ + { id: "jeans", name: "Jeans", description: "Premium denim jeans with comfortable fit and durable construction." }, + { id: "shirt", name: "Shirt", description: "Cotton shirt with modern cut and breathable fabric, perfect for any occasion." }, + { id: "jacket", name: "Jacket", description: "Stylish jacket with weather-resistant materials and multiple pockets." } + ], + books: [ + { id: "novel", name: "Novel", description: "Bestselling fiction novel with compelling characters and engaging plot." }, + { id: "cookbook", name: "Cookbook", description: "Collection of delicious recipes from around the world with step-by-step instructions." }, + { id: "biography", name: "Biography", description: "Inspiring life story of a remarkable person who changed the world." } + ], + home: [ + { id: "chair", name: "Chair", description: "Ergonomic office chair with lumbar support and adjustable height." }, + { id: "lamp", name: "Lamp", description: "Modern LED lamp with adjustable brightness and energy-efficient design." }, + { id: "plant", name: "Plant", description: "Low-maintenance indoor plant that purifies air and adds natural beauty." } + ], + sports: [ + { id: "shoes", name: "Running Shoes", description: "Professional running shoes with advanced cushioning and lightweight design." }, + { id: "ball", name: "Football", description: "Official size football with durable leather construction and excellent grip." }, + { id: "racket", name: "Tennis Racket", description: "Professional tennis racket with carbon fiber frame and comfortable grip." } + ] +}; -fcl.addEventListener("layout-configuration-change", (e) => { +// Navigation functionality +const categoriesList = document.getElementById("categoriesList"); +const productsList = document.getElementById("productsList"); +const categoryTitle = document.getElementById("categoryTitle"); +const productTitle = document.getElementById("productTitle"); +const productDetails = document.getElementById("productDetails"); +const closeEndColumn = document.getElementById("closeEndColumn"); +const productDetailsTemplate = document.getElementById("productDetailsTemplate"); + +// Helper function to create product details from template +function createProductDetailsFromTemplate(product, category) { + // Clone the template content + const templateContent = productDetailsTemplate.content.cloneNode(true); + + // Populate the template with product data + templateContent.getElementById("productName").textContent = product.name; + templateContent.getElementById("productDescription").textContent = product.description; + templateContent.getElementById("productCategory").textContent = category.charAt(0).toUpperCase() + category.slice(1); + templateContent.getElementById("productId").textContent = product.id; + + return templateContent; +} + +// Helper function to clear product details to initial state +function clearProductDetails() { + productDetails.innerHTML = ""; + const textElement = document.createElement("ui5-text"); + textElement.textContent = "Select a product to view details"; + productDetails.appendChild(textElement); +} + +// Handle category selection +categoriesList.addEventListener("ui5-item-click", (e) => { + const category = e.detail.item.dataset.category; + const categoryName = e.detail.item.textContent; + + // Update middle column + categoryTitle.textContent = categoryName; + productsList.innerHTML = ""; + + // Populate products list + categoryData[category].forEach(product => { + const li = document.createElement("ui5-li"); + li.textContent = product.name; + li.dataset.productId = product.id; + li.dataset.category = category; + li.setAttribute("icon", "slim-arrow-right"); + li.setAttribute("icon-end", ""); + productsList.appendChild(li); + }); + + productsList.style.display = "block"; + + // Clear product details + clearProductDetails(); + productTitle.textContent = "Product Details"; + + // Navigate to two column layout + fcl.layout = "TwoColumnsMidExpanded"; + selectLayout.value = "TwoColumnsMidExpanded"; displayCustomLayoutConfigurationInfo(); }); -fcl.addEventListener("layout-change", (e) => { - selectLayout.value = e.detail.layout; +// Handle product selection +productsList.addEventListener("ui5-item-click", (e) => { + const productId = e.detail.item.dataset.productId; + const category = e.detail.item.dataset.category; + + // Find product data + const product = categoryData[category].find(p => p.id === productId); + + if (product) { + // Update end column + productTitle.textContent = product.name; + + // Clear existing content and add new content from template + productDetails.innerHTML = ""; + const productDetailsContent = createProductDetailsFromTemplate(product, category); + productDetails.appendChild(productDetailsContent); + + // Navigate to three column layout + fcl.layout = "ThreeColumnsMidExpanded"; + selectLayout.value = "ThreeColumnsMidExpanded"; + displayCustomLayoutConfigurationInfo(); + } }); -window.addEventListener("resize", () => { +// Handle close button in end column +closeEndColumn.addEventListener("click", () => { + // Clear product details + clearProductDetails(); + productTitle.textContent = "Product Details"; + + // Navigate back to two column layout + fcl.layout = "TwoColumnsMidExpanded"; + selectLayout.value = "TwoColumnsMidExpanded"; displayCustomLayoutConfigurationInfo(); }); - -displayCustomLayoutConfigurationInfo(); \ No newline at end of file diff --git a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/sample.html b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/sample.html index 53758d7aac22..3d5a599a2d49 100644 --- a/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/sample.html +++ b/packages/website/docs/_samples/fiori/FlexibleColumnLayout/LayoutsConfiguration/sample.html @@ -15,82 +15,64 @@
Current layout + OneColumn TwoColumnsStartExpanded - TwoColumnsMidExpanded + TwoColumnsMidExpanded ThreeColumnsMidExpanded ThreeColumnsEndExpanded ThreeColumnsStartExpandedEndHidden ThreeColumnsMidExpandedEndHidden Custom configuration for current layout - + none
- +
- Column 1 + Categories
- - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! + + Electronics + Clothing + Books + Home & Garden + Sports
- Column 2 + Select a category
- - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! - Hello worild! +
- Column 3 + Product Details +
+ +
+
+
+ Select a product to view details
- - Hello worild! - Hello worild! - Hello worild! -
+ + + + From fd5dd40b75a11d05cdeb0f28f3d23da7a4a10783 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Mon, 29 Sep 2025 11:03:08 +0300 Subject: [PATCH 17/27] feat(ui5-flexible-column-layout): preserve legacy property for backward compatibility --- packages/fiori/src/FlexibleColumnLayout.ts | 48 ++++++++++++++-------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index 4f4545eb3f6e..a701af4c016f 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -271,6 +271,25 @@ class FlexibleColumnLayout extends UI5Element { @property({ type: Object }) accessibilityAttributes: FCLAccessibilityAttributes = {}; + /** + * Allows to customize the proportions of the column widts per screen size and layout. + * If no custom proportion provided for a specific layout, the default will be used. + * + * **Notes:** + * + * - The proportions should be given in percentages. For example ["30%", "40%", "30%"], ["70%", "30%", 0], etc. + * - The proportions should add up to 100%. + * - Hidden columns are marked as "0px", e.g. ["0px", "70%", "30%"]. Specifying 0 or "0%" for hidden columns is also valid. + * - If the proportions do not match the layout (e.g. if provided proportions ["70%", "30%", "0px"] for "OneColumn" layout), then the default proportions will be used instead. + * - Whenever the user drags the columns separator to resize the columns, the `layoutsConfiguration` object will be updated with the user-specified proportions for the given layout (and the `layout-configuration-change` event will be fired). + * - No custom configuration available for the phone screen size, as the default of 100% column width is always used there. + * @public + * @since 2.15.1 + * @default {} + */ + @property({ type: Object }) + layoutsConfiguration: LayoutConfiguration = {}; + /** * Defines the component width in px. * @default 0 @@ -306,23 +325,20 @@ class FlexibleColumnLayout extends UI5Element { _resizing = false; /** - * Allows to customize the proportions of the column widts per screen size and layout. - * If no custom proportion provided for a specific layout, the default will be used. - * - * **Notes:** - * - * - The proportions should be given in percentages. For example ["30%", "40%", "30%"], ["70%", "30%", 0], etc. - * - The proportions should add up to 100%. - * - Hidden columns are marked as "0px", e.g. ["0px", "70%", "30%"]. Specifying 0 or "0%" for hidden columns is also valid. - * - If the proportions do not match the layout (e.g. if provided proportions ["70%", "30%", "0px"] for "OneColumn" layout), then the default proportions will be used instead. - * - Whenever the user drags the columns separator to resize the columns, the `layoutsConfiguration` object will be updated with the user-specified proportions for the given layout (and the `layout-configuration-change` event will be fired). - * - No custom configuration available for the phone screen size, as the default of 100% column width is always used there. - * @public - * @since 2.0.0 - * @default {} - */ + * This property is no longer used and is replaced by `layoutsConfiguration`. + * The property will be removed once all adopters migrate to `layoutsConfiguration`. + */ @property({ type: Object }) - layoutsConfiguration: LayoutConfiguration = {}; + _layoutsConfiguration?: { + [device in MEDIA]: { + [layoutName in FCLLayout]: { + layout: Array; + separators: Array<{ + visible: boolean; + }>; + }; + }; +}; /** * Defines the content in the start column. From 933ca09d0c41aa26d1d942202b52519470ee9e36 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Mon, 29 Sep 2025 11:16:03 +0300 Subject: [PATCH 18/27] feat(ui5-flexible-column-layout): correct indentation --- packages/fiori/src/FlexibleColumnLayout.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index a701af4c016f..f70c0b534693 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -330,15 +330,15 @@ class FlexibleColumnLayout extends UI5Element { */ @property({ type: Object }) _layoutsConfiguration?: { - [device in MEDIA]: { - [layoutName in FCLLayout]: { - layout: Array; - separators: Array<{ - visible: boolean; - }>; + [device in MEDIA]: { + [layoutName in FCLLayout]: { + layout: Array; + separators: Array<{ + visible: boolean; + }>; + }; }; }; -}; /** * Defines the content in the start column. From d7c4ad7e5d329af75db0a73edff3a0f70596321a Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Mon, 29 Sep 2025 11:27:38 +0300 Subject: [PATCH 19/27] feat(ui5-flexible-column-layout): docu improvement --- packages/fiori/src/FlexibleColumnLayout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index f70c0b534693..7ddc6ea93a2c 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -272,7 +272,7 @@ class FlexibleColumnLayout extends UI5Element { accessibilityAttributes: FCLAccessibilityAttributes = {}; /** - * Allows to customize the proportions of the column widts per screen size and layout. + * Allows to customize the column proportions per screen size and layout. * If no custom proportion provided for a specific layout, the default will be used. * * **Notes:** From d1449bffd4404c57bc2f2358c9d13b0c59675a69 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Mon, 29 Sep 2025 12:20:33 +0300 Subject: [PATCH 20/27] feat(ui5-flexible-column-layout): docu improvement --- packages/fiori/src/FlexibleColumnLayout.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index 7ddc6ea93a2c..336b3c073244 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -212,6 +212,7 @@ type FCLAccessibilityAttributes = { * @param {MEDIA} media The current media type * @param {array} columnLayout The effective column layout, f.e ["67%", "33%", "0px"] * @public + * @since 2.15.1 */ @event("layout-configuration-change", { bubbles: true, From a786139d269bbfc055a039953ab8d6d6a18c5ffc Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Mon, 29 Sep 2025 12:23:06 +0300 Subject: [PATCH 21/27] feat(ui5-flexible-column-layout): formatting improvement --- packages/fiori/src/FlexibleColumnLayout.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index 336b3c073244..4cccd3208322 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -108,7 +108,6 @@ type FlexibleColumnLayoutLayoutConfigurationChangeEventDetail = { }; type FCLAccessibilityRoles = Extract - type FCLAccessibilityAttributes = { startColumn?: { role: FCLAccessibilityRoles, From 63a84044fc3ce497379232ddee175ee4f2a15ded Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Mon, 29 Sep 2025 12:26:12 +0300 Subject: [PATCH 22/27] feat(ui5-flexible-column-layout): formatting improvement --- packages/fiori/src/FlexibleColumnLayout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index 4cccd3208322..7c3df1e56f15 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -327,7 +327,7 @@ class FlexibleColumnLayout extends UI5Element { /** * This property is no longer used and is replaced by `layoutsConfiguration`. * The property will be removed once all adopters migrate to `layoutsConfiguration`. - */ + */ @property({ type: Object }) _layoutsConfiguration?: { [device in MEDIA]: { From 0a9a26fd1222579df6097950252f106f20bc5348 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Tue, 30 Sep 2025 09:56:43 +0300 Subject: [PATCH 23/27] Merge remote-tracking branch 'origin/main' into fcl-layout-config-api From 6afaf64b963cffbc13482cc3c3fb0bbb03215d30 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Thu, 2 Oct 2025 16:09:41 +0300 Subject: [PATCH 24/27] feat(ui5-flexible-column-layout): correct version --- packages/fiori/src/FlexibleColumnLayout.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index 7c3df1e56f15..1123a9d657db 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -211,7 +211,7 @@ type FCLAccessibilityAttributes = { * @param {MEDIA} media The current media type * @param {array} columnLayout The effective column layout, f.e ["67%", "33%", "0px"] * @public - * @since 2.15.1 + * @since 2.16.0 */ @event("layout-configuration-change", { bubbles: true, @@ -284,7 +284,7 @@ class FlexibleColumnLayout extends UI5Element { * - Whenever the user drags the columns separator to resize the columns, the `layoutsConfiguration` object will be updated with the user-specified proportions for the given layout (and the `layout-configuration-change` event will be fired). * - No custom configuration available for the phone screen size, as the default of 100% column width is always used there. * @public - * @since 2.15.1 + * @since 2.16.0 * @default {} */ @property({ type: Object }) From 5d51e09073e7b9a896f8c9a1ee3028dd2a9e62fd Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Thu, 16 Oct 2025 12:07:28 +0300 Subject: [PATCH 25/27] feat(ui5-flexible-column-layout): add experimental flag --- packages/fiori/src/FlexibleColumnLayout.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index 1123a9d657db..12e0ff6fea52 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -212,6 +212,7 @@ type FCLAccessibilityAttributes = { * @param {array} columnLayout The effective column layout, f.e ["67%", "33%", "0px"] * @public * @since 2.16.0 + * @experimental */ @event("layout-configuration-change", { bubbles: true, @@ -283,9 +284,10 @@ class FlexibleColumnLayout extends UI5Element { * - If the proportions do not match the layout (e.g. if provided proportions ["70%", "30%", "0px"] for "OneColumn" layout), then the default proportions will be used instead. * - Whenever the user drags the columns separator to resize the columns, the `layoutsConfiguration` object will be updated with the user-specified proportions for the given layout (and the `layout-configuration-change` event will be fired). * - No custom configuration available for the phone screen size, as the default of 100% column width is always used there. + * @default {} * @public * @since 2.16.0 - * @default {} + * @experimental */ @property({ type: Object }) layoutsConfiguration: LayoutConfiguration = {}; From a35e9350d5efaae89bfe2fb943b5e1cbc30e3523 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Fri, 24 Oct 2025 14:40:49 +0300 Subject: [PATCH 26/27] feat(ui5-flexible-column-layout): correct documentation --- packages/fiori/src/FlexibleColumnLayout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index 12e0ff6fea52..adc911a15891 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -206,7 +206,7 @@ type FCLAccessibilityAttributes = { }) /** - * Fired when the `layoutConfiguration` changes via user interaction by dragging the separators. + * Fired when the `layoutsConfiguration` changes via user interaction by dragging the separators. * @param {FCLLayout} layout The current layout * @param {MEDIA} media The current media type * @param {array} columnLayout The effective column layout, f.e ["67%", "33%", "0px"] From ef113d980490ea29fa28c8c6e18c5aefe5226e30 Mon Sep 17 00:00:00 2001 From: Diana Pazheva Date: Fri, 24 Oct 2025 16:00:00 +0300 Subject: [PATCH 27/27] feat(ui5-flexible-column-layout): apply feedback --- packages/fiori/src/FlexibleColumnLayout.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/fiori/src/FlexibleColumnLayout.ts b/packages/fiori/src/FlexibleColumnLayout.ts index adc911a15891..6067ad3c2b10 100644 --- a/packages/fiori/src/FlexibleColumnLayout.ts +++ b/packages/fiori/src/FlexibleColumnLayout.ts @@ -666,7 +666,7 @@ class FlexibleColumnLayout extends UI5Element { isValidColumnLayout(columnLayout: (string | 0)[]) { const pxWidths = columnLayout?.map(w => this.convertColumnWidthToPixels(w)); - const totalWidth = pxWidths.reduce((i, sum) => i + sum); + const totalWidth = pxWidths.reduce((sum, i) => sum + i, 0); if (Math.round(totalWidth) !== Math.round(this._availableWidthForColumns)) { return false; @@ -840,7 +840,7 @@ class FlexibleColumnLayout extends UI5Element { updateLayoutsConfiguration(layout: `${FCLLayout}`, columnLayout: string[]) { if (this.mediaAllowsCustomConfiguration(this.media)) { this.layoutsConfiguration[this.media] ??= {}; - this.layoutsConfiguration[this.media]![layout] ??= { layout: columnLayout }; + this.layoutsConfiguration[this.media]![layout] ??= { layout: [] }; this.layoutsConfiguration[this.media]![layout]!.layout = columnLayout; } }