diff --git a/packages/base/cypress/specs/AnimationMode.cy.tsx b/packages/base/cypress/specs/AnimationMode.cy.tsx new file mode 100644 index 0000000000000..912005addd8dd --- /dev/null +++ b/packages/base/cypress/specs/AnimationMode.cy.tsx @@ -0,0 +1,41 @@ +import { setAnimationMode } from "../../src/config/AnimationMode.js"; +import { duration } from "../../src/animations/animate.js"; +import { getComponentStyles } from "../../src/theming/componentStyles.js"; +import Generic from "../../test/test-elements/Generic.js"; + +describe("AnimationMode - duration", () => { + it("is 0 when animation mode is none", () => { + cy.wrap({ setAnimationMode }).invoke("setAnimationMode", "none"); + + cy.wrap({ duration }).invoke("duration").should("equal", 0); + }); + + it("is 400 when animation mode is full", () => { + cy.wrap({ setAnimationMode }).invoke("setAnimationMode", "full"); + + cy.wrap({ duration }).invoke("duration").should("equal", 400); + }); +}); + +describe("AnimationMode - CommonStyles", () => { + it("common styles sheet exists in the shadow root of a component", () => { + cy.mount(); + + cy.get("[ui5-test-generic]").shadow().should($shadow => { + const shadowRoot = $shadow[0].getRootNode() as ShadowRoot; + const componentStylesSheet = getComponentStyles(); + expect(shadowRoot.adoptedStyleSheets).to.include(componentStylesSheet); + expect(componentStylesSheet.cssRules).not.be.empty; + }); + }); + + it("common styles sheet contains the animation-mode rule", () => { + cy.mount(); + + cy.wrap(null).should(() => { + const sheet = getComponentStyles(); + const rules = Array.from(sheet.cssRules).map(r => r.cssText); + expect(rules.some(r => r.includes("--_ui5-animation-mode"))).to.be.true; + }) + }); +}); diff --git a/packages/base/cypress/specs/ConfigurationChange.cy.tsx b/packages/base/cypress/specs/ConfigurationChange.cy.tsx index b79d914510631..9d034146174de 100644 --- a/packages/base/cypress/specs/ConfigurationChange.cy.tsx +++ b/packages/base/cypress/specs/ConfigurationChange.cy.tsx @@ -2,6 +2,7 @@ import "../../test/test-elements/Accessor.js"; import { setTheme, getTheme } from "../../src/config/Theme.js"; import { setThemeRoot, getThemeRoot } from "../../src/config/ThemeRoot.js"; import { setNoConflict, getNoConflict } from "../../src/config/NoConflict.js"; +import { setAnimationMode, getAnimationMode } from "../../src/config/AnimationMode.js"; describe("Some configuration options can be changed at runtime", () => { it("Tests that theme can be changed", () => { @@ -238,3 +239,24 @@ describe("ThemeRoot validation at runtime", () => { }); }); }); + +describe("AnimationMode CSS variable", () => { + const getCSSVar = () => { + return cy.window().then(win => { + return win.getComputedStyle(win.document.documentElement).getPropertyValue("--_ui5-animation-mode").trim(); + }); + }; + + it("sets the CSS variable on getAnimationMode", () => { + cy.wrap({ getAnimationMode }).invoke("getAnimationMode"); + getCSSVar().should("not.be.empty"); + }); + + it("updates the CSS variable when setAnimationMode is called", () => { + cy.wrap({ setAnimationMode }).invoke("setAnimationMode", "none"); + getCSSVar().should("equal", "none"); + + cy.wrap({ setAnimationMode }).invoke("setAnimationMode", "full"); + getCSSVar().should("equal", "full"); + }); +}); diff --git a/packages/base/cypress/specs/ConfigurationScript.cy.tsx b/packages/base/cypress/specs/ConfigurationScript.cy.tsx index 255ee8d5a36bb..ba5ebe8c50818 100644 --- a/packages/base/cypress/specs/ConfigurationScript.cy.tsx +++ b/packages/base/cypress/specs/ConfigurationScript.cy.tsx @@ -105,6 +105,11 @@ describe("Configuration script", () => { cy.wrap({ getAnimationMode }) .invoke("getAnimationMode") .should("equal", configurationObject.animationMode); + + cy.window().should(win => { + const value = win.getComputedStyle(win.document.documentElement).getPropertyValue("--_ui5-animation-mode").trim(); + expect(value).to.equal(configurationObject.animationMode); + }); }); it("getEnableDefaultTooltips", () => { diff --git a/packages/base/cypress/specs/ConfigurationURL.cy.tsx b/packages/base/cypress/specs/ConfigurationURL.cy.tsx index 8fcd3221a3b71..1777bcc893b27 100644 --- a/packages/base/cypress/specs/ConfigurationURL.cy.tsx +++ b/packages/base/cypress/specs/ConfigurationURL.cy.tsx @@ -50,6 +50,11 @@ describe("Some settings can be set via SAP UI URL params", () => { cy.wrap({ getAnimationMode }) .invoke("getAnimationMode") .should("equal", AnimationMode.Basic); + + cy.window().should(win => { + const value = win.getComputedStyle(win.document.documentElement).getPropertyValue("--_ui5-animation-mode").trim(); + expect(value).to.equal(AnimationMode.Basic); + }); }); }); @@ -573,5 +578,10 @@ describe("URL parameters are ignored when ignoreUrlParams is set", () => { cy.wrap({ getAnimationMode }) .invoke("getAnimationMode") .should("equal", AnimationMode.Basic); + + cy.window().should(win => { + const value = win.getComputedStyle(win.document.documentElement).getPropertyValue("--_ui5-animation-mode").trim(); + expect(value).to.equal(AnimationMode.Basic); + }); }); }); diff --git a/packages/base/src/animations/animate.ts b/packages/base/src/animations/animate.ts index 872cd1e88dddd..a18cc65775987 100644 --- a/packages/base/src/animations/animate.ts +++ b/packages/base/src/animations/animate.ts @@ -1,3 +1,4 @@ +import { getAnimationMode } from "../config/AnimationMode.js"; import AnimationQueue from "./AnimationQueue.js"; type AnimateOptions = { @@ -57,7 +58,10 @@ const animate = (options: AnimateOptions) => { stop: () => stop, }; }; -const duration = 400; + +const duration = () => { + return getAnimationMode() === "none" ? 0 : 400; +}; export { duration }; export type { AnimateOptions }; diff --git a/packages/base/src/animations/scroll.ts b/packages/base/src/animations/scroll.ts index d4bc159451415..4b77075cc881b 100644 --- a/packages/base/src/animations/scroll.ts +++ b/packages/base/src/animations/scroll.ts @@ -9,7 +9,7 @@ const scroll = (element: HTMLElement, dx: number, dy: number) => { scrollLeft = element.scrollLeft; scrollTop = element.scrollTop; }, - duration, + duration: duration(), element, advance: progress => { element.scrollLeft = scrollLeft + (progress * dx); // easing - linear diff --git a/packages/base/src/animations/slideDown.ts b/packages/base/src/animations/slideDown.ts index 2aa7a986d22a8..edbd47a05ce93 100644 --- a/packages/base/src/animations/slideDown.ts +++ b/packages/base/src/animations/slideDown.ts @@ -42,7 +42,7 @@ const slideDown = (element: HTMLElement) => { element.style.marginBottom = "0"; element.style.height = "0"; }, - duration, + duration: duration(), element, advance: progress => { // WORKAROUND diff --git a/packages/base/src/animations/slideUp.ts b/packages/base/src/animations/slideUp.ts index 968eaacd3a575..a1091233296b2 100644 --- a/packages/base/src/animations/slideUp.ts +++ b/packages/base/src/animations/slideUp.ts @@ -38,7 +38,7 @@ const slideUp = (element: HTMLElement) => { el.style.overflow = "hidden"; }, - duration, + duration: duration(), element, advance: progress => { element.style.paddingTop = `${paddingTop - (paddingTop * progress)}px`; diff --git a/packages/base/src/config/AnimationMode.ts b/packages/base/src/config/AnimationMode.ts index 69af240ca87f6..66ed86aa544cd 100644 --- a/packages/base/src/config/AnimationMode.ts +++ b/packages/base/src/config/AnimationMode.ts @@ -1,9 +1,14 @@ import { getAnimationMode as getConfiguredAnimationMode } from "../InitialConfiguration.js"; import AnimationMode from "../types/AnimationMode.js"; import { attachConfigurationReset } from "./ConfigurationReset.js"; +import { createOrUpdateStyle } from "../ManagedStyles.js"; let curAnimationMode: `${AnimationMode}` | undefined; +const applyAnimationMode = (animationMode: `${AnimationMode}`) => { + createOrUpdateStyle(`:root { --_ui5-animation-mode: ${animationMode}; }`, "data-ui5-animation-mode"); +}; + attachConfigurationReset(() => { curAnimationMode = undefined; }); @@ -16,6 +21,7 @@ attachConfigurationReset(() => { const getAnimationMode = (): `${AnimationMode}` => { if (curAnimationMode === undefined) { curAnimationMode = getConfiguredAnimationMode(); + applyAnimationMode(curAnimationMode); } return curAnimationMode; @@ -30,6 +36,7 @@ const setAnimationMode = (animationMode: `${AnimationMode}`) => { const options: string[] = Object.values(AnimationMode); if (options.includes(animationMode)) { curAnimationMode = animationMode; + applyAnimationMode(curAnimationMode); } }; diff --git a/packages/base/src/css/CommonStyles.css b/packages/base/src/css/CommonStyles.css new file mode 100644 index 0000000000000..9e63d6a4576b0 --- /dev/null +++ b/packages/base/src/css/CommonStyles.css @@ -0,0 +1,6 @@ +@container style(--_ui5-animation-mode: none) { + * { + transition-duration: 0s !important; + animation-duration: 0s !important; + } +} \ No newline at end of file diff --git a/packages/base/src/theming/componentStyles.ts b/packages/base/src/theming/componentStyles.ts index 32d96d6e44eee..4499097c9df37 100644 --- a/packages/base/src/theming/componentStyles.ts +++ b/packages/base/src/theming/componentStyles.ts @@ -1,9 +1,15 @@ +import CommonStylesCss from "../generated/css/CommonStyles.css.js"; + const packageMap = new Map(); + +packageMap.set("ui5-common-component-styles", CommonStylesCss); + let componentsStyleSheet: CSSStyleSheet; const getComponentStyles = () => { if (!componentsStyleSheet) { componentsStyleSheet = new CSSStyleSheet(); + componentsStyleSheet.replaceSync(Array.from(packageMap.values()).join("\n")); } return componentsStyleSheet; diff --git a/packages/compat/cypress/specs/Table.cy.tsx b/packages/compat/cypress/specs/Table.cy.tsx index 516307524dc46..31b4c1bc4a916 100644 --- a/packages/compat/cypress/specs/Table.cy.tsx +++ b/packages/compat/cypress/specs/Table.cy.tsx @@ -509,7 +509,6 @@ describe("Table general interaction", () => { cy.get("@popinChange") .should(stub => { expect(stub).to.have.been.calledTwice; - debugger // @ts-ignore expect(stub.args.slice(-1)[0][0].detail.poppedColumns.length).to.equal(2); })