-
{{ 'login.wayf.header' | translate }}
+
Choose your institution
diff --git a/src/app/login-page/login-page.component.spec.ts b/src/app/login-page/login-page.component.spec.ts
index aa46237e24a..94416492f31 100644
--- a/src/app/login-page/login-page.component.spec.ts
+++ b/src/app/login-page/login-page.component.spec.ts
@@ -14,10 +14,6 @@ import { APP_DATA_SERVICES_MAP } from '../../config/app-config.interface';
import { AuthService } from '../core/auth/auth.service';
import { XSRFService } from '../core/xsrf/xsrf.service';
import { AuthServiceMock } from '../shared/mocks/auth.service.mock';
-import { HardRedirectService } from '../core/services/hard-redirect.service';
-import { APP_CONFIG } from '../../config/app-config.interface';
-import { WAYF_CONFIG, WAYF_DEFAULTS } from '../clarin-wayf/wayf.config';
-import { environment } from '../../environments/environment.test';
import { ActivatedRouteStub } from '../shared/testing/active-router.stub';
import { LoginPageComponent } from './login-page.component';
@@ -46,9 +42,6 @@ describe('LoginPageComponent', () => {
{ provide: AuthService, useValue: new AuthServiceMock() },
{ provide: XSRFService, useValue: {} },
{ provide: APP_DATA_SERVICES_MAP, useValue: {} },
- { provide: HardRedirectService, useValue: { redirect: jasmine.createSpy('redirect'), getCurrentRoute: jasmine.createSpy('getCurrentRoute').and.returnValue('/') } },
- { provide: APP_CONFIG, useValue: environment },
- { provide: WAYF_CONFIG, useValue: { ...WAYF_DEFAULTS, feedUrl: '', spEntityId: '', loginEndpoint: '' } },
provideMockStore({}),
],
schemas: [NO_ERRORS_SCHEMA],
diff --git a/src/assets/mock/wayf-feed.json b/src/assets/mock/wayf-feed.json
deleted file mode 100644
index 2deaf8841c4..00000000000
--- a/src/assets/mock/wayf-feed.json
+++ /dev/null
@@ -1,115 +0,0 @@
-[
- {
- "entityID": "https://idp.example.org/shibboleth",
- "DisplayNames": [
- { "value": "Example University", "lang": "en" },
- { "value": "Příkladová Univerzita", "lang": "cs" }
- ],
- "Descriptions": [
- { "value": "Example university for research and education", "lang": "en" }
- ],
- "Keywords": [
- { "value": "example university research", "lang": "en" }
- ],
- "Logos": [
- { "value": "https://idp.example.org/logo.png", "height": 32, "width": 32 }
- ]
- },
- {
- "entityID": "https://shibboleth.muni.cz/idp/shibboleth",
- "DisplayNames": [
- { "value": "Masaryk University", "lang": "en" },
- { "value": "Masarykova univerzita", "lang": "cs" }
- ],
- "Keywords": [
- { "value": "masaryk brno czech republic muni", "lang": "en" }
- ],
- "Logos": [
- { "value": "https://shibboleth.muni.cz/logo-small.png", "height": 40, "width": 40 }
- ]
- },
- {
- "entityID": "https://login.cesnet.cz/idp/",
- "DisplayNames": [
- { "value": "CESNET e-Infrastructure", "lang": "en" },
- { "value": "e-Infrastruktura CESNET", "lang": "cs" }
- ],
- "Keywords": [
- { "value": "cesnet czech research network e-infra", "lang": "en" }
- ]
- },
- {
- "entityID": "https://login.feld.cvut.cz/idp/shibboleth",
- "DisplayNames": [
- { "value": "Czech Technical University in Prague", "lang": "en" },
- { "value": "České vysoké učení technické v Praze", "lang": "cs" }
- ],
- "Keywords": [
- { "value": "cvut ctu prague czech technical", "lang": "en" }
- ],
- "Logos": [
- { "value": "https://login.feld.cvut.cz/logo.png", "height": 32, "width": 120 }
- ]
- },
- {
- "entityID": "https://idp.cuni.cz/idp/shibboleth",
- "DisplayNames": [
- { "value": "Charles University", "lang": "en" },
- { "value": "Univerzita Karlova", "lang": "cs" }
- ],
- "Keywords": [
- { "value": "cuni charles prague czech karlov", "lang": "en" }
- ],
- "Logos": [
- { "value": "https://idp.cuni.cz/logo-small.png", "height": 36, "width": 36 }
- ]
- },
- {
- "entityID": "https://idp.ub.uni-muenchen.de/shibboleth",
- "DisplayNames": [
- { "value": "LMU Munich", "lang": "en" },
- { "value": "Ludwig-Maximilians-Universität München", "lang": "de" }
- ],
- "Keywords": [
- { "value": "lmu munich münchen bavaria germany", "lang": "en" }
- ]
- },
- {
- "entityID": "https://login.kuleuven.be/idp/shibboleth",
- "DisplayNames": [
- { "value": "KU Leuven", "lang": "en" }
- ],
- "Keywords": [
- { "value": "ku leuven belgium catholic university", "lang": "en" }
- ]
- },
- {
- "entityID": "https://aai.perun-aai.org/idp/",
- "DisplayNames": [
- { "value": "Perun MyAccessID", "lang": "en" }
- ],
- "Keywords": [
- { "value": "perun myaccessid proxy hub aai", "lang": "en" }
- ]
- },
- {
- "entityID": "https://cafe.rnp.br/idp/",
- "DisplayNames": [
- { "value": "Café - Federação Brasileira", "lang": "pt" },
- { "value": "Café - Brazilian Federation", "lang": "en" }
- ],
- "Keywords": [
- { "value": "cafe brazil rnp federation brasileiro", "lang": "en" }
- ]
- },
- {
- "entityID": "https://idp.uw.edu.pl/idp/shibboleth",
- "DisplayNames": [
- { "value": "University of Warsaw", "lang": "en" },
- { "value": "Uniwersytet Warszawski", "lang": "pl" }
- ],
- "Keywords": [
- { "value": "warsaw poland university uw", "lang": "en" }
- ]
- }
-]
From 01e701947eb81f422cc8e9c0aee7f9034c9b023a Mon Sep 17 00:00:00 2001
From: Juraj Roka <95219754+jr-rk@users.noreply.github.com>
Date: Tue, 21 Apr 2026 17:16:09 +0200
Subject: [PATCH 17/17] GENERATE A LIBRARY
---
angular.json | 29 ++
package-lock.json | 249 ++++++++++++++++++
package.json | 1 +
src/app/clarin-wayf/README.md | 32 ++-
src/app/clarin-wayf/clarin-wayf-routes.ts | 4 +-
.../clarin-wayf/clarin-wayf.component.html | 8 +-
.../clarin-wayf/clarin-wayf.component.scss | 9 +
src/app/clarin-wayf/clarin-wayf.component.ts | 27 +-
.../idp-card/wayf-idp-card.component.scss | 15 +-
.../idp-card/wayf-idp-card.component.spec.ts | 8 +-
.../idp-card/wayf-idp-card.component.ts | 11 +-
.../idp-list/wayf-idp-list.component.scss | 13 +
.../idp-list/wayf-idp-list.component.ts | 11 +-
.../wayf-recent-idps.component.scss | 10 +
.../wayf-recent-idps.component.spec.ts | 4 +-
.../recent-idps/wayf-recent-idps.component.ts | 17 +-
.../search-bar/wayf-search-bar.component.scss | 7 +
.../wayf-search-bar.component.spec.ts | 2 +-
.../search-bar/wayf-search-bar.component.ts | 14 +-
src/app/clarin-wayf/index.ts | 1 +
src/app/clarin-wayf/ng-package.json | 7 +
src/app/clarin-wayf/package.json | 23 ++
src/app/clarin-wayf/wayf.config.ts | 22 +-
src/app/clarin-wayf/wayf.module.ts | 18 +-
24 files changed, 457 insertions(+), 85 deletions(-)
create mode 100644 src/app/clarin-wayf/clarin-wayf.component.scss
create mode 100644 src/app/clarin-wayf/components/idp-list/wayf-idp-list.component.scss
create mode 100644 src/app/clarin-wayf/components/recent-idps/wayf-recent-idps.component.scss
create mode 100644 src/app/clarin-wayf/components/search-bar/wayf-search-bar.component.scss
create mode 100644 src/app/clarin-wayf/ng-package.json
create mode 100644 src/app/clarin-wayf/package.json
diff --git a/angular.json b/angular.json
index cf348ef8d54..d9a82ea00dd 100644
--- a/angular.json
+++ b/angular.json
@@ -276,6 +276,35 @@
}
}
}
+ },
+ "clarin-wayf": {
+ "projectType": "library",
+ "root": "src/app/clarin-wayf",
+ "sourceRoot": "src/app/clarin-wayf",
+ "prefix": "wayf",
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:ng-packagr",
+ "options": {
+ "project": "src/app/clarin-wayf/ng-package.json",
+ "tsConfig": "tsconfig.json"
+ },
+ "configurations": {
+ "production": {
+ "tsConfig": "tsconfig.json"
+ }
+ }
+ },
+ "test": {
+ "builder": "@angular-devkit/build-angular:karma",
+ "options": {
+ "tsConfig": "tsconfig.spec.json",
+ "include": [
+ "src/app/clarin-wayf/**/*.spec.ts"
+ ]
+ }
+ }
+ }
}
},
"cli": {
diff --git a/package-lock.json b/package-lock.json
index 1587edd6b88..655c3b31f33 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -145,6 +145,7 @@
"karma-mocha-reporter": "2.2.5",
"md5": "^2.3.0",
"ng-mocks": "^14.14.0",
+ "ng-packagr": "^20.3.2",
"ngx-mask": "14.2.4",
"postcss": "^8.5",
"postcss-import": "^14.0.0",
@@ -5068,6 +5069,17 @@
"@jridgewell/trace-mapping": "^0.3.24"
}
},
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -7558,6 +7570,50 @@
"react-dom": "^16.3.2"
}
},
+ "node_modules/@rollup/plugin-json": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz",
+ "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
+ "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
@@ -7907,6 +7963,26 @@
"win32"
]
},
+ "node_modules/@rollup/wasm-node": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/wasm-node/-/wasm-node-4.60.2.tgz",
+ "integrity": "sha512-FOfZOg752WSyKNefpSM3WrhggSTSuKuwcSfF7tdWC9PBYYg7BLwBR267uShFAI1ZyA0gNkdqK16LL9mNOPsQ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -10944,6 +11020,13 @@
"node": ">= 12.0.0"
}
},
+ "node_modules/common-path-prefix": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz",
+ "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/common-tags": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
@@ -12150,6 +12233,16 @@
"node": ">= 0.8"
}
},
+ "node_modules/dependency-graph": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz",
+ "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
@@ -13457,6 +13550,13 @@
"node": ">=4.0"
}
},
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -13972,6 +14072,39 @@
"url": "https://github.com/avajs/find-cache-dir?sponsor=1"
}
},
+ "node_modules/find-cache-directory": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/find-cache-directory/-/find-cache-directory-6.0.0.tgz",
+ "integrity": "sha512-CvFd5ivA6HcSHbD+59P7CyzINHXzwhuQK8RY7CxJZtgDSAtRlHiCaQpZQ2lMR/WRyUIEmzUvL6G2AGurMfegZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "common-path-prefix": "^3.0.0",
+ "pkg-dir": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/find-cache-directory/node_modules/pkg-dir": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-8.0.0.tgz",
+ "integrity": "sha512-4peoBq4Wks0riS0z8741NVv+/8IiTvqnZAr8QGgtdifrtpdXbNw/FxRS1l6NFqm4EMzuS0EDqNNx4XGaz8cuyQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up-simple": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -13989,6 +14122,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/find-up-simple": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz",
+ "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/flat": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
@@ -15196,6 +15342,16 @@
"node": "^18.17.0 || >=20.5.0"
}
},
+ "node_modules/injection-js": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/injection-js/-/injection-js-2.6.1.tgz",
+ "integrity": "sha512-dbR5bdhi7TWDoCye9cByZqeg/gAfamm8Vu3G1KZOTYkOif8WkuM8CD0oeDPtZYMzT5YH76JAFB7bkmyY9OJi2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ }
+ },
"node_modules/internal-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
@@ -18781,6 +18937,56 @@
"@angular/platform-browser": "5.0.0-alpha - 5 || 6.0.0-alpha - 6 || 7.0.0-alpha - 7 || 8.0.0-alpha - 8 || 9.0.0-alpha - 9 || 10.0.0-alpha - 10 || 11.0.0-alpha - 11 || 12.0.0-alpha - 12 || 13.0.0-alpha - 13 || 14.0.0-alpha - 14 || 15.0.0-alpha - 15 || 16.0.0-alpha - 16 || 17.0.0-alpha - 17 || 18.0.0-alpha - 18 || 19.0.0-alpha - 19 || 20.0.0-alpha - 20"
}
},
+ "node_modules/ng-packagr": {
+ "version": "20.3.2",
+ "resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-20.3.2.tgz",
+ "integrity": "sha512-yW5ME0hqTz38r/th/7zVwX5oSIw1FviSA2PUlGZdVjghDme/KX6iiwmOBmlt9E9whNmwijEC6Gn3KKbrsBx8ig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.3.0",
+ "@rollup/plugin-json": "^6.1.0",
+ "@rollup/wasm-node": "^4.24.0",
+ "ajv": "^8.17.1",
+ "ansi-colors": "^4.1.3",
+ "browserslist": "^4.22.1",
+ "chokidar": "^4.0.1",
+ "commander": "^14.0.0",
+ "dependency-graph": "^1.0.0",
+ "esbuild": "^0.25.0",
+ "find-cache-directory": "^6.0.0",
+ "injection-js": "^2.4.0",
+ "jsonc-parser": "^3.3.1",
+ "less": "^4.2.0",
+ "ora": "^8.2.0",
+ "piscina": "^5.0.0",
+ "postcss": "^8.4.47",
+ "rollup-plugin-dts": "^6.2.0",
+ "rxjs": "^7.8.1",
+ "sass": "^1.81.0",
+ "tinyglobby": "^0.2.12"
+ },
+ "bin": {
+ "ng-packagr": "src/cli/main.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ },
+ "optionalDependencies": {
+ "rollup": "^4.24.0"
+ },
+ "peerDependencies": {
+ "@angular/compiler-cli": "^20.0.0",
+ "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0",
+ "tslib": "^2.3.0",
+ "typescript": ">=5.8 <6.0"
+ },
+ "peerDependenciesMeta": {
+ "tailwindcss": {
+ "optional": true
+ }
+ }
+ },
"node_modules/ng2-file-upload": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/ng2-file-upload/-/ng2-file-upload-9.0.0.tgz",
@@ -22483,6 +22689,49 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/rollup-plugin-dts": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.4.1.tgz",
+ "integrity": "sha512-l//F3Zf7ID5GoOfLfD8kroBjQKEKpy1qfhtAdnpibFZMffPaylrg1CoDC2vGkPeTeyxUe4bVFCln2EFuL7IGGg==",
+ "dev": true,
+ "license": "LGPL-3.0-only",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "@jridgewell/sourcemap-codec": "^1.5.5",
+ "convert-source-map": "^2.0.0",
+ "magic-string": "^0.30.21"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/Swatinem"
+ },
+ "optionalDependencies": {
+ "@babel/code-frame": "^7.29.0"
+ },
+ "peerDependencies": {
+ "rollup": "^3.29.4 || ^4",
+ "typescript": "^4.5 || ^5.0 || ^6.0"
+ }
+ },
+ "node_modules/rollup-plugin-dts/node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/rollup-plugin-dts/node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
diff --git a/package.json b/package.json
index 4b18e68dacf..7848a1d075c 100644
--- a/package.json
+++ b/package.json
@@ -218,6 +218,7 @@
"karma-mocha-reporter": "2.2.5",
"md5": "^2.3.0",
"ng-mocks": "^14.14.0",
+ "ng-packagr": "^20.3.2",
"ngx-mask": "14.2.4",
"postcss": "^8.5",
"postcss-import": "^14.0.0",
diff --git a/src/app/clarin-wayf/README.md b/src/app/clarin-wayf/README.md
index 0b2c23efaa2..ad92d4d5bc5 100644
--- a/src/app/clarin-wayf/README.md
+++ b/src/app/clarin-wayf/README.md
@@ -74,11 +74,41 @@ src/app/clarin-wayf/
| **LastIdP Memory** | Persists user's last institution to localStorage for quick re-login. |
| **Keyboard Navigation** | Full keyboard control: Arrow keys, Enter, Escape. Screen reader compatible. |
| **SSR Safe** | All side effects (fetch, localStorage, navigation) guarded by platform checks. |
-| **Zero External Deps** | Only depends on Angular core (~signals, routing, forms). No i18n, no custom libs. |
+| **Explicit Peer Deps** | Library declares Angular, RxJS, Bootstrap, and Font Awesome peer dependencies for predictable host integration. |
| **Portable** | Single config token (`WAYF_CONFIG`). Can be used in 3+ ways: routes, config.yml bridge, app config. |
---
+## 📦 Package Peer Dependencies
+
+The extractable package manifest is in `src/app/clarin-wayf/package.json`.
+
+Required peer dependencies:
+
+- `@angular/core` `^20.0.0`
+- `@angular/common` `^20.0.0`
+- `@angular/router` `^20.0.0`
+- `rxjs` `^7.8.0`
+- `bootstrap` `^5.3.0`
+- `@fortawesome/fontawesome-free` `^6.7.0`
+
+### Consumer setup (mandatory)
+
+In the consuming app, ensure both CSS files are loaded globally:
+
+```json
+{
+ "styles": [
+ "node_modules/bootstrap/dist/css/bootstrap.min.css",
+ "node_modules/@fortawesome/fontawesome-free/css/all.min.css"
+ ]
+}
+```
+
+If these packages are installed but the styles are not imported, visual appearance may degrade.
+
+---
+
## 🧪 Test Coverage
- **Total:** 112 tests across 9 spec files
diff --git a/src/app/clarin-wayf/clarin-wayf-routes.ts b/src/app/clarin-wayf/clarin-wayf-routes.ts
index d13c173dca5..fbb06cfc03f 100644
--- a/src/app/clarin-wayf/clarin-wayf-routes.ts
+++ b/src/app/clarin-wayf/clarin-wayf-routes.ts
@@ -2,7 +2,7 @@ import { Route } from '@angular/router';
import { ClarinWayfComponent } from './clarin-wayf.component';
-export const ROUTES: Route[] = [
+export const CLARIN_WAYF_ROUTES: Route[] = [
{
path: '',
pathMatch: 'full',
@@ -10,3 +10,5 @@ export const ROUTES: Route[] = [
data: { title: 'Select Your Institution' },
},
];
+
+export const ROUTES = CLARIN_WAYF_ROUTES;
diff --git a/src/app/clarin-wayf/clarin-wayf.component.html b/src/app/clarin-wayf/clarin-wayf.component.html
index a9b738f97d5..7a97713ac6e 100644
--- a/src/app/clarin-wayf/clarin-wayf.component.html
+++ b/src/app/clarin-wayf/clarin-wayf.component.html
@@ -38,7 +38,7 @@
{{ resolvedServiceName() }}
/>
@if (searchQuery().length > 0) {
-
+
{{ filteredEntries().length }} institutions found
}
@@ -55,7 +55,7 @@
{{ resolvedServiceName() }}
@if (displayEntries().length < filteredEntries().length) {
-
+
-
{{ shortcutLabel() }}
-
{{ shortcutDisplayName() }}
+
{{ shortcutLabel() }}
+
{{ shortcutDisplayName() }}
}
`,
- styles: [`
- .wayf-shortcut__card {
- cursor: pointer;
- background-color: var(--bs-primary-bg-subtle, #e7f1ff);
- border-color: var(--bs-primary, #0d6efd) !important;
- transition: background-color 0.15s ease;
- }
- .wayf-shortcut__card:hover {
- background-color: var(--bs-primary-bg-subtle, #cfe2ff);
- }
- `],
+ styleUrls: ['./wayf-recent-idps.component.scss'],
})
export class WayfRecentIdpsComponent {
diff --git a/src/app/clarin-wayf/components/search-bar/wayf-search-bar.component.scss b/src/app/clarin-wayf/components/search-bar/wayf-search-bar.component.scss
new file mode 100644
index 00000000000..601275a1286
--- /dev/null
+++ b/src/app/clarin-wayf/components/search-bar/wayf-search-bar.component.scss
@@ -0,0 +1,7 @@
+.wayf-search-bar {
+ margin-bottom: 0.75rem;
+}
+
+.input-group-text {
+ background-color: var(--bs-body-bg, #fff);
+}
diff --git a/src/app/clarin-wayf/components/search-bar/wayf-search-bar.component.spec.ts b/src/app/clarin-wayf/components/search-bar/wayf-search-bar.component.spec.ts
index 898c38b6703..d295e1bafc7 100644
--- a/src/app/clarin-wayf/components/search-bar/wayf-search-bar.component.spec.ts
+++ b/src/app/clarin-wayf/components/search-bar/wayf-search-bar.component.spec.ts
@@ -28,7 +28,7 @@ describe('WayfSearchBarComponent', () => {
it('should have a label for accessibility', () => {
const label = fixture.nativeElement.querySelector('label');
expect(label).toBeTruthy();
- expect(label.getAttribute('for')).toBe('wayf-search-input');
+ expect(label.getAttribute('for')).toMatch(/^wayf-search-input-/);
});
it('should emit queryChange on input', () => {
diff --git a/src/app/clarin-wayf/components/search-bar/wayf-search-bar.component.ts b/src/app/clarin-wayf/components/search-bar/wayf-search-bar.component.ts
index 7bccdfdc2e6..5037ff9e473 100644
--- a/src/app/clarin-wayf/components/search-bar/wayf-search-bar.component.ts
+++ b/src/app/clarin-wayf/components/search-bar/wayf-search-bar.component.ts
@@ -12,6 +12,7 @@ import {
*/
@Component({
selector: 'ds-wayf-search-bar',
+ standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@@ -39,18 +40,13 @@ import {
`,
- styles: [`
- .wayf-search-bar {
- margin-bottom: 0.75rem;
- }
- .input-group-text {
- background-color: var(--bs-body-bg, #fff);
- }
- `],
+ styleUrls: ['./wayf-search-bar.component.scss'],
})
export class WayfSearchBarComponent {
- readonly inputId = 'wayf-search-input';
+ private static nextInputId = 0;
+
+ readonly inputId = `wayf-search-input-${WayfSearchBarComponent.nextInputId++}`;
/** Current search value (two-way via parent). */
readonly value = input('');
diff --git a/src/app/clarin-wayf/index.ts b/src/app/clarin-wayf/index.ts
index 839cdb01f47..4ce8151964a 100644
--- a/src/app/clarin-wayf/index.ts
+++ b/src/app/clarin-wayf/index.ts
@@ -8,6 +8,7 @@ export { WayfModule } from './wayf.module';
// Main component
export { ClarinWayfComponent } from './clarin-wayf.component';
+export { CLARIN_WAYF_ROUTES, ROUTES } from './clarin-wayf-routes';
// Models
export { IdentityProvider, DiscoFeedEntry, DiscoFeedLocalizedValue, DiscoFeedLogoEntry } from './models/idp-entry.model';
diff --git a/src/app/clarin-wayf/ng-package.json b/src/app/clarin-wayf/ng-package.json
new file mode 100644
index 00000000000..97e279dc106
--- /dev/null
+++ b/src/app/clarin-wayf/ng-package.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
+ "dest": "../../../dist/clarin-wayf",
+ "lib": {
+ "entryFile": "index.ts"
+ }
+}
diff --git a/src/app/clarin-wayf/package.json b/src/app/clarin-wayf/package.json
new file mode 100644
index 00000000000..016376a93d5
--- /dev/null
+++ b/src/app/clarin-wayf/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "@dspace/clarin-wayf",
+ "version": "0.1.0",
+ "description": "Standalone CLARIN WAYF Angular component library",
+ "license": "BSD-3-Clause",
+ "sideEffects": false,
+ "peerDependencies": {
+ "@angular/core": "^20.0.0",
+ "@angular/common": "^20.0.0",
+ "@angular/router": "^20.0.0",
+ "rxjs": "^7.8.0",
+ "bootstrap": "^5.3.0",
+ "@fortawesome/fontawesome-free": "^6.7.0"
+ },
+ "peerDependenciesMeta": {
+ "bootstrap": {
+ "optional": false
+ },
+ "@fortawesome/fontawesome-free": {
+ "optional": false
+ }
+ }
+}
diff --git a/src/app/clarin-wayf/wayf.config.ts b/src/app/clarin-wayf/wayf.config.ts
index 247b4d16858..4f8040ccfc5 100644
--- a/src/app/clarin-wayf/wayf.config.ts
+++ b/src/app/clarin-wayf/wayf.config.ts
@@ -6,9 +6,11 @@ import { IdentityProvider } from './models/idp-entry.model';
/**
* Configuration for the WAYF (Where Are You From) component.
*
- * Three fields are **required** — they have no defaults because they are
- * deployment-specific. All other fields have sensible defaults that can
- * be overridden per-instance via component inputs or via the token.
+ * `feedUrl` is the only field required by the standalone widget itself.
+ * Host applications may also supply integration fields such as
+ * `loginEndpoint` when they build the surrounding sign-in redirect flow.
+ * All other widget fields have sensible defaults that can be overridden
+ * per-instance via component inputs or via the token.
*
* Resolution priority for each field:
* 1. Component `@Input()` binding (e.g. `[feedUrl]="…"`)
@@ -16,18 +18,20 @@ import { IdentityProvider } from './models/idp-entry.model';
* 3. Built-in default from `WAYF_DEFAULTS`
*/
export interface WayfConfig {
- // ── Required (no defaults — consumer must supply) ────────────
+ // ── Required by the standalone widget ────────────────────────
/** URL of the JSON IdP feed (Shibboleth DiscoFeed or IdentityProvider[]). */
feedUrl: string;
+ // ── Host integration fields (used outside the core widget) ──
+
/** SAML entityID of the Service Provider. */
spEntityId: string;
/** Shibboleth SP login endpoint for redirect after IdP selection. */
loginEndpoint: string;
- // ── Optional (have defaults in WAYF_DEFAULTS) ────────────────
+ // ── Optional widget fields (have defaults in WAYF_DEFAULTS) ──
/** Branding title shown in the overlay header. */
serviceName: string;
@@ -101,8 +105,14 @@ export const WAYF_DEFAULTS: Omit