Skip to content

Commit

Permalink
feat: introduce view transition directive
Browse files Browse the repository at this point in the history
  • Loading branch information
manekinekko committed Mar 13, 2023
1 parent 1dd6046 commit d64f934
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 73 deletions.
8 changes: 8 additions & 0 deletions src/__patch__/zone.js/lib/browser/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

export function patchViewTransitionAPI(_global: any, api: _ZonePrivate) {

}

Zone.__load_patch('viewTransition', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
patchViewTransitionAPI(global, api);
});
4 changes: 3 additions & 1 deletion src/app/app.component.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ main {
}

.header__logo:after {
content: 'beta';
content: "beta";
background: #f57375;
color: #fae9bc;
padding: 3px 5px;
Expand All @@ -50,6 +50,8 @@ mat-toolbar {
display: flex;
justify-content: flex-start;
z-index: 10;
view-transition-name: mat-toolbar;
contain: layout;
}

mat-toolbar a {
Expand Down
11 changes: 7 additions & 4 deletions src/app/shared/card/card.component.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<mat-card class="mat-elevation-z4" *ngIf="package" [ngStyle]="{
'view-transition-name': 'view-' + package.rev,
'contain': 'layout'
}">
<mat-card
class="mat-elevation-z4"
*ngIf="package"
[id]="id()"
appViewTransition
#viewTransitionRef
>
<span
[ngClass]="{ 'is-schematic': package.computedMetadata['schematics'] }"
></span>
Expand Down
23 changes: 0 additions & 23 deletions src/app/shared/card/card.component.spec.ts

This file was deleted.

58 changes: 28 additions & 30 deletions src/app/shared/card/card.component.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { DOCUMENT } from '@angular/common';
import { Component, Inject, Input, Renderer2 } from '@angular/core';
import { Component, Input, Renderer2, ViewChild, signal } from '@angular/core';
import { Router } from '@angular/router';
import { DeeplinkService } from 'src/app/shared/deeplink.service';
import {
CSSStyleDeclarationWithViewTransitionAPI,
DocumentWithViewTransitionAPI,
PackageType,
} from 'src/typings';
import { PackageType } from 'src/typings';
import { ViewTransitionDirective } from '../view-transition.directive';

@Component({
selector: 'app-card',
Expand All @@ -18,13 +14,26 @@ export class CardComponent {

@Input() isFullMode = true;

@ViewChild(ViewTransitionDirective)
viewTransitionRef!: ViewTransitionDirective;

id = signal('');

constructor(
private renderer: Renderer2,
private deeplink: DeeplinkService,
private router: Router,
@Inject(DOCUMENT) private document: Document

private router: Router
) {}

ngOnChanges() {
this.id.set('card-' + this.package!.rev + (this.isFullMode ? '-full' : ''));
}

ngAfterContentInit() {
debugger;
}

onAvatarImageError(avatarImage: HTMLImageElement, avatarIcon: HTMLElement) {
this.renderer.setStyle(avatarImage, 'display', 'none');
this.renderer.setStyle(avatarIcon, 'display', 'block');
Expand Down Expand Up @@ -78,28 +87,17 @@ export class CardComponent {
}

async navigateTo(pkg: PackageType, event: Event) {

this.#startViewTransition(
() => {
this.router.navigate([`pkg`, pkg.name], {
state: {
pkg,
query: this.deeplink.getState(),
},
});
}
);
await this.viewTransitionRef.startViewTransition(async () => {
await this.onViewTransition(pkg);
});
}

#startViewTransition(onStart: () => void, onFinish: () => void = () => {}) {
if (!(this.document as DocumentWithViewTransitionAPI).startViewTransition) {
console.warn('View transition API is not available in this browser.');
return onStart();
}

const transition = (
this.document as DocumentWithViewTransitionAPI
).startViewTransition(onStart);
transition.finished.finally(onFinish);
async onViewTransition(pkg: PackageType) {
return await this.router.navigate([`pkg`, pkg.name], {
state: {
pkg,
query: this.deeplink.getState(),
},
});
}
}
3 changes: 2 additions & 1 deletion src/app/shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { MatChipsModule } from '@angular/material/chips';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { InfiniteScrollDirective } from './infinite-scroll.directive';
import { ViewTransitionDirective } from './view-transition.directive';

const MAT_MODULES = [
MatCardModule,
Expand All @@ -21,7 +22,7 @@ const MAT_MODULES = [

@NgModule({
imports: [CommonModule, RouterModule, ...MAT_MODULES],
declarations: [NotFoundComponent, CardComponent, HumanDatePipe, InfiniteScrollDirective],
declarations: [NotFoundComponent, CardComponent, HumanDatePipe, InfiniteScrollDirective, ViewTransitionDirective],
exports: [NotFoundComponent, CardComponent, HumanDatePipe, InfiniteScrollDirective]
})
export class SharedModule {}
49 changes: 49 additions & 0 deletions src/app/shared/view-transition.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { DOCUMENT } from '@angular/common';
import {
Directive,
ElementRef,
Inject,
NgZone,
Renderer2,
} from '@angular/core';
import { DocumentWithViewTransitionAPI } from 'src/typings';

@Directive({
selector: '[appViewTransition]',
})
export class ViewTransitionDirective {
constructor(
private zone: NgZone,
@Inject(DOCUMENT) private document: Document,
private element: ElementRef<HTMLElement>,
private renderer: Renderer2
) {}

ngOnInit() {
this.renderer.setStyle(this.element.nativeElement, 'contain', 'layout');
this.setElementTransitionName(this.element.nativeElement.id);
}

setElementTransitionName(name: string) {
this.renderer.setStyle(
this.element.nativeElement,
'viewTransitionName',
name
);
}

async startViewTransition(callback: () => Promise<void>) {
if (!(this.document as DocumentWithViewTransitionAPI).startViewTransition) {
console.warn('View transition API is not available in this browser.');
return await callback();
}

this.setElementTransitionName(this.element.nativeElement.id + '-full');

(this.document as DocumentWithViewTransitionAPI).startViewTransition(() => {
// TODO: patch zone.js to support view transition api callbacks
this.zone.run(async () => await callback());
this.setElementTransitionName(this.element.nativeElement.id);
});
}
}
2 changes: 1 addition & 1 deletion src/polyfills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
* Zone JS is required by default for Angular itself.
*/
import 'zone.js'; // Included with Angular CLI.

import './__patch__/zone.js/lib/browser/browser.ts';

/***************************************************************************************************
* APPLICATION IMPORTS
Expand Down
14 changes: 2 additions & 12 deletions src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,14 @@ body {

// View Transition API

mat-toolbar {
view-transition-name: mat-toolbar;
contain: layout;
}

.package-details-readme {
view-transition-name: package-details-readme;
contain: layout;
}
::view-transition-old(package-details-readme) {
html::view-transition-old(root) {
animation: 300ms cubic-bezier(0.68, -0.55, 0.265, 1.55) both slide-up;
}
::view-transition-new(package-details-readme) {
html::view-transition-new(root) {
animation: 300ms cubic-bezier(0.68, -0.55, 0.265, 1.55) both slide-down;
}

Expand All @@ -66,11 +61,6 @@ mat-toolbar {
contain: layout;
}

::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 300ms;
}

@keyframes slide-down {
from {
transform: translateY(60px);
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
},
"files": [
"src/main.ts",
"src/polyfills.ts"
"src/polyfills.ts",
"src/__patch__/zone.js/lib/browser/browser.ts"
],
"include": [
"src/**/*.d.ts"
Expand Down

0 comments on commit d64f934

Please sign in to comment.