diff --git a/packages/base/src/Theming.ts b/packages/base/src/Theming.ts index ffe80a2ab06c..7686b474d0a5 100644 --- a/packages/base/src/Theming.ts +++ b/packages/base/src/Theming.ts @@ -1,4 +1,9 @@ -import { addCustomCSS } from "./theming/CustomStyle.js"; +import { addCustomCSS, removeCustomCSS } from "./theming/CustomStyle.js"; import { attachThemeLoaded, detachThemeLoaded } from "./theming/ThemeLoaded.js"; -export { addCustomCSS, attachThemeLoaded, detachThemeLoaded }; +export { + addCustomCSS, + removeCustomCSS, + attachThemeLoaded, + detachThemeLoaded, +}; diff --git a/packages/base/src/index.ts b/packages/base/src/index.ts index a73b95fd0f12..080721b274c4 100644 --- a/packages/base/src/index.ts +++ b/packages/base/src/index.ts @@ -101,7 +101,12 @@ import { } from "./Render.js"; // Theming.ts -import { addCustomCSS, attachThemeLoaded, detachThemeLoaded } from "./Theming.js"; +import { + addCustomCSS, + removeCustomCSS, + attachThemeLoaded, + detachThemeLoaded, +} from "./Theming.js"; // UI5Element.ts import UI5Element from "./UI5Element.js"; @@ -208,6 +213,7 @@ export { // Theming.ts addCustomCSS, + removeCustomCSS, attachThemeLoaded, detachThemeLoaded, diff --git a/packages/base/src/theming/CustomStyle.ts b/packages/base/src/theming/CustomStyle.ts index 1f12944b5be7..eb480e760dbb 100644 --- a/packages/base/src/theming/CustomStyle.ts +++ b/packages/base/src/theming/CustomStyle.ts @@ -3,10 +3,17 @@ import getSharedResource from "../getSharedResource.js"; import EventProvider from "../EventProvider.js"; type CustomCSSChangeCallback = (tag: string) => void; +type CustomCSSEntry = { id: string, css: string }; const getEventProvider = () => getSharedResource("CustomStyle.eventProvider", new EventProvider()); const CUSTOM_CSS_CHANGE = "CustomCSSChange"; +let currentId = 0; +const nextId = () => { + currentId++; + return `custom-css-${currentId}`; +}; + const attachCustomCSSChange = (listener: CustomCSSChangeCallback) => { getEventProvider().attachEvent(CUSTOM_CSS_CHANGE, listener); }; @@ -19,7 +26,7 @@ const fireCustomCSSChange = (tag: string) => { return getEventProvider().fireEvent(CUSTOM_CSS_CHANGE, tag); }; -const getCustomCSSFor = () => getSharedResource>>("CustomStyle.customCSSFor", {}); +const getCustomCSSFor = () => getSharedResource>>("CustomStyle.customCSSFor", {}); // Listen to the eventProvider, in case other copies of this CustomStyle module fire this // event, and this copy would therefore need to reRender the ui5 webcomponents; but @@ -31,13 +38,7 @@ attachCustomCSSChange((tag: string) => { } }); -const addCustomCSS = (tag: string, css: string) => { - const customCSSFor = getCustomCSSFor(); - if (!customCSSFor[tag]) { - customCSSFor[tag] = []; - } - customCSSFor[tag].push(css); - +const fireChangeAndRerender = (tag: string) => { skipRerender = true; try { // The event is fired and the attached event listeners are all called synchronously @@ -51,13 +52,43 @@ const addCustomCSS = (tag: string, css: string) => { return reRenderAllUI5Elements({ tag }); }; +const addCustomCSS = (tag: string, css: string): string => { + const customCSSFor = getCustomCSSFor(); + if (!customCSSFor[tag]) { + customCSSFor[tag] = []; + } + + const id = nextId(); + customCSSFor[tag].push({ id, css }); + fireChangeAndRerender(tag); + + return id; +}; + +const removeCustomCSS = (tag: string, id: string) => { + const customCSSFor = getCustomCSSFor(); + const entries = customCSSFor[tag]; + if (!entries) { + return; + } + + const index = entries.findIndex(entry => entry.id === id); + if (index === -1) { + return; + } + + entries.splice(index, 1); + fireChangeAndRerender(tag); +}; + const getCustomCSS = (tag: string) => { const customCSSFor = getCustomCSSFor(); - return customCSSFor[tag] ? customCSSFor[tag].join("") : ""; + return customCSSFor[tag] ? customCSSFor[tag].map(entry => entry.css).join("") : ""; }; export { addCustomCSS, + removeCustomCSS, getCustomCSS, attachCustomCSSChange, detachCustomCSSChange, diff --git a/packages/main/cypress/specs/CustomCSS.cy.tsx b/packages/main/cypress/specs/CustomCSS.cy.tsx new file mode 100644 index 000000000000..ff2d221ec084 --- /dev/null +++ b/packages/main/cypress/specs/CustomCSS.cy.tsx @@ -0,0 +1,145 @@ +import { addCustomCSS, removeCustomCSS } from "@ui5/webcomponents-base/dist/Theming.js"; +import Select from "../../src/Select.js"; +import Option from "../../src/Option.js"; + +describe("CustomCSS", () => { + it("addCustomCSS applies custom styles", () => { + cy.mount( + + ); + + cy.get("[ui5-select]") + .shadow() + .find(".ui5-select-root") + .then($el => { + addCustomCSS("ui5-select", ".ui5-select-root { background-color: rgb(255, 0, 0) !important; }"); + }); + + cy.get("[ui5-select]") + .shadow() + .find(".ui5-select-root") + .should("have.css", "background-color", "rgb(255, 0, 0)"); + }); + + it("addCustomCSS returns a string id", () => { + const id = addCustomCSS("ui5-select", ":host { /* noop */ }"); + + expect(id).to.be.a("string"); + expect(id).to.include("custom-css-"); + }); + + it("removeCustomCSS removes the applied custom styles", () => { + cy.mount( + + ); + + let cssId: string; + + cy.get("[ui5-select]") + .shadow() + .find(".ui5-select-root") + .then(() => { + cssId = addCustomCSS("ui5-select", ".ui5-select-root { outline: 5px solid rgb(0, 128, 0); }"); + }); + + cy.get("[ui5-select]") + .shadow() + .find(".ui5-select-root") + .should("have.css", "outline-color", "rgb(0, 128, 0)") + .then(() => { + removeCustomCSS("ui5-select", cssId); + }); + + cy.get("[ui5-select]") + .shadow() + .find(".ui5-select-root") + .should("not.have.css", "outline-color", "rgb(0, 128, 0)"); + }); + + it("removeCustomCSS with invalid id does nothing", () => { + cy.mount( + + ); + + let cssId: string; + + cy.get("[ui5-select]") + .shadow() + .find(".ui5-select-root") + .then(() => { + cssId = addCustomCSS("ui5-select", ".ui5-select-root { outline: 5px solid rgb(0, 0, 255); }"); + }); + + cy.get("[ui5-select]") + .shadow() + .find(".ui5-select-root") + .should("have.css", "outline-color", "rgb(0, 0, 255)") + .then(() => { + removeCustomCSS("ui5-select", "non-existent-id"); + }); + + // Style should still be applied + cy.get("[ui5-select]") + .shadow() + .find(".ui5-select-root") + .should("have.css", "outline-color", "rgb(0, 0, 255)") + .then(() => { + removeCustomCSS("ui5-select", cssId); + }); + }); + + it("removeCustomCSS with non-existent tag does nothing", () => { + // Should not throw + removeCustomCSS("ui5-nonexistent", "some-id"); + }); + + it("multiple addCustomCSS calls can be individually removed", () => { + cy.mount( + + ); + + let id1: string; + let id2: string; + + cy.get("[ui5-select]") + .shadow() + .find(".ui5-select-root") + .then(() => { + id1 = addCustomCSS("ui5-select", ".ui5-select-root { outline: 3px solid rgb(255, 0, 0); }"); + id2 = addCustomCSS("ui5-select", ".ui5-select-root { border: 3px solid rgb(0, 0, 255); }"); + }); + + cy.get("[ui5-select]") + .shadow() + .find(".ui5-select-root") + .should("have.css", "outline-color", "rgb(255, 0, 0)") + .should("have.css", "border-color", "rgb(0, 0, 255)") + .then(() => { + removeCustomCSS("ui5-select", id1); + }); + + // First style removed, second still present + cy.get("[ui5-select]") + .shadow() + .find(".ui5-select-root") + .should("not.have.css", "outline-color", "rgb(255, 0, 0)") + .should("have.css", "border-color", "rgb(0, 0, 255)") + .then(() => { + removeCustomCSS("ui5-select", id2); + }); + + // Both removed + cy.get("[ui5-select]") + .shadow() + .find(".ui5-select-root") + .should("not.have.css", "border-color", "rgb(0, 0, 255)"); + }); +}); diff --git a/packages/main/src/bundle.common.bootstrap.ts b/packages/main/src/bundle.common.bootstrap.ts index fd20788fffac..3f5e86d1f295 100644 --- a/packages/main/src/bundle.common.bootstrap.ts +++ b/packages/main/src/bundle.common.bootstrap.ts @@ -20,7 +20,12 @@ import "@ui5/webcomponents-icons-business-suite/dist/Assets.js"; import "@ui5/webcomponents-icons-business-suite/dist/AllIcons.js"; import "@ui5/webcomponents-base/dist/features/F6Navigation.js"; -import { addCustomCSS, attachThemeLoaded, detachThemeLoaded } from "@ui5/webcomponents-base/dist/Theming.js"; +import { + addCustomCSS, + removeCustomCSS, + attachThemeLoaded, + detachThemeLoaded, +} from "@ui5/webcomponents-base/dist/Theming.js"; // import "./customI18n.js"; // Calendars @@ -119,6 +124,7 @@ const testAssets = { detachLanguageChange, ResizeHandler, addCustomCSS, + removeCustomCSS, attachThemeLoaded, detachThemeLoaded, getIconNames, diff --git a/packages/main/test/pages/CustomCSS.html b/packages/main/test/pages/CustomCSS.html index 28e071f869ac..863a6451b2ab 100644 --- a/packages/main/test/pages/CustomCSS.html +++ b/packages/main/test/pages/CustomCSS.html @@ -38,15 +38,33 @@

+ Remove custom css and rerender + +
+
+