diff --git a/web/package-lock.json b/web/package-lock.json index 5e7f5a503..1e94bb4fa 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -71,6 +71,7 @@ "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/chart.js": "^2.9.41", "@types/codemirror": "5.60.15", + "@types/dom-view-transitions": "^1.0.4", "@types/grecaptcha": "^3.0.7", "@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/parser": "^6.15.0", @@ -7287,6 +7288,12 @@ "integrity": "sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==", "dev": true }, + "node_modules/@types/dom-view-transitions": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/dom-view-transitions/-/dom-view-transitions-1.0.4.tgz", + "integrity": "sha512-oDuagM6G+xPLrLU4KeCKlr1oalMF5mJqV5pDPMDVIEaa8AkUW00i6u+5P02XCjdEEUQJC9dpnxqSLsZeAciSLQ==", + "dev": true + }, "node_modules/@types/ejs": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", diff --git a/web/package.json b/web/package.json index 786580462..b3b2d2462 100644 --- a/web/package.json +++ b/web/package.json @@ -96,6 +96,7 @@ "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/chart.js": "^2.9.41", "@types/codemirror": "5.60.15", + "@types/dom-view-transitions": "^1.0.4", "@types/grecaptcha": "^3.0.7", "@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/parser": "^6.15.0", diff --git a/web/src/common/styles/authentik.css b/web/src/common/styles/authentik.css index a9a085e54..c933d3a4e 100644 --- a/web/src/common/styles/authentik.css +++ b/web/src/common/styles/authentik.css @@ -148,3 +148,15 @@ html > form > input { margin-top: 13rem; } } + +/* View transition */ +::view-transition-image-pair(ak-flow-container) { + isolation: auto; +} + +::view-transition-old(ak-flow-container), +::view-transition-new(ak-flow-container) { + animation: none; + mix-blend-mode: normal; + display: block; +} diff --git a/web/src/elements/router/RouterOutlet.ts b/web/src/elements/router/RouterOutlet.ts index 8aa54e013..4d60e6587 100644 --- a/web/src/elements/router/RouterOutlet.ts +++ b/web/src/elements/router/RouterOutlet.ts @@ -58,6 +58,7 @@ export class RouterOutlet extends AKElement { css` :host { background-color: transparent !important; + view-transition-name: ak-router-outlet; } *:first-child { flex-direction: column; @@ -111,7 +112,9 @@ export class RouterOutlet extends AKElement { matchedRoute.arguments = route.url.exec(activeUrl)?.groups || {}; matchedRoute.fullUrl = activeUrl; } - this.current = matchedRoute; + document.startViewTransition(() => { + this.current = matchedRoute!; + }); } render(): TemplateResult | undefined { diff --git a/web/src/flow/FlowExecutor.ts b/web/src/flow/FlowExecutor.ts index bc1dec3a8..423af5e20 100644 --- a/web/src/flow/FlowExecutor.ts +++ b/web/src/flow/FlowExecutor.ts @@ -105,12 +105,13 @@ export class FlowExecutor extends Interface implements StageHost { --pf-c-background-image--BackgroundImage--sm-2x: var(--ak-flow-background); --pf-c-background-image--BackgroundImage--lg: var(--ak-flow-background); } - .ak-hidden { - display: none; - } :host { position: relative; } + .pf-c-login__main { + view-transition-name: ak-flow-container; + } + .pf-c-drawer__content { background-color: transparent; } @@ -189,6 +190,15 @@ export class FlowExecutor extends Interface implements StageHost { return globalAK()?.tenant.uiTheme || UiThemeEnum.Automatic; } + transitionChallenge(data: ChallengeTypes): void { + document.startViewTransition(() => { + this.challenge = data; + if (this.challenge.flowInfo) { + this.flowInfo = this.challenge.flowInfo; + } + }); + } + async submit(payload?: FlowChallengeResponseRequest): Promise { if (!payload) return Promise.reject(); if (!this.challenge) return Promise.reject(); @@ -209,7 +219,7 @@ export class FlowExecutor extends Interface implements StageHost { }), ); } - this.challenge = challenge; + this.transitionChallenge(challenge); if (this.challenge.flowInfo) { this.flowInfo = this.challenge.flowInfo; } @@ -241,10 +251,7 @@ export class FlowExecutor extends Interface implements StageHost { }), ); } - this.challenge = challenge; - if (this.challenge.flowInfo) { - this.flowInfo = this.challenge.flowInfo; - } + this.transitionChallenge(challenge); } catch (exc: unknown) { // Catch JSON or Update errors this.errorMessage(exc as Error | ResponseError); diff --git a/web/src/polyfill/poly.ts b/web/src/polyfill/poly.ts index 08d103117..5328d1aa6 100644 --- a/web/src/polyfill/poly.ts +++ b/web/src/polyfill/poly.ts @@ -5,3 +5,10 @@ import "core-js/actual"; import "@formatjs/intl-listformat/polyfill"; import "@formatjs/intl-listformat/locale-data/en"; + +if (!("startViewTransition" in document)) { + // @ts-ignore + document.startViewTransition = (cb) => { + cb(); + }; +}