Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions src/app/root/root.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
{{ 'root.skip-to-content' | translate }}
</button>

<div class="outer-wrapper" [class.d-none]="shouldShowFullscreenLoader" [@slideSidebarPadding]="{
value: (!(isSidebarVisible$ | async) ? 'hidden' : (slideSidebarOver$ | async) ? 'unpinned' : 'pinned'),
params: { collapsedWidth: (collapsedSidebarWidth$ | async), expandedWidth: (expandedSidebarWidth$ | async) }
}">
<div class="outer-wrapper" [class.d-none]="shouldShowFullscreenLoader"
[class.ds-admin-sidebar-animate]="gutterTransitionEnabled"
[ngClass]="'ds-admin-sidebar-' + (sidebarPaddingState$ | async)">
<ds-themed-admin-sidebar [expandedSidebarWidth$]="expandedSidebarWidth$" [collapsedSidebarWidth$]="collapsedSidebarWidth$"></ds-themed-admin-sidebar>
<div class="inner-wrapper">
<ds-system-wide-alert-banner></ds-system-wide-alert-banner>
Expand Down
28 changes: 28 additions & 0 deletions src/app/root/root.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,31 @@
top: 0;
}
}

// Admin-sidebar left gutter. Driven by the `ds-admin-sidebar-*` class set in root.component.html (from
// sidebarPaddingState$) rather than the @slideSidebarPadding Angular animation. The animation needed a
// concrete width from the browser-only CSS-variable store, so on the server it rendered padding-left:0
// and the authenticated page jumped right when the anti-flicker SSR snapshot was removed. Resolving the
// gutter from the `--ds-admin-sidebar-*` custom properties in CSS instead renders identically on the
// server (snapshot) and the browser (live app) — no hardcoded px, theme- and viewport-aware — and the
// transition keeps the pin/unpin slide. 'hidden' (no admin sidebar) keeps the default padding-left: 0.
.outer-wrapper {
// padding-left:0 (no admin sidebar); explicit for self-documentation.
&.ds-admin-sidebar-hidden {
padding-left: 0;
}

&.ds-admin-sidebar-unpinned {
padding-left: var(--ds-admin-sidebar-fixed-element-width);
}

&.ds-admin-sidebar-pinned {
padding-left: var(--ds-admin-sidebar-total-width);
}

// Slide only genuine pin/unpin toggles. The class is added after first paint (gutterTransitionEnabled)
// so the initial SSR->CSR gutter resolution behind the anti-flicker overlay never animates.
&.ds-admin-sidebar-animate {
transition: padding-left 300ms ease-in-out;
}
}
45 changes: 40 additions & 5 deletions src/app/root/root.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { first, map, skipWhile, startWith } from 'rxjs/operators';
import { Component, Input, OnInit } from '@angular/core';
import { AfterViewInit, Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';

import { combineLatest as combineLatestObservable, Observable, of } from 'rxjs';
Expand All @@ -8,7 +8,6 @@ import { MenuService } from '../shared/menu/menu.service';
import { HostWindowService } from '../shared/host-window.service';
import { ThemeConfig } from '../../config/theme.config';
import { environment } from '../../environments/environment';
import { slideSidebarPadding } from '../shared/animations/slide';
import { MenuID } from '../shared/menu/menu-id.model';
import { getPageInternalServerErrorRoute } from '../app-routing-paths';
import { INotificationBoardOptions } from 'src/config/notifications-config.interfaces';
Expand All @@ -17,14 +16,31 @@ import { INotificationBoardOptions } from 'src/config/notifications-config.inter
selector: 'ds-root',
templateUrl: './root.component.html',
styleUrls: ['./root.component.scss'],
animations: [slideSidebarPadding],
})
export class RootComponent implements OnInit {
export class RootComponent implements OnInit, AfterViewInit {
theme: Observable<ThemeConfig> = of({} as any);
isSidebarVisible$: Observable<boolean>;
slideSidebarOver$: Observable<boolean>;
collapsedSidebarWidth$: Observable<string>;
expandedSidebarWidth$: Observable<string>;

/**
* The admin-sidebar padding state ('hidden' | 'unpinned' | 'pinned') used to drive the
* outer-wrapper's left gutter via CSS classes (see root.component.scss) instead of an Angular
* animation. CSS resolves the gutter width from the `--ds-admin-sidebar-*` custom properties, so it
* is rendered identically on the server (the anti-flicker SSR snapshot) and the browser (the live
* app) — no browser-only CSS-variable read, no hardcoded px, and it stays theme- and viewport-aware.
*/
sidebarPaddingState$: Observable<string>;

/**
* Enables the gutter's `transition: padding-left` only AFTER the first browser paint. The initial
* SSR->CSR gutter resolution happens behind the anti-flicker overlay; without this gate a plain CSS
* transition would animate that initial 0->gutter change (the overlay settle detector only watches
* DOM mutations, not style changes), which could leak a 300ms slide right as the overlay is removed.
* Off on the server and on first render, so only genuine pin/unpin toggles animate.
*/
gutterTransitionEnabled = false;
notificationOptions: INotificationBoardOptions;
models: any;

Expand All @@ -42,14 +58,16 @@ export class RootComponent implements OnInit {
private router: Router,
private cssService: CSSVariableService,
private menuService: MenuService,
private windowService: HostWindowService
private windowService: HostWindowService,
) {
this.notificationOptions = environment.notifications;
}

ngOnInit() {
this.isSidebarVisible$ = this.menuService.isMenuVisibleWithVisibleSections(MenuID.ADMIN);

// Still provided to <ds-themed-admin-sidebar>; the sidebar element itself sizes from CSS vars, so a
// null value on the server (the store is browser-only) is harmless there.
this.expandedSidebarWidth$ = this.cssService.getVariable('--ds-admin-sidebar-total-width').pipe(
skipWhile((val) => !val),
first(),
Expand All @@ -66,11 +84,28 @@ export class RootComponent implements OnInit {
startWith(true),
);

// Drive the outer-wrapper gutter via a CSS class instead of the @slideSidebarPadding animation: the
// animation needs a concrete width from the browser-only CSS-variable store, so on the server it
// rendered padding-left:0 and the authenticated page jumped right when the SSR snapshot was removed.
// The CSS class resolves the gutter from `--ds-admin-sidebar-*` (see root.component.scss), identically
// on server and browser — fixing the jump without any hardcoded width.
this.sidebarPaddingState$ = combineLatestObservable([this.isSidebarVisible$, this.slideSidebarOver$]).pipe(
map(([visible, over]) => !visible ? 'hidden' : over ? 'unpinned' : 'pinned'),
);

if (this.router.url === getPageInternalServerErrorRoute()) {
this.shouldShowRouteLoader = false;
}
}

ngAfterViewInit(): void {
// Enable the gutter slide only after the first paint (browser only; requestAnimationFrame is not
// defined under SSR), so the initial padding resolution never animates — see gutterTransitionEnabled.
if (typeof requestAnimationFrame === 'function') {
requestAnimationFrame(() => { this.gutterTransitionEnabled = true; });
}
}

skipToMainContent() {
const mainContent = document.getElementById('main-content');
if (mainContent) {
Expand Down
2 changes: 0 additions & 2 deletions src/themes/custom/app/root/root.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Component } from '@angular/core';
import { slideSidebarPadding } from '../../../../app/shared/animations/slide';
import { RootComponent as BaseComponent } from '../../../../app/root/root.component';

@Component({
Expand All @@ -8,7 +7,6 @@ import { RootComponent as BaseComponent } from '../../../../app/root/root.compon
styleUrls: ['../../../../app/root/root.component.scss'],
// templateUrl: './root.component.html',
templateUrl: '../../../../app/root/root.component.html',
animations: [slideSidebarPadding],
})
export class RootComponent extends BaseComponent {

Expand Down
Loading