From ca8330e774b55362becad3151e8b64a28878a49f Mon Sep 17 00:00:00 2001 From: Nayden Naydenov <31909318+nnaydenow@users.noreply.github.com> Date: Mon, 9 Sep 2024 13:43:59 +0300 Subject: [PATCH 001/114] chore: migrate ui5-form tests to cypress (#9828) --- packages/main/cypress/specs/Form.cy.ts | 381 +++++++++++++++++++++++++ packages/main/test/specs/Form.spec.js | 106 ------- 2 files changed, 381 insertions(+), 106 deletions(-) create mode 100644 packages/main/cypress/specs/Form.cy.ts delete mode 100644 packages/main/test/specs/Form.spec.js diff --git a/packages/main/cypress/specs/Form.cy.ts b/packages/main/cypress/specs/Form.cy.ts new file mode 100644 index 000000000000..9a60a5b65f14 --- /dev/null +++ b/packages/main/cypress/specs/Form.cy.ts @@ -0,0 +1,381 @@ +import { html } from "lit"; +import "../../src/Form.js"; +import "../../src/FormItem.js"; +import "../../src/FormGroup.js"; + +describe("General API", () => { + it("tests calculated state of Form with layout='S1 M2 L3 XL6' and label-span='S12 M4 L4 XL4'", () => { + cy.mount(html` + + + Name: + Red Point Stores + + + + + + Twitter: + @sap + + + + + + Name: + Red Point Stores + + +`); + + cy.get("[ui5-form]") + .as("form"); + + cy.get("@form") + .invoke("prop", "columnsS") + .should("be.equal", 1); + + cy.get("@form") + .invoke("prop", "labelSpanS") + .should("be.equal", 12); + + cy.get("@form") + .invoke("prop", "columnsM") + .should("be.equal", 2); + + cy.get("@form") + .invoke("prop", "labelSpanM") + .should("be.equal", 4); + + cy.get("@form") + .invoke("prop", "columnsL") + .should("be.equal", 3); + + cy.get("@form") + .invoke("prop", "labelSpanL") + .should("be.equal", 4); + + cy.get("@form") + .invoke("prop", "columnsXl") + .should("be.equal", 6); + + cy.get("@form") + .invoke("prop", "labelSpanXl") + .should("be.equal", 4); + }); + + it("tests calculated state of Form with layout='S1 M2 L2 XL3' label-span='S12 M12 L12 XL12'", () => { + cy.mount(html` + + Name: + + + + + ZIP Code/City: + + + + + + Street: + + + + + + Country: + + + + + WebSite: + + + + + Delivery address: + + +`); + + cy.get("[ui5-form]") + .as("form"); + + cy.get("@form") + .invoke("prop", "columnsS") + .should("be.equal", 1); + + cy.get("@form") + .invoke("prop", "labelSpanS") + .should("be.equal", 12); + + cy.get("@form") + .invoke("prop", "columnsM") + .should("be.equal", 2); + + cy.get("@form") + .invoke("prop", "labelSpanM") + .should("be.equal", 12); + + cy.get("@form") + .invoke("prop", "columnsL") + .should("be.equal", 2); + + cy.get("@form") + .invoke("prop", "labelSpanL") + .should("be.equal", 12); + + cy.get("@form") + .invoke("prop", "columnsXl") + .should("be.equal", 3); + + cy.get("@form") + .invoke("prop", "labelSpanXl") + .should("be.equal", 12); + }); + + it("tests calculated state of two FormGroups in layout='S1 M2 L3 XL4'", () => { + cy.mount(html` + + + Name: + Red Point Stores + + + + + + Twitter: + @sap + + + + Email: + john.smith@sap.com + +`); + + cy.get("#testFormGroup4") + .as("formGr1"); + + cy.get("#testFormGroup5") + .as("formGr2"); + + cy.get("@formGr1") + .invoke("prop", "colsS") + .should("be.equal", 1); + + cy.get("@formGr2") + .invoke("prop", "colsS") + .should("be.equal", 1); + + cy.get("@formGr1") + .invoke("prop", "colsM") + .should("be.equal", 1); + + cy.get("@formGr2") + .invoke("prop", "colsM") + .should("be.equal", 1); + + cy.get("@formGr1") + .invoke("prop", "colsL") + .should("be.equal", 1); + + cy.get("@formGr2") + .invoke("prop", "colsL") + .should("be.equal", 2); + + cy.get("@formGr1") + .invoke("prop", "colsXl") + .should("be.equal", 2); + + cy.get("@formGr2") + .invoke("prop", "colsXl") + .should("be.equal", 2); + }); + + it("tests calculated state of three FormGroups in layout='S1 M2 L3 XL6'", () => { + cy.mount(html` + + + Name: + Red Point Stores + + + + + + Twitter: + @sap + + + + Email: + john.smith@sap.com + + + + Tel: + +49 6227 747474 + + + + + + Name: + Red Point Stores + + + + ZIP Code/City: + 411 Maintown + + +`); + + cy.get("#testFormGroup1") + .as("formGr1"); + + cy.get("#testFormGroup2") + .as("formGr2"); + + cy.get("#testFormGroup3") + .as("formGr3"); + + cy.get("@formGr1") + .invoke("prop", "colsS") + .should("be.equal", 1); + + cy.get("@formGr2") + .invoke("prop", "colsS") + .should("be.equal", 1); + + cy.get("@formGr3") + .invoke("prop", "colsS") + .should("be.equal", 1); + + cy.get("@formGr1") + .invoke("prop", "colsM") + .should("be.equal", 1); + + cy.get("@formGr2") + .invoke("prop", "colsM") + .should("be.equal", 1); + + cy.get("@formGr3") + .invoke("prop", "colsM") + .should("be.equal", 1); + + cy.get("@formGr1") + .invoke("prop", "colsL") + .should("be.equal", 1); + + cy.get("@formGr2") + .invoke("prop", "colsL") + .should("be.equal", 1); + + cy.get("@formGr3") + .invoke("prop", "colsL") + .should("be.equal", 1); + + cy.get("@formGr1") + .invoke("prop", "colsXl") + .should("be.equal", 2); + + cy.get("@formGr2") + .invoke("prop", "colsXl") + .should("be.equal", 2); + + cy.get("@formGr3") + .invoke("prop", "colsXl") + .should("be.equal", 2); + }); + + it("tests calculated state of three FormGroups in layout='S1 M2 L3 XL4'", () => { + cy.mount(html` + + + Name: + Red Point Stores + + + + + + Twitter: + @sap + + + + Email: + john.smith@sap.com + + + + + + Name: + Red Point Stores + + +`); + + cy.get("#testFormGroup6") + .as("formGr1"); + + cy.get("#testFormGroup7") + .as("formGr2"); + + cy.get("#testFormGroup8") + .as("formGr3"); + + cy.get("@formGr1") + .invoke("prop", "colsS") + .should("be.equal", 1); + + cy.get("@formGr2") + .invoke("prop", "colsS") + .should("be.equal", 1); + + cy.get("@formGr3") + .invoke("prop", "colsS") + .should("be.equal", 1); + + cy.get("@formGr1") + .invoke("prop", "colsM") + .should("be.equal", 1); + + cy.get("@formGr2") + .invoke("prop", "colsM") + .should("be.equal", 1); + + cy.get("@formGr3") + .invoke("prop", "colsM") + .should("be.equal", 1); + + cy.get("@formGr1") + .invoke("prop", "colsL") + .should("be.equal", 1); + + cy.get("@formGr2") + .invoke("prop", "colsL") + .should("be.equal", 1); + + cy.get("@formGr3") + .invoke("prop", "colsL") + .should("be.equal", 1); + + cy.get("@formGr1") + .invoke("prop", "colsXl") + .should("be.equal", 1); + + cy.get("@formGr2") + .invoke("prop", "colsXl") + .should("be.equal", 2); + + cy.get("@formGr3") + .invoke("prop", "colsXl") + .should("be.equal", 1); + }); +}); diff --git a/packages/main/test/specs/Form.spec.js b/packages/main/test/specs/Form.spec.js deleted file mode 100644 index d3416c99c892..000000000000 --- a/packages/main/test/specs/Form.spec.js +++ /dev/null @@ -1,106 +0,0 @@ -import { assert } from "chai"; - -describe("General API", () => { - before(async () => { - await browser.url(`test/pages/form/FormBasic.html`); - }); - - it("tests calculated state of Form with layout='S1 M2 L3 XL6' and label-span='S12 M4 L4 XL4'", async () => { - const form = await browser.$("#testForm1"); - - // Given that layout="S1 M2 L3 XL6" and label-span="S12 M4 L4 XL4" - assert.strictEqual(await form.getProperty("columnsS"), 1, "Columns in S is 1."); - assert.strictEqual(await form.getProperty("labelSpanS"), 12, "Label span in S is 12."); - - assert.strictEqual(await form.getProperty("columnsM"), 2, "Columns in M are 2."); - assert.strictEqual(await form.getProperty("labelSpanM"), 4, "Label span in M is 4."); - - assert.strictEqual(await form.getProperty("columnsL"), 3, "Columns in L are 3."); - assert.strictEqual(await form.getProperty("labelSpanL"), 4, "Label span in L is 4."); - - assert.strictEqual(await form.getProperty("columnsXl"), 6, "Columns in XL are 6."); - assert.strictEqual(await form.getProperty("labelSpanXl"), 4, "Label span in XL is 4."); - }); - - it("tests calculated state of Form with layout='S1 M2 L2 XL3' label-span='S12 M12 L12 XL12'", async () => { - const form = await browser.$("#testForm2"); - - // Given that layout="S1 M2 L3 XL6" and label-span="S12 M4 L4 XL4" - assert.strictEqual(await form.getProperty("columnsS"), 1, "Columns in S is 1."); - assert.strictEqual(await form.getProperty("labelSpanS"), 12, "Label span in S is 12."); - - assert.strictEqual(await form.getProperty("columnsM"), 2, "Columns in M are 2."); - assert.strictEqual(await form.getProperty("labelSpanM"), 12, "Label span in M is 12."); - - assert.strictEqual(await form.getProperty("columnsL"), 2, "Columns in L are 2."); - assert.strictEqual(await form.getProperty("labelSpanL"), 12, "Label span in L is 12."); - - assert.strictEqual(await form.getProperty("columnsXl"), 3, "Columns in XL are 3."); - assert.strictEqual(await form.getProperty("labelSpanXl"), 12, "Label span in XL is 12."); - }); - - it("tests calculated state of two FormGroups in layout='S1 M2 L3 XL4'", async () => { - const formGr1 = await browser.$("#testFormGroup4"); - const formGr2 = await browser.$("#testFormGroup5"); - - // Gven that there are 2 groups and layout="S1 M2 L3 XL4" - assert.strictEqual(await formGr1.getProperty("colsS"), 1, "In S both groups take by 1 column."); - assert.strictEqual(await formGr2.getProperty("colsS"), 1, "In M both groups take by 1 column."); - - assert.strictEqual(await formGr1.getProperty("colsM"), 1, "In M both groups take by 1 column."); - assert.strictEqual(await formGr2.getProperty("colsM"), 1, "In M both groups take by 1 column."); - - assert.strictEqual(await formGr1.getProperty("colsL"), 1, "Group1 takes 1 column in L."); - assert.strictEqual(await formGr2.getProperty("colsL"), 2, "Group1 takes 2 columns in L."); - - assert.strictEqual(await formGr1.getProperty("colsXl"), 2, "In XL both groups take 2 cols each."); - assert.strictEqual(await formGr2.getProperty("colsXl"), 2, "In XL both groups take 2 cols each."); - }); - - it("tests calculated state of three FormGroups in layout='S1 M2 L3 XL6'", async () => { - const formGr1 = await browser.$("#testFormGroup1"); - const formGr2 = await browser.$("#testFormGroup2"); - const formGr3 = await browser.$("#testFormGroup3"); - - // Gven that there are 3 groups and layout="S1 M2 L3 XL6" - assert.strictEqual(await formGr1.getProperty("colsS"), 1, "In S all groups take 1 column."); - assert.strictEqual(await formGr2.getProperty("colsS"), 1, "In S all groups take 1 column."); - assert.strictEqual(await formGr3.getProperty("colsS"), 1, "In S all groups take 1 column."); - - assert.strictEqual(await formGr1.getProperty("colsM"), 1, "In M all groups take 1 column."); - assert.strictEqual(await formGr2.getProperty("colsM"), 1, "In M all groups take 1 column."); - assert.strictEqual(await formGr3.getProperty("colsM"), 1, "In M all groups take 1 column."); - - assert.strictEqual(await formGr1.getProperty("colsL"), 1, "In L all groups take 1 column."); - assert.strictEqual(await formGr2.getProperty("colsL"), 1, "In L all groups take 1 column."); - assert.strictEqual(await formGr3.getProperty("colsL"), 1, "In L all groups take 1 column."); - - assert.strictEqual(await formGr1.getProperty("colsXl"), 2, "In XL both groups take 2 cols each."); - assert.strictEqual(await formGr2.getProperty("colsXl"), 2, "In XL both groups take 2 cols each."); - assert.strictEqual(await formGr3.getProperty("colsXl"), 2, "In XL both groups take 2 cols each."); - }); - - it("tests calculated state of three FormGroups in layout='S1 M2 L3 XL4'", async () => { - const formGr1 = await browser.$("#testFormGroup6"); - const formGr2 = await browser.$("#testFormGroup7"); - const formGr3 = await browser.$("#testFormGroup8"); - - // Gven that there are 3 groups and layout="S1 M2 L3 XL4" - assert.strictEqual(await formGr1.getProperty("colsS"), 1, "In S all groups take 1 column."); - assert.strictEqual(await formGr2.getProperty("colsS"), 1, "In S all groups take 1 column."); - assert.strictEqual(await formGr3.getProperty("colsS"), 1, "In S all groups take 1 column."); - - assert.strictEqual(await formGr1.getProperty("colsM"), 1, "In M all groups take 1 column."); - assert.strictEqual(await formGr2.getProperty("colsM"), 1, "In M all groups take 1 column."); - assert.strictEqual(await formGr3.getProperty("colsM"), 1, "In M all groups take 1 column."); - - assert.strictEqual(await formGr1.getProperty("colsL"), 1, "In L all groups take 1 column."); - assert.strictEqual(await formGr2.getProperty("colsL"), 1, "In L all groups take 1 column."); - assert.strictEqual(await formGr3.getProperty("colsL"), 1, "In L all groups take 1 column."); - - assert.strictEqual(await formGr1.getProperty("colsXl"), 1, "In XL first group takes 1 col."); - assert.strictEqual(await formGr2.getProperty("colsXl"), 2, "In XL second group takes 2 cols."); - assert.strictEqual(await formGr3.getProperty("colsXl"), 1, "In XL third group takes 1 col."); - }); - -}); From e64c8f13b96d5569984c8ef903bc3fbaf9acba1e Mon Sep 17 00:00:00 2001 From: Peter Skelin Date: Mon, 9 Sep 2024 17:15:37 +0300 Subject: [PATCH 002/114] docs: fix shortcut opens search while typing in playground editor (#9831) --- packages/website/src/components/Editor/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/website/src/components/Editor/index.js b/packages/website/src/components/Editor/index.js index 8e8d6fa3fac5..50cd61d240bc 100644 --- a/packages/website/src/components/Editor/index.js +++ b/packages/website/src/components/Editor/index.js @@ -301,6 +301,13 @@ ${fixAssetPaths(_js)}`, tabBarRef.current.project = projectRef.current; fileEditorRef.current.project = projectRef.current; previewRef.current.project = projectRef.current; + + // algolia search opens the search on key `/` because this custom element is the event target but has no `isContentEditable` + Object.defineProperty(fileEditorRef.current, "isContentEditable", { + get() { + return true; + }, + }); tabBarRef.current.editor = fileEditorRef.current; From 485fd0dd1205543e50e7db011d6c5a7bedbe5c68 Mon Sep 17 00:00:00 2001 From: Petar Dimov <32839090+dimovpetar@users.noreply.github.com> Date: Mon, 9 Sep 2024 17:24:14 +0300 Subject: [PATCH 003/114] =?UTF-8?q?fix(ui5-tabcontainer):=20fix=20drag=20a?= =?UTF-8?q?nd=20drop=20issue=20with=20home=20key=20and=20fix=D0=B5d=20tabs?= =?UTF-8?q?=20(#9812)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/dragAndDrop/findClosestPosition.ts | 41 +++++++---- packages/main/src/List.ts | 34 ++++----- packages/main/src/TabContainer.ts | 70 ++++++++++--------- .../test/pages/TabContainerDragAndDrop.html | 2 +- .../specs/TabContainerDragAndDrop.spec.js | 7 ++ 5 files changed, 91 insertions(+), 63 deletions(-) diff --git a/packages/base/src/util/dragAndDrop/findClosestPosition.ts b/packages/base/src/util/dragAndDrop/findClosestPosition.ts index 0284246364e3..fbc6f90a4696 100644 --- a/packages/base/src/util/dragAndDrop/findClosestPosition.ts +++ b/packages/base/src/util/dragAndDrop/findClosestPosition.ts @@ -74,38 +74,53 @@ const findClosestPosition = (elements: Array, point: number, layout }; }; -const findClosestPositionByKey = (elements: Array, element: HTMLElement, e: KeyboardEvent) => { - let placement; +const findClosestPositionsByKey = (elements: Array, element: HTMLElement, e: KeyboardEvent) => { let index = elements.indexOf(element); + const positions = []; switch (e.key) { case "ArrowLeft": case "ArrowUp": - placement = MovePlacement.Before; index--; + if (index >= 0) { + positions.push({ + element: elements[index], + placement: MovePlacement.Before, + }); + } break; case "ArrowRight": case "ArrowDown": - placement = MovePlacement.After; index++; + if (index < elements.length) { + positions.push({ + element: elements[index], + placement: MovePlacement.After, + }); + } break; case "Home": - placement = MovePlacement.Before; - index = 0; + elements.forEach(el => { + positions.push({ + element: el, + placement: MovePlacement.Before, + }); + }); break; case "End": - placement = MovePlacement.After; - index = elements.length - 1; + elements.reverse().forEach(el => { + positions.push({ + element: el, + placement: MovePlacement.After, + }); + }); break; } - return { - element: elements[index], - placement, - }; + return positions; }; export { findClosestPosition, - findClosestPositionByKey, + findClosestPositionsByKey, }; diff --git a/packages/main/src/List.ts b/packages/main/src/List.ts index c734ae59d1b0..0f77f780f458 100644 --- a/packages/main/src/List.ts +++ b/packages/main/src/List.ts @@ -17,7 +17,7 @@ import { isCtrl, } from "@ui5/webcomponents-base/dist/Keys.js"; import DragRegistry from "@ui5/webcomponents-base/dist/util/dragAndDrop/DragRegistry.js"; -import { findClosestPosition, findClosestPositionByKey } from "@ui5/webcomponents-base/dist/util/dragAndDrop/findClosestPosition.js"; +import { findClosestPosition, findClosestPositionsByKey } from "@ui5/webcomponents-base/dist/util/dragAndDrop/findClosestPosition.js"; import NavigationMode from "@ui5/webcomponents-base/dist/types/NavigationMode.js"; import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js"; import getNormalizedTarget from "@ui5/webcomponents-base/dist/util/getNormalizedTarget.js"; @@ -906,27 +906,16 @@ class List extends UI5Element { return; } - const { placement, element } = findClosestPositionByKey(this.items, item, e); + const closestPositions = findClosestPositionsByKey(this.items, item, e); - if (!element || !placement) { + if (!closestPositions.length) { return; } e.preventDefault(); - const placementAccepted = !this.fireEvent("move-over", { - originalEvent: e, - source: { - element: item, - }, - destination: { - element, - placement, - }, - }, true); - - if (placementAccepted) { - this.fireEvent("move", { + const acceptedPosition = closestPositions.find(({ element, placement }) => { + return !this.fireEvent("move-over", { originalEvent: e, source: { element: item, @@ -935,6 +924,19 @@ class List extends UI5Element { element, placement, }, + }, true); + }); + + if (acceptedPosition) { + this.fireEvent("move", { + originalEvent: e, + source: { + element: item, + }, + destination: { + element: acceptedPosition.element, + placement: acceptedPosition.placement, + }, }); item.focus(); diff --git a/packages/main/src/TabContainer.ts b/packages/main/src/TabContainer.ts index 1f3a842eafe2..2f02befeea38 100644 --- a/packages/main/src/TabContainer.ts +++ b/packages/main/src/TabContainer.ts @@ -29,7 +29,7 @@ import { getScopedVarName } from "@ui5/webcomponents-base/dist/CustomElementsSco import "@ui5/webcomponents-icons/dist/slim-arrow-up.js"; import "@ui5/webcomponents-icons/dist/slim-arrow-down.js"; import arraysAreEqual from "@ui5/webcomponents-base/dist/util/arraysAreEqual.js"; -import { findClosestPosition, findClosestPositionByKey } from "@ui5/webcomponents-base/dist/util/dragAndDrop/findClosestPosition.js"; +import { findClosestPosition, findClosestPositionsByKey } from "@ui5/webcomponents-base/dist/util/dragAndDrop/findClosestPosition.js"; import Orientation from "@ui5/webcomponents-base/dist/types/Orientation.js"; import DragRegistry from "@ui5/webcomponents-base/dist/util/dragAndDrop/DragRegistry.js"; import type { SetDraggedElementFunction } from "@ui5/webcomponents-base/dist/util/dragAndDrop/DragRegistry.js"; @@ -615,39 +615,29 @@ class TabContainer extends UI5Element { return; } - const headerItems = this.items.map(item => item.getDomRefInStrip()).filter((item): item is TabInStrip => !item?.hasAttribute("hidden")); - let { placement, element } = findClosestPositionByKey(headerItems, tab.getDomRefInStrip()!, e); + const headerItems = this.items.map(item => item.getDomRefInStrip()) + .filter((item): item is TabInStrip => !item?.hasAttribute("hidden")); + let positions = findClosestPositionsByKey(headerItems, tab.getDomRefInStrip()!, e); - if (!element || !placement) { - return; - } - - while (element && (element as TabInStrip).realTabReference.hasAttribute("ui5-tab-separator") && placement === MovePlacement.Before) { - element = element.previousElementSibling as HTMLElement; - placement = MovePlacement.After; - } - - while (element && (element as TabInStrip).realTabReference.hasAttribute("ui5-tab-separator") && placement === MovePlacement.After) { - element = element.nextElementSibling as HTMLElement; - placement = MovePlacement.Before; - } + positions = positions.map(({ element, placement }) => { + while (element && (element as TabInStrip).realTabReference.hasAttribute("ui5-tab-separator") && placement === MovePlacement.Before) { + element = headerItems.at(headerItems.indexOf(element as TabInStrip) - 1) as HTMLElement; + placement = MovePlacement.After; + } - if (!element) { - return; - } + while (element && (element as TabInStrip).realTabReference.hasAttribute("ui5-tab-separator") && placement === MovePlacement.After) { + element = headerItems.at(headerItems.indexOf(element as TabInStrip) + 1) as HTMLElement; + placement = MovePlacement.Before; + } - const placementAccepted = !this.fireEvent("move-over", { - source: { - element: tab, - }, - destination: { - element: (element as TabInStrip).realTabReference, + return { + element, placement, - }, - }, true); + }; + }); - if (placementAccepted) { - this.fireEvent("move", { + const acceptedPosition = positions.find(({ element, placement }) => { + return !this.fireEvent("move-over", { source: { element: tab, }, @@ -655,6 +645,18 @@ class TabContainer extends UI5Element { element: (element as TabInStrip).realTabReference, placement, }, + }, true); + }); + + if (acceptedPosition) { + this.fireEvent("move", { + source: { + element: tab, + }, + destination: { + element: (acceptedPosition.element as TabInStrip).realTabReference, + placement: acceptedPosition.placement, + }, }); tab.focus(); @@ -674,6 +676,7 @@ class TabContainer extends UI5Element { const draggedElement = DragRegistry.getDraggedElement()!; let destinationElement: HTMLElement = (destination.element as TabInStrip | TabSeparatorInStrip).realTabReference; + // workaround to simulate tree behavior if (e.detail.originalEvent instanceof KeyboardEvent) { const realTabReference = (source.element as TabInOverflow).realTabReference; const siblings = this._findSiblings(realTabReference); @@ -685,8 +688,8 @@ class TabContainer extends UI5Element { }); } - const nextPlacement = findClosestPositionByKey(items, realTabReference, e.detail.originalEvent); - destinationElement = nextPlacement.element; + const nextPosition = findClosestPositionsByKey(items, realTabReference, e.detail.originalEvent); + destinationElement = nextPosition[0]?.element; } if (!destinationElement) { @@ -723,6 +726,7 @@ class TabContainer extends UI5Element { const draggedElement = DragRegistry.getDraggedElement()!; let destinationElement: HTMLElement = (destination.element as TabInStrip).realTabReference; + // Workaround to simulate tree behavior if (e.detail.originalEvent instanceof KeyboardEvent) { const realTabReference = (source.element as TabInOverflow).realTabReference; const siblings = this._findSiblings(realTabReference); @@ -734,8 +738,8 @@ class TabContainer extends UI5Element { }); } - const nextPlacement = findClosestPositionByKey(items, realTabReference, e.detail.originalEvent); - destinationElement = nextPlacement.element; + const nextPosition = findClosestPositionsByKey(items, realTabReference, e.detail.originalEvent); + destinationElement = nextPosition[0]?.element; } if (!destinationElement) { diff --git a/packages/main/test/pages/TabContainerDragAndDrop.html b/packages/main/test/pages/TabContainerDragAndDrop.html index 0e2a444c3d0d..6ca4d5ec4825 100644 --- a/packages/main/test/pages/TabContainerDragAndDrop.html +++ b/packages/main/test/pages/TabContainerDragAndDrop.html @@ -87,7 +87,7 @@

Fixed Tabs

- + diff --git a/packages/main/test/specs/TabContainerDragAndDrop.spec.js b/packages/main/test/specs/TabContainerDragAndDrop.spec.js index 37c403634a01..e3688bbfa5b1 100644 --- a/packages/main/test/specs/TabContainerDragAndDrop.spec.js +++ b/packages/main/test/specs/TabContainerDragAndDrop.spec.js @@ -344,5 +344,12 @@ describe("Keyboard drag and drop tests", () => { assert.strictEqual(await browser.$("#fixedTabsTabSeven").previousElement().getAttribute("id"), "fixedTabsSeparatorOne", "Tab seven has stopped when reached fixed tabs"); }); + + it("Moving strip item with Home", async () => { + await tabContainer.focusItem("fixedTabsTabSix"); + await browser.keys(["Control", "Home"]); + + assert.strictEqual(await browser.$("#fixedTabsTabSix").previousElement().getAttribute("id"), "fixedTabsSeparatorOne", "Tab six is placed after fixed tabs"); + }); }); }); \ No newline at end of file From 027f24d89126532e59d8983372cc2e63b1d7d9a5 Mon Sep 17 00:00:00 2001 From: Petar Dimov <32839090+dimovpetar@users.noreply.github.com> Date: Tue, 10 Sep 2024 09:49:07 +0300 Subject: [PATCH 004/114] docs(ui5-tabcontainer, ui5-list, ui5-tree): simplify drag and drop samples (#9814) --- packages/main/test/pages/TreeDragAndDrop.html | 28 +++++++---------- .../_samples/main/List/DragAndDrop/main.js | 25 ++++++++-------- .../main/TabContainer/ReorderTabs/main.js | 18 ++++++----- .../ReorderTabsMaxNestingLevel/main.js | 20 +++++++------ .../_samples/main/Tree/DragAndDrop/main.js | 30 ++++++++----------- 5 files changed, 56 insertions(+), 65 deletions(-) diff --git a/packages/main/test/pages/TreeDragAndDrop.html b/packages/main/test/pages/TreeDragAndDrop.html index 6c974102c930..130307a8e2ab 100644 --- a/packages/main/test/pages/TreeDragAndDrop.html +++ b/packages/main/test/pages/TreeDragAndDrop.html @@ -79,23 +79,17 @@

Drag and drop

const handleMove = (e) => { const { destination, source } = e.detail; - const parent = destination.element.parentNode.closest("[ui5-tree-item]") || - destination.element.closest("[ui5-tree]"); - - if (destination.placement === "Before") { - parent.insertBefore( - source.element, - destination.element - ); - } else if (destination.placement === "After") { - const nextElement = Array.from(parent.children).at(Array.from(parent.children).indexOf(destination.element) + 1); - - parent.insertBefore( - source.element, - nextElement, - ); - } else if (destination.placement === "On") { - destination.element.prepend(source.element); + + switch (destination.placement) { + case "Before": + destination.element.before(source.element); + break; + case "After": + destination.element.after(source.element); + break; + case "On": + destination.element.prepend(source.element); + break; } }; diff --git a/packages/website/docs/_samples/main/List/DragAndDrop/main.js b/packages/website/docs/_samples/main/List/DragAndDrop/main.js index 4a393aa05a2e..fe206ba4ec15 100644 --- a/packages/website/docs/_samples/main/List/DragAndDrop/main.js +++ b/packages/website/docs/_samples/main/List/DragAndDrop/main.js @@ -1,7 +1,7 @@ import "@ui5/webcomponents/dist/List.js"; import "@ui5/webcomponents/dist/ListItemStandard.js"; - import "@ui5/webcomponents-icons/dist/checklist-item.js"; +import MovePlacement from "@ui5/webcomponents-base/dist/types/MovePlacement.js"; const list = document.getElementById('listDnd1'); const handleBeforeItemMove = (e) => { @@ -25,18 +25,17 @@ const handleMoveOver = (e) => { const handleMove = (e) => { const { destination, source } = e.detail; - const parent = destination.element.closest('[ui5-list]'); - - if (destination.placement === 'Before') { - parent.insertBefore(source.element, destination.element); - } else if (destination.placement === 'After') { - const nextElement = Array.from(parent.children).at( - Array.from(parent.children).indexOf(destination.element) + 1 - ); - - parent.insertBefore(source.element, nextElement); - } else if (destination.placement === 'On') { - destination.element.prepend(source.element); + + switch (destination.placement) { + case MovePlacement.Before: + destination.element.before(source.element); + break; + case MovePlacement.After: + destination.element.after(source.element); + break; + case MovePlacement.On: + destination.element.prepend(source.element); + break; } }; diff --git a/packages/website/docs/_samples/main/TabContainer/ReorderTabs/main.js b/packages/website/docs/_samples/main/TabContainer/ReorderTabs/main.js index 316f085c5c7e..aeff18219094 100644 --- a/packages/website/docs/_samples/main/TabContainer/ReorderTabs/main.js +++ b/packages/website/docs/_samples/main/TabContainer/ReorderTabs/main.js @@ -14,15 +14,17 @@ tabContainer.addEventListener("move-over", (event) => { tabContainer.addEventListener("move", (event) => { const { source, destination } = event.detail; - const currentParent = destination.element.parentElement; - if (destination.placement === MovePlacement.Before) { - currentParent.insertBefore(source.element, destination.element); - } else if (destination.placement === MovePlacement.After) { - const nextElement = Array.from(currentParent.children).at(Array.from(currentParent.children).indexOf(destination.element) + 1); - currentParent.insertBefore(source.element, nextElement); - } else if (destination.placement === MovePlacement.On) { - destination.element.prepend(source.element); + switch (destination.placement) { + case MovePlacement.Before: + destination.element.before(source.element); + break; + case MovePlacement.After: + destination.element.after(source.element); + break; + case MovePlacement.On: + destination.element.prepend(source.element); + break; } const newParent = source.element.parentElement; diff --git a/packages/website/docs/_samples/main/TabContainer/ReorderTabsMaxNestingLevel/main.js b/packages/website/docs/_samples/main/TabContainer/ReorderTabsMaxNestingLevel/main.js index eab3bb3a77b9..a7d2365d7332 100644 --- a/packages/website/docs/_samples/main/TabContainer/ReorderTabsMaxNestingLevel/main.js +++ b/packages/website/docs/_samples/main/TabContainer/ReorderTabsMaxNestingLevel/main.js @@ -38,15 +38,17 @@ tabContainer.addEventListener("move-over", (event) => { tabContainer.addEventListener("move", (event) => { const { source, destination } = event.detail; - const currentParent = destination.element.parentElement; - - if (destination.placement === MovePlacement.Before) { - currentParent.insertBefore(source.element, destination.element); - } else if (destination.placement === MovePlacement.After) { - const nextElement = Array.from(currentParent.children).at(Array.from(currentParent.children).indexOf(destination.element) + 1); - currentParent.insertBefore(source.element, nextElement); - } else if (destination.placement === MovePlacement.On) { - destination.element.prepend(source.element); + + switch (destination.placement) { + case MovePlacement.Before: + destination.element.before(source.element); + break; + case MovePlacement.After: + destination.element.after(source.element); + break; + case MovePlacement.On: + destination.element.prepend(source.element); + break; } const newParent = source.element.parentElement; diff --git a/packages/website/docs/_samples/main/Tree/DragAndDrop/main.js b/packages/website/docs/_samples/main/Tree/DragAndDrop/main.js index df2de25c58b7..7e4f5b139a26 100644 --- a/packages/website/docs/_samples/main/Tree/DragAndDrop/main.js +++ b/packages/website/docs/_samples/main/Tree/DragAndDrop/main.js @@ -2,7 +2,7 @@ import "@ui5/webcomponents/dist/Tree.js"; import "@ui5/webcomponents/dist/TreeItem.js"; import "@ui5/webcomponents/dist/Title.js"; import "@ui5/webcomponents/dist/Label.js"; - +import MovePlacement from "@ui5/webcomponents-base/dist/types/MovePlacement.js"; const tree = document.getElementById("tree"); const handleBeforeItemMove = (e) => { @@ -31,23 +31,17 @@ const handleMoveOver = (e) => { const handleMove = (e) => { const { destination, source } = e.detail; - const parent = destination.element.parentNode.closest("[ui5-tree-item]") || - destination.element.closest("[ui5-tree]"); - - if (destination.placement === "Before") { - parent.insertBefore( - source.element, - destination.element - ); - } else if (destination.placement === "After") { - const nextElement = Array.from(parent.children).at(Array.from(parent.children).indexOf(destination.element) + 1); - - parent.insertBefore( - source.element, - nextElement, - ); - } else if (destination.placement === "On") { - destination.element.prepend(source.element); + + switch (destination.placement) { + case MovePlacement.Before: + destination.element.before(source.element); + break; + case MovePlacement.After: + destination.element.after(source.element); + break; + case MovePlacement.On: + destination.element.prepend(source.element); + break; } }; From 712d94e3c27c96cb1ab44f57d0e0fd10f7288a4b Mon Sep 17 00:00:00 2001 From: Nikolay Deshev Date: Tue, 10 Sep 2024 10:04:20 +0300 Subject: [PATCH 005/114] fix(ui5-multi-combobox): restore focus to input after value state header is removed (#9827) * fix(ui5-multi-combobox): restore focus to input after value state header is removed fixes: #9709 --- packages/main/src/MultiComboBox.ts | 1 + packages/main/test/specs/MultiComboBox.spec.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/main/src/MultiComboBox.ts b/packages/main/src/MultiComboBox.ts index e776919e5d8c..f1d3e8a93840 100644 --- a/packages/main/src/MultiComboBox.ts +++ b/packages/main/src/MultiComboBox.ts @@ -1318,6 +1318,7 @@ class MultiComboBox extends UI5Element implements IFormInputElement { this._dialogInputValueState = valueState; this.valueState = valueState; this._validationTimeout = null; + this._innerInput.focus(); callback && callback(); }, 2000); diff --git a/packages/main/test/specs/MultiComboBox.spec.js b/packages/main/test/specs/MultiComboBox.spec.js index b204e1a672ca..38bbec973285 100644 --- a/packages/main/test/specs/MultiComboBox.spec.js +++ b/packages/main/test/specs/MultiComboBox.spec.js @@ -243,8 +243,8 @@ describe("MultiComboBox general interaction", () => { assert.strictEqual(await innerInput.getAttribute("value-state"), "Negative", "Value state is changed to Negative"); await browser.waitUntil(async () => { - return await mcb.getAttribute("_dialog-input-value-state") === "None"; - }, 2500, "expect _dialog-input-value-state to be reset after 2.5 seconds"); + return await mcb.getAttribute("_dialog-input-value-state") === "None" && await mcb.hasAttribute("focused") === true; + }, 2500, "expect _dialog-input-value-state to be reset after 2.5 seconds and the MultiComboBox to be focused"); }); it("tests if entering valid text is possible while validation is triggered", async () => { From 10016f55abfdfc947f211572a85dc4861e11c88c Mon Sep 17 00:00:00 2001 From: Nayden Naydenov <31909318+nnaydenow@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:38:10 +0300 Subject: [PATCH 006/114] chore: migrate tests to cypress (#9833) --- packages/main/cypress/specs/Eventing.cy.ts | 44 + packages/main/cypress/specs/FormSupport.cy.ts | 896 ++++++++++++++++++ .../main/cypress/specs/LitKeyFunction.cy.ts | 50 + packages/main/cypress/specs/RTL.cy.ts | 19 + packages/main/test/specs/FormSupport.spec.js | 446 --------- .../main/test/specs/LitKeyFunction.spec.js | 39 - packages/main/test/specs/RTL.spec.js | 10 - 7 files changed, 1009 insertions(+), 495 deletions(-) create mode 100644 packages/main/cypress/specs/Eventing.cy.ts create mode 100644 packages/main/cypress/specs/FormSupport.cy.ts create mode 100644 packages/main/cypress/specs/LitKeyFunction.cy.ts create mode 100644 packages/main/cypress/specs/RTL.cy.ts delete mode 100644 packages/main/test/specs/FormSupport.spec.js delete mode 100644 packages/main/test/specs/LitKeyFunction.spec.js delete mode 100644 packages/main/test/specs/RTL.spec.js diff --git a/packages/main/cypress/specs/Eventing.cy.ts b/packages/main/cypress/specs/Eventing.cy.ts new file mode 100644 index 000000000000..2f4f1886e4ba --- /dev/null +++ b/packages/main/cypress/specs/Eventing.cy.ts @@ -0,0 +1,44 @@ +// import { html } from "lit"; +import "../../src/Button.js"; + +describe("Eventing", () => { + it("Default prevented", () => { + // cy.mount(html`
+ // Submit + //
`); + + // cy.get("form") + // .then($item => { + // $item.get(0).addEventListener("submit", cy.stub().as("submit")); + // }); + + // cy.get("[ui5-button]") + // .then($item => { + // $item.get(0).addEventListener("click", e => e.preventDefault()); + // }); + + // cy.get("[ui5-button]") + // .realClick(); + + // cy.get("@submit") + // .should("have.not.been.called"); + }); + + // it("Default not prevented", () => { + // cy.mount(html`
+ // Submit + //
`); + + // cy.get("form") + // .then($item => { + // $item.get(0).addEventListener("submit", e => e.preventDefault()); + // $item.get(0).addEventListener("submit", cy.stub().as("submit")); + // }); + + // cy.get("[ui5-button]") + // .realClick(); + +// cy.get("@submit") +// .should("have.been.called"); +// }); +}); diff --git a/packages/main/cypress/specs/FormSupport.cy.ts b/packages/main/cypress/specs/FormSupport.cy.ts new file mode 100644 index 000000000000..cbd3c4664bbf --- /dev/null +++ b/packages/main/cypress/specs/FormSupport.cy.ts @@ -0,0 +1,896 @@ +import { html } from "lit"; +import "../../src/Button.js"; +import "../../src/CheckBox.js"; +import "../../src/ColorPicker.js"; +import "../../src/ComboBox.js"; +import "../../src/ComboBoxItem.js"; +import "../../src/DatePicker.js"; +import "../../src/DateRangePicker.js"; +import "../../src/DateTimePicker.js"; +import "../../src/Input.js"; +import "../../src/MultiComboBox.js"; +import "../../src/MultiComboBoxItem.js"; +import "../../src/MultiInput.js"; +import "../../src/Token.js"; +import "../../src/RadioButton.js"; +import "../../src/RangeSlider.js"; +import "../../src/Select.js"; +import "../../src/Option.js"; +import "../../src/Slider.js"; +import "../../src/StepInput.js"; +import "../../src/Switch.js"; +import "../../src/TextArea.js"; +import "../../src/TimePicker.js"; + +const getFormData = ($form: HTMLFormElement) => { + const formData = new FormData($form); + const entries = [...formData.entries()]; + return entries.map(entry => { + return `${entry[0]}=${entry[1] as string}`; + }).join("&"); +}; + +describe("Form support", () => { + it("ui5-checkbox in form", () => { + cy.mount(html`
+ + + + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + + cy.get("#cb5") + .realClick(); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "checkbox4=on&checkbox5=on"); + }); + + it("ui5-color-picker in form", () => { + cy.mount(html`
+ + + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "color_picker3=rgba(255,255,255,1)&color_picker4=blue"); + }); + + it("ui5-combobox in form", () => { + cy.mount(html`
+ + + + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + + cy.get("#combobox5") + .realClick(); + + cy.get("#combobox5") + .realType("ok", { delay: 100 }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "combobox3=&combobox4=ok&combobox5=ok"); + }); + + it("ui5-date-picker in form", () => { + cy.mount(html`
+ + + + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + + cy.get("#date_picker5") + .realClick(); + + cy.get("#date_picker5") + .realType("ok", { delay: 100 }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "date_picker3=&date_picker4=ok&date_picker5=ok"); + }); + + it("ui5-daterange-picker in form", () => { + cy.mount(html`
+ + + + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + + cy.get("#daterange_picker5") + .realClick(); + + cy.get("#daterange_picker5") + .realType("ok", { delay: 100 }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "daterange_picker3=&daterange_picker4=ok&daterange_picker5=ok"); + }); + + it("ui5-datetime-picker in form", () => { + cy.mount(html`
+ + + + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + + cy.get("#datetime_picker5") + .realClick(); + + cy.get("#datetime_picker5") + .realType("ok", { delay: 100 }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "datetime_picker3=&datetime_picker4=ok&datetime_picker5=ok"); + }); + + it("ui5-input in form", () => { + cy.mount(html`
+ + + + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + + cy.get("#input5") + .realClick(); + + cy.get("#input5") + .realType("ok", { delay: 100 }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "input3=&input4=ok&input5=ok"); + }); + + it("ui5-multi-combobox in form", () => { + cy.mount(html`
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + + cy.get("#multi_combobox9") + .realClick(); + + cy.get("#multi_combobox9") + .realType("ok", { delay: 100 }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "multi_combobox5=&multi_combobox6=ok&multi_combobox7=&multi_combobox7=ok&multi_combobox8=ok&multi_combobox8=ok&multi_combobox9=ok&multi_combobox10=ok&multi_combobox11=&multi_combobox11=ok&multi_combobox12=ok&multi_combobox12=ok"); + }); + + it("ui5-multi-input in form", () => { + cy.mount(html`
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + + cy.get("#multi_input9") + .realClick(); + + cy.get("#multi_input9") + .realType("ok", { delay: 100 }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "multi_input5=&multi_input6=ok&multi_input7=&multi_input7=ok&multi_input8=ok&multi_input8=ok&multi_input9=ok&multi_input10=ok&multi_input11=&multi_input11=ok&multi_input12=ok&multi_input12=ok"); + }); + + it("ui5-radio-button in form 1", () => { + cy.mount(html`
+ + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + + cy.get("#rb_1") + .realClick(); + + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(200); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "rb1=on"); + }); + + it("ui5-radio-button in form 2", () => { + cy.mount(html`
+ ui5-radio-button + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", ""); + }); + + it("ui5-radio-button in form 3", () => { + cy.mount(html`
+ + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + + cy.get("#rb_4") + .realClick(); + + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(200); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "rb3=B"); + }); + + it("ui5-range-slider in form", () => { + cy.mount(html`
+ + + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "range_slider3=0&range_slider3=100&range_slider4=25&range_slider4=75"); + }); + + it("ui5-select in form", () => { + cy.mount(html`
+ + Option 1 + Option 2 + Option 3 + + + Option 1 + Option 2 + Option 3 + + + Option 1 + Option 2 + Option 3 + + + + Option 1 + Option 2 + Option 3 + + + Option 1 + Option 2 + Option 3 + + + Option 1 + Option 2 + Option 3 + + + + Option 1 + Option 2 + Option 3 + + + Option 1 + Option 2 + Option 3 + + + Option 1 + Option 2 + Option 3 + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + + cy.get("#select9") + .realClick(); + + cy.get("#select9") + .should("have.attr", "opened"); + + cy.get("#select9") + .find("[ui5-option]") + .eq(1) + .realClick(); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "select4=Option 1&select5=option2&select6=&select7=Option 1&select8=option2&select9=option2"); + }); + + it("ui5-slider in form", () => { + cy.mount(html`
+ + + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "slider3=0&slider4=100"); + }); + + it("ui5-step-input in form", () => { + cy.mount(html`
+ + + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "step_input3=0&step_input4=4"); + }); + + it("ui5-switch in form", () => { + cy.mount(html`
+ + + + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + + cy.get("#switch5") + .realClick(); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "switch4=on&switch5=on"); + }); + + it("ui5-textarea in form", () => { + cy.mount(html`
+ + + + + + +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + + cy.get("#textarea5") + .realClick(); + + cy.get("#textarea5") + .realType("ok", { delay: 100 }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "textarea3=&textarea4=ok&textarea5=ok"); + }); + + it("ui5-time-picker in form", () => { + /* eslint-disable no-irregular-whitespace */ + cy.mount(html`
+ + + + + +
`); + /* eslint-enable no-irregular-whitespace */ + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + + cy.get("#time_picker3") + .realClick(); + + cy.get("#time_picker3") + .realType("ok", { delay: 100 }); + + cy.get("button") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "time_picker3=ok&time_picker4=1:10:10 PM"); + }); + + it("Normal button does not submit forms", () => { + cy.mount(html`
+ Does not submit forms +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("#b1") + .realClick(); + + cy.get("@submit") + .should("have.not.been.called"); + }); + + it("Submit button does submit forms", () => { + cy.mount(html`
+ + + + Cozy + Compact + Condensed + +

+ + +

+ + +

+ + +

+ + + +

+ + +

+ Does not submit forms + Submits forms +
`); + + cy.get("form") + .then($item => { + $item.get(0).addEventListener("submit", e => e.preventDefault()); + $item.get(0).addEventListener("submit", cy.stub().as("submit")); + }); + + cy.get("#ta") + .realClick(); + + cy.get("#b2") + .realClick(); + + cy.get("@submit") + .should("have.been.called"); + + cy.get("form") + .then($el => { + return getFormData($el.get(0)); + }) + .should("be.equal", "input=ok&sel=condensed&ta=ok&dp=Apr 10, 2019&cb=on&radio=b&si=5"); + }); +}); diff --git a/packages/main/cypress/specs/LitKeyFunction.cy.ts b/packages/main/cypress/specs/LitKeyFunction.cy.ts new file mode 100644 index 000000000000..367b47cf0609 --- /dev/null +++ b/packages/main/cypress/specs/LitKeyFunction.cy.ts @@ -0,0 +1,50 @@ +import { html } from "lit"; +import "../../src/MultiComboBox.js"; +import "../../src/MultiComboBoxItem.js"; + +describe("Lit HTML key function for #each", () => { + it("LIT HTML does not mess up keys when looping over lists", () => { + cy.mount(html` + + + + +`); + + cy.get("#mcb") + .as("mcb") + .realClick(); + + cy.realPress("u"); + cy.realPress("s"); + cy.realPress("a"); + + cy.get("@mcb") + .shadow() + .find(".ui5-multi-combobox-all-items-responsive-popover") + .as("popover"); + + cy.get("@popover") + .find(".ui5-multi-combobox-all-items-list > ui5-li") + .as("items"); + + cy.get("@items") + .eq(0) + .realClick(); + + cy.get("@mcb") + .shadow() + .find(".inputIcon") + .realClick(); + + cy.get("@items") + .eq(0) + .should("contain.text", "") + .should("not.have.attr", "selected"); + + cy.get("@items") + .eq(3) + .should("contain.text", "USA") + .should("have.attr", "selected"); + }); +}); diff --git a/packages/main/cypress/specs/RTL.cy.ts b/packages/main/cypress/specs/RTL.cy.ts new file mode 100644 index 000000000000..6a171fe9dfb6 --- /dev/null +++ b/packages/main/cypress/specs/RTL.cy.ts @@ -0,0 +1,19 @@ +import { html } from "lit"; +import "../../src/CheckBox.js"; + +describe("RTL", () => { + it("tests effectiveDir", () => { + cy.mount(html`
+ + +
`); + + cy.get("#cbRTL") + .invoke("prop", "effectiveDir") + .should("be.equal", "rtl"); + + cy.get("#cbLTR") + .invoke("prop", "effectiveDir") + .should("be.equal", "ltr"); + }); +}); diff --git a/packages/main/test/specs/FormSupport.spec.js b/packages/main/test/specs/FormSupport.spec.js deleted file mode 100644 index 48968031035b..000000000000 --- a/packages/main/test/specs/FormSupport.spec.js +++ /dev/null @@ -1,446 +0,0 @@ -import { assert } from "chai"; - -describe("Form support", () => { - it("ui5-checkbox in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#cb_btn1"); - await submitBtn.click(); - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - - - assert.ok(hrefIsSame, "Form wasn't submitted due not filled required field"); - - const cb5 = await browser.$("#cb5"); - await cb5.click(); - - await submitBtn.click(); - - hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?checkbox4=on&checkbox5=on")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-color-picker in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#color_picker_btn1"); - await submitBtn.click(); - - const hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?color_picker3=rgba%28255%2C255%2C255%2C1%29&color_picker4=blue")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-combobox in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#combobox_btn1"); - await submitBtn.click(); - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - - assert.ok(hrefIsSame, "Form wasn't submitted due not filled required field"); - - const combobox5 = await browser.$("#combobox5"); - await combobox5.click(); - await browser.keys(["o", "k"]) - - await submitBtn.click(); - - hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?combobox3=&combobox4=ok&combobox5=ok")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-date-picker in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#date_picker_btn1"); - await submitBtn.click(); - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - - assert.ok(hrefIsSame, "Form wasn't submitted due not filled required field"); - - const date_picker5 = await browser.$("#date_picker5"); - await date_picker5.click(); - await browser.keys(["o", "k"]) - - await submitBtn.click(); - - hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?date_picker3=&date_picker4=ok&date_picker5=ok")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-daterange-picker in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#daterange_picker_btn1"); - await submitBtn.click(); - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - - assert.ok(hrefIsSame, "Form wasn't submitted due not filled required field"); - - const daterange_picker5 = await browser.$("#daterange_picker5"); - await daterange_picker5.click(); - await browser.keys(["o", "k"]) - - await submitBtn.click(); - - hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?daterange_picker3=&daterange_picker4=ok&daterange_picker5=ok")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-datetime-picker in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#datetime_picker_btn1"); - await submitBtn.click(); - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - - assert.ok(hrefIsSame, "Form wasn't submitted due not filled required field"); - - const datetime_picker5 = await browser.$("#datetime_picker5"); - await datetime_picker5.click(); - await browser.keys(["o", "k"]) - - await submitBtn.click(); - - hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?datetime_picker3=&datetime_picker4=ok&datetime_picker5=ok")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-input in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#input_btn1"); - await submitBtn.click(); - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - - assert.ok(hrefIsSame, "Form wasn't submitted due not filled required field"); - - const input5 = await browser.$("#input5"); - await input5.click(); - await browser.keys(["o", "k"]) - - await submitBtn.click(); - - hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?input3=&input4=ok&input5=ok")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-multi-combobox in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#multi_combobox_btn1"); - await submitBtn.click(); - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - - assert.ok(hrefIsSame, "Form wasn't submitted due not filled required field"); - - const multi_combobox9 = await browser.$("#multi_combobox9"); - await multi_combobox9.click(); - await browser.keys(["o", "k"]) - - await submitBtn.click(); - - hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?multi_combobox5=&multi_combobox6=ok&multi_combobox7=&multi_combobox7=ok&multi_combobox8=ok&multi_combobox8=ok&multi_combobox9=ok&multi_combobox10=ok&multi_combobox11=&multi_combobox11=ok&multi_combobox12=ok&multi_combobox12=ok")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-multi-input in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#multi_input_btn1"); - await submitBtn.click(); - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - - assert.ok(hrefIsSame, "Form wasn't submitted due not filled required field"); - - const multi_input9 = await browser.$("#multi_input9"); - await multi_input9.click(); - await browser.keys(["o", "k"]) - - await submitBtn.click(); - - hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?multi_input5=&multi_input6=ok&multi_input7=&multi_input7=ok&multi_input8=ok&multi_input8=ok&multi_input9=ok&multi_input10=ok&multi_input11=&multi_input11=ok&multi_input12=ok&multi_input12=ok")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-radio-button in form 1", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#rb_btn1"); - await submitBtn.click(); - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - - assert.ok(hrefIsSame, "Form wasn't submitted due not filled required field"); - - const rb_1 = await browser.$("#rb_1"); - await rb_1.click(); - - await submitBtn.click(); - - hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?rb1=on")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-radio-button in form 2", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#rb_btn2"); - await submitBtn.click(); - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?")); - }); - - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-radio-button in form 3", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#rb_btn3"); - await submitBtn.click(); - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - - assert.ok(hrefIsSame, "Form wasn't submitted due not filled required field"); - - const rb_4 = await browser.$("#rb_4"); - await rb_4.click(); - - await submitBtn.click(); - - hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?rb3=B")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-range-slider in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#range_slider_btn1"); - await submitBtn.click(); - - const hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?range_slider3=0&range_slider3=100&range_slider4=25&range_slider4=75")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-select in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#select_btn1"); - await submitBtn.click(); - - - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - - assert.ok(hrefIsSame, "Form wasn't submitted due not filled required field"); - - const select9 = await browser.$("#select9"); - await select9.click(); - await browser.keys("ArrowUp"); - await browser.keys("Enter"); - - await submitBtn.click(); - - hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?select4=Option+1&select5=option2&select6=&select7=Option+1&select8=option2&select9=option2")); - }); - - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-slider in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#slider_btn1"); - await submitBtn.click(); - - const hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?slider3=0&slider4=100")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-step-input in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#step_input_btn1"); - await submitBtn.click(); - - const hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?step_input3=0&step_input4=4")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-switch in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#switch_btn1"); - await submitBtn.click(); - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - - assert.ok(hrefIsSame, "Form wasn't submitted due not filled required field"); - - const switch5 = await browser.$("#switch5"); - await switch5.click(); - - await submitBtn.click(); - - hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?switch4=on&switch5=on")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-textarea in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#textarea_btn1"); - await submitBtn.click(); - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - - assert.ok(hrefIsSame, "Form wasn't submitted due not filled required field"); - - const textarea5 = await browser.$("#textarea5"); - await textarea5.click(); - await browser.keys(["o", "k"]) - - await submitBtn.click(); - - hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?textarea3=&textarea4=ok&textarea5=ok")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("ui5-time-picker in form", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const submitBtn = await browser.$("#time_picker_btn1"); - await submitBtn.click(); - - let hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - - assert.ok(hrefIsSame, "Form wasn't submitted due not filled required field"); - - const time_picker3 = await browser.$("#time_picker3"); - await time_picker3.click(); - - await browser.keys(["o", "k"]) - - await submitBtn.click(); - - hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html?time_picker3=ok&time_picker4=1%3A10%3A10%E2%80%AFPM")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("Normal button does not submit forms", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const noSubmitButton = await browser.$("#b1"); - await noSubmitButton.click(); - - const hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - assert.ok(hrefIsSame, "Form was submitted with correct parameters"); - }); - - it("Submit button does submit forms", async () => { - await browser.url(`test/pages/FormSupport.html`); - - // Enter multiline text in TextArea - const textarea = await browser.$("#ta"); - await textarea.click() - await browser.keys("Enter"); - await browser.keys("o"); - await browser.keys("k"); - - const submitButton = await browser.$("#b2"); - await submitButton.click(); - - const formWasSubmitted = await browser.executeAsync(done => { - const expectedFormData = "?input=ok&sel=condensed&ta=ok%0D%0Aok&dp=Apr+10%2C+2019&cb=on&radio=b&si=5"; - done(location.href.endsWith(expectedFormData)); - }); - assert.ok(formWasSubmitted, "For was submitted and URL changed"); - }); - - it("Prevent default on submit event", async () => { - await browser.url(`test/pages/FormSupport.html`); - - const noSubmitButton = await browser.$("#b3"); - await noSubmitButton.click(); - - const hrefIsSame = await browser.executeAsync(done => { - done(location.href.endsWith("FormSupport.html")); - }); - assert.ok(hrefIsSame, "Form is not submitted when prevent default is called"); - }); -}); diff --git a/packages/main/test/specs/LitKeyFunction.spec.js b/packages/main/test/specs/LitKeyFunction.spec.js deleted file mode 100644 index dadd0d04f6d2..000000000000 --- a/packages/main/test/specs/LitKeyFunction.spec.js +++ /dev/null @@ -1,39 +0,0 @@ -import { assert } from "chai"; - -describe("Lit HTML key function for #each", async () => { - before(async () => { - await browser.url(`test/pages/LitKeyFunction.html`); - }); - - it("LIT HTML does not mess up keys when looping over lists", async () => { - // Focus the input - const input = await browser.$("#mcb").shadow$("[inner-input]"); - await input.click(); - - // Type "usa" - await input.keys("u"); - await input.keys("s"); - await input.keys("a"); - - // Click on the first item - const popover = await browser.$("#mcb").shadow$(".ui5-multi-combobox-all-items-responsive-popover"); - const firstItem = await popover.$$(".ui5-multi-combobox-all-items-list > ui5-li")[0]; - await firstItem.click(); - - // Open the popover with the arrow - const icon = await browser.$("#mcb").shadow$(".inputIcon"); - await icon.click(); - - // The first item () should not be selected - const newFirstItem = (await popover.$$(".ui5-multi-combobox-all-items-list > ui5-li"))[0]; - const newFirstItemHtml = await newFirstItem.getHTML(false); - assert.include(newFirstItemHtml, "empty", "First item is "); - assert.notOk(await newFirstItem.getProperty("selected"), " is not selected"); - - // The last item (USA) should be selected - const lastItem = (await popover.$$(".ui5-multi-combobox-all-items-list > ui5-li"))[3]; - const lastItemHtml = await lastItem.getHTML(false); - assert.include(lastItemHtml, "USA", "Last item is USA"); - assert.ok(await lastItem.getProperty("selected"), "USA is selected"); - }); -}); diff --git a/packages/main/test/specs/RTL.spec.js b/packages/main/test/specs/RTL.spec.js deleted file mode 100644 index 6256afd64294..000000000000 --- a/packages/main/test/specs/RTL.spec.js +++ /dev/null @@ -1,10 +0,0 @@ -import { assert } from "chai"; - -describe("RTL", () => { - it("tests effectiveDir", async () => { - await browser.url(`test/pages/RTL.html`); - - assert.strictEqual(await browser.$("#cbRTL").getProperty("effectiveDir"), "rtl", "effectiveDir correctly returns 'rtl'"); - assert.strictEqual(await browser.$("#cbLTR").getProperty("effectiveDir"), "ltr", "effectiveDir correctly returns 'ltr'"); - }); -}); From ae7d8cee44e25807af12f0bb7bf68b05a6510c80 Mon Sep 17 00:00:00 2001 From: ilhan orhan Date: Tue, 10 Sep 2024 11:13:53 +0300 Subject: [PATCH 007/114] chore: migrate F6 tests from wdio to cypress (#9834) --- packages/main/cypress/specs/F6.cy.ts | 659 ++++++++++++++++++++++++ packages/main/test/specs/F6Test.spec.js | 241 --------- 2 files changed, 659 insertions(+), 241 deletions(-) create mode 100644 packages/main/cypress/specs/F6.cy.ts delete mode 100644 packages/main/test/specs/F6Test.spec.js diff --git a/packages/main/cypress/specs/F6.cy.ts b/packages/main/cypress/specs/F6.cy.ts new file mode 100644 index 000000000000..31ae4d7b5e8d --- /dev/null +++ b/packages/main/cypress/specs/F6.cy.ts @@ -0,0 +1,659 @@ +import { html } from "lit"; +import "@ui5/webcomponents-base/dist/features/F6Navigation.js"; +import "../../src/Button.js"; + +describe("F6 navigation", () => { + describe("F6 Forward navigation", () => { + it("tests navigation", () => { + cy.mount(html`
+
+ +
+
+ First focusable +
+
+ Something focusable +
+
+ Second focusable +
+
+ Something focusable +
+
+ Third focusable +
+
+ After Element +
+ { + cy.mount(html`
+
+ +
+
+ First focusable +
+
+ Something focusable +
+
+ Group without focusable element +
+
+ Something focusable +
+
+ Second focusable +
+
+ After Element +
+ { + cy.mount(html`
+ +
+
+ First focusable +
+ Second focusable +
+
+
+ Something focusable +
+
+ Third focusable +
+
+ After Element +
`); + + // act + cy.get("#before").focus(); + cy.realPress("F6"); + + // assert 1st group is focused + cy.get("#first") + .should("be.focused"); + + // act + cy.realPress("F6"); + + // assert 2nd group is focused (an empty group is skipped) + cy.get("#second") + .should("be.focused"); + + // act + cy.realPress("F6"); + + // assert 3rd group is focused + cy.get("#third") + .should("be.focused"); + + // act + cy.realPress("F6"); + + // assert 1st group is focused agian + cy.get("#first") + .should("be.focused"); + }); + + it("tests navigation with nesting inside empty fastnav-group parent", () => { + cy.mount(html`
+ +
+
+
+ First focusable +
+
+
+ Something focusable +
+
+
+ First focusable +
+
+
+ Something focusable +
+
+ Second focusable +
+
+ After Element +
`); + + // act + cy.get("#before").focus(); + cy.realPress("F6"); + + // assert 1st group is focused + cy.get("#first") + .should("be.focused"); + + // act + cy.realPress("F6"); + + // assert 2nd group is focused (an empty group is skipped) + cy.get("#second") + .should("be.focused"); + + // act + cy.get("#second").realPress("F6"); + + // assert 3rd group is focused + cy.get("#third") + .should("be.focused"); + + // act + cy.realPress("F6"); + + // assert 1st group is focused agian + cy.get("#first") + .should("be.focused"); + }); + + it("tests navigation with group as a focusable element", () => { + cy.mount(html`
+ +
+
+ First focusable +
+
+ Something focusable +
+
+ Second focusable +
+
+ Something focusable +
+
+ Third focusable +
+
+ After Element +
`); + + // act + cy.get("#before").focus(); + cy.realPress("F6"); + + // assert 1st group is focused + cy.get("#first") + .should("be.focused"); + + // act + cy.get("#first").realPress("F6"); + + // assert 2nd group is focused (an empty group is skipped) + cy.get("#second") + .should("be.focused"); + + // act + cy.realPress("F6"); + + // assert 3rd group is focused + cy.get("#third") + .should("be.focused"); + + // act + cy.realPress("F6"); + + // assert 1st group is focused agian + cy.get("#first") + .should("be.focused"); + }); + + it("tests navigation without a focusable element", () => { + cy.mount(html`
+ Before element +
+
+ Group without focusable element +
+
+ Something focusable +
+
+ Group without focusable element +
+
+ Something focusable +
+
+ After Element +
`); + + // act + cy.get("#first") + .realClick(); + + // assert clicked btn is also the focused element + cy.get("#first") + .should("be.focused"); + + // act + cy.realPress("F6"); + + // assert same button remains focused as there is no fasnav group with focusable elements + cy.get("#first") + .should("be.focused"); + }); + + it("tests navigation with a single group", () => { + cy.mount(html`
+ +
+
+ Before element +
+
+ Something focusable +
+
+ Something focusable +
+
+ After Element +
`); + + // act + cy.get("#before").focus(); + cy.realPress("F6"); + + // assert 1st group is focused + cy.get("#first") + .should("be.focused"); + }); + }); + + describe("F6 Backward navigation", () => { + it("tests navigation", () => { + cy.mount(html`
+
+ +
+
+ First focusable +
+
+ Something focusable +
+
+ Second focusable +
+
+ Something focusable +
+
+ Third focusable +
+
+ After Element +
+ { + cy.mount(html`
+
+ +
+
+ First focusable +
+
+ Something focusable +
+
+ Group without focusable element +
+
+ Something focusable +
+
+ Second focusable +
+
+ After Element +
+ { + cy.mount(html`
+ +
+
+ First focusable +
+ Second focusable +
+
+
+ Something focusable +
+
+ Third focusable +
+
+ After Element +
`); + + // act + cy.get("#before").focus(); + cy.realPress(["Shift", "F6"]); + + // assert 3rd group is focused + cy.get("#third") + .should("be.focused"); + + // act + cy.realPress(["Shift", "F6"]); + + // assert 2nd group is focused (an empty group is skipped) + cy.get("#second") + .should("be.focused"); + + // act + cy.realPress(["Shift", "F6"]); + + // assert 1st group is focused + cy.get("#first") + .should("be.focused"); + + // act + cy.realPress(["Shift", "F6"]); + + // assert 3rd group is focused agian + cy.get("#third") + .should("be.focused"); + }); + + it("tests navigation with nesting inside empty fastnav-group parent", () => { + cy.mount(html`
+ +
+
+
+ First focusable +
+
+
+ Something focusable +
+
+
+ First focusable +
+
+
+ Something focusable +
+
+ Second focusable +
+
+ After Element +
`); + + // act + cy.get("#before").focus(); + cy.realPress(["Shift", "F6"]); + + // assert 3rd group is focused + cy.get("#third") + .should("be.focused"); + + // act + cy.realPress(["Shift", "F6"]); + + // assert 2nd group is focused (an empty group is skipped) + cy.get("#second") + .should("be.focused"); + + // act + cy.realPress(["Shift", "F6"]); + + // assert 1st group is focused + cy.get("#first") + .should("be.focused"); + + // act + cy.realPress(["Shift", "F6"]); + + // assert 1st group is focused agian + cy.get("#third") + .should("be.focused"); + }); + + it("tests navigation with group as a focusable element", () => { + cy.mount(html`
+ +
+
+ First focusable +
+
+ Something focusable +
+
+ Second focusable +
+
+ Something focusable +
+
+ Third focusable +
+
+ After Element +
`); + + // act + cy.get("#before").focus(); + cy.realPress(["Shift", "F6"]); + + // assert 3rd group is focused + cy.get("#third") + .should("be.focused"); + + // act + cy.realPress(["Shift", "F6"]); + + // assert 2nd group is focused (an empty group is skipped) + cy.get("#second") + .should("be.focused"); + + // act + cy.realPress(["Shift", "F6"]); + + // assert 1st group is focused + cy.get("#first") + .should("be.focused"); + + // act + cy.realPress(["Shift", "F6"]); + + // assert 3rd group is focused agian + cy.get("#third") + .should("be.focused"); + }); + + it("tests navigation without a focusable element", () => { + cy.mount(html`
+ Before element +
+
+ Group without focusable element +
+
+ Something focusable +
+
+ Group without focusable element +
+
+ Something focusable +
+
+ After Element +
`); + + // act + cy.get("#first") + .realClick(); + + // assert clicked btn is also the focused element + cy.get("#first") + .should("be.focused"); + + // act + cy.realPress(["Shift", "F6"]); + + // assert same button remains focused as there is no fasnav group with focusable elements + cy.get("#first") + .should("be.focused"); + }); + + it("tests navigation with a single group", () => { + cy.mount(html`
+ +
+
+ Before element +
+
+ Something focusable +
+
+ Something focusable +
+
+ After Element +
`); + + // act + cy.get("#before").focus(); + cy.realPress(["Shift", "F6"]); + + // assert 1st group is focused + cy.get("#first") + .should("be.focused"); + }); + }); +}); diff --git a/packages/main/test/specs/F6Test.spec.js b/packages/main/test/specs/F6Test.spec.js deleted file mode 100644 index e01292f79a75..000000000000 --- a/packages/main/test/specs/F6Test.spec.js +++ /dev/null @@ -1,241 +0,0 @@ -import { assert } from "chai"; - -describe("F6 Test", () => { - describe("Forward", () => { - it("Basic", async () => { - await browser.url(`test/pages/F6Test1.html`); - - // Go to next group - await browser.keys("F6"); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys("F6"); - assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys("F6"); - assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to first group (circle) - await browser.keys("F6"); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - }); - - it("Basic with an empty group", async () => { - await browser.url(`test/pages/F6Test2.html`); - - // Go to next group - await browser.keys("F6"); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys("F6"); - assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to first group (circle) - await browser.keys("F6"); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - }); - - it("Nested groups", async () => { - await browser.url(`test/pages/F6Test3.html`); - - // Go to next group - await browser.keys("F6"); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys("F6"); - assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys("F6"); - assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to first group (circle) - await browser.keys("F6"); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - }); - - it("Nested inside empty fastnav-group parent", async () => { - await browser.url(`test/pages/F6Test4.html`); - - // Go to next group - await browser.keys("F6"); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys("F6"); - assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys("F6"); - assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to first group (circle) - await browser.keys("F6"); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct "); - }); - - it("Basic with group as focusable element", async () => { - await browser.url(`test/pages/F6Test5.html`); - - // Go to next group - await browser.keys("F6"); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys("F6"); - assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys("F6"); - assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to first group (circle) - await browser.keys("F6"); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - }); - - it("Groups without focusable element", async () => { - await browser.url(`test/pages/F6Test6.html`); - - const button = await browser.$("#first"); - - await button.click(); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys("F6"); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - }); - - it("One group", async () => { - await browser.url(`test/pages/F6Test7.html`); - - // Go to next group - await browser.keys("F6"); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - }); - }) - - describe("Backward", () => { - it("Basic", async () => { - await browser.url(`test/pages/F6Test1.html`); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to first group (circle) - await browser.keys(["Shift", "F6"]); - assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - }); - - it("Basic with an empty group", async () => { - await browser.url(`test/pages/F6Test2.html`); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to first group (circle) - await browser.keys(["Shift", "F6"]); - assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - }); - - it("Nested groups", async () => { - await browser.url(`test/pages/F6Test3.html`); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to first group (circle) - await browser.keys(["Shift", "F6"]); - assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - }); - - it("Nested inside empty fastnav-group parent", async () => { - await browser.url(`test/pages/F6Test4.html`); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to first group (circle) - await browser.keys(["Shift", "F6"]); - assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct "); - }); - - it("Basic with group as focusable element", async () => { - await browser.url(`test/pages/F6Test5.html`); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("second", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to first group (circle) - await browser.keys(["Shift", "F6"]); - assert.equal("third", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - }); - - it("Groups without focusable element", async () => { - await browser.url(`test/pages/F6Test6.html`); - - const button = await browser.$("#first"); - - await button.click(); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - }); - - it("One group", async () => { - await browser.url(`test/pages/F6Test7.html`); - - // Go to next group - await browser.keys(["Shift", "F6"]); - assert.equal("first", await browser.$(await browser.getActiveElement()).getAttribute("id"), "correct focus"); - }); - }) -}); From daa3399654b81fb51259129e493708b06b6ee8ff Mon Sep 17 00:00:00 2001 From: ilhan orhan Date: Tue, 10 Sep 2024 11:27:35 +0300 Subject: [PATCH 008/114] fix(ui5-table): add aria reference to growing btn sub text (#9836) The moreDataText has never been assigned, probably a degradation in the past after growingButtonSubText was introduced. Due to this even if growingButtonSubText is set, the span ({this._id}-growingButton-subtext) that is internally created for a11y reasons has never been referenced and read out by screen readers. Related to: #9807 --- packages/compat/src/Table.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/compat/src/Table.ts b/packages/compat/src/Table.ts index 36e95c6eb288..2620f2f6fd51 100644 --- a/packages/compat/src/Table.ts +++ b/packages/compat/src/Table.ts @@ -440,7 +440,6 @@ class Table extends UI5Element { fnOnRowFocused: (e: CustomEvent) => void; _handleResize: ResizeObserverCallback; - moreDataText?: string; tableEndObserved: boolean; visibleColumns: Array; visibleColumnsCount?: number; @@ -1161,7 +1160,7 @@ class Table extends UI5Element { } get loadMoreAriaLabelledBy(): string { - if (this.moreDataText) { + if (this.growingButtonSubtext) { return `${this._id}-growingButton-text ${this._id}-growingButton-subtext`; } From 1ebf7db3faa8a2be1d35347407a365f5cf3004de Mon Sep 17 00:00:00 2001 From: Nayden Naydenov <31909318+nnaydenow@users.noreply.github.com> Date: Tue, 10 Sep 2024 14:06:39 +0300 Subject: [PATCH 009/114] fix(f6 navigation): work properly when multiple runtimes (#9810) F6 navigation adds `keydown` handler to the body element that will move the focus to the previous / next group that has focusable element. If F6 navigation feature is enabled in multiple runtimes this handler will be attach multiple times. With current change, we ensure that only one callback will be executed to move the focus to the correct element. This callback is coming from the latest registered runtime (newest version). Fixes: https://github.com/SAP/ui5-webcomponents/issues/9795 --- packages/base/src/features/F6Navigation.ts | 39 +++++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/base/src/features/F6Navigation.ts b/packages/base/src/features/F6Navigation.ts index a8f0e819d0b2..a67b65eb95c2 100644 --- a/packages/base/src/features/F6Navigation.ts +++ b/packages/base/src/features/F6Navigation.ts @@ -4,9 +4,23 @@ import { instanceOfUI5Element } from "../UI5Element.js"; import { getFirstFocusableElement } from "../util/FocusableElements.js"; import getFastNavigationGroups from "../util/getFastNavigationGroups.js"; import isElementClickable from "../util/isElementClickable.js"; +import { getCurrentRuntimeIndex, compareRuntimes } from "../Runtimes.js"; +import getSharedResource from "../getSharedResource.js"; + +type F6Registry = { + instance?: F6Navigation, +} + +const currentRuntimeINdex = getCurrentRuntimeIndex(); + +const shouldUpdate = (runtimeIndex: number | undefined) => { + if (runtimeIndex === undefined) { + return true; + } + return compareRuntimes(currentRuntimeINdex, runtimeIndex) === 1; // 1 means the current is newer, 0 means the same, -1 means the resource's runtime is newer +}; class F6Navigation { - static _instance: F6Navigation; keydownHandler: (event: KeyboardEvent) => void; selectedGroup: HTMLElement | null = null; groups: Array = []; @@ -20,6 +34,10 @@ class F6Navigation { document.addEventListener("keydown", this.keydownHandler); } + removeEventListeners() { + document.removeEventListener("keydown", this.keydownHandler); + } + async groupElementToFocus(nextElement: HTMLElement) { const nextElementDomRef = instanceOfUI5Element(nextElement) ? nextElement.getDomRef() : nextElement; @@ -149,10 +167,6 @@ class F6Navigation { elementToFocus?.focus(); } - removeEventListeners() { - document.removeEventListener("keydown", this.keydownHandler); - } - updateGroups() { this.setSelectedGroup(); this.groups = getFastNavigationGroups(document.body); @@ -181,12 +195,19 @@ class F6Navigation { this.removeEventListeners(); } + get _ui5RuntimeIndex() { + return currentRuntimeINdex; + } + static init() { - if (!this._instance) { - this._instance = new F6Navigation(); - } + const f6Registry = getSharedResource("F6Registry", {}); - return this._instance; + if (!f6Registry.instance) { + f6Registry.instance = new F6Navigation(); + } else if (shouldUpdate(f6Registry.instance?._ui5RuntimeIndex)) { + f6Registry.instance?.destroy(); + f6Registry.instance = new F6Navigation(); + } } } From 5ba5c9b0291240d46c0bcb5182fd0d6c34b7db8a Mon Sep 17 00:00:00 2001 From: ilhan orhan Date: Tue, 10 Sep 2024 14:48:10 +0300 Subject: [PATCH 010/114] docs: enhance deep dive section (#9816) Related to: #9190 --- docs/2-advanced/11-styles.md | 2 +- docs/3-frameworks/03-Vue.md | 20 +- docs/4-development/04-slots.md | 50 +- .../11-deep-dive-and-best-practices.md | 495 +++++++++++++++++- 4 files changed, 525 insertions(+), 42 deletions(-) diff --git a/docs/2-advanced/11-styles.md b/docs/2-advanced/11-styles.md index 8f10e5ec5b31..dbfe5ec24af6 100644 --- a/docs/2-advanced/11-styles.md +++ b/docs/2-advanced/11-styles.md @@ -17,7 +17,7 @@ We designed some components such as Title, Label, Tag, Button, Input, and a few background: purple; } ``` -You can try this yourself on the Input [test page](https://sap.github.io/ui5-webcomponents/main/playground/main/pages/Input/). +You can try this yourself using the Input [sample](https://sap.github.io/ui5-webcomponents/components/Input/#custom-styling/). Unfortunately, this can't be done for all components because it depends on the complexity of the DOM structure. diff --git a/docs/3-frameworks/03-Vue.md b/docs/3-frameworks/03-Vue.md index 0287c4cf5b4e..31daa04d8eee 100644 --- a/docs/3-frameworks/03-Vue.md +++ b/docs/3-frameworks/03-Vue.md @@ -3,7 +3,6 @@ In this tutorial you will learn how to add UI5 Web Components to your application. You can add UI5 Web Components both to new Vue.js applications and to already existing ones. ## Setting up a Vite and Vue.js project with UI5 Web Components -
### Step 1. Setup a Vue project with Vite. @@ -55,15 +54,20 @@ import "@ui5/webcomponents/dist/Button.js"; ```bash npm run dev ``` -## Additional Info -### Two-Way Data Binding -`v-model` binding doesn't work for custom elements. In order to use two-way data binding, you need to bind and update the value yourself like this: +## Two-Way Data Binding + +In order to use two-way data binding, use `v-model` as follows: + +```html + +``` + +For the `CheckBox` and `RadioButton` web components, you need to include an additional `type` attribute. This informs the Vue compiler that these components use the `checked` property (unlike most input-type components that use the `value` property). ```html - - + + ``` + diff --git a/docs/4-development/04-slots.md b/docs/4-development/04-slots.md index 8d20f172e2ba..b43267a7f2eb 100644 --- a/docs/4-development/04-slots.md +++ b/docs/4-development/04-slots.md @@ -8,14 +8,26 @@ Currently, there are two types of slots: "named" and "unnamed". The difference b ## Unnamed slots -Use unnamed slots when your component does not need to know if any children have been passed to a certain slot, or generally interact with its children from the said slot. - -To define an unnamed slot, you simply add a `` element inside your `.hbs` template, for example: +Use unnamed slots when your component doesn't need to be aware of or interact with the children passed to a specific slot. +To define an unnamed slot, simply add a `` element within your `.hbs` template. For example: ```hbs - +{{!-- MyDemoComponent.hbs --}} +
+ +
``` +On the consuming side, elements can be passed to this slot using the `slot` attribute: + +```html + + + Hello World + +``` + + **Note:** It is recommended to describe your unnamed slots inside a JSDoc comment that describes your class using the `@slot` tag, following the pattern `@slot {type} name - description`. ## Named slots and the `@slot` decorator @@ -39,15 +51,15 @@ import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; @customElement("my-demo-component") class MyDemoComponent extends UI5Element { - @slot({ type: HTMLElement }) + @slot() mySlot!: Array; } ``` You can see the available options below. -### type -This option is required and accepts a type constructor (e.g., `HTMLElement`, `Node`) and is used to define the type of children that can be slotted inside the slot. +### Type +The `type` option accepts a type constructor (e.g., `HTMLElement`, `Node`) and is used to define the type of children that can be slotted inside the slot. ```ts import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; @@ -67,10 +79,14 @@ Available types: | HTMLElement | Accepts HTML Elements only | | Node | Accepts both Text nodes and HTML Elements | +**Note**: If the slot configuration object is not provided (e.g. `@slot()`), `HTMLElement` will be used as the default type. + + ### Default Slot -This option accepts a boolean value and is used to define whether this slot is the default one. -**Note:** The default slot is defined simply as an empty slot tag: `` (without a `name` attribute). +The `"default"` option accepts a boolean value and is used to define whether this slot is the default one. + +**Note:** The default slot is defined simply as an empty slot tag `` (without a `name` attribute) in the component's template. ```ts import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; @@ -79,13 +95,14 @@ import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; @customElement("my-demo-component") class MyDemoComponent extends UI5Element { - @slot({ type: HTMLElement, default: true }) + @slot({ type: HTMLElement, "default": true }) mySlot!: Array; } ``` -### `individualSlots` -This option accepts a boolean value and determines whether each child will have its own slot, allowing you to arrange or wrap the children arbitrarily. This means that you need to handle the rendering on your own. +### Individual Slots + +The `individualSlots` option accepts a boolean value and determines whether each child will have its own slot, allowing you to arrange or wrap the children arbitrarily. This means that you need to handle the rendering on your own. ```ts import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; @@ -99,7 +116,7 @@ class MyDemoComponent extends UI5Element { } ``` -To render individual slots, you have to iterate all children in that slot and use the `_individualSlot` property that the framework sets automatically set on each child: +To render individual slots, you have to iterate all children in that slot and use the `_individualSlot` property that the framework sets automatically on each child: ```hbs {{#each mySlot}} @@ -109,12 +126,11 @@ To render individual slots, you have to iterate all children in that slot and us **Note:** When this option is set to `true`, the `_individualSlot` property is set to each direct child, where `_individualSlot` returns a string following the pattern `{nameOfTheSlot}-{index}` and the slot attribute is changed based on that pattern. -### `invalidateOnChildChange` -This option accepts a boolean value or an object literal containing a configuration with more specific settings, determining whether the component should be invalidated on child change. +### Invalidation upon child changes -**NOTE: This is an experimental option and should not be used.** +The `invalidateOnChildChange` option accepts a boolean value or an object literal containing a configuration with more specific settings, determining whether the component should be invalidated on child change. -Important: `invalidateOnChildChange` is not meant to be used with standard DOM elements and is not to be confused with MutationObserver-like functionality. It targets the use case of components that slot abstract items (UI5Element instances without a template) and require invalidation whenever these items are invalidated. +**Note**: `invalidateOnChildChange` is not meant to be used with standard DOM elements and is not to be confused with MutationObserver-like functionality. It targets the use case of components that slot UI5Element instances and require invalidation whenever these items are invalidated. ```ts import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; diff --git a/docs/4-development/11-deep-dive-and-best-practices.md b/docs/4-development/11-deep-dive-and-best-practices.md index c19d141a126b..a278838b6435 100644 --- a/docs/4-development/11-deep-dive-and-best-practices.md +++ b/docs/4-development/11-deep-dive-and-best-practices.md @@ -100,10 +100,10 @@ or another type of selector (for example by ID): The framework will create a getter/setter pair on your component's prototype for each property, defined with `@property` decorator. -For example, defining text property: +For example, defining `text` property: ```ts -@property +@property() text = "" ``` @@ -118,9 +118,25 @@ Whenever `text` is read or set, the framework-defined getter/setter will be call #### Properties vs attributes -The `properties` section defines both properties and attributes for your component. By default, for each property (`camelCase` name) an attribute with the +The `properties` defined via the `@property` decorator results in both properties and attributes for your component. By default, for each property (`camelCase` name) an attribute with the same name but in `kebab-case` is supported. Properties of type `Object` have no attribute counterparts. If you wish to not have an attribute for a given property regardless of type, you can configure it with `noAttribute: true` setting. +For example, defining `headerText` property: + +```ts +@property() +headerText = "" +``` + +you can use both the `headerText` property and `header-text` attribute: + +```ts +let t = myComponent.text; +myComponent.headerText = "New text"; +myComponent.setAttrbite("header-text", "New text"); +``` + + #### Public vs private properties The framework does not distinguish between *public* and *private* properties. You can treat some properties as private in a sense that you can document them as such and not advertise them to users. @@ -131,11 +147,9 @@ are *component state*, therefore cause the component to be invalidated and subse The most common types of properties are `String`, `Boolean`, `Object`and `Number`. -Most property types can have a default but `Boolean` should always `false` by default. +Most property types can have a default value, but `Boolean `properties should always default to `false`. When a boolean attribute is absent, it's treated as false, therefore, the default value of an attribute must be always false. -#### Examples - -Example of defining properties: +For example, defining different types of properties: ```ts class MyComponent extends UI5Element { @@ -155,7 +169,7 @@ class MyComponent extends UI5Element { * @private */ @property({ type: Boolean }) - _isPhone = {}; + _isPhone = false; } ``` @@ -163,12 +177,14 @@ Here `text`, `width`, `scale` and `data` are public properties, and `_isPhone` p #### Best practices for using properties -The best practice is to **never** change public properties from within the component (they are owned by the application) unless the property changes due to user interaction (f.e. the user typed in an input - so you change the `value` property; or the user clicked a checkbox - and you flip the `checked` property). It is also -a best practice to always **fire an event** if you change a public property due to user interaction, to let the application know and synchronize its own state. +- **Аvoid directly modifying public properties** from within a component, as these properties are typically controlled by the parent application. The only exception to this rule is when the property change results directly from user interaction (e.g., updating a value after a user types in an input field, or toggling a checked property after a user clicks a checkbox). Additionally, whenever you modify a public property due to user interaction, it's important to **fire an event** to notify the parent application. This ensures that the application can synchronize its state accordingly. + +- As for private properties, the best practice is to **only** change them internally and never let the application know about their existence. + +- Using attribute selectors instead of setting and using CSS classes on your component. Both public and private properties are great ways to create CSS selectors for your component with the `:host()` selector. The `:host()` selector targets the custom element itself, and can be combined with other selectors. -As for private properties, the best practice is to **only** change them internally and never let the application know about their existence. +For example, using the `size` property (respectively the attribute with the same name) to change component's dimensions for certain values - `size="XS"`: -Both public and private properties are great ways to create CSS selectors for your component with the `:host()` selector. The `:host()` selector targets the custom element itself, and can be combined with other selectors: ```css :host { @@ -186,8 +202,6 @@ Both public and private properties are great ways to create CSS selectors for yo ``` -Here for example, if the `size` property (respectively the attribute with the same name) is set to `XS`, the component's dimensions will be changed from `5rem` to `2rem`. -Using attribute selectors is the best practice as you don't have to set CSS classes on your component - you can write CSS selectors with `:host()` by attribute. #### Metadata properties vs standard JS properties @@ -205,6 +219,455 @@ However, only metadata-defined properties are managed by the framework: cause in Feel free to create as many regular JS properties for the purpose of your component's functionality as you need, but bear in mind that they will not be managed by the framework. + +## Events + +Most UI5 components emit events to inform the application about user interactions. Defining and firing events involves several key aspects: + +### Describing the Event + +Use the `@event` decorator to define the event. If the event name consists of multiple words, use kebab-case: + +```ts +@event("selection-change", { + detail: { + valid: { type: Boolean }, + }, +}) +class MyDemoComponent extends UI5Element { +} +``` + +### Firing the Event + +Use the `UI5Element#fireEvent` method to trigger the event: + +```ts +@event("selection-change", { + detail: { + valid: { type: Boolean }, + }, +}) +class MyDemoComponent extends UI5Element { + onItemSelected(e: Event) { + this.fireEvent("selection-change", { + valid: true, + }); + } +} +``` + +### Describing the Event Detail + +When an event includes a detail it's recommended to create a TypeScript type that describes the event detail and use it in the `fireEvent` (as it's a generic method) to force static checks ensuring that proper event detail is passed. +The naming convention for the type is a combination of the component class name ("MyDemoComponent"), the event name ("SelectionChange"), followed by "EventDetail", written in PascalCase, e.g "MyDemoComponentSelectionChangeEventDetail": + + +```ts +export type MyDemoComponentSelectionChangeEventDetail = { + valid: boolean; +}; + + +@event("selection-change", { + detail: { + valid: { type: Boolean }, + }, +}) +class MyDemoComponent extends UI5Element { + + onItemSelected(e: Event) { + this.fireEvent("selection-change", { + valid: true, + }); + } +} +``` + +**Note:** it's a best practice to export the type to make it available for outside usage. + + +### Handling Events in Templates + + When attaching event handlers within your component's template for events fired by other web components, use the `ui5-` prefix for the event name. +For example, if a ui5-list component emits a `selection-change` event, handle it using the `ui5-selection-change` event name: + +```handlebars +
+ +
+``` + +By default, events are fired in pairs: one with the standard name and another prefixed with `ui5-`. While the `ui5-` prefixed event is always emitted, the non-prefixed event can be suppressed if the `noConflict` configuration setting is enabled. In this case, only the prefixed event will be triggered. For more details on the `noConflict` setting, refer to the [Configuration](../2-advanced/01-configuration.md) section. + +### Preventable Events + +It's common to prevent certain events in an application. You must enable the `cancelable` flag to make the event preventable. + +```ts +this.fireEvent("change", null, true /* cancelable */); +``` + +Sometimes, you may also need to update (or revert) the component's state when an event is prevented by the consuming side. To determine if an event was prevented, check the return value of the `fireEvent` method. It returns false if the event was cancelled (`preventDefault` was called) and true otherwise: + +```ts +class Switch extends UI5Element { + toggle() { + this.checked = !this.checked; + const changePrevented = !this.fireEvent("change", null, true /* cancelable */); + + if (changePrevented) { + this.checked = !this.checked; + } + } +} +``` + +## Slots + +Web Components offer a `slot` mechanism for component composition, allowing components to render children +or other components in specific locations within their shadow root. + +To enable slotting for your component, simply add a `` element within your `.hbs` template. +This acts as a placeholder that can be filled with any HTML markup. + +```hbs +{{!-- MyDemoComponent.hbs --}} +
+ +
+``` + +On the consuming side, you can insert HTML elements into your component: + +```html + + + Hello World + +``` + +For documentation purposes and to inform component consumers about the available slot, +we should describe it with a brief JSDoc comment at component class level as shown below: + +```ts +/* + * @slot {Array} default - Defines the content of the component. + */ +@customElement({ + tag: "ui5-demo-component", +}) +class MyDemoComponent extends UI5Element {} +``` + +### Slot as Class Member + +We can define our slots as class members via the `@slot` decorator as follows: +```ts +import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; + +@customElement("my-demo-component") +class MyDemoComponent extends UI5Element { + @slot() + items!: Array; +} +``` + +Defining a slot with the `@slot` decorator means that this slot will be managed by the framework: +- If any of the children are custom elements, the framework will wait until they are all defined and upgraded before rendering the component. +- The component will be re-rendered when its children are added, removed, or rearranged. + +Also, we define slots as class members when we need to access the slotted children for some reason. +For example, to get the slotted elements count: + +```ts +const itemsCount = this.items.length; +``` + +Or, to read some state of the slotted elements: + + +```ts +const hasDisabledItem = this.items.some(el => el.disabled); +``` + +Or, sometimes even set some private state on the slotted elements: + +```ts +this.items.forEach((item, key) => { + const isLastChild = key === this.items.length - 1; + item.showBorder = isLastChild; +}); +``` + +All slots, declared with the `@slot` decorator, are arrays with elements of type Node or HTMLElement. +So, you can safely and **must** declare slots (by convention) with `!:` as the accessor will return an empty array in the worst case. + +Also, when you declare slots as class members, you can document them in place - you don't need to describe them at class level as mentioned in the previous section. + +```ts +/** + * Defines the items of the component. + * @public + */ +@slot() +items: Array +``` + + +### Default and Named Slot + +Default slot is the one that can be used without setting the `slot` attribute of the slotted elements, while +named slot requires setting the `slot` attribute: + +- Default slot + +```hbs +{{!-- MyDemoComponent.hbs --}} +
+ +
+``` + +```html + + + Hello World + +``` + +- Named slot + +The named slot requires a small change in the component's template. You must pass the `name` attrbite to the `slot` element: + +```hbs +{{!-- MyDemoComponent.hbs --}} +
+ +
+ +```html + + + Hello World + +``` + + +- Declare default slot + +All slots are named if you simply use the `@slot` decorator without any settings, while the default slots must be explicitly marked as such with the `"default"` setting: + +```ts +import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; + +@customElement("my-demo-component") +class MyDemoComponent extends UI5Element { + @slot({ type: HTMLElement, "default": true }) + content!: Array; +} +``` + +- Declare named slot + +Simply use the `@slot` decorator without any settings: + +```ts +import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; + +@customElement("my-demo-component") +class MyDemoComponent extends UI5Element { + @slot() + content!: Array; +} +``` + +It's a good practice is to make use of the default slot as it requires less code to use your component. +And, if your component has multiple slots - to pick the most important and used one as the default. + +For example, here we assume that the "content" slot is more important and we declared it as default. + +```hbs +{{!-- MyDemoComponent.hbs --}} +
+
+ +
+ + +
+``` + +```html + + +

Heading

+ Hello World +
+``` + +```ts +import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; + +@customElement("my-demo-component") +class MyDemoComponent extends UI5Element { + @slot({ type: HTMLElement, "default": true }) + content!: Array; + + @slot() + heading!: Array; +} +``` + +**Note:** If the slot configuration object is not provided (e.g. `@slot()`), `HTMLElement` will be used as the default type. +However, if you provide this object, the `type` field is mandatory. + + +### Individual Slots + +The `@slot` decorator provides an option called `individualSlots`, which is of boolean type. This option determines if each child element will be placed in its own slot, allowing for flexible arrangement or wrapping of the children within the component. When `individualSlots` is enabled, the framework assigns a unique `_individualSlot` property to each child element. This property can then be used within the component's template, as shown in the following example. + +First, enable `individualSlots` by setting it to `true`: +```ts +import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; + +@customElement("my-demo-component") +class MyDemoComponent extends UI5Element { + @slot({ type: HTMLElement, individualSlots: true }) + content!: Array; +} +``` + +Next, iterate over the child elements in the template, using the `_individualSlot` property in the name attribute of the slot element: +```hbs +{{#each mySlot}} + +{{/each}} +``` + +Here is an example using the `Carousel` web component, which leverages `individualSlots` to wrap each slotted child within the content slot to achieve a specific design: +```ts +import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; + +@customElement("ui5-carousel") +class Carousel extends UI5Element { + @slot({ type: HTMLElement, individualSlots: true }) + content!: Array; +} +``` + +```hbs +{{!-- Carousel.hbs --}} +
+ {{#each content}} + + {{/each}} +
+``` + +**Note**: When `individualSlots` is enabled, the `_individualSlot` property is assigned to each direct child. The value of `_individualSlot` follows the pattern `{nameOfTheSlot}-{index}`, and the slot attribute is updated accordingly. + + +### Invalidation on Child Change + +The `@slot` decorator offers an `invalidateOnChildChange` option, which can be set as a boolean or a configuration object. This option determines whether a component should be invalidated when changes occur within its child elements. + +By default, if child elements are added or removed from a slot, the component will be invalidated automatically. The `invalidateOnChildChange` option goes a step further by triggering invalidation even when properties or slots of the child elements change. This is useful if the state of parent component depends on the state of its children. + +The simplest way to use this option is to set `invalidateOnChildChange` to `"true"`. This configuration ensures that the `my-demo-component` web component will be invalidated whenever any of the UI5Element instances slotted into the content slot are updated, whether due to a property or slot change. + + +```ts +import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; + +@customElement("my-demo-component") +class MyDemoComponent extends UI5Element { + @slot({ type: HTMLElement, invalidateOnChildChange: true }) + content!: Array; +} +``` + +For more specific scenarios, you can use a more detailed configuration. The following example demonstrates how to invalidate the `"my-demo-component"` web component only when certain properties or slots of the slotted UI5Element instances change. In this case, the component will be invalidated if the "myProp" property or the "mySlot" slot of the child elements are modified. + +```ts +import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; + +@customElement("my-demo-component") +class MyDemoComponent extends UI5Element { + @slot({ type: HTMLElement, invalidateOnChildChange: { properties: ["myProp"], slots: ["mySlot"] }}) + content!: Array; +} +``` + +The `invalidateOnChildChange` option is especially useful when working with "abstract" elements option is particularly useful when dealing with "abstract" elements, such as UI5Element instances that do not have their own templates. In these cases, the parent component is responsible for rendering the content based on the state of its child elements. + +For instance, consider a `Wizard` web component that accepts `WizardStep` elements in its `"steps"` slot. Since `WizardStep` does not have its own template, the `Wizard` must handle rendering based on the properties and state of the steps. Therefore, the `Wizard` needs to be invalidated whenever any changes occur within its child elements to ensure proper rendering. + +```ts +class Wizard extends UI5Element { + @slot({ + "default": true, + type: HTMLElement, + invalidateOnChildChange: true, + }) + steps!: Array +} +``` + +```html + + + + + +``` + +```hbs +{{!-- Wizard.hbs --}} +
+ +
+``` + +**Note**: The `invalidateOnChildChange` option is meant to be used with slots that are UI5Element instances. + +### Styling of Slotted Children + +The `:slotted` CSS selector applies to any element that has been placed into a slot. +It works when used inside CSS placed within the shadow DOM of the component that offers the slot. + +For example: + +```html + + +

Heading

+ Hello World +
+``` + +```css +/* MyDemoComponent.css */ +::slotted([slot="heading"]) { + width: 200px; + height: 100px; +} +``` + ## Understanding rendering ### What is rendering? @@ -221,7 +684,7 @@ Example: import MyComponentTemplate from "./generated/templates/MyComponentTemplate.lit.js"; @customElement({ - template: MyComponentTemplate + template: MyComponentTemplate }) ``` @@ -244,7 +707,7 @@ renders HTML corresponding to each of its children (`ui5-date` instances) as par Invalidation means scheduling an already rendered component for asynchronous re-rendering (in the next animation frame). If an already invalidated component gets changed again, before having been re-rendered, this will have no downside - it's in the queue of components to be re-rendered anyway. -Important: when a component is re-rendered, only the parts of its shadow DOM, dependent on the changed properties/slots are changed, which makes most updates very fast. +**Important:** when a component is re-rendered, only the parts of its shadow DOM, dependent on the changed properties/slots are changed, which makes most updates very fast. A component becomes *invalidated* whenever: - a *metadata-defined* **property** changes (not regular properties that f.e. you define in the constructor) From a40a45160d4efe1e1ebb8c43ee3216a2f758558c Mon Sep 17 00:00:00 2001 From: Georgieva Date: Tue, 10 Sep 2024 15:00:43 +0300 Subject: [PATCH 011/114] feat(ui5-li-notification-group): add new features - growing and sticky header (#9783) * feat(ui5-li-notification-group): add new features - growing and sticky header According to the new design the notification groups should be able to grow by pressing "More" button. Then the "load-more" event will be fired and more notifications can be loaded. The other new feature is the sticky header, which stays on top of the popover with notifications, when the user scrolls down. When next group is reached the sticky header will be replaced with the new one corresponding to the currently visible group. JIRA: BGSOFUIRODOPI-3224 --------- Co-authored-by: Teodor Taushanov --- .../fiori/src/NotificationListGroupItem.hbs | 4 +- .../fiori/src/NotificationListGroupItem.ts | 28 ++ packages/fiori/src/NotificationListItem.ts | 3 +- .../fiori/src/themes/NotificationList.css | 4 + .../src/themes/NotificationListGroupItem.css | 33 ++- .../base/NotificationListItem-parameters.css | 1 + .../NotificationListItem-parameters.css | 3 +- .../NotificationListItem-parameters.css | 3 +- .../NotificationListItem-parameters.css | 3 +- .../NotificationListItem-parameters.css | 3 +- .../test/pages/NotificationListGroupItem.html | 240 +++++++++++++++++- .../test/pages/NotificationListItem.html | 15 ++ .../styles/NotificationListGroupItem.css | 33 +++ packages/main/src/List.hbs | 3 +- packages/main/src/List.ts | 2 + .../src/types/NotificationListGrowingMode.ts | 20 ++ .../NotificationList/InShellBar/sample.html | 2 +- 17 files changed, 390 insertions(+), 10 deletions(-) create mode 100644 packages/main/src/types/NotificationListGrowingMode.ts diff --git a/packages/fiori/src/NotificationListGroupItem.hbs b/packages/fiori/src/NotificationListGroupItem.hbs index 99baf070c552..5b4a6a10e976 100644 --- a/packages/fiori/src/NotificationListGroupItem.hbs +++ b/packages/fiori/src/NotificationListGroupItem.hbs @@ -43,7 +43,9 @@ id="{{_id}}-notificationsList" class="ui5-nli-group-items" role="list" - aria-labelledby="{{_id}}-title-text"> + aria-labelledby="{{_id}}-title-text" + growing={{growing}} + @ui5-load-more="{{_onLoadMore}}">
diff --git a/packages/fiori/src/NotificationListGroupItem.ts b/packages/fiori/src/NotificationListGroupItem.ts index 9a2b8900cce1..9a112ffa39c4 100644 --- a/packages/fiori/src/NotificationListGroupItem.ts +++ b/packages/fiori/src/NotificationListGroupItem.ts @@ -8,6 +8,7 @@ import event from "@ui5/webcomponents-base/dist/decorators/event.js"; import Button from "@ui5/webcomponents/dist/Button.js"; import BusyIndicator from "@ui5/webcomponents/dist/BusyIndicator.js"; import Icon from "@ui5/webcomponents/dist/Icon.js"; +import type NotificationListGrowingMode from "@ui5/webcomponents/dist/types/NotificationListGrowingMode.js"; import NotificationListGroupList from "./NotificationListGroupList.js"; import NotificationListItemBase from "./NotificationListItemBase.js"; import type NotificationListItem from "./NotificationListItem.js"; @@ -89,6 +90,14 @@ type NotificationListGroupItemToggleEventDetail = { */ @event("toggle") +/** + * Fired when additional items are requested. + * + * @public + * @since 2.2.0 + */ +@event("load-more") + class NotificationListGroupItem extends NotificationListItemBase { /** * Defines if the group is collapsed or expanded. @@ -98,6 +107,16 @@ class NotificationListGroupItem extends NotificationListItemBase { @property({ type: Boolean }) collapsed = false; + /** + * Defines whether the component will have growing capability by pressing a `More` button. + * When button is pressed `load-more` event will be fired. + * @default false + * @public + * @since 2.2.0 + */ + @property() + growing: `${NotificationListGrowingMode}` = "None"; + /** * Defines the items of the `ui5-li-notification-group`, * usually `ui5-li-notification` items. @@ -190,6 +209,15 @@ class NotificationListGroupItem extends NotificationListItemBase { this.toggleCollapsed(); } + _onLoadMore() { + this.fireEvent("load-more"); + } + + get loadMoreButton() { + const innerList = this.getDomRef()?.querySelector("[ui5-notification-group-list]") as NotificationListGroupList; + return innerList.getDomRef()?.querySelector("[growing-button-inner]") as HTMLElement; + } + async _onkeydown(e: KeyboardEvent) { const isFocused = this.matches(":focus"); if (!isFocused) { diff --git a/packages/fiori/src/NotificationListItem.ts b/packages/fiori/src/NotificationListItem.ts index 5972a6812ac8..3485deb783e6 100644 --- a/packages/fiori/src/NotificationListItem.ts +++ b/packages/fiori/src/NotificationListItem.ts @@ -17,6 +17,7 @@ import Icon from "@ui5/webcomponents/dist/Icon.js"; import WrappingType from "@ui5/webcomponents/dist/types/WrappingType.js"; import type Menu from "@ui5/webcomponents/dist/Menu.js"; import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js"; +import willShowContent from "@ui5/webcomponents-base/dist/util/willShowContent.js"; import NotificationListItemImportance from "./types/NotificationListItemImportance.js"; import NotificationListItemBase from "./NotificationListItemBase.js"; import type NotificationList from "./NotificationList.js"; @@ -300,7 +301,7 @@ class NotificationListItem extends NotificationListItemBase { } get hasDesc() { - return !!this.description.length; + return willShowContent(this.description); } get hasImportance() { diff --git a/packages/fiori/src/themes/NotificationList.css b/packages/fiori/src/themes/NotificationList.css index d6dbccfe15e0..7c2de8ff9fdf 100644 --- a/packages/fiori/src/themes/NotificationList.css +++ b/packages/fiori/src/themes/NotificationList.css @@ -1,3 +1,7 @@ :host(:not([hidden])) { display: block; +} + +[ui5-notification-list-internal] { + height: 100%; } \ No newline at end of file diff --git a/packages/fiori/src/themes/NotificationListGroupItem.css b/packages/fiori/src/themes/NotificationListGroupItem.css index 549b7201c142..3f5c2064a482 100644 --- a/packages/fiori/src/themes/NotificationListGroupItem.css +++ b/packages/fiori/src/themes/NotificationListGroupItem.css @@ -27,7 +27,9 @@ .ui5-nli-group-header { height: 2.75rem; - position: relative; + position: sticky; + top: 0; + z-index: 90; /* the z-index of the busy-indicator is 99 and the header shouldn't be over it */ background: var(--sapList_GroupHeaderBackground); display: flex; align-items: center; @@ -79,3 +81,32 @@ :host([ui5-li-notification-group]) { -webkit-tap-highlight-color: transparent; } + +/* "More" button overrides follows */ +[ui5-notification-group-list]::part(growing-button) { + border: none; +} + +[ui5-notification-group-list]::part(growing-button-inner) { + margin: var(--_ui5-notification_item-margin); + border: var(--_ui5-notification_item-border-top-left-right); + border-radius: var(--_ui5-notification_item-border-radius); +} + +[ui5-notification-group-list]::part(growing-button-inner) { + border-radius: var(--_ui5-notification_item-border-radius); +} + +[ui5-notification-group-list]::part(growing-button-inner):focus, +[ui5-notification-group-list]::part(growing-button-inner):focus-visible { + outline: var(--sapContent_FocusWidth) var(--sapContent_FocusStyle) var(--sapContent_FocusColor);; + outline-offset: var(--_ui5-notification_item-outline-offset); +} + +[ui5-notification-group-list]::part(growing-button-inner):focus:active, +[ui5-notification-group-list]::part(growing-button-inner):focus-visible:active { + background-color: var(--_ui5-notification_item-background-color-active); + border-radius: var(--_ui5-notification_item-border-radius); + border: var(--_ui5-notification_item-border-active); +} + diff --git a/packages/fiori/src/themes/base/NotificationListItem-parameters.css b/packages/fiori/src/themes/base/NotificationListItem-parameters.css index 55ad7aed037f..d62423f663a0 100644 --- a/packages/fiori/src/themes/base/NotificationListItem-parameters.css +++ b/packages/fiori/src/themes/base/NotificationListItem-parameters.css @@ -18,6 +18,7 @@ --_ui5-notification_item-description-margin-top: 0.75rem; --_ui5-notification_item-footer-margin-top: 0.75rem; --_ui5-notification_item-focus-offset: 0; + --_ui5-notification_item-outline-offset: 0; } [data-ui5-compact-size], diff --git a/packages/fiori/src/themes/sap_horizon/NotificationListItem-parameters.css b/packages/fiori/src/themes/sap_horizon/NotificationListItem-parameters.css index 6ddf860508d8..190a152ea514 100644 --- a/packages/fiori/src/themes/sap_horizon/NotificationListItem-parameters.css +++ b/packages/fiori/src/themes/sap_horizon/NotificationListItem-parameters.css @@ -3,7 +3,7 @@ :root { --_ui5-notification_item-border-radius: var(--sapTile_BorderCornerRadius); --_ui5-notification_group_header-border-bottom-width: 0; - --_ui5-notification_group_header-margin: 0.75rem 0.5rem; + --_ui5-notification_group_header-margin: 0.75rem; --_ui5-notification_group_header-padding: 0.5rem; --_ui5-notification_item-state-icon-padding: 0.25rem; --_ui5-notification_item-border-bottom: var(--sapList_BorderWidth) solid var(--sapList_BorderColor); @@ -16,4 +16,5 @@ --_ui5-notification_item-content-padding: 0.75rem 0; --_ui5-notification_item-title-margin-bottom: 0; --_ui5-notification_item-focus-offset: 0.1875rem; + --_ui5-notification_item-outline-offset: -0.375rem; /*2 x --_ui5-notification_item-focus-offset*/ } \ No newline at end of file diff --git a/packages/fiori/src/themes/sap_horizon_dark/NotificationListItem-parameters.css b/packages/fiori/src/themes/sap_horizon_dark/NotificationListItem-parameters.css index 6ddf860508d8..190a152ea514 100644 --- a/packages/fiori/src/themes/sap_horizon_dark/NotificationListItem-parameters.css +++ b/packages/fiori/src/themes/sap_horizon_dark/NotificationListItem-parameters.css @@ -3,7 +3,7 @@ :root { --_ui5-notification_item-border-radius: var(--sapTile_BorderCornerRadius); --_ui5-notification_group_header-border-bottom-width: 0; - --_ui5-notification_group_header-margin: 0.75rem 0.5rem; + --_ui5-notification_group_header-margin: 0.75rem; --_ui5-notification_group_header-padding: 0.5rem; --_ui5-notification_item-state-icon-padding: 0.25rem; --_ui5-notification_item-border-bottom: var(--sapList_BorderWidth) solid var(--sapList_BorderColor); @@ -16,4 +16,5 @@ --_ui5-notification_item-content-padding: 0.75rem 0; --_ui5-notification_item-title-margin-bottom: 0; --_ui5-notification_item-focus-offset: 0.1875rem; + --_ui5-notification_item-outline-offset: -0.375rem; /*2 x --_ui5-notification_item-focus-offset*/ } \ No newline at end of file diff --git a/packages/fiori/src/themes/sap_horizon_hcb/NotificationListItem-parameters.css b/packages/fiori/src/themes/sap_horizon_hcb/NotificationListItem-parameters.css index 251e30ff77b3..abec6e1be6bd 100644 --- a/packages/fiori/src/themes/sap_horizon_hcb/NotificationListItem-parameters.css +++ b/packages/fiori/src/themes/sap_horizon_hcb/NotificationListItem-parameters.css @@ -3,7 +3,7 @@ :root { --_ui5-notification_item-border-radius: var(--sapTile_BorderCornerRadius); --_ui5-notification_group_header-border-bottom-width: 0; - --_ui5-notification_group_header-margin: 0.75rem 0.5rem; + --_ui5-notification_group_header-margin: 0.75rem; --_ui5-notification_group_header-padding: 0.5rem; --_ui5-notification_item-state-icon-padding: 0.25rem; --_ui5-notification_item-border-bottom: var(--sapList_BorderWidth) solid var(--sapList_BorderColor); @@ -18,4 +18,5 @@ --_ui5-notification_item-title-padding-end-two-buttons: 4.875rem; --_ui5-notification_item-title-padding-end-one-button: 2.375rem; --_ui5-notification_item-focus-offset: 0.1875rem; + --_ui5-notification_item-outline-offset: -0.375rem; /*2 x --_ui5-notification_item-focus-offset*/ } \ No newline at end of file diff --git a/packages/fiori/src/themes/sap_horizon_hcw/NotificationListItem-parameters.css b/packages/fiori/src/themes/sap_horizon_hcw/NotificationListItem-parameters.css index 251e30ff77b3..abec6e1be6bd 100644 --- a/packages/fiori/src/themes/sap_horizon_hcw/NotificationListItem-parameters.css +++ b/packages/fiori/src/themes/sap_horizon_hcw/NotificationListItem-parameters.css @@ -3,7 +3,7 @@ :root { --_ui5-notification_item-border-radius: var(--sapTile_BorderCornerRadius); --_ui5-notification_group_header-border-bottom-width: 0; - --_ui5-notification_group_header-margin: 0.75rem 0.5rem; + --_ui5-notification_group_header-margin: 0.75rem; --_ui5-notification_group_header-padding: 0.5rem; --_ui5-notification_item-state-icon-padding: 0.25rem; --_ui5-notification_item-border-bottom: var(--sapList_BorderWidth) solid var(--sapList_BorderColor); @@ -18,4 +18,5 @@ --_ui5-notification_item-title-padding-end-two-buttons: 4.875rem; --_ui5-notification_item-title-padding-end-one-button: 2.375rem; --_ui5-notification_item-focus-offset: 0.1875rem; + --_ui5-notification_item-outline-offset: -0.375rem; /*2 x --_ui5-notification_item-focus-offset*/ } \ No newline at end of file diff --git a/packages/fiori/test/pages/NotificationListGroupItem.html b/packages/fiori/test/pages/NotificationListGroupItem.html index 40d92b5bb07f..f23604c487b9 100644 --- a/packages/fiori/test/pages/NotificationListGroupItem.html +++ b/packages/fiori/test/pages/NotificationListGroupItem.html @@ -18,6 +18,194 @@ Toggle sapUiSizeCompact + + +
+ + Notifications + Show M. Strip + Clear All + + + + Something went wrong. + + + + + Office Notifications + 3 Days + + + + + And with a very long description and long labels of the action buttons - Lorem ipsum dolor sit amet, + consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula + dolor, + nec elementum lectus turpis at nunc. + + + + + Office Notifications + 3 Days + + + + + Office Notifications + 3 Days + + + + + Office Notifications + 3 Days + + + + + Office Notifications + 3 Days + + + + + Office Notifications + 3 Days + + + + + Office Notifications + 3 Days + + + + + And with a very long description and long labels of the action buttons - Lorem ipsum dolor sit amet, + consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula + dolor, + nec elementum lectus turpis at nunc. + + + + + + Office Notifications + 3 Days + + + + And with a very long description - Lorem ipsum dolor sit amet, + consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula + dolor, + nec elementum lectus turpis at nunc. + + + + + Office Notifications + 3 Days + + + + And with a very long description - Lorem ipsum dolor sit amet, + consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula + dolor, + nec elementum lectus turpis at nunc. + + + + + + + Office Notifications + 3 Days + And with a very long description - Lorem ipsum dolor sit amet, + consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula + dolor, + nec elementum lectus turpis at nunc. + + + + + Office Notifications + 3 Days + And with a very long description - Lorem ipsum dolor sit amet, + consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula + dolor, + nec elementum lectus turpis at nunc. + + + + + Office Notifications + 3 Days + And with a very long description - Lorem ipsum dolor sit amet, + consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula + dolor, + nec elementum lectus turpis at nunc. + + + + + Office Notifications + 3 Days + And with a very long description - Lorem ipsum dolor sit amet, + consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula + dolor, + nec elementum lectus turpis at nunc. + + + + + Office Notifications + 3 Days + And with a very long description - Lorem ipsum dolor sit amet, + consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula + dolor, + nec elementum lectus turpis at nunc. + + + + + Office Notifications + 3 Days + And with a very long description - Lorem ipsum dolor sit amet, + consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula + dolor, + nec elementum lectus turpis at nunc. + + + +
+
+ - + Notifications Clear All @@ -432,6 +621,55 @@ btnCompact.addEventListener("click", function() { document.getElementsByTagName("body")[0].classList.toggle("sapUiSizeCompact"); }); + + // Growing and Sticky interaction features + var notificationsPopoverGrowingAndSticky = document.querySelector("#popover-with-notifications"); + var notificationsPopoverMessageStrip = document.querySelector("#message-strip-error"); + + var btnShowMessageStrip = document.querySelector("#show-message-strip"); + + shellbarGrowingAndSticky.addEventListener("notifications-click", e => { + e.preventDefault(); + notificationsPopoverGrowingAndSticky.opener = e.detail.targetRef; + notificationsPopoverGrowingAndSticky.open = true; + }); + + let itemsLoaded = 0; + const itemToLoad = 5; + const notificationsListGroupGrowing = document.querySelector("#notificationsListGroupGrowing"); + const notificationListItemTemplate = (index) => { + var notificationLi = document.createElement("ui5-li-notification"); + notificationLi.titleText = "New notification"; + notificationLi.state = "Critical"; + + return notificationLi; + } + + const insertItems = (list) => { + for (var i = itemsLoaded; i < itemsLoaded + itemToLoad; i++) { + list.appendChild(notificationListItemTemplate(i)); + } + + itemsLoaded += itemToLoad; + }; + + notificationsListGroupGrowing.addEventListener("load-more", (e) => { + const focusIndex = notificationsListGroupGrowing.items.length; + + notificationsListGroupGrowing.loading = true; // as in the ui5-list (the whole list gets 'busy') + setTimeout(() => { + insertItems(notificationsListGroupGrowing); + notificationsListGroupGrowing.loading = false; + + setTimeout(() => { + notificationsListGroupGrowing.items[focusIndex].focus(); + }, 500); + }, 500); + }); + + btnShowMessageStrip.addEventListener("click", function() { + notificationsPopoverMessageStrip.style.display = "inline-block"; + }); diff --git a/packages/fiori/test/pages/NotificationListItem.html b/packages/fiori/test/pages/NotificationListItem.html index 8a59a1af5d9e..3e6a2cdb08ea 100644 --- a/packages/fiori/test/pages/NotificationListItem.html +++ b/packages/fiori/test/pages/NotificationListItem.html @@ -109,6 +109,21 @@

Notification List with Menu

Notification List

+ + + + Office Notifications + 3 Days + + + + + + +
- + Office Notifications 3 Days From 6058d7873d36ed77d9b9af5ece1f2f3b19f59651 Mon Sep 17 00:00:00 2001 From: Dobrin Dimchev Date: Wed, 11 Sep 2024 11:33:10 +0300 Subject: [PATCH 012/114] refactor(ui5-breadcrumbs): increase links touch area (#9803) Top and bottom touch area spacings are aded because of non-intersecting target area requirement. The positioning of the drop-down popover also is changed accordingly. --- packages/main/src/Link.hbs | 1 + packages/main/src/themes/Breadcrumbs.css | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/packages/main/src/Link.hbs b/packages/main/src/Link.hbs index aec220cbbf7d..8c8a6ac7dc6a 100644 --- a/packages/main/src/Link.hbs +++ b/packages/main/src/Link.hbs @@ -1,4 +1,5 @@ Date: Wed, 11 Sep 2024 15:01:27 +0300 Subject: [PATCH 013/114] fix(ui5-popover): fix arrow placement (#9844) fix(ui5-popover): fix arrow --- packages/main/src/themes/Popover.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/main/src/themes/Popover.css b/packages/main/src/themes/Popover.css index 29023fe7c8d8..f500d7ee0d22 100644 --- a/packages/main/src/themes/Popover.css +++ b/packages/main/src/themes/Popover.css @@ -20,7 +20,7 @@ :host([actual-placement="Bottom"]) .ui5-popover-arrow { left: calc(50% - 0.5625rem); top: -0.5rem; - height: 0.5625rem; + height: 0.5rem; } :host([actual-placement="Bottom"]) .ui5-popover-arrow:after { From ac26962944f730aaf28812587c7424bbff0ab21c Mon Sep 17 00:00:00 2001 From: Nikolay Deshev Date: Wed, 11 Sep 2024 15:19:19 +0300 Subject: [PATCH 014/114] chore: update cypress config path for windows (#9404) --- packages/fiori/cypress.config.js | 4 +++- packages/main/cypress.config.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/fiori/cypress.config.js b/packages/fiori/cypress.config.js index 252d8d0fb727..5e0308b0d522 100644 --- a/packages/fiori/cypress.config.js +++ b/packages/fiori/cypress.config.js @@ -1,7 +1,9 @@ import cypressConfig from "@ui5/webcomponents-tools/components-package/cypress.config.js"; import path from "path"; +import { fileURLToPath } from "node:url"; -const __dirname = path.dirname(new URL(import.meta.url).pathname); +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); cypressConfig.component.supportFile = path.join(__dirname, "cypress/support/component.ts"); diff --git a/packages/main/cypress.config.js b/packages/main/cypress.config.js index 0f3866dbbc0a..0f37fc0d4714 100644 --- a/packages/main/cypress.config.js +++ b/packages/main/cypress.config.js @@ -1,7 +1,9 @@ import cypressConfig from "@ui5/webcomponents-tools/components-package/cypress.config.js"; import path from "path"; +import { fileURLToPath } from "node:url"; -const __dirname = path.dirname(new URL(import.meta.url).pathname); +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); cypressConfig.component.supportFile = path.join(__dirname, "cypress/support/component.ts"); From faeec232dafaf8820737b4704275d3b22422a7f8 Mon Sep 17 00:00:00 2001 From: Petar Dimov <32839090+dimovpetar@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:48:55 +0300 Subject: [PATCH 015/114] docs(ui5-tabcontainer): add sample with drag and drop and fixed tabs (#9818) --- .../main/TabContainer/TabContainer.mdx | 4 ++ .../ReorderTabsFixedTabs.md | 4 ++ .../TabContainer/ReorderTabsFixedTabs/main.js | 44 +++++++++++++++++++ .../ReorderTabsFixedTabs/sample.html | 35 +++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 packages/website/docs/_samples/main/TabContainer/ReorderTabsFixedTabs/ReorderTabsFixedTabs.md create mode 100644 packages/website/docs/_samples/main/TabContainer/ReorderTabsFixedTabs/main.js create mode 100644 packages/website/docs/_samples/main/TabContainer/ReorderTabsFixedTabs/sample.html diff --git a/packages/website/docs/_components_pages/main/TabContainer/TabContainer.mdx b/packages/website/docs/_components_pages/main/TabContainer/TabContainer.mdx index 9e5bf90f624f..4cd21548227f 100644 --- a/packages/website/docs/_components_pages/main/TabContainer/TabContainer.mdx +++ b/packages/website/docs/_components_pages/main/TabContainer/TabContainer.mdx @@ -11,6 +11,7 @@ import OverflowModeStartAndEnd from "../../../_samples/main/TabContainer/Overflo import NestedTabs from "../../../_samples/main/TabContainer/NestedTabs/NestedTabs.md"; import ReorderTabs from "../../../_samples/main/TabContainer/ReorderTabs/ReorderTabs.md"; import ReorderTabsMaxNestingLevel from "../../../_samples/main/TabContainer/ReorderTabsMaxNestingLevel/ReorderTabsMaxNestingLevel.md"; +import ReorderTabsFixedTabs from "../../../_samples/main/TabContainer/ReorderTabsFixedTabs/ReorderTabsFixedTabs.md"; <%COMPONENT_OVERVIEW%> @@ -48,3 +49,6 @@ It can be switched to "StartAndEnd": ### Reorder Tabs with Max Nesting Level + +### Reorder Tabs with Fixed Tabs + diff --git a/packages/website/docs/_samples/main/TabContainer/ReorderTabsFixedTabs/ReorderTabsFixedTabs.md b/packages/website/docs/_samples/main/TabContainer/ReorderTabsFixedTabs/ReorderTabsFixedTabs.md new file mode 100644 index 000000000000..ffccbf6dd13e --- /dev/null +++ b/packages/website/docs/_samples/main/TabContainer/ReorderTabsFixedTabs/ReorderTabsFixedTabs.md @@ -0,0 +1,4 @@ +import html from '!!raw-loader!./sample.html'; +import js from '!!raw-loader!./main.js'; + + \ No newline at end of file diff --git a/packages/website/docs/_samples/main/TabContainer/ReorderTabsFixedTabs/main.js b/packages/website/docs/_samples/main/TabContainer/ReorderTabsFixedTabs/main.js new file mode 100644 index 000000000000..12e8ea535bba --- /dev/null +++ b/packages/website/docs/_samples/main/TabContainer/ReorderTabsFixedTabs/main.js @@ -0,0 +1,44 @@ +import "@ui5/webcomponents/dist/TabContainer.js"; +import "@ui5/webcomponents/dist/Tab.js"; +import "@ui5/webcomponents/dist/TabSeparator.js"; +import MovePlacement from "@ui5/webcomponents-base/dist/types/MovePlacement.js"; + +const tabContainer = document.getElementById("tabContainer"); + +tabContainer.addEventListener("move-over", (event) => { + const { source, destination } = event.detail; + + if (!tabContainer.contains(source.element)) { + return; + } + + if (destination.element.dataset.fixed) { + return; + } + + event.preventDefault() +}); + +tabContainer.addEventListener("move", (event) => { + const { source, destination } = event.detail; + + switch (destination.placement) { + case MovePlacement.Before: + destination.element.before(source.element); + break; + case MovePlacement.After: + destination.element.after(source.element); + break; + case MovePlacement.On: + destination.element.prepend(source.element); + break; + } + + const newParent = source.element.parentElement; + + if (newParent.hasAttribute("ui5-tab")) { + source.element.slot = "items"; + } else { + source.element.slot = ""; + } +}); \ No newline at end of file diff --git a/packages/website/docs/_samples/main/TabContainer/ReorderTabsFixedTabs/sample.html b/packages/website/docs/_samples/main/TabContainer/ReorderTabsFixedTabs/sample.html new file mode 100644 index 000000000000..dd36e526d426 --- /dev/null +++ b/packages/website/docs/_samples/main/TabContainer/ReorderTabsFixedTabs/sample.html @@ -0,0 +1,35 @@ + + + + + + + Sample + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 03daa728429491b9284d898d819b0e7cd892b644 Mon Sep 17 00:00:00 2001 From: Petar Dimov <32839090+dimovpetar@users.noreply.github.com> Date: Wed, 11 Sep 2024 16:58:05 +0300 Subject: [PATCH 016/114] fix(ui5-tabcontainer): prevent page scrolling when reordering tabs with home and end keys (#9848) --- packages/main/src/TabContainer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/main/src/TabContainer.ts b/packages/main/src/TabContainer.ts index 2f02befeea38..bf7148cc6395 100644 --- a/packages/main/src/TabContainer.ts +++ b/packages/main/src/TabContainer.ts @@ -855,6 +855,7 @@ class TabContainer extends UI5Element { if (isCtrl(e)) { this._moveHeaderItem(tab.realTabReference, e); + e.preventDefault(); return; } From 90bac41605bb8923c5e2546645d900b1f333b669 Mon Sep 17 00:00:00 2001 From: ui5-webcomponents-bot Date: Thu, 12 Sep 2024 08:07:43 +0000 Subject: [PATCH 017/114] chore(release): publish v2.3.0-rc.1 [ci skip] --- CHANGELOG.md | 22 ++++++++++++++++++++++ lerna.json | 2 +- packages/ai/CHANGELOG.md | 8 ++++++++ packages/ai/package.json | 12 ++++++------ packages/base/CHANGELOG.md | 12 ++++++++++++ packages/base/package.json | 4 ++-- packages/compat/CHANGELOG.md | 11 +++++++++++ packages/compat/package.json | 12 ++++++------ packages/create-package/CHANGELOG.md | 8 ++++++++ packages/create-package/package.json | 2 +- packages/fiori/CHANGELOG.md | 11 +++++++++++ packages/fiori/package.json | 12 ++++++------ packages/icons-business-suite/CHANGELOG.md | 8 ++++++++ packages/icons-business-suite/package.json | 6 +++--- packages/icons-tnt/CHANGELOG.md | 8 ++++++++ packages/icons-tnt/package.json | 6 +++--- packages/icons/CHANGELOG.md | 8 ++++++++ packages/icons/package.json | 6 +++--- packages/localization/CHANGELOG.md | 8 ++++++++ packages/localization/package.json | 6 +++--- packages/main/CHANGELOG.md | 20 ++++++++++++++++++++ packages/main/package.json | 16 ++++++++-------- packages/theming/CHANGELOG.md | 8 ++++++++ packages/theming/package.json | 6 +++--- packages/tools/CHANGELOG.md | 8 ++++++++ packages/tools/package.json | 2 +- packages/website/CHANGELOG.md | 11 +++++++++++ packages/website/package.json | 2 +- 28 files changed, 198 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e10c4cf8050a..6cdf54521928 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.3.0-rc.0...v2.3.0-rc.1) (2024-09-12) + + +### Bug Fixes + +* **f6 navigation:** work properly when multiple runtimes ([#9810](https://github.com/SAP/ui5-webcomponents/issues/9810)) ([1ebf7db](https://github.com/SAP/ui5-webcomponents/commit/1ebf7db3faa8a2be1d35347407a365f5cf3004de)) +* **ui5-list:** adjust growing button padding ([#9747](https://github.com/SAP/ui5-webcomponents/issues/9747)) ([0373527](https://github.com/SAP/ui5-webcomponents/commit/03735275752df1263a58af456f11e206935c6e9b)) +* **ui5-multi-combobox:** restore focus to input after value state header is removed ([#9827](https://github.com/SAP/ui5-webcomponents/issues/9827)) ([712d94e](https://github.com/SAP/ui5-webcomponents/commit/712d94e3c27c96cb1ab44f57d0e0fd10f7288a4b)), closes [#9709](https://github.com/SAP/ui5-webcomponents/issues/9709) +* **ui5-popover:** fix arrow placement ([#9844](https://github.com/SAP/ui5-webcomponents/issues/9844)) ([6109040](https://github.com/SAP/ui5-webcomponents/commit/6109040822898aea2276925cba951d4a5a766304)) +* **ui5-tabcontainer:** fix drag and drop issue with home key and fixеd tabs ([#9812](https://github.com/SAP/ui5-webcomponents/issues/9812)) ([485fd0d](https://github.com/SAP/ui5-webcomponents/commit/485fd0dd1205543e50e7db011d6c5a7bedbe5c68)) +* **ui5-tabcontainer:** prevent page scrolling when reordering tabs with home and end keys ([#9848](https://github.com/SAP/ui5-webcomponents/issues/9848)) ([03daa72](https://github.com/SAP/ui5-webcomponents/commit/03daa728429491b9284d898d819b0e7cd892b644)) +* **ui5-table:** add aria reference to growing btn sub text ([#9836](https://github.com/SAP/ui5-webcomponents/issues/9836)) ([daa3399](https://github.com/SAP/ui5-webcomponents/commit/daa3399654b81fb51259129e493708b06b6ee8ff)), closes [#9807](https://github.com/SAP/ui5-webcomponents/issues/9807) + + +### Features + +* **ui5-li-notification-group:** add new features - growing and sticky header ([#9783](https://github.com/SAP/ui5-webcomponents/issues/9783)) ([a40a451](https://github.com/SAP/ui5-webcomponents/commit/a40a45160d4efe1e1ebb8c43ee3216a2f758558c)) + + + + + # [2.3.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.2.0...v2.3.0-rc.0) (2024-09-05) diff --git a/lerna.json b/lerna.json index 31ea5cac6c8a..1c675e7720a5 100644 --- a/lerna.json +++ b/lerna.json @@ -14,7 +14,7 @@ "packages/create-package", "packages/compat" ], - "version": "2.3.0-rc.0", + "version": "2.3.0-rc.1", "command": { "publish": { "allowBranch": "*", diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 2234885c402d..02edfd7c2c8b 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.3.0-rc.0...v2.3.0-rc.1) (2024-09-12) + +**Note:** Version bump only for package @ui5/webcomponents-ai + + + + + # [2.3.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.2.0...v2.3.0-rc.0) (2024-09-05) **Note:** Version bump only for package @ui5/webcomponents-ai diff --git a/packages/ai/package.json b/packages/ai/package.json index 21de6c098064..43e31c69154c 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -1,6 +1,6 @@ { "name": "@ui5/webcomponents-ai", - "version": "2.3.0-rc.0", + "version": "2.3.0-rc.1", "description": "UI5 Web Components: webcomponents.ai", "ui5": { "webComponentsPackage": true @@ -45,13 +45,13 @@ "directory": "packages/ai" }, "dependencies": { - "@ui5/webcomponents": "2.3.0-rc.0", - "@ui5/webcomponents-base": "2.3.0-rc.0", - "@ui5/webcomponents-icons": "2.3.0-rc.0", - "@ui5/webcomponents-theming": "2.3.0-rc.0" + "@ui5/webcomponents": "2.3.0-rc.1", + "@ui5/webcomponents-base": "2.3.0-rc.1", + "@ui5/webcomponents-icons": "2.3.0-rc.1", + "@ui5/webcomponents-theming": "2.3.0-rc.1" }, "devDependencies": { - "@ui5/webcomponents-tools": "2.3.0-rc.0", + "@ui5/webcomponents-tools": "2.3.0-rc.1", "chromedriver": "^127.0.3" } } diff --git a/packages/base/CHANGELOG.md b/packages/base/CHANGELOG.md index 2f00ec0ef4b5..f400fc26c01a 100644 --- a/packages/base/CHANGELOG.md +++ b/packages/base/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.3.0-rc.0...v2.3.0-rc.1) (2024-09-12) + + +### Bug Fixes + +* **f6 navigation:** work properly when multiple runtimes ([#9810](https://github.com/SAP/ui5-webcomponents/issues/9810)) ([1ebf7db](https://github.com/SAP/ui5-webcomponents/commit/1ebf7db3faa8a2be1d35347407a365f5cf3004de)) +* **ui5-tabcontainer:** fix drag and drop issue with home key and fixеd tabs ([#9812](https://github.com/SAP/ui5-webcomponents/issues/9812)) ([485fd0d](https://github.com/SAP/ui5-webcomponents/commit/485fd0dd1205543e50e7db011d6c5a7bedbe5c68)) + + + + + # [2.3.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.2.0...v2.3.0-rc.0) (2024-09-05) diff --git a/packages/base/package.json b/packages/base/package.json index 9a5063f7cdaa..2c5e9bb397f7 100644 --- a/packages/base/package.json +++ b/packages/base/package.json @@ -1,6 +1,6 @@ { "name": "@ui5/webcomponents-base", - "version": "2.3.0-rc.0", + "version": "2.3.0-rc.1", "description": "UI5 Web Components: webcomponents.base", "author": "SAP SE (https://www.sap.com)", "license": "Apache-2.0", @@ -52,7 +52,7 @@ }, "devDependencies": { "@openui5/sap.ui.core": "1.120.17", - "@ui5/webcomponents-tools": "2.3.0-rc.0", + "@ui5/webcomponents-tools": "2.3.0-rc.1", "chromedriver": "^127.0.3", "clean-css": "^5.2.2", "copy-and-watch": "^0.1.5", diff --git a/packages/compat/CHANGELOG.md b/packages/compat/CHANGELOG.md index fc00c7ffdd5d..235caa5c47bc 100644 --- a/packages/compat/CHANGELOG.md +++ b/packages/compat/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.3.0-rc.0...v2.3.0-rc.1) (2024-09-12) + + +### Bug Fixes + +* **ui5-table:** add aria reference to growing btn sub text ([#9836](https://github.com/SAP/ui5-webcomponents/issues/9836)) ([daa3399](https://github.com/SAP/ui5-webcomponents/commit/daa3399654b81fb51259129e493708b06b6ee8ff)), closes [#9807](https://github.com/SAP/ui5-webcomponents/issues/9807) + + + + + # [2.3.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.2.0...v2.3.0-rc.0) (2024-09-05) **Note:** Version bump only for package @ui5/webcomponents-compat diff --git a/packages/compat/package.json b/packages/compat/package.json index 5232c1303df7..afb77a9c268c 100644 --- a/packages/compat/package.json +++ b/packages/compat/package.json @@ -1,6 +1,6 @@ { "name": "@ui5/webcomponents-compat", - "version": "2.3.0-rc.0", + "version": "2.3.0-rc.1", "description": "UI5 Web Components: webcomponents.compat", "ui5": { "webComponentsPackage": true @@ -45,13 +45,13 @@ "directory": "packages/compat" }, "dependencies": { - "@ui5/webcomponents": "2.3.0-rc.0", - "@ui5/webcomponents-base": "2.3.0-rc.0", - "@ui5/webcomponents-icons": "2.3.0-rc.0", - "@ui5/webcomponents-theming": "2.3.0-rc.0" + "@ui5/webcomponents": "2.3.0-rc.1", + "@ui5/webcomponents-base": "2.3.0-rc.1", + "@ui5/webcomponents-icons": "2.3.0-rc.1", + "@ui5/webcomponents-theming": "2.3.0-rc.1" }, "devDependencies": { - "@ui5/webcomponents-tools": "2.3.0-rc.0", + "@ui5/webcomponents-tools": "2.3.0-rc.1", "chromedriver": "^127.0.3" } } diff --git a/packages/create-package/CHANGELOG.md b/packages/create-package/CHANGELOG.md index 953a4d667a3a..3ef68fcb5509 100644 --- a/packages/create-package/CHANGELOG.md +++ b/packages/create-package/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.3.0-rc.0...v2.3.0-rc.1) (2024-09-12) + +**Note:** Version bump only for package @ui5/create-webcomponents-package + + + + + # [2.3.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.2.0...v2.3.0-rc.0) (2024-09-05) **Note:** Version bump only for package @ui5/create-webcomponents-package diff --git a/packages/create-package/package.json b/packages/create-package/package.json index 1f49abd789fb..fd9e493ef668 100644 --- a/packages/create-package/package.json +++ b/packages/create-package/package.json @@ -1,6 +1,6 @@ { "name": "@ui5/create-webcomponents-package", - "version": "2.3.0-rc.0", + "version": "2.3.0-rc.1", "description": "UI5 Web Components: create package", "author": "SAP SE (https://www.sap.com)", "license": "Apache-2.0", diff --git a/packages/fiori/CHANGELOG.md b/packages/fiori/CHANGELOG.md index 805090396be1..33d680d7785c 100644 --- a/packages/fiori/CHANGELOG.md +++ b/packages/fiori/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.3.0-rc.0...v2.3.0-rc.1) (2024-09-12) + + +### Features + +* **ui5-li-notification-group:** add new features - growing and sticky header ([#9783](https://github.com/SAP/ui5-webcomponents/issues/9783)) ([a40a451](https://github.com/SAP/ui5-webcomponents/commit/a40a45160d4efe1e1ebb8c43ee3216a2f758558c)) + + + + + # [2.3.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.2.0...v2.3.0-rc.0) (2024-09-05) **Note:** Version bump only for package @ui5/webcomponents-fiori diff --git a/packages/fiori/package.json b/packages/fiori/package.json index 077fa0e8aa2b..eb05655c612a 100644 --- a/packages/fiori/package.json +++ b/packages/fiori/package.json @@ -1,6 +1,6 @@ { "name": "@ui5/webcomponents-fiori", - "version": "2.3.0-rc.0", + "version": "2.3.0-rc.1", "description": "UI5 Web Components: webcomponents.fiori", "ui5": { "webComponentsPackage": true @@ -48,14 +48,14 @@ "directory": "packages/fiori" }, "dependencies": { - "@ui5/webcomponents": "2.3.0-rc.0", - "@ui5/webcomponents-base": "2.3.0-rc.0", - "@ui5/webcomponents-icons": "2.3.0-rc.0", - "@ui5/webcomponents-theming": "2.3.0-rc.0", + "@ui5/webcomponents": "2.3.0-rc.1", + "@ui5/webcomponents-base": "2.3.0-rc.1", + "@ui5/webcomponents-icons": "2.3.0-rc.1", + "@ui5/webcomponents-theming": "2.3.0-rc.1", "@zxing/library": "^0.17.1" }, "devDependencies": { - "@ui5/webcomponents-tools": "2.3.0-rc.0", + "@ui5/webcomponents-tools": "2.3.0-rc.1", "chromedriver": "^127.0.3", "lit": "^2.0.0" } diff --git a/packages/icons-business-suite/CHANGELOG.md b/packages/icons-business-suite/CHANGELOG.md index 3f7d5f7c933a..ef61fe13b107 100644 --- a/packages/icons-business-suite/CHANGELOG.md +++ b/packages/icons-business-suite/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.3.0-rc.0...v2.3.0-rc.1) (2024-09-12) + +**Note:** Version bump only for package @ui5/webcomponents-icons-business-suite + + + + + # [2.3.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.2.0...v2.3.0-rc.0) (2024-09-05) **Note:** Version bump only for package @ui5/webcomponents-icons-business-suite diff --git a/packages/icons-business-suite/package.json b/packages/icons-business-suite/package.json index 61768b6a248a..7fed46f5d104 100644 --- a/packages/icons-business-suite/package.json +++ b/packages/icons-business-suite/package.json @@ -1,6 +1,6 @@ { "name": "@ui5/webcomponents-icons-business-suite", - "version": "2.3.0-rc.0", + "version": "2.3.0-rc.1", "description": "UI5 Web Components: SAP Fiori Tools icon set", "author": "SAP SE (https://www.sap.com)", "license": "Apache-2.0", @@ -28,9 +28,9 @@ "directory": "packages/icons-business-suite" }, "dependencies": { - "@ui5/webcomponents-base": "2.3.0-rc.0" + "@ui5/webcomponents-base": "2.3.0-rc.1" }, "devDependencies": { - "@ui5/webcomponents-tools": "2.3.0-rc.0" + "@ui5/webcomponents-tools": "2.3.0-rc.1" } } diff --git a/packages/icons-tnt/CHANGELOG.md b/packages/icons-tnt/CHANGELOG.md index aed9b7b60446..195d1813c71d 100644 --- a/packages/icons-tnt/CHANGELOG.md +++ b/packages/icons-tnt/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.3.0-rc.0...v2.3.0-rc.1) (2024-09-12) + +**Note:** Version bump only for package @ui5/webcomponents-icons-tnt + + + + + # [2.3.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.2.0...v2.3.0-rc.0) (2024-09-05) **Note:** Version bump only for package @ui5/webcomponents-icons-tnt diff --git a/packages/icons-tnt/package.json b/packages/icons-tnt/package.json index 0c678604c86a..706bc55403a4 100644 --- a/packages/icons-tnt/package.json +++ b/packages/icons-tnt/package.json @@ -1,6 +1,6 @@ { "name": "@ui5/webcomponents-icons-tnt", - "version": "2.3.0-rc.0", + "version": "2.3.0-rc.1", "description": "UI5 Web Components: SAP Fiori Tools icon set", "author": "SAP SE (https://www.sap.com)", "license": "Apache-2.0", @@ -28,9 +28,9 @@ "directory": "packages/icons-tnt" }, "dependencies": { - "@ui5/webcomponents-base": "2.3.0-rc.0" + "@ui5/webcomponents-base": "2.3.0-rc.1" }, "devDependencies": { - "@ui5/webcomponents-tools": "2.3.0-rc.0" + "@ui5/webcomponents-tools": "2.3.0-rc.1" } } diff --git a/packages/icons/CHANGELOG.md b/packages/icons/CHANGELOG.md index c097dd8c849d..de20ec62a5c1 100644 --- a/packages/icons/CHANGELOG.md +++ b/packages/icons/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.3.0-rc.0...v2.3.0-rc.1) (2024-09-12) + +**Note:** Version bump only for package @ui5/webcomponents-icons + + + + + # [2.3.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.2.0...v2.3.0-rc.0) (2024-09-05) **Note:** Version bump only for package @ui5/webcomponents-icons diff --git a/packages/icons/package.json b/packages/icons/package.json index ea9615e0afe5..b7f9ed5c81e1 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -1,6 +1,6 @@ { "name": "@ui5/webcomponents-icons", - "version": "2.3.0-rc.0", + "version": "2.3.0-rc.1", "description": "UI5 Web Components: webcomponents.SAP-icons", "author": "SAP SE (https://www.sap.com)", "license": "Apache-2.0", @@ -28,9 +28,9 @@ "directory": "packages/icons" }, "dependencies": { - "@ui5/webcomponents-base": "2.3.0-rc.0" + "@ui5/webcomponents-base": "2.3.0-rc.1" }, "devDependencies": { - "@ui5/webcomponents-tools": "2.3.0-rc.0" + "@ui5/webcomponents-tools": "2.3.0-rc.1" } } diff --git a/packages/localization/CHANGELOG.md b/packages/localization/CHANGELOG.md index d5458ffae1bf..9a18f4df9ebc 100644 --- a/packages/localization/CHANGELOG.md +++ b/packages/localization/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.3.0-rc.0...v2.3.0-rc.1) (2024-09-12) + +**Note:** Version bump only for package @ui5/webcomponents-localization + + + + + # [2.3.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.2.0...v2.3.0-rc.0) (2024-09-05) **Note:** Version bump only for package @ui5/webcomponents-localization diff --git a/packages/localization/package.json b/packages/localization/package.json index f8577b1ed827..397d855718f1 100644 --- a/packages/localization/package.json +++ b/packages/localization/package.json @@ -1,6 +1,6 @@ { "name": "@ui5/webcomponents-localization", - "version": "2.3.0-rc.0", + "version": "2.3.0-rc.1", "description": "Localization for UI5 Web Components", "author": "SAP SE (https://www.sap.com)", "license": "Apache-2.0", @@ -33,7 +33,7 @@ "@babel/generator": "^7.23.6", "@babel/parser": "^7.23.6", "@openui5/sap.ui.core": "1.120.17", - "@ui5/webcomponents-tools": "2.3.0-rc.0", + "@ui5/webcomponents-tools": "2.3.0-rc.1", "babel-plugin-amd-to-esm": "^2.0.3", "chromedriver": "^127.0.3", "estree-walk": "^2.2.0", @@ -42,6 +42,6 @@ }, "dependencies": { "@types/openui5": "^1.113.0", - "@ui5/webcomponents-base": "2.3.0-rc.0" + "@ui5/webcomponents-base": "2.3.0-rc.1" } } diff --git a/packages/main/CHANGELOG.md b/packages/main/CHANGELOG.md index 7d75749f8074..798fbba1f997 100644 --- a/packages/main/CHANGELOG.md +++ b/packages/main/CHANGELOG.md @@ -3,6 +3,26 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.3.0-rc.0...v2.3.0-rc.1) (2024-09-12) + + +### Bug Fixes + +* **ui5-list:** adjust growing button padding ([#9747](https://github.com/SAP/ui5-webcomponents/issues/9747)) ([0373527](https://github.com/SAP/ui5-webcomponents/commit/03735275752df1263a58af456f11e206935c6e9b)) +* **ui5-multi-combobox:** restore focus to input after value state header is removed ([#9827](https://github.com/SAP/ui5-webcomponents/issues/9827)) ([712d94e](https://github.com/SAP/ui5-webcomponents/commit/712d94e3c27c96cb1ab44f57d0e0fd10f7288a4b)), closes [#9709](https://github.com/SAP/ui5-webcomponents/issues/9709) +* **ui5-popover:** fix arrow placement ([#9844](https://github.com/SAP/ui5-webcomponents/issues/9844)) ([6109040](https://github.com/SAP/ui5-webcomponents/commit/6109040822898aea2276925cba951d4a5a766304)) +* **ui5-tabcontainer:** fix drag and drop issue with home key and fixеd tabs ([#9812](https://github.com/SAP/ui5-webcomponents/issues/9812)) ([485fd0d](https://github.com/SAP/ui5-webcomponents/commit/485fd0dd1205543e50e7db011d6c5a7bedbe5c68)) +* **ui5-tabcontainer:** prevent page scrolling when reordering tabs with home and end keys ([#9848](https://github.com/SAP/ui5-webcomponents/issues/9848)) ([03daa72](https://github.com/SAP/ui5-webcomponents/commit/03daa728429491b9284d898d819b0e7cd892b644)) + + +### Features + +* **ui5-li-notification-group:** add new features - growing and sticky header ([#9783](https://github.com/SAP/ui5-webcomponents/issues/9783)) ([a40a451](https://github.com/SAP/ui5-webcomponents/commit/a40a45160d4efe1e1ebb8c43ee3216a2f758558c)) + + + + + # [2.3.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.2.0...v2.3.0-rc.0) (2024-09-05) diff --git a/packages/main/package.json b/packages/main/package.json index 8fe8ab79bdb2..ce2bc4430e68 100644 --- a/packages/main/package.json +++ b/packages/main/package.json @@ -1,6 +1,6 @@ { "name": "@ui5/webcomponents", - "version": "2.3.0-rc.0", + "version": "2.3.0-rc.1", "description": "UI5 Web Components: webcomponents.main", "ui5": { "webComponentsPackage": true @@ -50,15 +50,15 @@ "directory": "packages/main" }, "dependencies": { - "@ui5/webcomponents-base": "2.3.0-rc.0", - "@ui5/webcomponents-icons": "2.3.0-rc.0", - "@ui5/webcomponents-icons-business-suite": "2.3.0-rc.0", - "@ui5/webcomponents-icons-tnt": "2.3.0-rc.0", - "@ui5/webcomponents-localization": "2.3.0-rc.0", - "@ui5/webcomponents-theming": "2.3.0-rc.0" + "@ui5/webcomponents-base": "2.3.0-rc.1", + "@ui5/webcomponents-icons": "2.3.0-rc.1", + "@ui5/webcomponents-icons-business-suite": "2.3.0-rc.1", + "@ui5/webcomponents-icons-tnt": "2.3.0-rc.1", + "@ui5/webcomponents-localization": "2.3.0-rc.1", + "@ui5/webcomponents-theming": "2.3.0-rc.1" }, "devDependencies": { - "@ui5/webcomponents-tools": "2.3.0-rc.0", + "@ui5/webcomponents-tools": "2.3.0-rc.1", "chromedriver": "^127.0.3", "lit": "^2.0.0" } diff --git a/packages/theming/CHANGELOG.md b/packages/theming/CHANGELOG.md index 4dbf58a5e7cc..83b58c545ea0 100644 --- a/packages/theming/CHANGELOG.md +++ b/packages/theming/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.3.0-rc.0...v2.3.0-rc.1) (2024-09-12) + +**Note:** Version bump only for package @ui5/webcomponents-theming + + + + + # [2.3.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.2.0...v2.3.0-rc.0) (2024-09-05) **Note:** Version bump only for package @ui5/webcomponents-theming diff --git a/packages/theming/package.json b/packages/theming/package.json index e15a0001a6e4..6443b961352d 100644 --- a/packages/theming/package.json +++ b/packages/theming/package.json @@ -1,6 +1,6 @@ { "name": "@ui5/webcomponents-theming", - "version": "2.3.0-rc.0", + "version": "2.3.0-rc.1", "description": "UI5 Web Components: webcomponents.theming", "author": "SAP SE (https://www.sap.com)", "license": "Apache-2.0", @@ -31,10 +31,10 @@ }, "dependencies": { "@sap-theming/theming-base-content": "11.17.1", - "@ui5/webcomponents-base": "2.3.0-rc.0" + "@ui5/webcomponents-base": "2.3.0-rc.1" }, "devDependencies": { - "@ui5/webcomponents-tools": "2.3.0-rc.0", + "@ui5/webcomponents-tools": "2.3.0-rc.1", "globby": "^13.1.1", "json-beautify": "^1.1.1", "nps": "^5.10.0", diff --git a/packages/tools/CHANGELOG.md b/packages/tools/CHANGELOG.md index 026afc661f09..23d74dbd382f 100644 --- a/packages/tools/CHANGELOG.md +++ b/packages/tools/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.3.0-rc.0...v2.3.0-rc.1) (2024-09-12) + +**Note:** Version bump only for package @ui5/webcomponents-tools + + + + + # [2.3.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.2.0...v2.3.0-rc.0) (2024-09-05) **Note:** Version bump only for package @ui5/webcomponents-tools diff --git a/packages/tools/package.json b/packages/tools/package.json index 4d8a23cf557a..ebef69f23d9a 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -1,6 +1,6 @@ { "name": "@ui5/webcomponents-tools", - "version": "2.3.0-rc.0", + "version": "2.3.0-rc.1", "description": "UI5 Web Components: webcomponents.tools", "author": "SAP SE (https://www.sap.com)", "license": "Apache-2.0", diff --git a/packages/website/CHANGELOG.md b/packages/website/CHANGELOG.md index c99b8fd8119f..95bb14e80070 100644 --- a/packages/website/CHANGELOG.md +++ b/packages/website/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.3.0-rc.1](https://github.com/SAP/ui5-webcomponents/compare/v2.3.0-rc.0...v2.3.0-rc.1) (2024-09-12) + + +### Features + +* **ui5-li-notification-group:** add new features - growing and sticky header ([#9783](https://github.com/SAP/ui5-webcomponents/issues/9783)) ([a40a451](https://github.com/SAP/ui5-webcomponents/commit/a40a45160d4efe1e1ebb8c43ee3216a2f758558c)) + + + + + # [2.3.0-rc.0](https://github.com/SAP/ui5-webcomponents/compare/v2.2.0...v2.3.0-rc.0) (2024-09-05) diff --git a/packages/website/package.json b/packages/website/package.json index 12d98325ca26..31c9a87aa648 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -1,6 +1,6 @@ { "name": "@ui5/webcomponents-website", - "version": "2.3.0-rc.0", + "version": "2.3.0-rc.1", "private": true, "scripts": { "generate-local-cdn": "rimraf ./local-cdn && node ./build-scripts/local-cdn.mjs", From c5d02499a849b09cd076b9419c723a81b642bf17 Mon Sep 17 00:00:00 2001 From: Stoyan <88034608+hinzzx@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:31:49 +0300 Subject: [PATCH 018/114] fix(ui5-dynamic-side-content): fix scrollbar styling (#9842) Previously, when content was added to the ui5-dynamic-side-content component, a scrollbar would appear using the default browser scrollbar styles and sizes. Now, custom scrollbar styles have been added, which adapt to the current theme. --- packages/fiori/src/DynamicSideContent.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/fiori/src/DynamicSideContent.ts b/packages/fiori/src/DynamicSideContent.ts index ed548f0d25c6..9205773221b1 100644 --- a/packages/fiori/src/DynamicSideContent.ts +++ b/packages/fiori/src/DynamicSideContent.ts @@ -3,6 +3,7 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement import event from "@ui5/webcomponents-base/dist/decorators/event.js"; import property from "@ui5/webcomponents-base/dist/decorators/property.js"; import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; +import getEffectiveScrollbarStyle from "@ui5/webcomponents-base/dist/util/getEffectiveScrollbarStyle.js"; import type { ClassMap } from "@ui5/webcomponents-base/dist/types.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; @@ -109,7 +110,7 @@ type DynamicSideContentLayoutChangeEventDetail = { @customElement({ tag: "ui5-dynamic-side-content", renderer: litRender, - styles: DynamicSideContentCss, + styles: [DynamicSideContentCss, getEffectiveScrollbarStyle()], template: DynamicSideContentTemplate, }) /** From bf1ede71a3917267c6eac934721abd097c970d72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:05:24 +0300 Subject: [PATCH 019/114] chore(deps): bump express from 4.19.2 to 4.21.0 (#9855) Bumps [express](https://github.com/expressjs/express) from 4.19.2 to 4.21.0. - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0) --- updated-dependencies: - dependency-name: express dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 110 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 65 insertions(+), 45 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7a16d376ad3b..f37f03db0de8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5458,10 +5458,10 @@ bluebird@^3.5.1, bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -body-parser@1.20.2: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== dependencies: bytes "3.1.2" content-type "~1.0.5" @@ -5471,7 +5471,7 @@ body-parser@1.20.2: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.11.0" + qs "6.13.0" raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" @@ -5726,7 +5726,7 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" -call-bind@^1.0.5: +call-bind@^1.0.5, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== @@ -7715,6 +7715,11 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + encoding@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" @@ -8347,36 +8352,36 @@ exponential-backoff@^3.1.1: integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== express@^4.14.0, express@^4.17.3: - version "4.19.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" - integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + version "4.21.0" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915" + integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.2" + body-parser "1.20.3" content-disposition "0.5.4" content-type "~1.0.4" cookie "0.6.0" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "1.2.0" + finalhandler "1.3.1" fresh "0.5.2" http-errors "2.0.0" - merge-descriptors "1.0.1" + merge-descriptors "1.0.3" methods "~1.1.2" on-finished "2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.7" + path-to-regexp "0.1.10" proxy-addr "~2.0.7" - qs "6.11.0" + qs "6.13.0" range-parser "~1.2.1" safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" + send "0.19.0" + serve-static "1.16.2" setprototypeof "1.2.0" statuses "2.0.1" type-is "~1.6.18" @@ -8561,13 +8566,13 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== dependencies: debug "2.6.9" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" on-finished "2.4.1" parseurl "~1.3.3" @@ -12054,10 +12059,10 @@ meow@^8.0.0, meow@^8.1.2: type-fest "^0.18.0" yargs-parser "^20.2.3" -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== merge-stream@^2.0.0: version "2.0.0" @@ -13246,6 +13251,11 @@ object-inspect@^1.10.3, object-inspect@^1.12.3, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -13801,10 +13811,10 @@ path-scurry@^1.10.1, path-scurry@^1.6.1: lru-cache "^9.1.1 || ^10.0.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +path-to-regexp@0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" + integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== path-to-regexp@2.2.1: version "2.2.1" @@ -14612,12 +14622,12 @@ qs@6.10.4: dependencies: side-channel "^1.0.4" -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== +qs@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== dependencies: - side-channel "^1.0.4" + side-channel "^1.0.6" query-selector-shadow-dom@^1.0.0: version "1.0.1" @@ -15616,10 +15626,10 @@ semver@^7.3.2: dependencies: lru-cache "^6.0.0" -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== dependencies: debug "2.6.9" depd "2.0.0" @@ -15683,15 +15693,15 @@ serve-index@^1.9.1: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== dependencies: - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.18.0" + send "0.19.0" set-blocking@^2.0.0: version "2.0.0" @@ -15779,6 +15789,16 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + signal-exit@3.0.7, signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" From 2e0aa039e652534c8d7b667f431306cde1321fab Mon Sep 17 00:00:00 2001 From: Nikolay Deshev Date: Fri, 13 Sep 2024 09:26:15 +0300 Subject: [PATCH 020/114] fix(ui5-multi-combobox): fix aria-describedby token count (#9792) * fix(ui5-multi-combobox): adjust the failing aria-describedby test the test is fixed to focus out the tested ui5-multi-combobox in order for the aria-describedby attribute to be updated --- packages/main/src/MultiComboBox.ts | 6 +++++- packages/main/test/specs/MultiComboBox.spec.js | 3 +-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/main/src/MultiComboBox.ts b/packages/main/src/MultiComboBox.ts index f1d3e8a93840..1f9feba88741 100644 --- a/packages/main/src/MultiComboBox.ts +++ b/packages/main/src/MultiComboBox.ts @@ -497,6 +497,7 @@ class MultiComboBox extends UI5Element implements IFormInputElement { _isOpenedByKeyboard?: boolean; _itemToFocus?: IMultiComboBoxItem; _itemsBeforeOpen: Array; + _tokenCount: number | undefined; selectedItems: Array; static i18nBundle: I18nBundle; @@ -706,6 +707,7 @@ class MultiComboBox extends UI5Element implements IFormInputElement { } _tokenDelete(e: CustomEvent) { + this._tokenCount = this._tokenizer.tokens.length - e.detail.tokens.length; this._previouslySelectedItems = this._getSelectedItems(); const token: Token[] = e.detail.tokens; const deletingItems = this._getItems().filter(item => token.some(t => t.getAttribute("data-ui5-id") === item._id)); @@ -1891,7 +1893,9 @@ class MultiComboBox extends UI5Element implements IFormInputElement { if (!this._tokenizer) { return; } - return getTokensCountText(this._tokenizer.tokens.length); + + this._tokenCount = this._tokenCount !== undefined ? this._tokenCount : this._tokenizer.tokens.length; + return getTokensCountText(this._tokenCount); } get _tokensCountTextId() { diff --git a/packages/main/test/specs/MultiComboBox.spec.js b/packages/main/test/specs/MultiComboBox.spec.js index 38bbec973285..f8cccfc0bfba 100644 --- a/packages/main/test/specs/MultiComboBox.spec.js +++ b/packages/main/test/specs/MultiComboBox.spec.js @@ -1745,7 +1745,7 @@ describe("MultiComboBox general interaction", () => { assert.strictEqual(await innerInput.getAttribute("aria-describedby"), ariaDescribedBy, "aria-describedby has a reference for the value state and the tokens count"); }); - it.skip("aria-describedby value according to the tokens count", async () => { + it("aria-describedby value according to the tokens count", async () => { const mcb = await browser.$("#mcb-compact"); await mcb.scrollIntoView(); @@ -1780,7 +1780,6 @@ describe("MultiComboBox general interaction", () => { assert.ok(await ariaHiddenText.includes(resourceBundleText), "aria-describedby text is correct"); await innerInput.keys("Backspace"); - await innerInput.keys("Tab"); tokens = await mcb.shadow$$(".ui5-multi-combobox-token"); invisibleText = await mcb.shadow$(".ui5-hidden-text"); From bd3aab9d7765f31019d3dd93b8923e9bfea8a71f Mon Sep 17 00:00:00 2001 From: ilhan orhan Date: Fri, 13 Sep 2024 09:36:47 +0300 Subject: [PATCH 021/114] chore: migrate more base tests to Cypress (#9849) --- .../specs/base/IgnoreCustomElements.cy.ts | 35 ++++++++++++ .../cypress/specs/base/InvisibleMessage.cy.ts | 31 +++++++++++ .../main/cypress/specs/base/Tooltips.cy.ts | 55 +++++++++++++++++++ packages/main/src/SegmentedButtonItem.hbs | 2 +- .../specs/base/IgnoreCustomElements.spec.js | 23 -------- .../test/specs/base/InvisibleMessage.spec.js | 41 -------------- .../main/test/specs/base/Tooltips.spec.js | 27 --------- .../components-package/cypress.config.js | 2 +- 8 files changed, 123 insertions(+), 93 deletions(-) create mode 100644 packages/main/cypress/specs/base/IgnoreCustomElements.cy.ts create mode 100644 packages/main/cypress/specs/base/InvisibleMessage.cy.ts create mode 100644 packages/main/cypress/specs/base/Tooltips.cy.ts delete mode 100644 packages/main/test/specs/base/IgnoreCustomElements.spec.js delete mode 100644 packages/main/test/specs/base/InvisibleMessage.spec.js delete mode 100644 packages/main/test/specs/base/Tooltips.spec.js diff --git a/packages/main/cypress/specs/base/IgnoreCustomElements.cy.ts b/packages/main/cypress/specs/base/IgnoreCustomElements.cy.ts new file mode 100644 index 000000000000..a807a40e7090 --- /dev/null +++ b/packages/main/cypress/specs/base/IgnoreCustomElements.cy.ts @@ -0,0 +1,35 @@ +import { html } from "lit"; +import "../../../src/Card.js"; +import "../../../src/CardHeader.js"; + +import { ignoreCustomElements, shouldIgnoreCustomElement } from "@ui5/webcomponents-base/dist/IgnoreCustomElements.js"; + +ignoreCustomElements("app-"); +ignoreCustomElements("my-"); + +describe("Ignore Custom Elements", () => { + it("tests shouldIgnoreCustomElement method", () => { + cy.mount(html` + + + + + + `); + + cy.wrap({ shouldIgnoreCustomElement }) + .invoke("shouldIgnoreCustomElement", "app-trip-calendar") + .should("be.true"); + + cy.wrap({ shouldIgnoreCustomElement }) + .invoke("shouldIgnoreCustomElement", "my-trip-calendar") + .should("be.true"); + + cy.wrap({ shouldIgnoreCustomElement }) + .invoke("shouldIgnoreCustomElement", "ui5-card-header") + .should("be.false"); + }); +}); diff --git a/packages/main/cypress/specs/base/InvisibleMessage.cy.ts b/packages/main/cypress/specs/base/InvisibleMessage.cy.ts new file mode 100644 index 000000000000..a39366621508 --- /dev/null +++ b/packages/main/cypress/specs/base/InvisibleMessage.cy.ts @@ -0,0 +1,31 @@ +import announce from "@ui5/webcomponents-base/dist/util/InvisibleMessage.js"; +import "../../../src/Button.js"; + +describe("InvisibleMessage", () => { + it("Initial rendering", () => { + cy.get(".ui5-invisiblemessage-polite") + .should("exist"); + + cy.get(".ui5-invisiblemessage-assertive") + .should("exist"); + }); + + it("String annoucement", () => { + cy.wrap({ announce }) + .invoke("announce", "announcement", "Polite"); + cy.wrap({ announce }) + .invoke("announce", "announcement", "Assertive"); + + // assert + cy.get(".ui5-invisiblemessage-polite") + .should("contain", "announcement"); + cy.get(".ui5-invisiblemessage-assertive") + .should("contain", "announcement"); + + // assert - announcement is cleared + cy.get(".ui5-invisiblemessage-polite") + .should("not.contain", "announcement"); + cy.get(".ui5-invisiblemessage-assertive") + .should("not.contain", "announcement"); + }); +}); diff --git a/packages/main/cypress/specs/base/Tooltips.cy.ts b/packages/main/cypress/specs/base/Tooltips.cy.ts new file mode 100644 index 000000000000..7428088e6fa4 --- /dev/null +++ b/packages/main/cypress/specs/base/Tooltips.cy.ts @@ -0,0 +1,55 @@ +import { setEnableDefaultTooltips } from "@ui5/webcomponents-base/dist/config/Tooltips.js"; +import { html } from "lit"; +import "../../../src/Icon.js"; +import "../../../src/Button.js"; +import "../../../src/ToggleButton.js"; +import "../../../src/SegmentedButton.js"; +import "../../../src/SegmentedButtonItem.js"; +import "../../../src/RatingIndicator.js"; + +setEnableDefaultTooltips(false); + +describe("Default Tooltips", () => { + it("tests navigation", () => { + cy.mount(html` + + + + + + + + + `); + + cy.get("#ic") + .shadow() + .find("title") + .should("not.exist"); + + cy.get("#btn") + .shadow() + .find(".ui5-button-icon") + .should("not.have.attr", "title"); + + cy.get("#togglebtn") + .shadow() + .find(".ui5-button-icon") + .should("not.have.attr", "title"); + + cy.get("#rt") + .shadow() + .find(".ui5-rating-indicator-root") + .should("not.have.attr", "title"); + + cy.get("#segBtnItem") + .shadow() + .find(".ui5-segmented-button-item-root") + .should("not.have.attr", "title"); + + cy.get("#segBtnItem") + .shadow() + .find(".ui5-segmented-button-item-icon") + .should("not.have.attr", "title"); + }); +}); diff --git a/packages/main/src/SegmentedButtonItem.hbs b/packages/main/src/SegmentedButtonItem.hbs index a3354dedce25..099b579ed5e3 100644 --- a/packages/main/src/SegmentedButtonItem.hbs +++ b/packages/main/src/SegmentedButtonItem.hbs @@ -18,7 +18,7 @@ class="ui5-segmented-button-item-icon" name="{{icon}}" part="icon" - ?show-tooltip={{iconOnly}} + ?show-tooltip={{showIconTooltip}} > {{/if}} diff --git a/packages/main/test/specs/base/IgnoreCustomElements.spec.js b/packages/main/test/specs/base/IgnoreCustomElements.spec.js deleted file mode 100644 index 547ec32475b6..000000000000 --- a/packages/main/test/specs/base/IgnoreCustomElements.spec.js +++ /dev/null @@ -1,23 +0,0 @@ -import { assert } from "chai"; - -describe("Ignore Custom Elements", () => { - before(async () => { - await browser.url("test/pages/base/IgnoreCustomElements.html"); - }); - - it("Tests ignore custom elements", async () => { - const result = await browser.executeAsync(done => { - const bundle = window['sap-ui-webcomponents-bundle']; - - const res = {}; - res.ignoreApp = bundle.shouldIgnoreCustomElement("app-trip-calendar"); // true - see ignoreCustomElements("app-"); in bundle.js - res.ignoreMy = bundle.shouldIgnoreCustomElement("my-trip-calendar "); // true - see ignoreCustomElements("my-"); in bundle.js - res.ignoreUI5 = bundle.shouldIgnoreCustomElement("ui5-card-header"); // false - done(res); - }); - - assert.ok(result.ignoreApp, "The app-trip-calendar tag is ignored"); - assert.ok(result.ignoreMy, "The my-trip-calendar tag is ignored"); - assert.notOk(result.ignoreUI5, "The ui5-card-header tag is not ignored"); - }); -}); \ No newline at end of file diff --git a/packages/main/test/specs/base/InvisibleMessage.spec.js b/packages/main/test/specs/base/InvisibleMessage.spec.js deleted file mode 100644 index eb519e126707..000000000000 --- a/packages/main/test/specs/base/InvisibleMessage.spec.js +++ /dev/null @@ -1,41 +0,0 @@ -import { assert } from "chai"; - -describe("InvisibleMessage", () => { - before(async () => { - await browser.url(`test/pages/base/InvisibleMessage.html`); - }); - - it("Initial rendering", async () => { - const politeSpan = await browser.$(".ui5-invisiblemessage-polite"); - const assertiveSpan = await browser.$(".ui5-invisiblemessage-assertive"); - - assert.ok(politeSpan, "Polite span is rendered"); - assert.ok(assertiveSpan, "Assertive span is rendered"); - }); - - it("String annoucement", async () => { - const politeSpan = await browser.$(".ui5-invisiblemessage-polite"); - const assertiveSpan = await browser.$(".ui5-invisiblemessage-assertive"); - const button = await browser.$("#announce-button"); - const checkBox = await browser.$("#announce-checkbox"); - - await browser.$("#announce-textarea").setProperty("value", "announcement"); - - await button.click(); - await checkBox.click(); - await button.click(); - - let politeSpanHtml = await politeSpan.getHTML(); - let assertiveSpanHtml = await assertiveSpan.getHTML(); - assert.include(politeSpanHtml, "announcement", "Value has been rendered."); - assert.include(assertiveSpanHtml, "announcement", "Value has been rendered."); - - await browser.pause(3000); - - politeSpanHtml = await politeSpan.getHTML(); - assertiveSpanHtml = await assertiveSpan.getHTML(); - - assert.notInclude(politeSpanHtml, "announcement", "Value should be cleared."); - assert.notInclude(assertiveSpanHtml, "announcement", "Value should be cleared."); - }); -}); diff --git a/packages/main/test/specs/base/Tooltips.spec.js b/packages/main/test/specs/base/Tooltips.spec.js deleted file mode 100644 index 5a6012f9f6dd..000000000000 --- a/packages/main/test/specs/base/Tooltips.spec.js +++ /dev/null @@ -1,27 +0,0 @@ -import { assert } from "chai"; - -describe("Default Tooltips", () => { - before(async () => { - await browser.url(`test/pages/base/Tooltips.html?sap-ui-enableDefaultTooltips=false`); - }); - - it("Tooltips turned off", async () => { - const btn = await browser.$("#btn").shadow$(".ui5-button-root"); - const btnIcon = await browser.$("#btn").shadow$(".ui5-button-icon"); - const rt = await browser.$("#rt").shadow$(".ui5-rating-indicator-root"); - const segBtnItem = await browser.$("#segBtnItem").shadow$(".ui5-segmented-button-item-root"); - const segBtnItemIcon = await browser.$("#segBtnItem").shadow$(".ui5-segmented-button-item-icon"); - - const btnTitle = await btn.getAttribute("title"); - const btnIconTitle = await btnIcon.getAttribute("title"); - const rtTitle = await rt.getAttribute("title"); - const segBtnItemTitle = await segBtnItem.getAttribute("title"); - const segBtnItemIconTitle = await segBtnItemIcon.getAttribute("title"); - - assert.notOk(btnTitle, "An icon only Button has no default tooltip."); - assert.notOk(btnIconTitle, "An icon only Button icon has no default tooltip."); - assert.notOk(rtTitle, "The Rating Indicator has no default tooltip."); - assert.notOk(segBtnItemTitle, "An icon only Segmented Button Item has no default tooltip"); - assert.notOk(segBtnItemIconTitle, "An icon only Segmented Button Item icon has no default tooltip"); - }); -}); diff --git a/packages/tools/components-package/cypress.config.js b/packages/tools/components-package/cypress.config.js index 8abac3663795..ffce82df2dae 100644 --- a/packages/tools/components-package/cypress.config.js +++ b/packages/tools/components-package/cypress.config.js @@ -6,7 +6,7 @@ module.exports = defineConfig({ component: { supportFile: path.join(__dirname, "cypress/support/component.js"), indexHtmlFile: path.join(__dirname, "cypress/support/component-index.html"), - specPattern: "**/specs/*.cy.{js,ts}", + specPattern: ["**/specs/*.cy.{js,ts}", "**/specs/**/*.cy.{js,ts}"], devServer: { framework: 'cypress-ct-lit', bundler: 'vite', From 0b575120c079a3726320046d033aeb1ba4a4dee5 Mon Sep 17 00:00:00 2001 From: PetyaMarkovaBogdanova Date: Mon, 16 Sep 2024 10:07:28 +0300 Subject: [PATCH 022/114] fix(ui5-illustrated-message): illustration fixed (#9739) Co-authored-by: PetyaMarkovaBogdanova --- .../fiori/src/themes/IllustratedMessage.css | 3 +- .../test/specs/IllustratedMessage.spec.js | 32 ++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/fiori/src/themes/IllustratedMessage.css b/packages/fiori/src/themes/IllustratedMessage.css index db27bed45881..cb13578a1835 100644 --- a/packages/fiori/src/themes/IllustratedMessage.css +++ b/packages/fiori/src/themes/IllustratedMessage.css @@ -16,7 +16,8 @@ flex-direction: column; align-items: center; justify-content: center; - height:inherit; + height: inherit; + min-height: 0; flex-basis: content; } diff --git a/packages/fiori/test/specs/IllustratedMessage.spec.js b/packages/fiori/test/specs/IllustratedMessage.spec.js index bfe854a31e24..c916127cf859 100644 --- a/packages/fiori/test/specs/IllustratedMessage.spec.js +++ b/packages/fiori/test/specs/IllustratedMessage.spec.js @@ -110,7 +110,7 @@ describe("Vertical responsiveness", () => { it("content with fixed design fits the parent container", async () => { - const newContainerHeight = 200, + const newContainerHeight = 250, expectedMedia = "dialog", illustratedMsg = await browser.$("#illustratedMsg5"); @@ -150,6 +150,35 @@ describe("Vertical responsiveness", () => { assert.strictEqual(cssHeight, "160px", "svg has expected height"); }); + + it("Illustration visible, when container fit content height", async () => { + + const illustratedMsgContainer = await browser.$(".illustratedmessage1auto"); + const illustratedMsg = await browser.$("#illustratedMsg1"); + await illustratedMsg.setProperty("design", "Scene"); + + // Act + await illustratedMsgContainer.setAttribute("style", "height: 440px"); + const illustration = await illustratedMsg.shadow$(".ui5-illustrated-message-illustration svg"); + + // Check + assert.notEqual(await illustration.getProperty("scrollHeight"), 0, "Illustration fits its container inherited height"); + await illustratedMsgContainer.setAttribute("style", ""); + }); + + it("Illustration visible, when IM slotted and container has fixed height", async () => { + + const panel = await browser.$("#panel1"); + const illustratedMsg = await browser.$("#illustratedMsg4"); + const illustration = await illustratedMsg.shadow$(".ui5-illustrated-message-illustration svg"); + + // Act + await panel.setAttribute("style", "height: 19rem"); + + // Check + assert.notEqual(await illustration.getProperty("scrollHeight"), 0, "Illustration fits its container inherited height"); + await panel.setAttribute("style", ""); + }); }); describe("Dot design resource handling", () => { @@ -182,4 +211,5 @@ describe("Dot design resource handling", () => { // Check assert.strictEqual(await illustration.getProperty("id"), "sapIllus-Dot-AddPeople", "Dot is present, therefore used"); }); + }); From 7d88950f4589c0e1ddeff618fa2d114e374889b0 Mon Sep 17 00:00:00 2001 From: ilhan orhan Date: Mon, 16 Sep 2024 10:39:50 +0300 Subject: [PATCH 023/114] chore: migrate more tests to Cypress (#9863) --- .../cypress/specs/base/AriaLabelHelper.cy.ts | 305 ++++++++++++++++++ .../cypress/specs/base/IconCollection.cy.ts | 55 ++++ .../specs/base/css/redfish.custom.theme.css | 13 + .../base/IconCollectionInCustomTheme.html | 2 +- .../test/specs/base/AriaLabelHelper.spec.js | 137 -------- .../test/specs/base/IconCollection.spec.js | 70 ---- 6 files changed, 374 insertions(+), 208 deletions(-) create mode 100644 packages/main/cypress/specs/base/AriaLabelHelper.cy.ts create mode 100644 packages/main/cypress/specs/base/IconCollection.cy.ts create mode 100644 packages/main/cypress/specs/base/css/redfish.custom.theme.css delete mode 100644 packages/main/test/specs/base/AriaLabelHelper.spec.js delete mode 100644 packages/main/test/specs/base/IconCollection.spec.js diff --git a/packages/main/cypress/specs/base/AriaLabelHelper.cy.ts b/packages/main/cypress/specs/base/AriaLabelHelper.cy.ts new file mode 100644 index 000000000000..4b018805b158 --- /dev/null +++ b/packages/main/cypress/specs/base/AriaLabelHelper.cy.ts @@ -0,0 +1,305 @@ +import { html } from "lit"; +import "../../../src/Label.js"; +import "../../../src/Input.js"; + +describe("AriaLabelHelper", () => { + it("Label-for tests", () => { + cy.mount(html` + + Desc1 + Desc2 + Desc3 + + `); + + // assert + cy.get("#myInput") + .shadow() + .find("input") + .invoke("attr", "aria-label") + .should("eq", "Desc1 Desc2 Desc3 Desc4"); + + // act + cy.get("#lblDesc2") + .invoke("attr", "for", "other"); + + cy.get("#lblDesc3") + .invoke("remove"); + + // assert + cy.get("#myInput") + .shadow() + .find("input") + .invoke("attr", "aria-label") + .should("eq", "Desc1 Desc4"); + }); + + it("Input accessibleNameRef Tests", () => { + cy.mount(html` + FirstDesc + SecondDesc + ThirdDesc + + `); + + // assert + cy.get("#inputEnterName") + .shadow() + .find("input") + .invoke("attr", "aria-label") + .should("eq", "FirstDesc ThirdDesc"); + + // act - update text of referenced label + cy.get("#lblEnterName1") + .then($el => { + $el.get(0).innerHTML = "First Label Desc"; + }); + + // assert + cy.get("#inputEnterName") + .shadow() + .find("input") + .invoke("attr", "aria-label") + .should("eq", "First Label Desc ThirdDesc"); + + // act - update accessible-name-ref + cy.get("#inputEnterName") + .invoke("attr", "accessible-name-ref", "lblEnterName3 lblEnterName1"); + + // assert + cy.get("#inputEnterName") + .shadow() + .find("input") + .invoke("attr", "aria-label") + .should("eq", "ThirdDesc First Label Desc"); + + // act - update accessible-name-ref + cy.get("#inputEnterName") + .invoke("attr", "accessible-name-ref", "lblEnterName2"); + + // assert + cy.get("#inputEnterName") + .shadow() + .find("input") + .invoke("attr", "aria-label") + .should("eq", "SecondDesc"); + }); + + it("Input accessibleName and accessibleNameRef Tests", () => { + cy.mount(html` + Label for inputEnterDesc + Label to be added/removed as accessible-name-ref + + `); + + const INITIAL_ACCESSIBLE_NAME = "Some description added by accessibleName"; + const UPDATED_ACCESSIBLE_NAME = "Another description added by accessibleName"; + const ACCESSIBLE_NAME_REF = "lblEnterDesc3"; + const ACCESSIBLE_NAME_REF_TEXT = "Label to be added/removed as accessible-name-ref"; + + cy.get("#inputEnterDesc") + .shadow() + .find("input") + .as("input"); + + // assert + cy.get("@input") + .invoke("attr", "aria-label") + .should("eq", INITIAL_ACCESSIBLE_NAME); + + cy.get("#inputEnterDesc") + .invoke("attr", "accessible-name", UPDATED_ACCESSIBLE_NAME); + + // assert + cy.get("@input") + .invoke("attr", "aria-label") + .should("eq", UPDATED_ACCESSIBLE_NAME); + + // act - remove acccessible-name + cy.get("#inputEnterDesc") + .invoke("removeAttr", "accessible-name"); + + // assert - aria-label fallbacks to use the label's for, pointing to this input + cy.get("@input") + .invoke("attr", "aria-label") + .should("eq", "Label for inputEnterDesc"); + + // act - add acccessible-name-ref + cy.get("#inputEnterDesc") + .invoke("attr", "accessible-name-ref", ACCESSIBLE_NAME_REF); + + // assert - the text of the elment labelled with accessible-name-ref is used + cy.get("@input") + .invoke("attr", "aria-label") + .should("eq", ACCESSIBLE_NAME_REF_TEXT); + + // act - add acccessible-name once again + cy.get("#inputEnterDesc") + .invoke("attr", "accessible-name", INITIAL_ACCESSIBLE_NAME); + + // assert - the text of the elment labelled with accessible-name-ref is still used + cy.get("@input") + .invoke("attr", "aria-label") + .should("eq", ACCESSIBLE_NAME_REF_TEXT); + + // act - remove acccessible-name-ref + cy.get("#inputEnterDesc") + .invoke("removeAttr", "accessible-name-ref"); + + // assert - after acccessible-name-ref is removed, fallbacks to use acccessible-name + cy.get("@input") + .invoke("attr", "aria-label") + .should("eq", INITIAL_ACCESSIBLE_NAME); + + // act - remove acccessible-name + cy.get("#inputEnterDesc") + .invoke("removeAttr", "accessible-name"); + + // assert - aria-label fallbacks to use the label's for, pointing to this input + cy.get("@input") + .invoke("attr", "aria-label") + .should("eq", "Label for inputEnterDesc"); + + // act - remove ui5-label's for + cy.get("#lblEnterDesc1") + .invoke("removeAttr", "for"); + + // assert - aria-label is undefined + cy.get("@input") + .invoke("attr", "aria-label") + .should("eq", undefined); + }); + + it("Three inputs with same label accessibleNameRef Tests", () => { + cy.mount(html` + Label for testInput1 Desc + + + + `); + + const LBL_TEXT_CONTENT = "Label for testInput1 Desc"; + const LBL_TEXT_CONTENT_UPDATED = "Another description for testing"; + + cy.get("#testInput1") + .shadow() + .find("input") + .as("input1"); + + cy.get("#testInput2") + .shadow() + .find("input") + .as("input2"); + + cy.get("#testInput3") + .shadow() + .find("input") + .as("input3"); + + // assert + cy.get("@input1") + .invoke("attr", "aria-label") + .should("eq", LBL_TEXT_CONTENT); + + cy.get("@input2") + .invoke("attr", "aria-label") + .should("eq", LBL_TEXT_CONTENT); + + cy.get("@input3") + .invoke("attr", "aria-label") + .should("eq", LBL_TEXT_CONTENT); + + // act + + cy.get("#lblTestDesc") + .then($el => { + $el.get(0).innerHTML = LBL_TEXT_CONTENT_UPDATED; + }); + + // assert + cy.get("@input1") + .invoke("attr", "aria-label") + .should("eq", LBL_TEXT_CONTENT_UPDATED); + + cy.get("@input2") + .invoke("attr", "aria-label") + .should("eq", LBL_TEXT_CONTENT_UPDATED); + + cy.get("@input3") + .invoke("attr", "aria-label") + .should("eq", LBL_TEXT_CONTENT_UPDATED); + + // act - remove "for" attribute + cy.get("#lblTestDesc") + .invoke("removeAttr", "for"); + + // assert - aria-label is undefined + cy.get("@input1") + .invoke("attr", "aria-label") + .should("eq", undefined); + + // act - remove accessible-name-ref + cy.get("#testInput2") + .invoke("removeAttr", "accessible-name-ref"); + + // assert - aria-label is the existing accessible-name + cy.get("@input2") + .invoke("attr", "aria-label") + .should("eq", "Hello"); + + // act - remove accessible-name-ref + cy.get("#testInput3") + .invoke("removeAttr", "accessible-name-ref"); + + // assert - shouldn't be any aria-label + cy.get("@input3") + .invoke("attr", "aria-label") + .should("eq", undefined); + }); + + it("Tests generic html elements with for attribute", () => { + cy.mount(html` + + + +
Desc3
+ Desc4 + Desc5 + `); + + cy.get("#myInput2") + .shadow() + .find("input") + .as("input"); + + // assert + cy.get("@input") + .invoke("attr", "aria-label") + .should("eq", "Desc1 Desc2 Desc3 Desc4 Desc5"); + + // act + cy.get("#elId1") + .then($el => { + $el.get(0).innerHTML = `${$el.get(0).innerHTML}X`; + }); + + cy.get("#elId2") + .invoke("remove"); + + cy.get("#elId3") + .invoke("attr", "for", "other"); + + cy.get("#elId4") + .then($el => { + $el.get(0).innerHTML = `${$el.get(0).innerHTML}X`; + }); + + cy.get("#elId5") + .invoke("removeAttr", "for"); + + // assert + cy.get("@input") + .invoke("attr", "aria-label") + .should("eq", "Desc1X Desc4X"); + }); +}); diff --git a/packages/main/cypress/specs/base/IconCollection.cy.ts b/packages/main/cypress/specs/base/IconCollection.cy.ts new file mode 100644 index 000000000000..8fe2f07e7f75 --- /dev/null +++ b/packages/main/cypress/specs/base/IconCollection.cy.ts @@ -0,0 +1,55 @@ +import "./css/redfish.custom.theme.css"; +import getEffectiveIconCollection from "@ui5/webcomponents-base/dist/asset-registries/util/getIconCollectionByTheme.js"; +import { setTheme, isLegacyThemeFamily } from "@ui5/webcomponents-base/dist/config/Theme.js"; +import { html } from "lit"; +import "../../../src/Assets.js"; +import "../../../src/Icon.js"; + +setTheme("sap_fiori_3_dark"); + +describe("Icon collection", () => { + it("tests the icon collection in built-in themes", () => { + cy.mount(html``); + + cy.wrap({ getEffectiveIconCollection }) + .invoke("getEffectiveIconCollection") + .should("equal", "SAP-icons-v4"); + + cy.wrap({ isLegacyThemeFamily }) + .invoke("isLegacyThemeFamily") + .should("be.true"); + + // act + cy.wrap({ setTheme }) + .invoke("setTheme", "sap_horizon"); + + // assert + cy.wrap({ getEffectiveIconCollection }) + .invoke("getEffectiveIconCollection") + .should("equal", "SAP-icons-v5"); + + cy.wrap({ isLegacyThemeFamily }) + .invoke("isLegacyThemeFamily") + .should("be.false"); + }); + + it("tests the icon collection in built-in themes", () => { + cy.mount(html``); + + // act + cy.wrap({ setTheme }) + .invoke("setTheme", "redfish"); + + // assert + // The 'SAP-icons-v5' collection is correctly used in 'redfish' - extending 'sap_horizon' + cy.wrap({ getEffectiveIconCollection }) + .invoke("getEffectiveIconCollection") + .should("equal", "SAP-icons-v5"); + + // assert + // "The 'redfish' custom theme is not part of legacy theme family, as it's extending 'sap_horizon'. + cy.wrap({ isLegacyThemeFamily }) + .invoke("isLegacyThemeFamily") + .should("be.false"); + }); +}); diff --git a/packages/main/cypress/specs/base/css/redfish.custom.theme.css b/packages/main/cypress/specs/base/css/redfish.custom.theme.css new file mode 100644 index 000000000000..648a200fbc78 --- /dev/null +++ b/packages/main/cypress/specs/base/css/redfish.custom.theme.css @@ -0,0 +1,13 @@ +/** +* Copyright (c) 2012-2020 SAP SE or an SAP affiliate company. All rights reserved. +* +* Theming Engine 1.60.0 +* data:{"Path": "Base.baseLib.redfish.css_variables", "PathPattern": "/%frameworkId%/%libId%/%themeId%/%fileId%.css", "Extends": ["sap_horizon","sap_base_fiori","baseTheme"], "Tags": ["Horizon","LightColorScheme"], "Version": { "Build":"11.1.27.20210312160011", "Source": "11.1.27", "Engine": "1.60.0"}} +*/ +.sapThemeMetaData-Base-baseLib{background-image: url('data:text/plain;utf-8,{"Path": "Base.baseLib.redfish.css_variables", "PathPattern": "/%frameworkId%/%libId%/%themeId%/%fileId%.css", "Extends": ["sap_horizon","sap_base_fiori","baseTheme"], "Tags": ["Horizon","LightColorScheme"], "Version": { "Build":"11.1.27.20210312160011", "Source": "11.1.27", "Engine": "1.60.0"}}');} +:root { + --sapBrandColor: #f42015; + --sapBaseColor: #fff; + --sapBackgroundColor: #f7f7f7; + --sapTextColor: #000; +} diff --git a/packages/main/test/pages/base/IconCollectionInCustomTheme.html b/packages/main/test/pages/base/IconCollectionInCustomTheme.html index 3d1cb8d08414..57208952bb39 100644 --- a/packages/main/test/pages/base/IconCollectionInCustomTheme.html +++ b/packages/main/test/pages/base/IconCollectionInCustomTheme.html @@ -11,7 +11,7 @@ diff --git a/packages/main/test/specs/base/AriaLabelHelper.spec.js b/packages/main/test/specs/base/AriaLabelHelper.spec.js deleted file mode 100644 index 91bd3e759a81..000000000000 --- a/packages/main/test/specs/base/AriaLabelHelper.spec.js +++ /dev/null @@ -1,137 +0,0 @@ -import { assert } from "chai"; - -describe('AriaLabelHelper', () => { - before(async () => { - await browser.url(`test/pages/base/AriaLabelHelper.html`); - }); - - const getMessageAriaLabelAsExpected = (actual, expected) => { - return `aria-label ${actual} is as expected ${expected}.`; - }; - - const testInputAriaLabelMatchesLabels = async (inputId, labelIds) => { - const input = await browser.$(`#${inputId}`); - const innerInput = await input.shadow$('input'); - const actualAriaLabel = await innerInput.getAttribute('aria-label'); - - const promises = labelIds.map(async (labelId) => { - const label = await browser.$(`#${labelId}`); - return await label.getText(); - }); - const texts = await Promise.all(promises); - const expectedAriaLabel = texts.join(' '); - - assert.equal( - actualAriaLabel, - expectedAriaLabel, - getMessageAriaLabelAsExpected(actualAriaLabel, expectedAriaLabel) - ); - }; - - const testInputAriaLabelMatchesAccessibleName = async (inputId) => { - const input = await browser.$(`#${inputId}`); - const innerInput = await input.shadow$('input'); - const accessibleNameValue = await input.getAttribute('accessible-name'); - const actualAriaLabel = await innerInput.getAttribute('aria-label'); - assert.equal( - actualAriaLabel, - accessibleNameValue, - getMessageAriaLabelAsExpected(actualAriaLabel, accessibleNameValue) - ); - }; - - const testInputAriaLabelIsUndefined = async (inputId) => { - const input = await browser.$(`#${inputId}`); - const innerInput = await input.shadow$('input'); - const actualAriaLabel = await innerInput.getAttribute('aria-label'); - assert.strictEqual( - actualAriaLabel, - null, - `Aria Label is null. attr=${actualAriaLabel}` - ); - }; - - it('Label-for tests', async () => { - const btn = await browser.$('#btnChange'); - await testInputAriaLabelMatchesLabels('myInput', ['lblDesc1', 'lblDesc2', 'lblDesc3', 'lblDesc4']); - await btn.click(); - await testInputAriaLabelMatchesLabels('myInput', ['lblDesc1', 'lblDesc4']); - }); - - it('Input accessibleNameRef Tests', async () => { - const btnChangeDesc1 = await browser.$('#btnChange2'); //Change Desc lblEnterName1 - const btnChangeDesc2 = await browser.$('#btnChange22'); //Change Desc lblEnterName2 - const btnSwap = await browser.$('#btnChange3'); // Swap Accessible Name Ref 1 and 2 - const btnRemove = await browser.$('#btnChange35'); // Remove lblEnterName3 from accessible-name-ref - - await testInputAriaLabelMatchesLabels('inputEnterName', ['lblEnterName1', 'lblEnterName3']); - await btnChangeDesc1.click(); - await testInputAriaLabelMatchesLabels('inputEnterName', ['lblEnterName1', 'lblEnterName3']); - await btnSwap.click(); - await btnChangeDesc2.click(); - await testInputAriaLabelMatchesLabels('inputEnterName', ['lblEnterName2', 'lblEnterName3']); - await btnRemove.click(); - await testInputAriaLabelMatchesLabels('inputEnterName', ['lblEnterName2']); - }); - - it('Input accessibleName and accessibleNameRef Tests', async () => { - const toggleAccessibleName = await browser.$('#btnChange4'); // Toggle AccessibleName Value - const addRemoveAccessibleName = await browser.$('#btnChange5'); // Add/Remove AccessibleName Attribute - const addRemoveAccessibleNameRef = await browser.$('#btnChange6'); // Add/Remove Accessible Name Ref - const removeLabelForAttr = await browser.$('#btnChange65'); // Removes the for-attribute for the associated label - - await testInputAriaLabelMatchesAccessibleName('inputEnterDesc'); - await toggleAccessibleName.click(); // toggle the accessible-name - await testInputAriaLabelMatchesAccessibleName('inputEnterDesc'); - await addRemoveAccessibleName.click(); // remove accessible name - await testInputAriaLabelMatchesLabels('inputEnterDesc', ['lblEnterDesc1']); - await addRemoveAccessibleNameRef.click(); // add accessible-name-ref - await testInputAriaLabelMatchesLabels('inputEnterDesc', ['lblEnterDesc3']); - await addRemoveAccessibleName.click(); // add accessible-name - await testInputAriaLabelMatchesLabels('inputEnterDesc', ['lblEnterDesc3']); - await addRemoveAccessibleNameRef.click(); // remove accessible-name-ref - await testInputAriaLabelMatchesAccessibleName('inputEnterDesc'); - await addRemoveAccessibleName.click(); // remove accessible-name - await testInputAriaLabelMatchesLabels('inputEnterDesc', ['lblEnterDesc1']); - await removeLabelForAttr.click(); // remove label-for from DOM - await testInputAriaLabelIsUndefined('inputEnterDesc'); - }); - - it('Three inputs with same label accessibleNameRef Tests', async () => { - const addRemoveForAttribute = await browser.$('#btnChange71'); // Add/Remove For Attribute On Label - const removeAccessibleNameRef2 = await browser.$('#btnChange72'); // Remove AccessibleNameRef Attribute For Input 2 - const removeAccessibleNameRef3 = await browser.$('#btnChange73'); // Remove AccessibleNameRef Attribute For Input 3 - const btnChangeDesc = await browser.$('#btnChange74'); // Change Description - - await testInputAriaLabelMatchesLabels('testInput1', ['lblTestDesc']); - await testInputAriaLabelMatchesLabels('testInput2', ['lblTestDesc']); - await testInputAriaLabelMatchesLabels('testInput3', ['lblTestDesc']); - - await btnChangeDesc.click(); - await testInputAriaLabelMatchesLabels('testInput1', ['lblTestDesc']); - await testInputAriaLabelMatchesLabels('testInput2', ['lblTestDesc']); - await testInputAriaLabelMatchesLabels('testInput3', ['lblTestDesc']); - - await addRemoveForAttribute.click(); - await testInputAriaLabelIsUndefined('testInput1'); - await testInputAriaLabelMatchesLabels('testInput2', ['lblTestDesc']); - await testInputAriaLabelMatchesLabels('testInput3', ['lblTestDesc']); - - await removeAccessibleNameRef2.click(); - await testInputAriaLabelIsUndefined('testInput1'); - await testInputAriaLabelMatchesAccessibleName('testInput2'); - await testInputAriaLabelMatchesLabels('testInput3', ['lblTestDesc']); - - await removeAccessibleNameRef3.click(); - await testInputAriaLabelIsUndefined('testInput1'); - await testInputAriaLabelMatchesAccessibleName('testInput2'); - await testInputAriaLabelIsUndefined('testInput3'); - }); - - it('Tests generic html elements with for attribute', async () => { - const btnChange8 = await browser.$('#btnChange8'); - await testInputAriaLabelMatchesLabels('myInput2', ['elId1', 'elId2', 'elId3', 'elId4', 'elId5']); - await btnChange8.click(); - await testInputAriaLabelMatchesLabels('myInput2', ['elId1', 'elId4']); - }); -}); diff --git a/packages/main/test/specs/base/IconCollection.spec.js b/packages/main/test/specs/base/IconCollection.spec.js deleted file mode 100644 index 703a8c5049aa..000000000000 --- a/packages/main/test/specs/base/IconCollection.spec.js +++ /dev/null @@ -1,70 +0,0 @@ -import { assert } from "chai"; - -describe("Icon collection", () => { - before(async () => { - await browser.url("test/pages/base/IconCollection.html"); - }); - - it("Tests the icon collection in built-in themes", async () => { - const result = await browser.executeAsync(done => { - const bundle = window['sap-ui-webcomponents-bundle']; - - const res = {}; - res.iconCollection = bundle.getEffectiveIconCollection(); - res.isLegacyThemeFamily = bundle.configuration.isLegacyThemeFamily(); - done(res); - }); - - // assert: "SAP-icons-v4" is used in legacy "sap_fiori_3_dark" theme - assert.strictEqual(result.iconCollection, "SAP-icons-v4", - "The 'SAP-icons-v4' collection is correctly used in 'sap_fiori_3_dark' theme"); - assert.strictEqual(result.isLegacyThemeFamily, true, - "The 'sap_fiori_3_dark' is part of legacy theme family"); - - - // act: setTheme("sap_horizon") - await browser.executeAsync(async (done) => { - await window['sap-ui-webcomponents-bundle'].configuration.setTheme("sap_horizon"); - done(); - }); - - const result2 = await browser.executeAsync(done => { - const bundle = window['sap-ui-webcomponents-bundle']; - - const res = {}; - res.iconCollection = bundle.getEffectiveIconCollection(); - res.isLegacyThemeFamily = bundle.configuration.isLegacyThemeFamily(); - done(res); - }); - - // assert: "SAP-icons-v5" is used in latest "sap_horizon" theme - assert.strictEqual(result2.iconCollection, "SAP-icons-v5", - "The 'SAP-icons-v5' collection is correctly used in 'sap_horizon' theme"); - assert.strictEqual(result2.isLegacyThemeFamily, false, - "The 'sap_horizon' is not part of legacy theme family, it's the latest one"); - }); -}); - -describe("Icon collection in Custom Theme", () => { - before(async () => { - // The test page is using custom theme (based on "sap_horizon") - await browser.url("test/pages/base/IconCollectionInCustomTheme.html"); - }); - - it("Tests the icon collection in a custom theme", async () => { - const result = await browser.executeAsync(done => { - const bundle = window['sap-ui-webcomponents-bundle']; - - const res = {}; - res.iconCollection = bundle.getEffectiveIconCollection(); - res.isLegacyThemeFamily = bundle.configuration.isLegacyThemeFamily(); - - done(res); - }); - - assert.strictEqual(result.iconCollection, "SAP-icons-v5", - "The 'SAP-icons-v5' collection is correctly used in 'redfish' - extending 'sap_horizon'"); - assert.strictEqual(result.isLegacyThemeFamily, false, - "The 'redfish' custom theme is not part of legacy theme family, as it's extending 'sap_horizon'."); - }); -}); \ No newline at end of file From 371c547676ed71582dc6ff64a2a1b1181dd9bf9d Mon Sep 17 00:00:00 2001 From: kskondov Date: Mon, 16 Sep 2024 11:09:55 +0300 Subject: [PATCH 024/114] fix(ui5-popover): correct opacity (#9839) The background of the modal popover was black which made the context page not visible. So opacity was added to grey out the background Fixes: #9823 --- packages/main/src/themes/Dialog.css | 2 +- packages/main/src/themes/Popover.css | 1 + packages/main/src/themes/base/Dialog-parameters.css | 1 - packages/main/src/themes/base/PopupBlockLayer-parameters.css | 1 + .../main/src/themes/sap_fiori_3_hcb/Dialog-parameters.css | 1 - .../themes/sap_fiori_3_hcb/PopupBlockLayer-parameters.css | 5 +++++ .../main/src/themes/sap_fiori_3_hcb/parameters-bundle.css | 2 +- .../main/src/themes/sap_fiori_3_hcw/Dialog-parameters.css | 1 - .../themes/sap_fiori_3_hcw/PopupBlockLayer-parameters.css | 5 +++++ .../main/src/themes/sap_fiori_3_hcw/parameters-bundle.css | 2 +- .../main/src/themes/sap_horizon_hcb/Dialog-parameters.css | 1 - .../themes/sap_horizon_hcb/PopupBlockLayer-parameters.css | 5 +++++ .../main/src/themes/sap_horizon_hcb/parameters-bundle.css | 2 +- .../main/src/themes/sap_horizon_hcw/Dialog-parameters.css | 1 - .../themes/sap_horizon_hcw/PopupBlockLayer-parameters.css | 5 +++++ .../main/src/themes/sap_horizon_hcw/parameters-bundle.css | 2 +- 16 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 packages/main/src/themes/sap_fiori_3_hcb/PopupBlockLayer-parameters.css create mode 100644 packages/main/src/themes/sap_fiori_3_hcw/PopupBlockLayer-parameters.css create mode 100644 packages/main/src/themes/sap_horizon_hcb/PopupBlockLayer-parameters.css create mode 100644 packages/main/src/themes/sap_horizon_hcw/PopupBlockLayer-parameters.css diff --git a/packages/main/src/themes/Dialog.css b/packages/main/src/themes/Dialog.css index 8cf4720a7568..d18c6aaa2616 100644 --- a/packages/main/src/themes/Dialog.css +++ b/packages/main/src/themes/Dialog.css @@ -152,7 +152,7 @@ :host::backdrop { background-color: var(--_ui5_popup_block_layer_background); - opacity: var(--_ui5_dialog_block_layer_opacity); + opacity: var(--_ui5_popup_block_layer_opacity); } .ui5-block-layer { diff --git a/packages/main/src/themes/Popover.css b/packages/main/src/themes/Popover.css index f500d7ee0d22..2fb213b92833 100644 --- a/packages/main/src/themes/Popover.css +++ b/packages/main/src/themes/Popover.css @@ -92,6 +92,7 @@ :host([modal])::backdrop { background-color: var(--_ui5_popup_block_layer_background); + opacity: var(--_ui5_popup_block_layer_opacity); } :host([modal]) .ui5-block-layer { diff --git a/packages/main/src/themes/base/Dialog-parameters.css b/packages/main/src/themes/base/Dialog-parameters.css index 21b62b15236c..8f647b107918 100644 --- a/packages/main/src/themes/base/Dialog-parameters.css +++ b/packages/main/src/themes/base/Dialog-parameters.css @@ -9,5 +9,4 @@ --_ui5_dialog_header_success_state_icon_color: var(--sapPositiveElementColor); --_ui5_dialog_header_warning_state_icon_color: var(--sapCriticalElementColor); --_ui5_dialog_header_state_line_height: 0.0625rem; - --_ui5_dialog_block_layer_opacity: 0.2; } diff --git a/packages/main/src/themes/base/PopupBlockLayer-parameters.css b/packages/main/src/themes/base/PopupBlockLayer-parameters.css index b5b0f0165b6c..2ecf160635a1 100644 --- a/packages/main/src/themes/base/PopupBlockLayer-parameters.css +++ b/packages/main/src/themes/base/PopupBlockLayer-parameters.css @@ -1,3 +1,4 @@ :root { --_ui5_popup_block_layer_background: var(--sapBlockLayer_Background); + --_ui5_popup_block_layer_opacity: 0.2; } diff --git a/packages/main/src/themes/sap_fiori_3_hcb/Dialog-parameters.css b/packages/main/src/themes/sap_fiori_3_hcb/Dialog-parameters.css index cf7a1122cda8..dfb0ce7d1d67 100644 --- a/packages/main/src/themes/sap_fiori_3_hcb/Dialog-parameters.css +++ b/packages/main/src/themes/sap_fiori_3_hcb/Dialog-parameters.css @@ -2,5 +2,4 @@ :root { --_ui5_dialog_header_state_line_height: 0.125rem; - --_ui5_dialog_block_layer_opacity: 0.3; } \ No newline at end of file diff --git a/packages/main/src/themes/sap_fiori_3_hcb/PopupBlockLayer-parameters.css b/packages/main/src/themes/sap_fiori_3_hcb/PopupBlockLayer-parameters.css new file mode 100644 index 000000000000..22509068636d --- /dev/null +++ b/packages/main/src/themes/sap_fiori_3_hcb/PopupBlockLayer-parameters.css @@ -0,0 +1,5 @@ +@import "../base/PopupBlockLayer-parameters.css"; + +:root { + --_ui5_popup_block_layer_opacity: 0.3; +} diff --git a/packages/main/src/themes/sap_fiori_3_hcb/parameters-bundle.css b/packages/main/src/themes/sap_fiori_3_hcb/parameters-bundle.css index c4f4e0eda090..dd134115f491 100644 --- a/packages/main/src/themes/sap_fiori_3_hcb/parameters-bundle.css +++ b/packages/main/src/themes/sap_fiori_3_hcb/parameters-bundle.css @@ -30,8 +30,8 @@ @import "./MessageStrip-parameters.css"; @import "./Panel-parameters.css"; @import "../base/Popover-parameters.css"; +@import "./PopupBlockLayer-parameters.css"; @import "./PopupsCommon-parameters.css"; -@import "../base/PopupBlockLayer-parameters.css"; @import "./ProgressIndicator-parameters.css"; @import "./RadioButton-parameters.css"; @import "../base/RatingIndicator-parameters.css"; diff --git a/packages/main/src/themes/sap_fiori_3_hcw/Dialog-parameters.css b/packages/main/src/themes/sap_fiori_3_hcw/Dialog-parameters.css index cf7a1122cda8..dfb0ce7d1d67 100644 --- a/packages/main/src/themes/sap_fiori_3_hcw/Dialog-parameters.css +++ b/packages/main/src/themes/sap_fiori_3_hcw/Dialog-parameters.css @@ -2,5 +2,4 @@ :root { --_ui5_dialog_header_state_line_height: 0.125rem; - --_ui5_dialog_block_layer_opacity: 0.3; } \ No newline at end of file diff --git a/packages/main/src/themes/sap_fiori_3_hcw/PopupBlockLayer-parameters.css b/packages/main/src/themes/sap_fiori_3_hcw/PopupBlockLayer-parameters.css new file mode 100644 index 000000000000..22509068636d --- /dev/null +++ b/packages/main/src/themes/sap_fiori_3_hcw/PopupBlockLayer-parameters.css @@ -0,0 +1,5 @@ +@import "../base/PopupBlockLayer-parameters.css"; + +:root { + --_ui5_popup_block_layer_opacity: 0.3; +} diff --git a/packages/main/src/themes/sap_fiori_3_hcw/parameters-bundle.css b/packages/main/src/themes/sap_fiori_3_hcw/parameters-bundle.css index 1439474b7bbc..cba172ae4184 100644 --- a/packages/main/src/themes/sap_fiori_3_hcw/parameters-bundle.css +++ b/packages/main/src/themes/sap_fiori_3_hcw/parameters-bundle.css @@ -29,8 +29,8 @@ @import "./MessageStrip-parameters.css"; @import "./Panel-parameters.css"; @import "../base/Popover-parameters.css"; +@import "./PopupBlockLayer-parameters.css"; @import "./PopupsCommon-parameters.css"; -@import "../base/PopupBlockLayer-parameters.css"; @import "./ProgressIndicator-parameters.css"; @import "./RadioButton-parameters.css"; @import "../base/RatingIndicator-parameters.css"; diff --git a/packages/main/src/themes/sap_horizon_hcb/Dialog-parameters.css b/packages/main/src/themes/sap_horizon_hcb/Dialog-parameters.css index cf7a1122cda8..dfb0ce7d1d67 100644 --- a/packages/main/src/themes/sap_horizon_hcb/Dialog-parameters.css +++ b/packages/main/src/themes/sap_horizon_hcb/Dialog-parameters.css @@ -2,5 +2,4 @@ :root { --_ui5_dialog_header_state_line_height: 0.125rem; - --_ui5_dialog_block_layer_opacity: 0.3; } \ No newline at end of file diff --git a/packages/main/src/themes/sap_horizon_hcb/PopupBlockLayer-parameters.css b/packages/main/src/themes/sap_horizon_hcb/PopupBlockLayer-parameters.css new file mode 100644 index 000000000000..22509068636d --- /dev/null +++ b/packages/main/src/themes/sap_horizon_hcb/PopupBlockLayer-parameters.css @@ -0,0 +1,5 @@ +@import "../base/PopupBlockLayer-parameters.css"; + +:root { + --_ui5_popup_block_layer_opacity: 0.3; +} diff --git a/packages/main/src/themes/sap_horizon_hcb/parameters-bundle.css b/packages/main/src/themes/sap_horizon_hcb/parameters-bundle.css index 41ad8c74114c..2a201b2850bf 100644 --- a/packages/main/src/themes/sap_horizon_hcb/parameters-bundle.css +++ b/packages/main/src/themes/sap_horizon_hcb/parameters-bundle.css @@ -30,8 +30,8 @@ @import "./MessageStrip-parameters.css"; @import "./Panel-parameters.css"; @import "../base/Popover-parameters.css"; +@import "./PopupBlockLayer-parameters.css"; @import "./PopupsCommon-parameters.css"; -@import "../base/PopupBlockLayer-parameters.css"; @import "./ProgressIndicator-parameters"; @import "./RadioButton-parameters.css"; @import "./RatingIndicator-parameters.css"; diff --git a/packages/main/src/themes/sap_horizon_hcw/Dialog-parameters.css b/packages/main/src/themes/sap_horizon_hcw/Dialog-parameters.css index cf7a1122cda8..dfb0ce7d1d67 100644 --- a/packages/main/src/themes/sap_horizon_hcw/Dialog-parameters.css +++ b/packages/main/src/themes/sap_horizon_hcw/Dialog-parameters.css @@ -2,5 +2,4 @@ :root { --_ui5_dialog_header_state_line_height: 0.125rem; - --_ui5_dialog_block_layer_opacity: 0.3; } \ No newline at end of file diff --git a/packages/main/src/themes/sap_horizon_hcw/PopupBlockLayer-parameters.css b/packages/main/src/themes/sap_horizon_hcw/PopupBlockLayer-parameters.css new file mode 100644 index 000000000000..22509068636d --- /dev/null +++ b/packages/main/src/themes/sap_horizon_hcw/PopupBlockLayer-parameters.css @@ -0,0 +1,5 @@ +@import "../base/PopupBlockLayer-parameters.css"; + +:root { + --_ui5_popup_block_layer_opacity: 0.3; +} diff --git a/packages/main/src/themes/sap_horizon_hcw/parameters-bundle.css b/packages/main/src/themes/sap_horizon_hcw/parameters-bundle.css index edb731ba3f49..dee5aa0a7a0c 100644 --- a/packages/main/src/themes/sap_horizon_hcw/parameters-bundle.css +++ b/packages/main/src/themes/sap_horizon_hcw/parameters-bundle.css @@ -29,8 +29,8 @@ @import "./MessageStrip-parameters.css"; @import "./Panel-parameters.css"; @import "../base/Popover-parameters.css"; +@import "./PopupBlockLayer-parameters.css"; @import "./PopupsCommon-parameters.css"; -@import "../base/PopupBlockLayer-parameters.css"; @import "../sap_horizon_hcb/ProgressIndicator-parameters"; @import "./RadioButton-parameters.css"; @import "./RatingIndicator-parameters.css"; From 85661f77ab757a5e2f720b88da2a77a442e05e43 Mon Sep 17 00:00:00 2001 From: Nayden Naydenov <31909318+nnaydenow@users.noreply.github.com> Date: Mon, 16 Sep 2024 13:38:38 +0300 Subject: [PATCH 025/114] chore: cleanup Cypress tests (#9877) --- .../cypress/specs/base/AriaLabelHelper.cy.ts | 69 +++++++------------ .../cypress/specs/base/IconCollection.cy.ts | 7 +- 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/packages/main/cypress/specs/base/AriaLabelHelper.cy.ts b/packages/main/cypress/specs/base/AriaLabelHelper.cy.ts index 4b018805b158..6447c7fe670d 100644 --- a/packages/main/cypress/specs/base/AriaLabelHelper.cy.ts +++ b/packages/main/cypress/specs/base/AriaLabelHelper.cy.ts @@ -16,8 +16,7 @@ describe("AriaLabelHelper", () => { cy.get("#myInput") .shadow() .find("input") - .invoke("attr", "aria-label") - .should("eq", "Desc1 Desc2 Desc3 Desc4"); + .should("have.attr", "aria-label", "Desc1 Desc2 Desc3 Desc4"); // act cy.get("#lblDesc2") @@ -30,8 +29,7 @@ describe("AriaLabelHelper", () => { cy.get("#myInput") .shadow() .find("input") - .invoke("attr", "aria-label") - .should("eq", "Desc1 Desc4"); + .should("have.attr", "aria-label", "Desc1 Desc4"); }); it("Input accessibleNameRef Tests", () => { @@ -46,8 +44,7 @@ describe("AriaLabelHelper", () => { cy.get("#inputEnterName") .shadow() .find("input") - .invoke("attr", "aria-label") - .should("eq", "FirstDesc ThirdDesc"); + .should("have.attr", "aria-label", "FirstDesc ThirdDesc"); // act - update text of referenced label cy.get("#lblEnterName1") @@ -59,8 +56,7 @@ describe("AriaLabelHelper", () => { cy.get("#inputEnterName") .shadow() .find("input") - .invoke("attr", "aria-label") - .should("eq", "First Label Desc ThirdDesc"); + .should("have.attr", "aria-label", "First Label Desc ThirdDesc"); // act - update accessible-name-ref cy.get("#inputEnterName") @@ -70,8 +66,7 @@ describe("AriaLabelHelper", () => { cy.get("#inputEnterName") .shadow() .find("input") - .invoke("attr", "aria-label") - .should("eq", "ThirdDesc First Label Desc"); + .should("have.attr", "aria-label", "ThirdDesc First Label Desc"); // act - update accessible-name-ref cy.get("#inputEnterName") @@ -81,8 +76,7 @@ describe("AriaLabelHelper", () => { cy.get("#inputEnterName") .shadow() .find("input") - .invoke("attr", "aria-label") - .should("eq", "SecondDesc"); + .should("have.attr", "aria-label", "SecondDesc"); }); it("Input accessibleName and accessibleNameRef Tests", () => { @@ -104,8 +98,7 @@ describe("AriaLabelHelper", () => { // assert cy.get("@input") - .invoke("attr", "aria-label") - .should("eq", INITIAL_ACCESSIBLE_NAME); + .should("have.attr", "aria-label", INITIAL_ACCESSIBLE_NAME); cy.get("#inputEnterDesc") .invoke("attr", "accessible-name", UPDATED_ACCESSIBLE_NAME); @@ -130,8 +123,7 @@ describe("AriaLabelHelper", () => { // assert - the text of the elment labelled with accessible-name-ref is used cy.get("@input") - .invoke("attr", "aria-label") - .should("eq", ACCESSIBLE_NAME_REF_TEXT); + .should("have.attr", "aria-label", ACCESSIBLE_NAME_REF_TEXT); // act - add acccessible-name once again cy.get("#inputEnterDesc") @@ -139,8 +131,7 @@ describe("AriaLabelHelper", () => { // assert - the text of the elment labelled with accessible-name-ref is still used cy.get("@input") - .invoke("attr", "aria-label") - .should("eq", ACCESSIBLE_NAME_REF_TEXT); + .should("have.attr", "aria-label", ACCESSIBLE_NAME_REF_TEXT); // act - remove acccessible-name-ref cy.get("#inputEnterDesc") @@ -148,8 +139,7 @@ describe("AriaLabelHelper", () => { // assert - after acccessible-name-ref is removed, fallbacks to use acccessible-name cy.get("@input") - .invoke("attr", "aria-label") - .should("eq", INITIAL_ACCESSIBLE_NAME); + .should("have.attr", "aria-label", INITIAL_ACCESSIBLE_NAME); // act - remove acccessible-name cy.get("#inputEnterDesc") @@ -157,8 +147,7 @@ describe("AriaLabelHelper", () => { // assert - aria-label fallbacks to use the label's for, pointing to this input cy.get("@input") - .invoke("attr", "aria-label") - .should("eq", "Label for inputEnterDesc"); + .should("have.attr", "aria-label", "Label for inputEnterDesc"); // act - remove ui5-label's for cy.get("#lblEnterDesc1") @@ -166,8 +155,7 @@ describe("AriaLabelHelper", () => { // assert - aria-label is undefined cy.get("@input") - .invoke("attr", "aria-label") - .should("eq", undefined); + .should("not.have.attr", "aria-label"); }); it("Three inputs with same label accessibleNameRef Tests", () => { @@ -198,16 +186,13 @@ describe("AriaLabelHelper", () => { // assert cy.get("@input1") - .invoke("attr", "aria-label") - .should("eq", LBL_TEXT_CONTENT); + .should("have.attr", "aria-label", LBL_TEXT_CONTENT); cy.get("@input2") - .invoke("attr", "aria-label") - .should("eq", LBL_TEXT_CONTENT); + .should("have.attr", "aria-label", LBL_TEXT_CONTENT); cy.get("@input3") - .invoke("attr", "aria-label") - .should("eq", LBL_TEXT_CONTENT); + .should("have.attr", "aria-label", LBL_TEXT_CONTENT); // act @@ -218,16 +203,13 @@ describe("AriaLabelHelper", () => { // assert cy.get("@input1") - .invoke("attr", "aria-label") - .should("eq", LBL_TEXT_CONTENT_UPDATED); + .should("have.attr", "aria-label", LBL_TEXT_CONTENT_UPDATED); cy.get("@input2") - .invoke("attr", "aria-label") - .should("eq", LBL_TEXT_CONTENT_UPDATED); + .should("have.attr", "aria-label", LBL_TEXT_CONTENT_UPDATED); cy.get("@input3") - .invoke("attr", "aria-label") - .should("eq", LBL_TEXT_CONTENT_UPDATED); + .should("have.attr", "aria-label", LBL_TEXT_CONTENT_UPDATED); // act - remove "for" attribute cy.get("#lblTestDesc") @@ -235,8 +217,7 @@ describe("AriaLabelHelper", () => { // assert - aria-label is undefined cy.get("@input1") - .invoke("attr", "aria-label") - .should("eq", undefined); + .should("not.have.attr", "aria-label"); // act - remove accessible-name-ref cy.get("#testInput2") @@ -244,8 +225,7 @@ describe("AriaLabelHelper", () => { // assert - aria-label is the existing accessible-name cy.get("@input2") - .invoke("attr", "aria-label") - .should("eq", "Hello"); + .should("have.attr", "aria-label", "Hello"); // act - remove accessible-name-ref cy.get("#testInput3") @@ -253,8 +233,7 @@ describe("AriaLabelHelper", () => { // assert - shouldn't be any aria-label cy.get("@input3") - .invoke("attr", "aria-label") - .should("eq", undefined); + .should("not.have.attr", "aria-label"); }); it("Tests generic html elements with for attribute", () => { @@ -274,8 +253,7 @@ describe("AriaLabelHelper", () => { // assert cy.get("@input") - .invoke("attr", "aria-label") - .should("eq", "Desc1 Desc2 Desc3 Desc4 Desc5"); + .should("have.attr", "aria-label", "Desc1 Desc2 Desc3 Desc4 Desc5"); // act cy.get("#elId1") @@ -299,7 +277,6 @@ describe("AriaLabelHelper", () => { // assert cy.get("@input") - .invoke("attr", "aria-label") - .should("eq", "Desc1X Desc4X"); + .should("have.attr", "aria-label", "Desc1X Desc4X"); }); }); diff --git a/packages/main/cypress/specs/base/IconCollection.cy.ts b/packages/main/cypress/specs/base/IconCollection.cy.ts index 8fe2f07e7f75..2716fa411083 100644 --- a/packages/main/cypress/specs/base/IconCollection.cy.ts +++ b/packages/main/cypress/specs/base/IconCollection.cy.ts @@ -5,9 +5,12 @@ import { html } from "lit"; import "../../../src/Assets.js"; import "../../../src/Icon.js"; -setTheme("sap_fiori_3_dark"); - describe("Icon collection", () => { + before(() => { + cy.wrap({ setTheme }) + .invoke("setTheme", "sap_fiori_3_dark"); + }); + it("tests the icon collection in built-in themes", () => { cy.mount(html``); From cfd82fded4b0a3e9e0a74f42c229ff9e299c0ae3 Mon Sep 17 00:00:00 2001 From: Petar Dimov <32839090+dimovpetar@users.noreply.github.com> Date: Mon, 16 Sep 2024 22:59:31 +0300 Subject: [PATCH 026/114] test(ui5-tabcontainer): stabilize test (#9883) --- packages/main/test/pageobjects/TabContainerTestPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/main/test/pageobjects/TabContainerTestPage.js b/packages/main/test/pageobjects/TabContainerTestPage.js index 6523f4fbfdbe..8ac77294d6bd 100644 --- a/packages/main/test/pageobjects/TabContainerTestPage.js +++ b/packages/main/test/pageobjects/TabContainerTestPage.js @@ -58,8 +58,8 @@ class TabContainerTestPage { } async focusItem(tabId) { - await browser.executeAsync((tabId, done) => { - document.getElementById(tabId).focus(); + await browser.executeAsync(async (tabId, done) => { + await document.getElementById(tabId).focus(); done(); }, tabId); } From ceb36429663330f8dbd0dd5153c646aff93f3bd4 Mon Sep 17 00:00:00 2001 From: Vladislav Tasev Date: Tue, 17 Sep 2024 11:05:02 +0300 Subject: [PATCH 027/114] chore: export InvalidationInfo type (#9881) --- packages/base/src/UI5Element.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/base/src/UI5Element.ts b/packages/base/src/UI5Element.ts index 472f227d6e82..0b6571360e52 100644 --- a/packages/base/src/UI5Element.ts +++ b/packages/base/src/UI5Element.ts @@ -1293,6 +1293,7 @@ export { }; export type { ChangeInfo, + InvalidationInfo, Renderer, RendererOptions, }; From a211dd2ce1fd29dbbfbea096539aee36fe835862 Mon Sep 17 00:00:00 2001 From: Siyana Todorova <72251110+s-todorova@users.noreply.github.com> Date: Tue, 17 Sep 2024 11:27:14 +0300 Subject: [PATCH 028/114] docs(ui5-dialog): improve basic sample (#9876) The samples now showcase achieving a confirmation dialog with two buttons like in opneui5, by using ui5-toolbar and ui5-toolbar-buttons. BGSOFUIRODOPI-3324 --- packages/main/src/themes/Dialog.css | 4 ++ packages/main/test/pages/Dialog.html | 49 +++++++++++++++++++ .../docs/_samples/main/Dialog/Basic/main.js | 2 + .../_samples/main/Dialog/Basic/sample.html | 18 +++++-- .../main/Dialog/DraggableAndResizable/main.js | 2 + .../Dialog/DraggableAndResizable/sample.html | 6 +-- .../_samples/main/Dialog/WithState/main.js | 2 + .../main/Dialog/WithState/sample.html | 6 +-- 8 files changed, 79 insertions(+), 10 deletions(-) diff --git a/packages/main/src/themes/Dialog.css b/packages/main/src/themes/Dialog.css index d18c6aaa2616..6105faad10ca 100644 --- a/packages/main/src/themes/Dialog.css +++ b/packages/main/src/themes/Dialog.css @@ -150,6 +150,10 @@ box-shadow: none; } +::slotted([slot="footer"][ui5-toolbar]) { + border: 0; +} + :host::backdrop { background-color: var(--_ui5_popup_block_layer_background); opacity: var(--_ui5_popup_block_layer_opacity); diff --git a/packages/main/test/pages/Dialog.html b/packages/main/test/pages/Dialog.html index 634b5ba85585..46b0f9a564f2 100644 --- a/packages/main/test/pages/Dialog.html +++ b/packages/main/test/pages/Dialog.html @@ -615,6 +615,43 @@ Close +
+
+ Confirmation dialog with Toolbar + +
+
+ Username + +
+
+ Password + +
+
+ Email + +
+
+ Address + +
+
+ + + + + + +


@@ -863,6 +900,18 @@ window["dialogAutofocus"].open = false; }); + window["btnConfirmationDialog"].addEventListener("click", function () { + window["confirmationDialog"].open = true; + }); + + window["btnConfirmationDialogSubmit"].addEventListener("click", function () { + window["confirmationDialog"].open = false; + }) + + window["btnConfirmationDialogCancel"].addEventListener("click", function () { + window["confirmationDialog"].open = false; + }) + document.getElementById("theme-switch").addEventListener("ui5-selection-change", event => { window["sap-ui-webcomponents-bundle"].configuration.setTheme(event.detail.selectedItems[0].getAttribute("data-ui5-theme-name")); }); diff --git a/packages/website/docs/_samples/main/Dialog/Basic/main.js b/packages/website/docs/_samples/main/Dialog/Basic/main.js index 14839550a965..e8b57d5341fa 100644 --- a/packages/website/docs/_samples/main/Dialog/Basic/main.js +++ b/packages/website/docs/_samples/main/Dialog/Basic/main.js @@ -3,6 +3,8 @@ import "@ui5/webcomponents/dist/Button.js"; import "@ui5/webcomponents/dist/Input.js"; import "@ui5/webcomponents/dist/Link.js"; import "@ui5/webcomponents/dist/Label.js"; +import "@ui5/webcomponents/dist/Toolbar.js" +import "@ui5/webcomponents/dist/ToolbarButton.js" var dialogOpener = document.getElementById("dialogOpener"); var dialog = document.getElementById("dialog"); diff --git a/packages/website/docs/_samples/main/Dialog/Basic/sample.html b/packages/website/docs/_samples/main/Dialog/Basic/sample.html index 4109b14a7aef..29f7fd4b3df6 100644 --- a/packages/website/docs/_samples/main/Dialog/Basic/sample.html +++ b/packages/website/docs/_samples/main/Dialog/Basic/sample.html @@ -33,10 +33,20 @@
-
-
- Register -
+ + + + + + diff --git a/packages/website/docs/_samples/main/Dialog/DraggableAndResizable/main.js b/packages/website/docs/_samples/main/Dialog/DraggableAndResizable/main.js index f08a72998313..5c2eb0c38497 100644 --- a/packages/website/docs/_samples/main/Dialog/DraggableAndResizable/main.js +++ b/packages/website/docs/_samples/main/Dialog/DraggableAndResizable/main.js @@ -1,5 +1,7 @@ import "@ui5/webcomponents/dist/Dialog.js"; import "@ui5/webcomponents/dist/Button.js"; +import "@ui5/webcomponents/dist/Toolbar.js"; +import "@ui5/webcomponents/dist/ToolbarButton.js"; var dialogOpener = document.getElementById("dialogOpener"); var dialog = document.getElementById("dialog"); diff --git a/packages/website/docs/_samples/main/Dialog/DraggableAndResizable/sample.html b/packages/website/docs/_samples/main/Dialog/DraggableAndResizable/sample.html index 3e53385b294f..f3826e330bd1 100644 --- a/packages/website/docs/_samples/main/Dialog/DraggableAndResizable/sample.html +++ b/packages/website/docs/_samples/main/Dialog/DraggableAndResizable/sample.html @@ -17,9 +17,9 @@

Move this dialog around the screen by dragging it by its header.

Resize this dialog by dragging it by its resize handle.

These features are available only on Desktop.

-
- OK -
+ + + diff --git a/packages/website/docs/_samples/main/Dialog/WithState/main.js b/packages/website/docs/_samples/main/Dialog/WithState/main.js index 3c3255b2463a..41a8572acb9d 100644 --- a/packages/website/docs/_samples/main/Dialog/WithState/main.js +++ b/packages/website/docs/_samples/main/Dialog/WithState/main.js @@ -1,6 +1,8 @@ import "@ui5/webcomponents/dist/Dialog.js"; import "@ui5/webcomponents/dist/Button.js"; import "@ui5/webcomponents/dist/Text.js"; +import "@ui5/webcomponents/dist/Toolbar.js"; +import "@ui5/webcomponents/dist/ToolbarButton.js"; var dialogOpener = document.getElementById("dialogOpener"); var dialog = document.getElementById("dialog"); diff --git a/packages/website/docs/_samples/main/Dialog/WithState/sample.html b/packages/website/docs/_samples/main/Dialog/WithState/sample.html index fb1cbc5617d4..d3ddb7ced209 100644 --- a/packages/website/docs/_samples/main/Dialog/WithState/sample.html +++ b/packages/website/docs/_samples/main/Dialog/WithState/sample.html @@ -15,9 +15,9 @@ Dialog with state -
- Close -
+ + +
From de59eac1df149caa7541a3bbd3ddad5ae2c298a6 Mon Sep 17 00:00:00 2001 From: Ivaylo Plashkov Date: Tue, 17 Sep 2024 13:02:26 +0300 Subject: [PATCH 029/114] feat(selectionAssistant): introduce SelectionAssistant (#9797) * feat(selectionAssistant): introdce selection assistant to cover AI scenarios * feat(selectionAssistant): minor improvements * feat(selectionAssistant): minor improvements * feat(selectionAssistant): apply feedback * feat(selectionAssistant): correct lint * feat(selectionAssistant): apply feedback * feat(selectionAssistant): correct lint --- packages/base/src/util/SelectionAssistant.ts | 112 ++++++++++++++++ packages/main/src/bundle.common.bootstrap.ts | 2 + .../test/pages/Input-SelectionAssistant.html | 126 ++++++++++++++++++ .../pages/TextArea-SelectionAssistant.html | 99 ++++++++++++++ .../InputSelectionAssistant/Basic/Basic.md | 4 + .../InputSelectionAssistant/Basic/main.js | 90 +++++++++++++ .../InputSelectionAssistant/Basic/sample.html | 39 ++++++ .../Selection Assistant.mdx | 24 ++++ .../TextAreaSelectionAssistant/Basic/Basic.md | 4 + .../TextAreaSelectionAssistant/Basic/main.js | 70 ++++++++++ .../Basic/sample.html | 31 +++++ 11 files changed, 601 insertions(+) create mode 100644 packages/base/src/util/SelectionAssistant.ts create mode 100644 packages/main/test/pages/Input-SelectionAssistant.html create mode 100644 packages/main/test/pages/TextArea-SelectionAssistant.html create mode 100644 packages/website/docs/components/patterns/SelectionAssistant/InputSelectionAssistant/Basic/Basic.md create mode 100644 packages/website/docs/components/patterns/SelectionAssistant/InputSelectionAssistant/Basic/main.js create mode 100644 packages/website/docs/components/patterns/SelectionAssistant/InputSelectionAssistant/Basic/sample.html create mode 100644 packages/website/docs/components/patterns/SelectionAssistant/Selection Assistant.mdx create mode 100644 packages/website/docs/components/patterns/SelectionAssistant/TextAreaSelectionAssistant/Basic/Basic.md create mode 100644 packages/website/docs/components/patterns/SelectionAssistant/TextAreaSelectionAssistant/Basic/main.js create mode 100644 packages/website/docs/components/patterns/SelectionAssistant/TextAreaSelectionAssistant/Basic/sample.html diff --git a/packages/base/src/util/SelectionAssistant.ts b/packages/base/src/util/SelectionAssistant.ts new file mode 100644 index 000000000000..51f43fbae81e --- /dev/null +++ b/packages/base/src/util/SelectionAssistant.ts @@ -0,0 +1,112 @@ +import getEffectiveScrollbarStyle from "../util/getEffectiveScrollbarStyle.js"; + +const copyAndApplyStyles = (element: HTMLElement, copiedElement: HTMLElement) => { + const computedStyles = getComputedStyle(element); + + for (let i = 0; i < computedStyles.length; i++) { + const propertyName = computedStyles[i]; + copiedElement.style.setProperty(propertyName, computedStyles.getPropertyValue(propertyName)); + } + + element.tagName === "INPUT" && setInputSpecificStyles(copiedElement); + + copiedElement.style.position = "absolute"; + copiedElement.style.left = `${element.getBoundingClientRect().left}px`; + copiedElement.style.top = `${element.getBoundingClientRect().top}px`; + + setUnInteractableStyles(copiedElement); + + document.body.appendChild(copiedElement); +}; + +const setUnInteractableStyles = (element: HTMLElement) => { + element.style.position = "absolute"; + element.style.userSelect = "none"; + element.style.pointerEvents = "none"; + element.style.zIndex = "-1"; + element.style.opacity = "0"; +}; + +const setInputSpecificStyles = (element: HTMLElement) => { + element.style.whiteSpace = "nowrap"; + element.style.overflowX = "auto"; + element.style.overflowY = "hidden"; +}; + +const applyScrollStylings = () => { + const sheet = new CSSStyleSheet(); + const styles = getEffectiveScrollbarStyle(); + + sheet.replaceSync(styles); + + document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet]; +}; + +const createCopy = () => { + const copiedElement = document.createElement("div"); + + copiedElement.id = "ui5-selection-mirror"; + copiedElement.contentEditable = "true"; + + applyScrollStylings(); + + document.body.appendChild(copiedElement); +}; + +const applyScrollPosition = (element: HTMLElement, copiedElement: HTMLElement) => { + copiedElement.scrollTop = element.scrollTop; + copiedElement.scrollLeft = element.scrollLeft; +}; + +const getSelectionCoordinates = (element: HTMLInputElement | HTMLTextAreaElement, mirror: HTMLDivElement) => { + const { selectionStart, selectionEnd } = element; + const selectedText = element.value.slice( + selectionStart!, + element.selectionEnd!, + ); + const range = document.createRange(); + + range.setStart(mirror.firstChild!, selectionEnd! - 1); + range.setEnd(mirror.firstChild!, selectionEnd!); + + applyScrollPosition(element, mirror); + + const rangeRect = range.getBoundingClientRect(); + const rectObject = { + x: rangeRect.x, + y: rangeRect.y, + width: rangeRect.width, + height: rangeRect.height, + top: rangeRect.top, + right: rangeRect.right, + bottom: rangeRect.bottom, + left: rangeRect.left, + }; + + document.body.removeChild(mirror); + + return { ...rectObject, selectedText }; +}; + +const getElementSelection = (element: HTMLElement) => { + const innerElement = element.shadowRoot!.querySelector("textarea") + || element.shadowRoot!.querySelector("input"); + + if (!document.getElementById("ui5-selection-mirror")) { + createCopy(); + } + + const copiedElement = document.getElementById( + "ui5-selection-mirror", + )!; + + copiedElement.textContent = innerElement!.value; + + if (innerElement) { + copyAndApplyStyles(innerElement, copiedElement)!; + } + + return getSelectionCoordinates(innerElement!, copiedElement as HTMLDivElement); +}; + +export default getElementSelection; diff --git a/packages/main/src/bundle.common.bootstrap.ts b/packages/main/src/bundle.common.bootstrap.ts index 1e9441ac95a9..810655cf1d79 100644 --- a/packages/main/src/bundle.common.bootstrap.ts +++ b/packages/main/src/bundle.common.bootstrap.ts @@ -73,6 +73,7 @@ import { attachDirectionChange } from "@ui5/webcomponents-base/dist/locale/direc import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; import announce from "@ui5/webcomponents-base/dist/util/InvisibleMessage.js"; import { ignoreCustomElements, shouldIgnoreCustomElement } from "@ui5/webcomponents-base/dist/IgnoreCustomElements.js"; +import getElementSelection from "@ui5/webcomponents-base/dist/util/SelectionAssistant.js"; import * as defaultTexts from "./generated/i18n/i18n-defaults.js"; @@ -106,6 +107,7 @@ const testAssets = { invisibleMessage: { announce, }, + getElementSelection, getLocaleData, applyDirection, attachDirectionChange, diff --git a/packages/main/test/pages/Input-SelectionAssistant.html b/packages/main/test/pages/Input-SelectionAssistant.html new file mode 100644 index 000000000000..0dea6bafaeaf --- /dev/null +++ b/packages/main/test/pages/Input-SelectionAssistant.html @@ -0,0 +1,126 @@ + + + + + + + + Input-SelectionAssisstant + + + + + Input with selection assistant. +
+ +
+
+ + Input with native API. +
+ + + + + + + + + \ No newline at end of file diff --git a/packages/main/test/pages/TextArea-SelectionAssistant.html b/packages/main/test/pages/TextArea-SelectionAssistant.html new file mode 100644 index 000000000000..d153b17dec03 --- /dev/null +++ b/packages/main/test/pages/TextArea-SelectionAssistant.html @@ -0,0 +1,99 @@ + + + + + + + + TextArea-SelectionAssisstant + + + + + TextArea with Selection Assistant. +
+ + + + + + + + + \ No newline at end of file diff --git a/packages/website/docs/components/patterns/SelectionAssistant/InputSelectionAssistant/Basic/Basic.md b/packages/website/docs/components/patterns/SelectionAssistant/InputSelectionAssistant/Basic/Basic.md new file mode 100644 index 000000000000..17798ecc59ab --- /dev/null +++ b/packages/website/docs/components/patterns/SelectionAssistant/InputSelectionAssistant/Basic/Basic.md @@ -0,0 +1,4 @@ +import html from '!!raw-loader!./sample.html'; +import js from '!!raw-loader!./main.js'; + + diff --git a/packages/website/docs/components/patterns/SelectionAssistant/InputSelectionAssistant/Basic/main.js b/packages/website/docs/components/patterns/SelectionAssistant/InputSelectionAssistant/Basic/main.js new file mode 100644 index 000000000000..b62f6a161d9a --- /dev/null +++ b/packages/website/docs/components/patterns/SelectionAssistant/InputSelectionAssistant/Basic/main.js @@ -0,0 +1,90 @@ +import '@ui5/webcomponents/dist/Icon.js'; +import '@ui5/webcomponents/dist/Label.js'; +import '@ui5/webcomponents/dist/Input.js'; +import '@ui5/webcomponents/dist/Text.js'; +import '@ui5/webcomponents/dist/Button.js'; +import '@ui5/webcomponents/dist/Toast.js'; +import '@ui5/webcomponents-icons/dist/ai.js'; +import getElementSelection from "@ui5/webcomponents-base/dist/util/SelectionAssistant.js"; + +const nativeInput = document.getElementById('ai-native-input'); +const input = document.getElementById('ai-input'); +const button = document.getElementById('btn'); +const toast = [...document.getElementsByTagName("ui5-toast")][0]; + +const repositionButtonAtSelection = (rect) => { + button.style.left = `${rect.left + rect.width}px`; + button.style.top = `${rect.top + rect.height}px`; + showButton(); +}; + +const repositionButtonAtInput = (rect) => { + button.style.left = `${rect.left + rect.width + 4}px`; + button.style.top = `${rect.top}px`; + showButton(); +}; + +const showButton = () => { + button.style.zIndex = '100'; + button.style.display = 'inline-block'; +}; + +const hideButton = () => { + button.style.display = 'none'; +}; + +input.addEventListener('ui5-select', (e) => { + const selectionRect = getElementSelection(input); + const inputRect = input.getBoundingClientRect(); + + if (selectionRect.bottom > inputRect.bottom || selectionRect.right > inputRect.right) { + repositionButtonAtInput(inputRect); + } else { + repositionButtonAtSelection(selectionRect); + } +}); + +input.addEventListener('mousedown', (e) => { + hideButton(); +}); + +input.addEventListener('ui5-scroll', (e) => { + hideButton(); +}); + +input.addEventListener('focusout', (e) => { + if (e.relatedTarget !== button) { + hideButton(); + } +}); + +nativeInput.addEventListener('ui5-select', (e) => { + const inputRect = nativeInput.getBoundingClientRect(); + repositionButtonAtInput(inputRect); +}); + +nativeInput.addEventListener('click', (e) => { + hideButton(); +}); + +nativeInput.addEventListener('ui5-scroll', (e) => { + hideButton(); +}); + +nativeInput.addEventListener('focusout', (e) => { + if (e.relatedTarget !== button) { + hideButton(); + } +}); + +button.addEventListener('focusout', (e) => { + hideButton(); +}); + +button.addEventListener('click', (e) => { + const selectedText = document.getSelection().toString(); + const message = `The selected text equals to: "${selectedText}"`; + + toast.textContent = message; + toast.open = true; +}); \ No newline at end of file diff --git a/packages/website/docs/components/patterns/SelectionAssistant/InputSelectionAssistant/Basic/sample.html b/packages/website/docs/components/patterns/SelectionAssistant/InputSelectionAssistant/Basic/sample.html new file mode 100644 index 000000000000..793e9ec5bef8 --- /dev/null +++ b/packages/website/docs/components/patterns/SelectionAssistant/InputSelectionAssistant/Basic/sample.html @@ -0,0 +1,39 @@ + + + + + + + + + Sample + + + + + + Input with selection assistant. +
+ +
+
+ + Input with native API. +
+ + + + + + + + + + \ No newline at end of file diff --git a/packages/website/docs/components/patterns/SelectionAssistant/Selection Assistant.mdx b/packages/website/docs/components/patterns/SelectionAssistant/Selection Assistant.mdx new file mode 100644 index 000000000000..210f8ed98cf1 --- /dev/null +++ b/packages/website/docs/components/patterns/SelectionAssistant/Selection Assistant.mdx @@ -0,0 +1,24 @@ +--- +sidebar_class_name: newComponentBadge expComponentBadge +--- + +import Input from "../../../components/patterns/SelectionAssistant/InputSelectionAssistant/Basic/Basic.md"; +import TextArea from "../../../components/patterns/SelectionAssistant/TextAreaSelectionAssistant/Basic/Basic.md"; + +**Note:** The util is in an experimental state + +### ES6 Module Import + +`import getElementSelection from "@ui5/webcomponents-base/dist/util/SelectionAssistant.js";` + +The sample demonstrates the usage of SelectionAssistant, which enhances user interactions by +returning the +coordinates of the selected text on the select event. This utility enables developers to create advanced +AI-powered scenarios by easily capturing and utilizing the exact coordinates of user-selected text. + +## Input with Selection Assistant sample + + +## TextArea with Selection Assistant sample +