@easygov/web-components 0.5.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 +214 -0
- package/dist/{chunk-4YTFOHWE.js → chunk-6QWPHBPC.js} +3 -3
- package/dist/{chunk-4YTFOHWE.js.map → chunk-6QWPHBPC.js.map} +1 -1
- package/dist/chunk-C75LXA3C.js +27 -0
- package/dist/chunk-C75LXA3C.js.map +1 -0
- package/dist/{chunk-O3IXHDLE.js → chunk-KIC76O54.js} +2 -2
- package/dist/{chunk-O3IXHDLE.js.map → chunk-KIC76O54.js.map} +1 -1
- package/dist/footer.js +1 -1
- package/dist/header.js +1 -1
- package/dist/index.d.ts +145 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-R2ZANGUH.js +0 -27
- package/dist/chunk-R2ZANGUH.js.map +0 -1
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
|