@easygov/web-components 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # @easygov/web-components
2
+
3
+ Framework-agnostic custom elements for EasyGov 2.0, wrapping
4
+ [`@easygov/react-components`](../react-components) as self-contained browser bundles via
5
+ [`@r2wc/react-to-web-component`](https://github.com/bitovi/react-to-web-component). Use this
6
+ package instead of `@easygov/react-components` when your app isn't React — Angular, Vue, plain
7
+ HTML/JS, etc.
8
+
9
+ Each bundle inlines React, ReactDOM, and its own copy of `@easygov/react-components`, so there is
10
+ nothing else to install and no risk of a version clash with an app that happens to use React for
11
+ something else.
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ npm install @easygov/web-components
17
+ ```
18
+
19
+ There are no runtime peer dependencies to install — React and ReactDOM are bundled in. (The
20
+ `package.json` lists `react`/`react-dom` as peer dependencies purely so npm doesn't warn a
21
+ React host app about a duplicate copy; you never need to satisfy them yourself.)
22
+
23
+ ## Usage
24
+
25
+ Each custom element is a side-effecting subpath import — importing it registers the element:
26
+
27
+ ```ts
28
+ import "@easygov/web-components/header"; // registers <eg-easygov-header>
29
+ import "@easygov/web-components/footer"; // registers <eg-easygov-footer>
30
+ ```
31
+
32
+ ```html
33
+ <eg-easygov-header></eg-easygov-header>
34
+
35
+ <main>...</main>
36
+
37
+ <eg-easygov-footer></eg-easygov-footer>
38
+ ```
39
+
40
+ Import only the subpath(s) you need — an app using just the footer never pulls in the header's
41
+ code. Both share one common chunk (React, the design tokens' fonts/CSS, `@easygov/react-components`
42
+ itself), so it's fetched once regardless of how many elements you import.
43
+
44
+ Each element renders into an open shadow root with the design system's styles and fonts attached
45
+ automatically — no separate stylesheet or font import required, unlike
46
+ `@easygov/react-components`.
47
+
48
+ ### `<eg-easygov-header>`
49
+
50
+ No attributes. Auth, the display language, and the environment/version badges are all handled
51
+ internally the same way they are in `@easygov/react-components` — see that package's README for
52
+ the underlying `NEXT_PUBLIC_*` env var configuration, since it applies here too (see
53
+ [Build-time configuration](#build-time-configuration) below for the web-components-specific
54
+ wrinkle).
55
+
56
+ No props are mapped, and none of the header's callback props (`onLanguageChange`, `onNavigate`,
57
+ `onContextChange`, `onLogout`) exist here either — function props can't cross the custom-element
58
+ boundary. Listen for the corresponding `CustomEvent`s instead:
59
+
60
+ ```js
61
+ const header = document.querySelector("eg-easygov-header");
62
+
63
+ header.addEventListener("languagechange", (e) => {
64
+ console.log(e.detail.locale); // 'de' | 'en' | 'fr' | 'it'
65
+ });
66
+ header.addEventListener("navigate", (e) => {
67
+ console.log(e.detail.path);
68
+ });
69
+ header.addEventListener("contextchange", (e) => {
70
+ const { contextJwt, claims } = e.detail; // decoded, NOT signature-verified — see below
71
+ });
72
+ header.addEventListener("sessionchange", (e) => {
73
+ console.log(e.detail.user);
74
+ });
75
+ ```
76
+
77
+ **`contextJwt`/`claims` are decoded but not verified.** Use them to prefill forms or drive
78
+ rendering; a backend that receives `contextJwt` must verify it against core-backend's published
79
+ JWKS before trusting any claim.
80
+
81
+ ### `<eg-easygov-footer>`
82
+
83
+ No attributes, no props, no events — fully static content.
84
+
85
+ ### DisplayLanguage API
86
+
87
+ The header owns the current display language (resolved from the `eg-display-language` cookie
88
+ shared across every EasyGov subdomain, then the browser language, then `"de"`). Read or change it
89
+ from the package root:
90
+
91
+ ```ts
92
+ import {
93
+ getDisplayLanguage,
94
+ setDisplayLanguage,
95
+ subscribeDisplayLanguage,
96
+ } from "@easygov/web-components";
97
+
98
+ getDisplayLanguage(); // 'de' | 'en' | 'fr' | 'it' | ...
99
+ setDisplayLanguage("fr"); // writes the shared cookie; every subscribed header re-renders
100
+ const unsubscribe = subscribeDisplayLanguage(() => {
101
+ console.log("display language changed:", getDisplayLanguage());
102
+ });
103
+ ```
104
+
105
+ **Don't mix this with `@easygov/react-components`'s `useEasygovDisplayLanguage()` on the same
106
+ page.** This bundle inlines its own copy of `@easygov/react-components`, so the two would read
107
+ from different store instances and drift apart the moment a user picks a language. If a page
108
+ already uses `@easygov/react-components` directly, use its hook/functions instead of this
109
+ package's.
110
+
111
+ ### Publishing the business module's version
112
+
113
+ A consuming app can publish its own version number so it shows up in the header's environment
114
+ badge tooltip, alongside the library and backend versions:
115
+
116
+ ```ts
117
+ import { setBusinessModuleVersion } from "@easygov/web-components";
118
+
119
+ setBusinessModuleVersion("2.4.1");
120
+ ```
121
+
122
+ Call it once at startup (or whenever your app's version is known); calling it again later simply
123
+ updates the tooltip. Same "don't mix stores" caveat as above applies if the same page also uses
124
+ `@easygov/react-components` directly.
125
+
126
+ ## Framework examples
127
+
128
+ ### Angular
129
+
130
+ `apps/angular-demo` in this repo is a full working example — install `@easygov/web-components`
131
+ as a real npm dependency (not `workspace:*`) and side-effect-import the subpaths you need, then
132
+ wrap each custom element in a small Angular component so its events become `@Output()`s:
133
+
134
+ ```ts
135
+ // main.ts
136
+ import "@easygov/web-components/header";
137
+ import "@easygov/web-components/footer";
138
+ ```
139
+
140
+ ```ts
141
+ // components/easygov-header/easygov-header.component.ts
142
+ import { AfterViewInit, Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, EventEmitter, OnDestroy, Output, ViewChild } from "@angular/core";
143
+
144
+ const BRIDGED_EVENTS = ["languagechange", "navigate", "contextchange", "sessionchange"] as const;
145
+
146
+ @Component({
147
+ selector: "app-easygov-header",
148
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
149
+ template: `<eg-easygov-header #host></eg-easygov-header>`,
150
+ })
151
+ export class EasygovHeaderComponent implements AfterViewInit, OnDestroy {
152
+ @ViewChild("host", { static: true }) private host!: ElementRef<HTMLElement>;
153
+ @Output() languagechange = new EventEmitter<CustomEvent>();
154
+ @Output() navigate = new EventEmitter<CustomEvent>();
155
+ @Output() contextchange = new EventEmitter<CustomEvent>();
156
+ @Output() sessionchange = new EventEmitter<CustomEvent>();
157
+
158
+ private readonly listeners = new Map<string, (event: Event) => void>();
159
+
160
+ ngAfterViewInit(): void {
161
+ for (const eventName of BRIDGED_EVENTS) {
162
+ const listener = (event: Event) => this[eventName].emit(event as CustomEvent);
163
+ this.listeners.set(eventName, listener);
164
+ this.host.nativeElement.addEventListener(eventName, listener);
165
+ }
166
+ }
167
+
168
+ ngOnDestroy(): void {
169
+ for (const [eventName, listener] of this.listeners) {
170
+ this.host.nativeElement.removeEventListener(eventName, listener);
171
+ }
172
+ }
173
+ }
174
+ ```
175
+
176
+ `CUSTOM_ELEMENTS_SCHEMA` is required on any Angular component template that renders a custom
177
+ element directly, or the Angular compiler rejects the unknown tag.
178
+
179
+ ### Plain HTML / vanilla JS
180
+
181
+ See `packages/web-components/demo/index.html` in this repo — run
182
+ `pnpm --filter @easygov/web-components demo` after building, then open
183
+ `http://localhost:4173/demo/` for a live local page exercising both elements, their events, and
184
+ the DisplayLanguage API.
185
+
186
+ ## Build-time configuration
187
+
188
+ `EasygovHeader` needs `NEXT_PUBLIC_CORE_ORIGIN` / `NEXT_PUBLIC_CORE_API_ORIGIN` /
189
+ `NEXT_PUBLIC_OIDC_AUTHORITY` / `NEXT_PUBLIC_INFO_ORIGIN` on any host outside `*.easygov.swiss`
190
+ (see `@easygov/react-components`'s README for what each does and when it's required).
191
+
192
+ **Unlike `@easygov/react-components`, you cannot set these when *your* app builds** — this
193
+ package ships a pre-built bundle with those values already inlined by tsup at the time
194
+ `@easygov/web-components` itself was built and published (see this repo's
195
+ `packages/web-components/tsup.config.ts` and the `@easygov/web-components#build` task in
196
+ `turbo.json`). If your deployment target needs origins other than what the published package
197
+ was built with, you have two options:
198
+
199
+ - Rely on the automatic `*.easygov.swiss` subdomain derivation (no env vars needed there), or
200
+ - Fork/rebuild this package yourself with the env vars set for your target, or use
201
+ `@easygov/react-components` directly instead, where `NEXT_PUBLIC_*` is inlined at *your*
202
+ app's build time.
203
+
204
+ ## Testing
205
+
206
+ Components rendered by this package carry the same `data-slot` attributes as
207
+ `@easygov/react-components` (see that package), but they live inside each element's shadow root —
208
+ an external Playwright suite must pierce the shadow boundary (e.g.
209
+ Playwright's `page.locator('eg-easygov-header').locator('...')`, which crosses open shadow roots
210
+ automatically) rather than querying `document` directly.
211
+
212
+ ## License
213
+
214
+ MIT