Content-only page transitions + dependency security overrides
- Persistent ShellComponent hosts the router-outlet so header/footer mount once (no re-mount flash); pages nest under a shell route. page-shell is now content-only; page-layout removed. - Native withViewTransitions() cross-fades only the routed content (chrome gets stable view-transition-names); respects prefers-reduced-motion. - package.json overrides pin patched transitive dev/build deps: npm audit 16 (3 high/9 mod/4 low) -> 5 low; shipped app stays at 0 (npm audit --omit=dev). No Angular downgrade, no breaking Babel 8 bump. jsdom 28 -> 29. - README: page-transitions section + honest dependency-security note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1517,55 +1517,9 @@
|
||||
"stylesData": "",
|
||||
"extends": []
|
||||
},
|
||||
{
|
||||
"name": "PageLayoutComponent",
|
||||
"id": "component-PageLayoutComponent-dd10c533ec0fae79341ba928861681e8d5b84e8856a5d98644f7cfa8bddacd9059014a17899fb35eb34256d29d46fe17910e7541f7c227f82ebd04bd7ab00e8e",
|
||||
"file": "src/app/templates/page-layout/page-layout.component.ts",
|
||||
"encapsulation": [],
|
||||
"entryComponents": [],
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"providers": [],
|
||||
"selector": "app-page-layout",
|
||||
"styleUrls": [],
|
||||
"styles": [
|
||||
":host{display:block}"
|
||||
],
|
||||
"template": "<a href=\"#main\" class=\"rhc-skip-link\" style=\"position:absolute;left:-999px\">Naar de inhoud</a>\n<!-- align-items:stretch overrides the design-system flex-start so the\n full-bleed header/footer bars fill the viewport width. -->\n<div class=\"utrecht-page-layout utrecht-page-layout--stretch\" style=\"--app-content-max:64rem;min-height:100vh;align-items:stretch\">\n <app-site-header />\n <main id=\"main\" class=\"utrecht-page-content\" style=\"flex:1;width:100%;box-sizing:border-box\">\n <div style=\"max-width:var(--app-content-max);margin:0 auto;padding:2rem 1.5rem;box-sizing:border-box\">\n <ng-content />\n </div>\n </main>\n <app-site-footer />\n</div>\n",
|
||||
"templateUrl": [],
|
||||
"viewProviders": [],
|
||||
"hostDirectives": [],
|
||||
"inputsClass": [],
|
||||
"outputsClass": [],
|
||||
"propertiesClass": [],
|
||||
"methodsClass": [],
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"hostBindings": [],
|
||||
"hostListeners": [],
|
||||
"standalone": false,
|
||||
"imports": [
|
||||
{
|
||||
"name": "SiteHeaderComponent",
|
||||
"type": "component"
|
||||
},
|
||||
{
|
||||
"name": "SiteFooterComponent",
|
||||
"type": "component"
|
||||
}
|
||||
],
|
||||
"description": "<p>Template: full-bleed header + footer with a centered content column. Wraps\nevery page. The colored bars span the viewport; content lines up via the\nshared --app-content-max width.</p>\n",
|
||||
"rawdescription": "\nTemplate: full-bleed header + footer with a centered content column. Wraps\nevery page. The colored bars span the viewport; content lines up via the\nshared --app-content-max width.",
|
||||
"type": "component",
|
||||
"sourceCode": "import { Component } from '@angular/core';\nimport { SiteHeaderComponent } from '../../organisms/site-header/site-header.component';\nimport { SiteFooterComponent } from '../../organisms/site-footer/site-footer.component';\n\n/** Template: full-bleed header + footer with a centered content column. Wraps\n every page. The colored bars span the viewport; content lines up via the\n shared --app-content-max width. */\n@Component({\n selector: 'app-page-layout',\n imports: [SiteHeaderComponent, SiteFooterComponent],\n styles: [':host{display:block}'],\n template: `\n <a href=\"#main\" class=\"rhc-skip-link\" style=\"position:absolute;left:-999px\">Naar de inhoud</a>\n <!-- align-items:stretch overrides the design-system flex-start so the\n full-bleed header/footer bars fill the viewport width. -->\n <div class=\"utrecht-page-layout utrecht-page-layout--stretch\" style=\"--app-content-max:64rem;min-height:100vh;align-items:stretch\">\n <app-site-header />\n <main id=\"main\" class=\"utrecht-page-content\" style=\"flex:1;width:100%;box-sizing:border-box\">\n <div style=\"max-width:var(--app-content-max);margin:0 auto;padding:2rem 1.5rem;box-sizing:border-box\">\n <ng-content />\n </div>\n </main>\n <app-site-footer />\n </div>\n `,\n})\nexport class PageLayoutComponent {}\n",
|
||||
"assetsDirs": [],
|
||||
"styleUrlsData": "",
|
||||
"stylesData": ":host{display:block}\n",
|
||||
"extends": []
|
||||
},
|
||||
{
|
||||
"name": "PageShellComponent",
|
||||
"id": "component-PageShellComponent-52728311f7328b4b0c4553918adf0b60fbfc082e4d7fcc85eb5375153b4231b4c11f8e83bb57b8bc14b0035921e4415ef241d802f7cf09e82557cef871c83623",
|
||||
"id": "component-PageShellComponent-f2d3224d1fdc66ba96a373ea16cc259b086450cd1700c9f2b59b9f3193d96b4cd69bc9218497aa73519a3545157d691f273fb410f78d9f7fe9373e5921be9c1b",
|
||||
"file": "src/app/templates/page-shell/page-shell.component.ts",
|
||||
"encapsulation": [],
|
||||
"entryComponents": [],
|
||||
@@ -1577,7 +1531,7 @@
|
||||
"styles": [
|
||||
":host{display:block}"
|
||||
],
|
||||
"template": "<app-page-layout>\n <div [style.max-width]=\"width() === 'narrow' ? '32rem' : null\">\n @if (backLink()) {\n <p><app-link [to]=\"backLink()!\">← {{ backLabel() }}</app-link></p>\n }\n <app-heading [level]=\"1\">{{ heading() }}</app-heading>\n @if (intro()) {\n <p class=\"rhc-paragraph\" style=\"margin-bottom:1.5rem\">{{ intro() }}</p>\n }\n <ng-content />\n </div>\n</app-page-layout>\n",
|
||||
"template": "<div [style.max-width]=\"width() === 'narrow' ? '32rem' : null\">\n @if (backLink()) {\n <p><app-link [to]=\"backLink()!\">← {{ backLabel() }}</app-link></p>\n }\n <app-heading [level]=\"1\">{{ heading() }}</app-heading>\n @if (intro()) {\n <p class=\"rhc-paragraph\" style=\"margin-bottom:1.5rem\">{{ intro() }}</p>\n }\n <ng-content />\n</div>\n",
|
||||
"templateUrl": [],
|
||||
"viewProviders": [],
|
||||
"hostDirectives": [],
|
||||
@@ -1590,7 +1544,7 @@
|
||||
"indexKey": "",
|
||||
"optional": false,
|
||||
"description": "",
|
||||
"line": 32,
|
||||
"line": 29,
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
@@ -1601,7 +1555,7 @@
|
||||
"indexKey": "",
|
||||
"optional": false,
|
||||
"description": "",
|
||||
"line": 31,
|
||||
"line": 28,
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
@@ -1612,7 +1566,7 @@
|
||||
"indexKey": "",
|
||||
"optional": false,
|
||||
"description": "",
|
||||
"line": 29,
|
||||
"line": 26,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
@@ -1623,7 +1577,7 @@
|
||||
"indexKey": "",
|
||||
"optional": false,
|
||||
"description": "",
|
||||
"line": 30,
|
||||
"line": 27,
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
@@ -1635,7 +1589,7 @@
|
||||
"indexKey": "",
|
||||
"optional": false,
|
||||
"description": "",
|
||||
"line": 33,
|
||||
"line": 30,
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
@@ -1648,10 +1602,6 @@
|
||||
"hostListeners": [],
|
||||
"standalone": false,
|
||||
"imports": [
|
||||
{
|
||||
"name": "PageLayoutComponent",
|
||||
"type": "component"
|
||||
},
|
||||
{
|
||||
"name": "HeadingComponent",
|
||||
"type": "component"
|
||||
@@ -1661,10 +1611,10 @@
|
||||
"type": "component"
|
||||
}
|
||||
],
|
||||
"description": "<p>Template: standard page body inside the chrome — optional back-link, a\nheading, optional intro, and projected content. Every content page plugs\ninto this, so the heading/back-link markup lives in one place.</p>\n",
|
||||
"rawdescription": "\nTemplate: standard page body inside the chrome — optional back-link, a\nheading, optional intro, and projected content. Every content page plugs\ninto this, so the heading/back-link markup lives in one place.",
|
||||
"description": "<p>Template: standard page body — optional back-link, a heading, optional intro,\nand projected content. Rendered inside the persistent ShellComponent via the\nrouter outlet, so it owns only the content (not the header/footer chrome).</p>\n",
|
||||
"rawdescription": "\nTemplate: standard page body — optional back-link, a heading, optional intro,\nand projected content. Rendered inside the persistent ShellComponent via the\nrouter outlet, so it owns only the content (not the header/footer chrome).",
|
||||
"type": "component",
|
||||
"sourceCode": "import { Component, input } from '@angular/core';\nimport { PageLayoutComponent } from '../page-layout/page-layout.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { LinkComponent } from '../../atoms/link/link.component';\n\n/** Template: standard page body inside the chrome — optional back-link, a\n heading, optional intro, and projected content. Every content page plugs\n into this, so the heading/back-link markup lives in one place. */\n@Component({\n selector: 'app-page-shell',\n imports: [PageLayoutComponent, HeadingComponent, LinkComponent],\n styles: [':host{display:block}'],\n template: `\n <app-page-layout>\n <div [style.max-width]=\"width() === 'narrow' ? '32rem' : null\">\n @if (backLink()) {\n <p><app-link [to]=\"backLink()!\">← {{ backLabel() }}</app-link></p>\n }\n <app-heading [level]=\"1\">{{ heading() }}</app-heading>\n @if (intro()) {\n <p class=\"rhc-paragraph\" style=\"margin-bottom:1.5rem\">{{ intro() }}</p>\n }\n <ng-content />\n </div>\n </app-page-layout>\n `,\n})\nexport class PageShellComponent {\n heading = input.required<string>();\n intro = input<string>();\n backLink = input<string>();\n backLabel = input('Terug naar overzicht');\n width = input<'default' | 'narrow'>('default');\n}\n",
|
||||
"sourceCode": "import { Component, input } from '@angular/core';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { LinkComponent } from '../../atoms/link/link.component';\n\n/** Template: standard page body — optional back-link, a heading, optional intro,\n and projected content. Rendered inside the persistent ShellComponent via the\n router outlet, so it owns only the content (not the header/footer chrome). */\n@Component({\n selector: 'app-page-shell',\n imports: [HeadingComponent, LinkComponent],\n styles: [':host{display:block}'],\n template: `\n <div [style.max-width]=\"width() === 'narrow' ? '32rem' : null\">\n @if (backLink()) {\n <p><app-link [to]=\"backLink()!\">← {{ backLabel() }}</app-link></p>\n }\n <app-heading [level]=\"1\">{{ heading() }}</app-heading>\n @if (intro()) {\n <p class=\"rhc-paragraph\" style=\"margin-bottom:1.5rem\">{{ intro() }}</p>\n }\n <ng-content />\n </div>\n `,\n})\nexport class PageShellComponent {\n heading = input.required<string>();\n intro = input<string>();\n backLink = input<string>();\n backLabel = input('Terug naar overzicht');\n width = input<'default' | 'narrow'>('default');\n}\n",
|
||||
"assetsDirs": [],
|
||||
"styleUrlsData": "",
|
||||
"stylesData": ":host{display:block}\n",
|
||||
@@ -1889,6 +1839,55 @@
|
||||
"stylesData": "",
|
||||
"extends": []
|
||||
},
|
||||
{
|
||||
"name": "ShellComponent",
|
||||
"id": "component-ShellComponent-a435ec99fc0234f16b0f1ab3b0860d9ec513acbf7cb2cefcaec3d7e36fd4c943f9b5e91c76481402563c62d8a63dd1363b29ac130c4d9a731839eccc4bbd12c2",
|
||||
"file": "src/app/templates/shell/shell.component.ts",
|
||||
"encapsulation": [],
|
||||
"entryComponents": [],
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"providers": [],
|
||||
"selector": "app-shell",
|
||||
"styleUrls": [],
|
||||
"styles": [
|
||||
":host{display:block}"
|
||||
],
|
||||
"template": "<a href=\"#main\" class=\"rhc-skip-link\" style=\"position:absolute;left:-999px\">Naar de inhoud</a>\n<div class=\"utrecht-page-layout utrecht-page-layout--stretch\" style=\"--app-content-max:64rem;min-height:100vh;align-items:stretch\">\n <app-site-header />\n <main id=\"main\" class=\"utrecht-page-content\" style=\"flex:1;width:100%;box-sizing:border-box\">\n <div style=\"max-width:var(--app-content-max);margin:0 auto;padding:2rem 1.5rem;box-sizing:border-box\">\n <router-outlet />\n </div>\n </main>\n <app-site-footer />\n</div>\n",
|
||||
"templateUrl": [],
|
||||
"viewProviders": [],
|
||||
"hostDirectives": [],
|
||||
"inputsClass": [],
|
||||
"outputsClass": [],
|
||||
"propertiesClass": [],
|
||||
"methodsClass": [],
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"hostBindings": [],
|
||||
"hostListeners": [],
|
||||
"standalone": false,
|
||||
"imports": [
|
||||
{
|
||||
"name": "RouterOutlet"
|
||||
},
|
||||
{
|
||||
"name": "SiteHeaderComponent",
|
||||
"type": "component"
|
||||
},
|
||||
{
|
||||
"name": "SiteFooterComponent",
|
||||
"type": "component"
|
||||
}
|
||||
],
|
||||
"description": "<p>Template: persistent app chrome. Header + footer mount once; only the routed\ncontent inside <router-outlet> changes (and cross-fades — see styles.scss).</p>\n",
|
||||
"rawdescription": "\nTemplate: persistent app chrome. Header + footer mount once; only the routed\ncontent inside <router-outlet> changes (and cross-fades — see styles.scss).",
|
||||
"type": "component",
|
||||
"sourceCode": "import { Component } from '@angular/core';\nimport { RouterOutlet } from '@angular/router';\nimport { SiteHeaderComponent } from '../../organisms/site-header/site-header.component';\nimport { SiteFooterComponent } from '../../organisms/site-footer/site-footer.component';\n\n/** Template: persistent app chrome. Header + footer mount once; only the routed\n content inside <router-outlet> changes (and cross-fades — see styles.scss). */\n@Component({\n selector: 'app-shell',\n imports: [RouterOutlet, SiteHeaderComponent, SiteFooterComponent],\n styles: [':host{display:block}'],\n template: `\n <a href=\"#main\" class=\"rhc-skip-link\" style=\"position:absolute;left:-999px\">Naar de inhoud</a>\n <div class=\"utrecht-page-layout utrecht-page-layout--stretch\" style=\"--app-content-max:64rem;min-height:100vh;align-items:stretch\">\n <app-site-header />\n <main id=\"main\" class=\"utrecht-page-content\" style=\"flex:1;width:100%;box-sizing:border-box\">\n <div style=\"max-width:var(--app-content-max);margin:0 auto;padding:2rem 1.5rem;box-sizing:border-box\">\n <router-outlet />\n </div>\n </main>\n <app-site-footer />\n </div>\n `,\n})\nexport class ShellComponent {}\n",
|
||||
"assetsDirs": [],
|
||||
"styleUrlsData": "",
|
||||
"stylesData": ":host{display:block}\n",
|
||||
"extends": []
|
||||
},
|
||||
{
|
||||
"name": "SiteFooterComponent",
|
||||
"id": "component-SiteFooterComponent-90391287c6e8f3ad80c034a7a6fdabdd0514fd9c0dcf549a8e4a2d3bc3d885fcd5a45a2eeeb24fa8a4bbcf2cfe865093d712765d9d1ed11b4bfca26d1fb35153",
|
||||
@@ -2617,7 +2616,7 @@
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"type": "ApplicationConfig",
|
||||
"defaultValue": "{\n providers: [\n provideBrowserGlobalErrorListeners(),\n provideRouter(routes),\n provideHttpClient(withInterceptors([scenarioInterceptor])),\n { provide: LOCALE_ID, useValue: 'nl' },\n ]\n}"
|
||||
"defaultValue": "{\n providers: [\n provideBrowserGlobalErrorListeners(),\n provideRouter(routes, withViewTransitions()),\n provideHttpClient(withInterceptors([scenarioInterceptor])),\n { provide: LOCALE_ID, useValue: 'nl' },\n ]\n}"
|
||||
},
|
||||
{
|
||||
"name": "ASYNC",
|
||||
@@ -2639,7 +2638,7 @@
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"type": "Routes",
|
||||
"defaultValue": "[\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('./pages/login/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', loadComponent: () => \"import('./pages/dashboard/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', loadComponent: () => \"import('./pages/registration-detail/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', loadComponent: () => \"import('./pages/herregistratie/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: '**', redirectTo: 'login' },\n]"
|
||||
"defaultValue": "[\n {\n path: '',\n component: ShellComponent, // persistent header/footer; only children swap\n children: [\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('./pages/login/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', loadComponent: () => \"import('./pages/dashboard/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', loadComponent: () => \"import('./pages/registration-detail/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', loadComponent: () => \"import('./pages/herregistratie/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]"
|
||||
},
|
||||
{
|
||||
"name": "scenarioInterceptor",
|
||||
@@ -2745,7 +2744,7 @@
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"type": "ApplicationConfig",
|
||||
"defaultValue": "{\n providers: [\n provideBrowserGlobalErrorListeners(),\n provideRouter(routes),\n provideHttpClient(withInterceptors([scenarioInterceptor])),\n { provide: LOCALE_ID, useValue: 'nl' },\n ]\n}"
|
||||
"defaultValue": "{\n providers: [\n provideBrowserGlobalErrorListeners(),\n provideRouter(routes, withViewTransitions()),\n provideHttpClient(withInterceptors([scenarioInterceptor])),\n { provide: LOCALE_ID, useValue: 'nl' },\n ]\n}"
|
||||
}
|
||||
],
|
||||
"src/app/molecules/async/async.component.ts": [
|
||||
@@ -2771,7 +2770,7 @@
|
||||
"deprecated": false,
|
||||
"deprecationMessage": "",
|
||||
"type": "Routes",
|
||||
"defaultValue": "[\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('./pages/login/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', loadComponent: () => \"import('./pages/dashboard/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', loadComponent: () => \"import('./pages/registration-detail/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', loadComponent: () => \"import('./pages/herregistratie/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: '**', redirectTo: 'login' },\n]"
|
||||
"defaultValue": "[\n {\n path: '',\n component: ShellComponent, // persistent header/footer; only children swap\n children: [\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('./pages/login/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', loadComponent: () => \"import('./pages/dashboard/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', loadComponent: () => \"import('./pages/registration-detail/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', loadComponent: () => \"import('./pages/herregistratie/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]"
|
||||
}
|
||||
],
|
||||
"src/app/core/scenario.interceptor.ts": [
|
||||
@@ -2889,6 +2888,11 @@
|
||||
"name": "<root>",
|
||||
"kind": "module",
|
||||
"children": [
|
||||
{
|
||||
"name": "ShellComponent",
|
||||
"kind": "component",
|
||||
"filename": "src/app/app.routes.ts"
|
||||
},
|
||||
{
|
||||
"name": "login",
|
||||
"kind": "route-path",
|
||||
@@ -3301,15 +3305,6 @@
|
||||
"coverageCount": "0/5",
|
||||
"status": "low"
|
||||
},
|
||||
{
|
||||
"filePath": "src/app/templates/page-layout/page-layout.component.ts",
|
||||
"type": "component",
|
||||
"linktype": "component",
|
||||
"name": "PageLayoutComponent",
|
||||
"coveragePercent": 100,
|
||||
"coverageCount": "1/1",
|
||||
"status": "very-good"
|
||||
},
|
||||
{
|
||||
"filePath": "src/app/templates/page-shell/page-shell.component.ts",
|
||||
"type": "component",
|
||||
@@ -3318,6 +3313,15 @@
|
||||
"coveragePercent": 16,
|
||||
"coverageCount": "1/6",
|
||||
"status": "low"
|
||||
},
|
||||
{
|
||||
"filePath": "src/app/templates/shell/shell.component.ts",
|
||||
"type": "component",
|
||||
"linktype": "component",
|
||||
"name": "ShellComponent",
|
||||
"coveragePercent": 100,
|
||||
"coverageCount": "1/1",
|
||||
"status": "very-good"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user