@startnext/chrome 0.3.4 → 0.4.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Startnext Header/Footer Web Components
4
4
 
5
- Wiederverwendbare Header- und Footer-Components fuer alle Startnext Microservices.
5
+ Wiederverwendbare Header- und Footer-Components für alle Startnext Microservices.
6
6
 
7
7
  ## Installation
8
8
 
@@ -14,41 +14,24 @@ pnpm add @startnext/chrome
14
14
 
15
15
  ## Quick Start
16
16
 
17
- Es gibt zwei Integrationsmodi:
18
-
19
- 1. **Client-Side Rendering** — Web Component fetcht Daten selbst (einfach, kein Server noetig)
20
- 2. **Server-Side Rendering (SSR)** — Server holt fertig gerendertes HTML von der API (sofort sichtbar ohne JS, bessere Performance + SEO)
21
-
22
- ### Client-Side: Via CDN (PHP, Vanilla HTML)
23
- ```html
24
- <script type="module" src="https://unpkg.com/@startnext/chrome@latest/dist/index.js"></script>
25
-
26
- <startnext-header api-url="https://scs-api.vercel.app"></startnext-header>
27
- <startnext-footer api-url="https://scs-api.vercel.app"></startnext-footer>
28
- ```
29
-
30
- ### Client-Side: Modern JavaScript (React, Vue, etc.)
17
+ ### Modern JavaScript (React, Vue, etc.)
31
18
  ```javascript
32
19
  import '@startnext/chrome';
33
20
  ```
34
21
 
35
22
  ```html
36
- <startnext-header api-url="https://scs-api.vercel.app"></startnext-header>
37
- <startnext-footer api-url="https://scs-api.vercel.app"></startnext-footer>
23
+ <startnext-header></startnext-header>
24
+ <startnext-footer></startnext-footer>
38
25
  ```
39
26
 
40
- ### Server-Side Rendering (empfohlen)
41
-
42
- SSR-HTML von der API holen und direkt einbinden. Die Web Component JS hydriert anschliessend (haengt Events an, statt DOM neu zu rendern). Die API-Daten werden als JSON im HTML eingebettet — der Component hat sofort echte Daten ohne Client-Fetch.
27
+ ### Via CDN (PHP, Vanilla HTML)
28
+ ```html
29
+ <script type="module" src="https://unpkg.com/@startnext/chrome@latest/dist/index.js"></script>
43
30
 
44
- ```bash
45
- # SSR-HTML holen (z.B. serverseitig per fetch, file_get_contents, ESI)
46
- curl "https://scs-api.vercel.app/api/header/render?lang=de&large-animation"
47
- curl "https://scs-api.vercel.app/api/footer/render?lang=de"
31
+ <startnext-header></startnext-header>
32
+ <startnext-footer></startnext-footer>
48
33
  ```
49
34
 
50
- Siehe Framework-spezifische Beispiele: [React / Next.js](#react--nextjs-app-router), [Vue / Nuxt](#vue--nuxt-3), [PHP / Vanilla](#php--vanilla-html)
51
-
52
35
  ## API
53
36
 
54
37
  ### `<startnext-header>`
@@ -57,47 +40,43 @@ Siehe Framework-spezifische Beispiele: [React / Next.js](#react--nextjs-app-rout
57
40
 
58
41
  | Attribute | Type | Default | Description |
59
42
  |---|---|---|---|
60
- | `lang` | `string` | `'de'` | Sprache (`'de'`, `'en'`) |
61
- | `light` | `boolean` | `false` | Heller Header (weisse Schrift/Icons ueber dunklem Hero, wechselt automatisch zu dunkel beim Scrollen). Standard ohne Attribut: dunkler Header |
62
- | `large-animation` | `boolean` | `false` | Grosse Lottie-Logo-Animation mit Claim und horizontaler Navigation |
43
+ | `lang` | `string` | `'de'` | Aktive Sprache (beliebiger 2-Buchstaben-Code, z. B. `'de'`, `'en'`, `'fr'`) |
44
+ | `languages` | `string` (JSON) | - | JSON-Array von Sprachcodes, auf die der Language-Switcher eingeschränkt wird, z. B. `'["de","en"]'`. Ohne Attribut: alle vom API gelieferten Sprachen. Der Switcher wird automatisch ausgeblendet, wenn nur 1 Sprache verfügbar ist |
45
+ | `light` | `boolean` | `false` | Heller Header (weiße Schrift/Icons über dunklem Hero, wechselt automatisch zu dunkel beim Scrollen). Standard ohne Attribut: dunkler Header |
46
+ | `large-animation` | `boolean` | `false` | Große Lottie-Logo-Animation mit Claim und horizontaler Navigation |
63
47
  | `authenticated` | `boolean` | `false` | Zeigt User-Avatar statt "Anmelden" Button |
64
48
  | `user-name` | `string` | - | Name des eingeloggten Users |
65
49
  | `user-avatar` | `string` | - | Avatar-URL des Users |
66
50
  | `color-mode` | `'light' \| 'dark'` | auto | Farbmodus. Ohne Attribut: Header scannt `<html>` nach Klasse `dark`/`light`. Ohne Klasse: Default `dark` (dunkle Seite, heller Header). Toggle-Button wechselt `<html>`-Klasse und Header-Darstellung |
67
51
  | `hide-color-mode` | `boolean` | `false` | Versteckt den Color-Mode Toggle-Button |
68
- | `hide-lang` | `boolean` | `false` | Versteckt den Language-Switcher |
69
- | `hide-login` | `boolean` | `false` | Versteckt den Login/Avatar-Button |
70
- | `show-back-link` | `boolean` | `false` | Zeigt einen "Zurueck zu Startnext"-Link (Text kommt von der API) |
71
- | `back-url` | `string` | - | Ueberschreibt die Back-Link-URL |
72
- | `back-label` | `string` | - | Ueberschreibt den Back-Link-Text |
73
- | `api-url` | `string` | - | API-Endpoint-URL fuer Live-Daten aus Notion (nicht noetig bei SSR) |
52
+ | `hide-lang` | `boolean` | `false` | Versteckt den Language-Switcher komplett |
74
53
 
75
54
  **Events:**
76
55
 
77
56
  | Event | Detail | Description |
78
57
  |---|---|---|
79
- | `burger-menu-toggle` | `{ open: boolean }` | Burger-Menue geoeffnet/geschlossen |
80
- | `user-menu-toggle` | `{ open: boolean }` | User-Menue geoeffnet/geschlossen |
58
+ | `burger-menu-toggle` | `{ open: boolean }` | Burger-Menü geöffnet/geschlossen |
59
+ | `user-menu-toggle` | `{ open: boolean }` | User-Menü geöffnet/geschlossen |
81
60
  | `navigation-click` | `{ item: NavigationItem }` | Navigation-Link geklickt |
82
61
  | `cta-click` | `{ url: string }` | "Projekt starten" Button geklickt |
83
62
  | `logout` | `{}` | Abmelden geklickt |
84
63
  | `language-change` | `{ language: string }` | Sprache gewechselt |
85
- | `scroll-state-change` | `{ scrolled: boolean, slideUp: boolean }` | Scroll-Zustand geaendert |
64
+ | `scroll-state-change` | `{ scrolled: boolean, slideUp: boolean }` | Scroll-Zustand geändert |
86
65
  | `color-mode-change` | `{ mode: 'light' \| 'dark' }` | Farbmodus gewechselt (per Toggle-Button) |
87
66
 
88
67
  **Usage Examples:**
89
68
 
90
69
  ```html
91
- <!-- Default: dunkler Header (dunkle Schrift fuer helle Seiten) -->
70
+ <!-- Default: dunkler Header (dunkle Schrift für helle Seiten) -->
92
71
  <startnext-header></startnext-header>
93
72
 
94
- <!-- Heller Header (weisse Schrift ueber dunklem Hero) -->
73
+ <!-- Heller Header (weiße Schrift über dunklem Hero) -->
95
74
  <startnext-header light></startnext-header>
96
75
 
97
- <!-- Heller Header + grosse Animation (z.B. Startseite) -->
76
+ <!-- Heller Header + große Animation (z.B. Startseite) -->
98
77
  <startnext-header light large-animation></startnext-header>
99
78
 
100
- <!-- Heller Header + gross + eingeloggt -->
79
+ <!-- Heller Header + groß + eingeloggt -->
101
80
  <startnext-header
102
81
  light
103
82
  large-animation
@@ -116,17 +95,6 @@ Siehe Framework-spezifische Beispiele: [React / Next.js](#react--nextjs-app-rout
116
95
  </html>
117
96
  ```
118
97
 
119
- ```html
120
- <!-- Embedded / Microservice-Modus: Login ausblenden, Back-Link anzeigen -->
121
- <startnext-header
122
- hide-login
123
- show-back-link
124
- back-url="https://www.startnext.com"
125
- back-label="Zurueck zu Startnext"
126
- api-url="https://scs-api.vercel.app"
127
- ></startnext-header>
128
- ```
129
-
130
98
  ```javascript
131
99
  const header = document.querySelector('startnext-header');
132
100
 
@@ -136,9 +104,8 @@ header.addEventListener('navigation-click', (e) => {
136
104
  });
137
105
 
138
106
  header.addEventListener('language-change', (e) => {
139
- // Footer synchronisiert Sprache automatisch (lang-sync ist default true).
140
- // Manuelle Synchronisation nur noetig wenn lang-sync="false" gesetzt ist:
141
- // document.querySelector('startnext-footer').setAttribute('lang', e.detail.language);
107
+ // Sprache auch im Footer synchronisieren
108
+ document.querySelector('startnext-footer').setAttribute('lang', e.detail.language);
142
109
  });
143
110
 
144
111
  header.addEventListener('logout', () => {
@@ -157,9 +124,9 @@ header.addEventListener('color-mode-change', (e) => {
157
124
 
158
125
  | Attribute | Type | Default | Description |
159
126
  |---|---|---|---|
160
- | `lang` | `string` | `'de'` | Sprache (`'de'`, `'en'`) |
161
- | `api-url` | `string` | - | API-Endpoint-URL fuer Live-Daten (nicht noetig bei SSR) |
162
- | `lang-sync` | `boolean` | `true` | Synchronisiert Sprache automatisch wenn der Header ein `language-change` Event emittiert. Mit `lang-sync="false"` deaktivieren |
127
+ | `lang` | `string` | `'de'` | Aktive Sprache (beliebiger 2-Buchstaben-Code) |
128
+ | `api-url` | `string` | - | URL des SCS API. Ohne Attribut: statische Default-Daten |
129
+ | `lang-sync` | `boolean` | `true` | Synchronisiert `lang` automatisch beim `language-change`-Event des Headers |
163
130
 
164
131
  **Events:**
165
132
 
@@ -186,7 +153,7 @@ startnext-footer {
186
153
 
187
154
  ## Fonts
188
155
 
189
- Standardmaessig nutzen die Components System-Fonts als Fallback. Fuer eigene Schriftarten `@font-face` im eigenen CSS deklarieren und `--font-family` setzen:
156
+ Standardmäßig nutzen die Components System-Fonts als Fallback. Für eigene Schriftarten `@font-face` im eigenen CSS deklarieren und `--font-family` setzen:
190
157
 
191
158
  ```css
192
159
  @font-face {
@@ -202,329 +169,11 @@ startnext-header, startnext-footer {
202
169
  }
203
170
  ```
204
171
 
205
- ## Server-Side Rendering (SSR)
206
-
207
- Die API liefert fertig gerendertes HTML mit **Declarative Shadow DOM** (`<template shadowrootmode="open">`) und eingebetteten **API-Daten als JSON** (`<script data-ssr-props>`). Der Browser zeigt den Content sofort an — ohne JavaScript. Die Web Component JS hydriert anschliessend (Events anbinden, kein Re-Render noetig).
208
-
209
- ### Wie es funktioniert
210
-
211
- 1. Server fetcht SSR-HTML von `/api/header/render` bzw. `/api/footer/render`
212
- 2. HTML wird direkt in die Seite eingebunden
213
- 3. Browser parst DSD → Shadow DOM existiert sofort, Content ist sichtbar
214
- 4. Web Component JS laedt → erkennt bestehenden `shadowRoot`, setzt `isHydrating = true`
215
- 5. `render()` ueberspringt `innerHTML`, ruft nur `attachEvents()` auf
216
- 6. `parseSsrProps()` liest eingebettetes JSON → populiert API-Cache mit echten Daten
217
- 7. Attribut-Aenderungen (z.B. `color-mode`, `large-animation`) funktionieren sofort — kein Client-Fetch noetig
218
-
219
- ```html
220
- <!-- Server liefert fertiges HTML (vereinfacht) -->
221
- <startnext-header lang="de" light large-animation>
222
- <template shadowrootmode="open">
223
- <style>/* headerCSS */</style>
224
- <div><!-- Shadow DOM HTML --></div>
225
- </template>
226
- <script type="application/json" data-ssr-props>{"mainNavigation":[...],...}</script>
227
- </startnext-header>
228
-
229
- <!-- JS hydriert automatisch -->
230
- <script type="module" src="@startnext/chrome"></script>
231
- ```
232
-
233
- ### Render-Endpoints (Query-Parameter)
234
-
235
- **Header:** `GET /api/header/render`
236
-
237
- | Parameter | Typ | Beschreibung |
238
- |-----------|-----|-------------|
239
- | `lang` | `de` \| `en` | Sprache |
240
- | `light` | flag | Heller Header |
241
- | `large-animation` | flag | Grosse Logo-Animation |
242
- | `authenticated` | flag | Eingeloggt |
243
- | `user-name` | string | User-Name |
244
- | `user-avatar` | string | Avatar-URL |
245
- | `hide-color-mode` | flag | Color-Mode Toggle verstecken |
246
- | `hide-lang` | flag | Language-Switcher verstecken |
247
- | `hide-login` | flag | Login-Button verstecken |
248
- | `show-back-link` | flag | Back-Link anzeigen |
249
- | `back-url` | string | Back-Link URL |
250
- | `back-label` | string | Back-Link Text |
251
-
252
- **Footer:** `GET /api/footer/render`
253
-
254
- | Parameter | Typ | Beschreibung |
255
- |-----------|-----|-------------|
256
- | `lang` | `de` \| `en` | Sprache |
257
-
258
- Beispiel: `GET /api/header/render?lang=de&large-animation&hide-login&show-back-link`
259
-
260
- ### `api-url` bei SSR nicht noetig
261
-
262
- Bei SSR werden die API-Daten als JSON im HTML eingebettet (`<script data-ssr-props>`). Der Component liest das beim Hydrating und hat sofort echte Daten im Cache. `api-url` ist daher **nicht noetig** — der Component rendert und reagiert auf Attribut-Aenderungen ohne zusaetzlichen Fetch.
263
-
264
- `api-url` ist nur fuer **Client-Side-Only** Rendering relevant, wenn kein SSR-HTML verfuegbar ist.
265
-
266
- ---
267
-
268
- ## Framework-Integration (SSR)
269
-
270
- ### React / Next.js (App Router)
271
-
272
- React's `dangerouslySetInnerHTML` nutzt `innerHTML`, das DSD nicht parsed. Nach der Hydration wird `setHTMLUnsafe()` als Fallback aufgerufen.
273
-
274
- **Layout (Server Component):**
275
-
276
- ```tsx
277
- // app/[locale]/layout.tsx
278
- async function fetchChromeHtml(path: string): Promise<string> {
279
- try {
280
- const res = await fetch(`${process.env.NEXT_PUBLIC_SCS_API_URL}${path}`, {
281
- next: { revalidate: 60 },
282
- });
283
- if (!res.ok) return "";
284
- return await res.text();
285
- } catch {
286
- return "";
287
- }
288
- }
289
-
290
- export default async function Layout({ children, params }) {
291
- const { locale } = await params;
292
- const [headerHtml, footerHtml] = await Promise.all([
293
- fetchChromeHtml(`/api/header/render?lang=${locale}&large-animation`),
294
- fetchChromeHtml(`/api/footer/render?lang=${locale}`),
295
- ]);
296
-
297
- return (
298
- <html lang={locale}>
299
- <body>
300
- <ChromeHeader ssrHtml={headerHtml} />
301
- <main>{children}</main>
302
- <ChromeFooter ssrHtml={footerHtml} />
303
- </body>
304
- </html>
305
- );
306
- }
307
- ```
308
-
309
- **Header (Client Component):**
310
-
311
- ```tsx
312
- // components/ChromeHeader.tsx
313
- "use client";
314
- import { useEffect, useRef, useState } from "react";
315
-
316
- export function ChromeHeader({ ssrHtml }: { ssrHtml?: string }) {
317
- const containerRef = useRef<HTMLDivElement>(null);
318
- const [mounted, setMounted] = useState(false);
319
-
320
- useEffect(() => {
321
- const container = containerRef.current;
322
- if (container && ssrHtml) {
323
- const header = container.querySelector("startnext-header");
324
- if (!header?.shadowRoot && "setHTMLUnsafe" in Element.prototype) {
325
- container.setHTMLUnsafe(ssrHtml);
326
- }
327
- }
328
- import("@startnext/chrome");
329
- setMounted(true);
330
- }, [ssrHtml]);
331
-
332
- // Attribut-Sync nach Mount (z.B. color-mode, large-animation)
333
- useEffect(() => {
334
- const el = containerRef.current?.querySelector("startnext-header");
335
- if (!el || !mounted) return;
336
- // Beispiel: el.setAttribute("color-mode", resolvedTheme);
337
- }, [mounted]);
338
-
339
- if (ssrHtml) {
340
- return (
341
- <div ref={containerRef} style={{ display: "contents" }}
342
- suppressHydrationWarning
343
- dangerouslySetInnerHTML={{ __html: ssrHtml }}
344
- />
345
- );
346
- }
347
-
348
- // Fallback ohne SSR
349
- return <startnext-header api-url={process.env.NEXT_PUBLIC_SCS_API_URL} />;
350
- }
351
- ```
352
-
353
- **Footer (Client Component):**
354
-
355
- ```tsx
356
- // components/ChromeFooter.tsx
357
- "use client";
358
- import { useEffect, useRef } from "react";
359
-
360
- export function ChromeFooter({ ssrHtml }: { ssrHtml?: string }) {
361
- const containerRef = useRef<HTMLDivElement>(null);
362
-
363
- useEffect(() => {
364
- const container = containerRef.current;
365
- if (container && ssrHtml) {
366
- const footer = container.querySelector("startnext-footer");
367
- if (!footer?.shadowRoot && "setHTMLUnsafe" in Element.prototype) {
368
- container.setHTMLUnsafe(ssrHtml);
369
- }
370
- }
371
- import("@startnext/chrome");
372
- }, [ssrHtml]);
373
-
374
- if (ssrHtml) {
375
- return (
376
- <div ref={containerRef} style={{ display: "contents" }}
377
- suppressHydrationWarning
378
- dangerouslySetInnerHTML={{ __html: ssrHtml }}
379
- />
380
- );
381
- }
382
-
383
- return <startnext-footer api-url={process.env.NEXT_PUBLIC_SCS_API_URL} />;
384
- }
385
- ```
386
-
387
- ### Vue / Nuxt 3
388
-
389
- Vue parsed DSD korrekt via `v-html` — kein `setHTMLUnsafe()` Workaround noetig.
390
-
391
- **Server Plugin (SSR-HTML fetchen):**
392
-
393
- ```ts
394
- // server/plugins/chrome.ts
395
- export default defineEventHandler(async (event) => {
396
- // Wird automatisch von useAsyncData / useFetch aufgerufen
397
- });
398
- ```
399
-
400
- **Layout:**
401
-
402
- ```vue
403
- <!-- layouts/default.vue -->
404
- <script setup lang="ts">
405
- const { locale } = useI18n();
406
- const config = useRuntimeConfig();
407
-
408
- const { data: headerHtml } = await useFetch(
409
- () => `${config.public.scsApiUrl}/api/header/render?lang=${locale.value}&large-animation`,
410
- { key: `chrome-header-${locale.value}` }
411
- );
412
-
413
- const { data: footerHtml } = await useFetch(
414
- () => `${config.public.scsApiUrl}/api/footer/render?lang=${locale.value}`,
415
- { key: `chrome-footer-${locale.value}` }
416
- );
417
-
418
- onMounted(() => {
419
- import('@startnext/chrome');
420
- });
421
- </script>
422
-
423
- <template>
424
- <div v-if="headerHtml" v-html="headerHtml" style="display: contents" />
425
- <startnext-header v-else :lang="locale" :api-url="config.public.scsApiUrl" />
426
-
427
- <main>
428
- <slot />
429
- </main>
430
-
431
- <div v-if="footerHtml" v-html="footerHtml" style="display: contents" />
432
- <startnext-footer v-else :lang="locale" :api-url="config.public.scsApiUrl" />
433
- </template>
434
- ```
435
-
436
- **Nuxt Config (Custom Elements registrieren):**
437
-
438
- ```ts
439
- // nuxt.config.ts
440
- export default defineNuxtConfig({
441
- vue: {
442
- compilerOptions: {
443
- isCustomElement: (tag) => tag.startsWith('startnext-'),
444
- },
445
- },
446
- runtimeConfig: {
447
- public: {
448
- scsApiUrl: process.env.SCS_API_URL || 'https://scs-api.vercel.app',
449
- },
450
- },
451
- });
452
- ```
453
-
454
- ### PHP / Vanilla HTML
455
-
456
- ```php
457
- <?php
458
- $apiUrl = 'https://scs-api.vercel.app';
459
- $lang = 'de';
460
-
461
- $headerHtml = @file_get_contents("$apiUrl/api/header/render?lang=$lang&large-animation");
462
- $footerHtml = @file_get_contents("$apiUrl/api/footer/render?lang=$lang");
463
- ?>
464
- <!DOCTYPE html>
465
- <html lang="<?= $lang ?>">
466
- <head>
467
- <script type="module" src="https://unpkg.com/@startnext/chrome@latest/dist/index.js"></script>
468
- </head>
469
- <body>
470
- <?= $headerHtml ?: '<startnext-header api-url="' . $apiUrl . '"></startnext-header>' ?>
471
-
472
- <main><!-- Content --></main>
473
-
474
- <?= $footerHtml ?: '<startnext-footer api-url="' . $apiUrl . '"></startnext-footer>' ?>
475
- </body>
476
- </html>
477
- ```
478
-
479
- ### Edge Side Includes (ESI)
480
-
481
- ```html
482
- <!-- Varnish / CDN ESI -->
483
- <esi:try>
484
- <esi:attempt>
485
- <esi:include src="/api/header/render?lang=de&large-animation" />
486
- </esi:attempt>
487
- <esi:except>
488
- <startnext-header api-url="https://scs-api.vercel.app"></startnext-header>
489
- </esi:except>
490
- </esi:try>
491
- ```
492
-
493
- ---
494
-
495
- ## Render Entry Point (Server-seitig)
496
-
497
- Das Paket exportiert einen server-sicheren Subpath `@startnext/chrome/render` mit reinen String-Funktionen (keine Browser-Abhaengigkeiten):
498
-
499
- ```javascript
500
- import {
501
- renderHeader, renderBurgerItems, renderUserItems,
502
- renderFooter,
503
- headerCSS, footerCSS,
504
- toHeaderRenderData, toFooterRenderData,
505
- getUiString, getIcon,
506
- } from '@startnext/chrome/render';
507
- ```
508
-
509
- | Export | Beschreibung |
510
- |--------|-------------|
511
- | `renderHeader(props)` | Header Shadow DOM HTML |
512
- | `renderBurgerItems(data, expandedSections)` | Burger-Menue Items HTML |
513
- | `renderUserItems(data, expandedSections)` | User-Menue Items HTML |
514
- | `renderFooter(data)` | Footer Shadow DOM HTML |
515
- | `headerCSS`, `footerCSS`, `resetCSS` | CSS als Strings |
516
- | `getIcon(name, size?)` | SVG-Icon als String |
517
- | `getUiString(key, lang)` | UI-String (ARIA Labels etc.) |
518
- | `toHeaderRenderData(apiData)` | API-Daten -> HeaderData (fuegt stub `theme` hinzu) |
519
- | `toFooterRenderData(apiData)` | API-Daten -> FooterData (fuegt stub `theme` hinzu) |
520
- | `renderHeaderCrawlerNav(data)` | Crawler-`<nav>` fuer Header (optional, fuer Client-Side Rendering) |
521
- | `renderFooterCrawlerNav(data)` | Crawler-`<nav>` fuer Footer (optional, fuer Client-Side Rendering) |
522
-
523
172
  ## Browser Support
524
173
 
525
- - Chrome/Edge 90+ (Declarative Shadow DOM: Chrome 90+, Firefox 123+, Safari 16.4+)
526
- - Firefox 88+ (123+ fuer SSR/Hydration)
527
- - Safari 14+ (16.4+ fuer SSR/Hydration)
174
+ - Chrome/Edge 90+
175
+ - Firefox 88+
176
+ - Safari 14+
528
177
 
529
178
  ## Development
530
179
 
@@ -542,7 +191,7 @@ pnpm --filter @startnext/chrome build
542
191
  pnpm --filter @startnext/chrome lint
543
192
  ```
544
193
 
545
- Demo-Seite oeffnen: `demo/index.html`
194
+ Demo-Seite öffnen: `packages/sn-chrome-web-component/examples/vanilla/index.html`
546
195
 
547
196
  ## Architecture
548
197
 
@@ -552,41 +201,36 @@ Jede Component ist modular in drei Dateien aufgeteilt:
552
201
  src/
553
202
  ├── components/
554
203
  │ ├── base/
555
- │ │ ├── BaseComponent.ts # Abstrakte Basis (Shadow DOM, DSD-Hydration, Events, Theming)
204
+ │ │ ├── BaseComponent.ts # Abstrakte Basis (Shadow DOM, Events, Theming)
556
205
  │ │ ├── icons.ts # SVG-Icon-Registry
557
206
  │ │ └── styles.ts # Shared Reset-CSS
558
207
  │ ├── header/
559
- │ │ ├── StartnextHeader.ts # Logik (Scroll, Drawers, Lottie, Events, Hydration)
208
+ │ │ ├── StartnextHeader.ts # Logik (Scroll, Drawers, Lottie, Events)
560
209
  │ │ ├── header.css.ts # CSS als Template-Literal-Export
561
210
  │ │ └── header.template.ts # Render-Funktionen (HTML-Strings)
562
211
  │ └── footer/
563
- │ ├── StartnextFooter.ts # Logik (Render, Events, Hydration)
212
+ │ ├── StartnextFooter.ts # Logik (Render, Events)
564
213
  │ ├── footer.css.ts # CSS als Template-Literal-Export
565
214
  │ └── footer.template.ts # Render-Funktion (HTML-String)
566
215
  ├── data/
567
- ├── mockData.ts # Mock-Daten
568
- │ └── uiStrings.ts # UI-Strings (ARIA Labels, de/en)
216
+ └── mockData.ts # Mock-Daten
569
217
  ├── types/
570
218
  │ └── index.ts # Alle TypeScript-Interfaces
571
- ├── index.ts # Browser-Entry (Custom Elements registrieren)
572
- └── render.ts # Server-Entry (reine String-Funktionen, kein Browser)
219
+ └── index.ts # Barrel-Export
573
220
  ```
574
221
 
575
222
  **Konventionen:**
576
- - CSS in `*.css.ts` als exportierter Template-Literal-String (kein extra Build-Plugin noetig)
577
- - Templates in `*.template.ts` als reine Funktionen die HTML-Strings zurueckgeben (server-safe)
578
- - Logik in der Hauptdatei: Event-Handling, State, Lifecycle, Hydration
579
- - BEM-Naming fuer CSS-Klassen (`headbar__left`, `drawer__item--highlighted`)
580
- - Shadow DOM (open mode), kein Virtual DOM, Declarative Shadow DOM fuer SSR
581
-
582
- **Zwei Entry Points:**
583
- - `src/index.ts` -> `dist/index.js` (ESM) + `dist/index.umd.js` (UMD) — Browser, registriert Custom Elements
584
- - `src/render.ts` -> `dist/render.js` (ESM) — Server, reine String-Funktionen ohne Browser-Abhaengigkeiten
223
+ - CSS in `*.css.ts` als exportierter Template-Literal-String (kein extra Build-Plugin nötig)
224
+ - Templates in `*.template.ts` als reine Funktionen die HTML-Strings zurückgeben
225
+ - Logik in der Hauptdatei: Event-Handling, State, Lifecycle
226
+ - BEM-Naming für CSS-Klassen (`headbar__left`, `drawer__item--highlighted`)
227
+ - Shadow DOM (open mode), kein Virtual DOM
585
228
 
586
229
  ## Monorepo Structure
587
230
 
588
231
  ```
589
232
  packages/
590
- scs-web-component/ <- dieses Paket
591
- scs-api/ <- Express API Service (nutzt @startnext/chrome/render fuer SSR)
233
+ scs-web-component/ this package
234
+ scs-api/ Express API service
592
235
  ```
236
+
@@ -5,8 +5,6 @@ import type { ThemeVariables } from '../../types/index.js';
5
5
  */
6
6
  export declare abstract class BaseComponent extends HTMLElement {
7
7
  protected shadow: ShadowRoot;
8
- /** True when Declarative Shadow DOM was present — first render should hydrate, not replace. */
9
- protected isHydrating: boolean;
10
8
  constructor();
11
9
  /** Emit a typed CustomEvent that bubbles and is composed (crosses shadow DOM). */
12
10
  protected emit<T>(name: string, detail: T): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"BaseComponent.d.ts","sourceRoot":"","sources":["../../../src/components/base/BaseComponent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;GAGG;AACH,8BAAsB,aAAc,SAAQ,WAAW;IACrD,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC;IAE7B,+FAA+F;IAC/F,SAAS,CAAC,WAAW,EAAE,OAAO,CAAC;;IAU/B,kFAAkF;IAClF,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO;IAWnD,mEAAmE;IACnE,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,OAAO;IAM9D,6DAA6D;IAC7D,SAAS,CAAC,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAMjD,kDAAkD;IAClD,SAAS,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB;CAKrD"}
1
+ {"version":3,"file":"BaseComponent.d.ts","sourceRoot":"","sources":["../../../src/components/base/BaseComponent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;GAGG;AACH,8BAAsB,aAAc,SAAQ,WAAW;IACrD,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC;;IAO7B,kFAAkF;IAClF,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO;IAWnD,mEAAmE;IACnE,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,OAAO;IAM9D,6DAA6D;IAC7D,SAAS,CAAC,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAMjD,kDAAkD;IAClD,SAAS,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB;CAKrD"}
@@ -4,6 +4,8 @@ export declare class StartnextFooter extends BaseComponent {
4
4
  private data;
5
5
  /** Cached API data per language. */
6
6
  private apiCache;
7
+ /** Languages currently being fetched — prevents duplicate in-flight requests. */
8
+ private fetchingLangs;
7
9
  /** Bound handler for document-level language-change events. */
8
10
  private _langSyncHandler;
9
11
  private get currentLang();
@@ -15,9 +17,6 @@ export declare class StartnextFooter extends BaseComponent {
15
17
  private teardownLangSync;
16
18
  /** Return cached API data if available, otherwise defaults (theme only). */
17
19
  private getFooterData;
18
- /** Parse SSR-embedded data to populate the API cache during hydration.
19
- * Eliminates the need for a client-side fetch when SSR HTML is used. */
20
- private parseSsrProps;
21
20
  /** If api-url is set, fetch footer data from API and re-render. */
22
21
  private loadApiData;
23
22
  private renderLightDomNav;
@@ -1 +1 @@
1
- {"version":3,"file":"StartnextFooter.d.ts","sourceRoot":"","sources":["../../../src/components/footer/StartnextFooter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAMzD,qBAAa,eAAgB,SAAQ,aAAa;IAChD,MAAM,CAAC,kBAAkB,WAAoC;IAE7D,OAAO,CAAC,IAAI,CAAc;IAC1B,oCAAoC;IACpC,OAAO,CAAC,QAAQ,CAAiC;IACjD,+DAA+D;IAC/D,OAAO,CAAC,gBAAgB,CAAqC;IAE7D,OAAO,KAAK,WAAW,GAEtB;IAED,OAAO,KAAK,eAAe,GAE1B;IAED,iBAAiB,IAAI,IAAI;IAiBzB,oBAAoB,IAAI,IAAI;IAK5B,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAe5C,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,gBAAgB;IAOxB,4EAA4E;IAC5E,OAAO,CAAC,aAAa;IAMrB;6EACyE;IACzE,OAAO,CAAC,aAAa;IAWrB,mEAAmE;YACrD,WAAW;IAuBzB,OAAO,CAAC,iBAAiB;IAiCzB,OAAO,CAAC,MAAM;IAkBd,OAAO,CAAC,YAAY;CAUrB"}
1
+ {"version":3,"file":"StartnextFooter.d.ts","sourceRoot":"","sources":["../../../src/components/footer/StartnextFooter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAMzD,qBAAa,eAAgB,SAAQ,aAAa;IAChD,MAAM,CAAC,kBAAkB,WAAoC;IAE7D,OAAO,CAAC,IAAI,CAAc;IAC1B,oCAAoC;IACpC,OAAO,CAAC,QAAQ,CAAiC;IACjD,iFAAiF;IACjF,OAAO,CAAC,aAAa,CAAqB;IAC1C,+DAA+D;IAC/D,OAAO,CAAC,gBAAgB,CAAqC;IAE7D,OAAO,KAAK,WAAW,GAEtB;IAED,OAAO,KAAK,eAAe,GAE1B;IAED,iBAAiB,IAAI,IAAI;IASzB,oBAAoB,IAAI,IAAI;IAK5B,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAe5C,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,gBAAgB;IAOxB,4EAA4E;IAC5E,OAAO,CAAC,aAAa;IAMrB,mEAAmE;YACrD,WAAW;IA2BzB,OAAO,CAAC,iBAAiB;IAiCzB,OAAO,CAAC,MAAM;IAWd,OAAO,CAAC,YAAY;CAUrB"}
@@ -12,6 +12,8 @@ export declare class StartnextHeader extends BaseComponent {
12
12
  private pendingRaf;
13
13
  /** Cached API data per language, so we don't re-fetch on every re-render. */
14
14
  private apiCache;
15
+ /** Languages currently being fetched — prevents duplicate in-flight requests. */
16
+ private fetchingLangs;
15
17
  private handleScrollBound;
16
18
  private handleKeydownBound;
17
19
  private handleResizeBound;
@@ -19,15 +21,13 @@ export declare class StartnextHeader extends BaseComponent {
19
21
  static get observedAttributes(): string[];
20
22
  private _skipCallback;
21
23
  private get currentLang();
24
+ private get requestedLanguages();
22
25
  /** True when header should show white text (dark page / hero background). */
23
26
  private get isLightHeader();
24
27
  private ui;
25
28
  connectedCallback(): void;
26
29
  /** Return cached API data if available, otherwise defaults (theme only). */
27
30
  private getHeaderData;
28
- /** Parse SSR-embedded data to populate the API cache during hydration.
29
- * Eliminates the need for a client-side fetch when SSR HTML is used. */
30
- private parseSsrProps;
31
31
  /** If api-url is set, fetch header data from API and re-render. */
32
32
  private loadApiData;
33
33
  disconnectedCallback(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"StartnextHeader.d.ts","sourceRoot":"","sources":["../../../src/components/header/StartnextHeader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAWzD,qBAAa,eAAgB,SAAQ,aAAa;IAChD,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,UAAU,CAA8B;IAChD,OAAO,CAAC,UAAU,CAAuB;IACzC,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,CAAiC;IAEjD,OAAO,CAAC,iBAAiB,CAAgC;IACzD,OAAO,CAAC,kBAAkB,CAAiC;IAC3D,OAAO,CAAC,iBAAiB,CAAgC;IACzD,OAAO,CAAC,mBAAmB,CAAkC;IAE7D,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,KAAK,WAAW,GAEtB;IAED,6EAA6E;IAC7E,OAAO,KAAK,aAAa,GAGxB;IAED,OAAO,CAAC,EAAE;IAIV,iBAAiB,IAAI,IAAI;IAuBzB,4EAA4E;IAC5E,OAAO,CAAC,aAAa;IAMrB;6EACyE;IACzE,OAAO,CAAC,aAAa;IAWrB,mEAAmE;YACrD,WAAW;IA6BzB,oBAAoB,IAAI,IAAI;IAU5B,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAa5C,gEAAgE;IAChE,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,UAAU;IA0BlB,OAAO,CAAC,YAAY;IAgDpB,OAAO,CAAC,mBAAmB;IAoC3B,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,oBAAoB;IAK5B,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,iBAAiB;IAsDzB,OAAO,CAAC,MAAM;IAoCd,OAAO,CAAC,YAAY;CAkErB"}
1
+ {"version":3,"file":"StartnextHeader.d.ts","sourceRoot":"","sources":["../../../src/components/header/StartnextHeader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAWzD,qBAAa,eAAgB,SAAQ,aAAa;IAChD,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,UAAU,CAA8B;IAChD,OAAO,CAAC,UAAU,CAAuB;IACzC,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,CAAiC;IACjD,iFAAiF;IACjF,OAAO,CAAC,aAAa,CAAqB;IAE1C,OAAO,CAAC,iBAAiB,CAAgC;IACzD,OAAO,CAAC,kBAAkB,CAAiC;IAC3D,OAAO,CAAC,iBAAiB,CAAgC;IACzD,OAAO,CAAC,mBAAmB,CAAkC;IAE7D,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAExC;IAED,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,KAAK,WAAW,GAEtB;IAED,OAAO,KAAK,kBAAkB,GAI7B;IAED,6EAA6E;IAC7E,OAAO,KAAK,aAAa,GAGxB;IAED,OAAO,CAAC,EAAE;IAIV,iBAAiB,IAAI,IAAI;IAgBzB,4EAA4E;IAC5E,OAAO,CAAC,aAAa;IAMrB,mEAAmE;YACrD,WAAW;IAiCzB,oBAAoB,IAAI,IAAI;IAU5B,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAa5C,gEAAgE;IAChE,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,UAAU;IA0BlB,OAAO,CAAC,YAAY;IAgDpB,OAAO,CAAC,mBAAmB;IAoC3B,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,iBAAiB;IAsDzB,OAAO,CAAC,MAAM;IAsCd,OAAO,CAAC,YAAY;CAkErB"}