weg-shared-layout 0.0.4 → 0.0.5

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.
@@ -23,7 +23,7 @@ const MyComponent = class {
23
23
  return format(this.first, this.middle, this.last);
24
24
  }
25
25
  render() {
26
- return h("div", { key: '71d8054165e44634586b27a0dd84f07f964d49b1' }, "Hello, world! I'm ", this.getText());
26
+ return h("div", { key: '5452c3b9c97f8879dbc26b788d1fa79409846ff0' }, "Hello, world! I'm ", this.getText());
27
27
  }
28
28
  };
29
29
  MyComponent.style = myComponentCss();
@@ -154,7 +154,7 @@ const WegFooter = class {
154
154
  return (h("div", { class: "standard__links" }, links.map((l) => (h("a", { class: "footer-link", href: l.href }, l.label)))));
155
155
  }
156
156
  render() {
157
- return (h("footer", { key: 'a19bba66844d21e3d08af47969bd213df22b03b5', class: "footer" }, h("div", { key: 'bb7ec7ce4c7051124989751fd09e3b7343da4da5', class: "container" }, this.renderSocialLinks(), h("div", { key: 'd1dd8e00c12dbe28daf127d2555d0e8def36b9e4', class: "standard" }, this.renderStandardLinks(), this.renderLegalText()))));
157
+ return (h("footer", { key: 'b5440fe948f8a4207b20d8e380ab50f559890440', class: "footer" }, h("div", { key: '2f4757c3a52203a89b53fa358513d832f153bfa2', class: "container" }, this.renderSocialLinks(), h("div", { key: '4ee53f39a16dab3739c70dadcaadb15602f08ce9', class: "standard" }, this.renderStandardLinks(), this.renderLegalText()))));
158
158
  }
159
159
  static get watchers() { return {
160
160
  "data": [{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weg-shared-layout",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Shared layout Web Components built with Stencil",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.js",
@@ -25,7 +25,8 @@
25
25
  "import": "./loader/index.js",
26
26
  "require": "./loader/index.cjs",
27
27
  "types": "./loader/index.d.ts"
28
- }
28
+ },
29
+ "./dummy-data.json": "./src/assets/dummy-data.json"
29
30
  },
30
31
  "repository": {
31
32
  "type": "git",
@@ -37,7 +38,8 @@
37
38
  "homepage": "https://github.com/jobsac/weg-shared-layout#readme",
38
39
  "files": [
39
40
  "dist/",
40
- "loader/"
41
+ "loader/",
42
+ "src/assets/dummy-data.json"
41
43
  ],
42
44
  "scripts": {
43
45
  "build": "stencil build",
package/readme.md CHANGED
@@ -12,62 +12,28 @@ npm i weg-shared-layout
12
12
 
13
13
  ## How it works
14
14
 
15
- `<weg-footer>` is a **dumb / presentational** Web Component. It does **not** fetch data itself. Your host app calls the layout API, then passes the resulting object into the component via the `data` prop.
15
+ `<weg-footer>` is a **presentational** Web Component: it does **not** fetch data. Your app loads layout data however you like (REST, GraphQL, SSR, etc.), then passes the object into the component via the **`data` property** (not a string HTML attribute).
16
16
 
17
- This keeps the component framework-agnostic, lets you share the same response with `<weg-header>` (coming soon), and means HTTP concerns like auth, caching, retries, SSR, and error handling live in your app — where they belong.
17
+ The payload shape matches the sample file **`dummy-data.json`**, shipped with the package:
18
18
 
19
- ## Layout API
19
+ - **Import:** `import layout from 'weg-shared-layout/dummy-data.json'` (enable `resolveJsonModule` in TypeScript if needed).
20
+ - **In this repo:** [`src/assets/dummy-data.json`](src/assets/dummy-data.json)
20
21
 
21
- Call this endpoint from your host app to retrieve the data:
22
-
23
- ```
24
- GET https://weg-payload-test.vercel.app/api/layout
25
- ```
26
-
27
- Response shape:
28
-
29
- ```json
30
- {
31
- "header": {},
32
- "footer": {
33
- "social": [
34
- { "platform": "LinkedIn", "href": "https://www.linkedin.com/" },
35
- { "platform": "Instagram", "href": "https://www.instagram.com/" },
36
- { "platform": "TikTok", "href": "https://www.tiktok.com/" },
37
- { "platform": "YouTube", "href": "https://www.youtube.com/" }
38
- ],
39
- "standardLinks": [
40
- { "label": "About Us", "href": "/about" },
41
- { "label": "Privacy Policy", "href": "/privacy" },
42
- { "label": "Terms of Use", "href": "/terms" },
43
- { "label": "Cookie Policy", "href": "/cookies" },
44
- { "label": "Accessibility Statement", "href": "/accessibility" }
45
- ],
46
- "credits": "Warwick Employment Group is a department of the Campus and Commercial Services Group at the University of Warwick.",
47
- "copyright": "Copyright © Warwick Employment Group."
48
- }
49
- }
50
- ```
51
-
52
- Only `social.platform` values of `LinkedIn`, `Instagram`, `TikTok`, and `YouTube` render an icon. Items with missing/invalid fields are silently dropped.
22
+ Use that JSON as fixture data to see the footer working, or as a reference for your own API responses. Only `social.platform` values `LinkedIn`, `Instagram`, `TikTok`, and `YouTube` render an icon; items with missing or invalid fields are dropped.
53
23
 
54
24
  ## Using in Angular
55
25
 
56
- This guide assumes a modern Angular project (v17+, **standalone components**) — the default for `ng new`. There's an `NgModule` section further down for older apps.
26
+ Assumes Angular 17+ with **standalone** components (default for `ng new`).
57
27
 
58
- ### 1. Install the package
28
+ ### 1. Install
59
29
 
60
30
  ```bash
61
31
  npm i weg-shared-layout
62
- # or: pnpm add weg-shared-layout
63
- # or: yarn add weg-shared-layout
64
32
  ```
65
33
 
66
- ### 2. Register the custom elements (once, at startup)
67
-
68
- Stencil ships a loader that calls `customElements.define()` for every component in the package. Call it **once** before Angular bootstraps your app, otherwise the browser sees `<weg-footer>` as an unknown element and renders nothing.
34
+ ### 2. Register custom elements (once, before bootstrap)
69
35
 
70
- Edit `src/main.ts`:
36
+ In `src/main.ts`, call `defineCustomElements()` **before** `bootstrapApplication` so the browser recognises `<weg-footer>`.
71
37
 
72
38
  ```ts
73
39
  import { bootstrapApplication } from '@angular/platform-browser';
@@ -76,50 +42,26 @@ import { defineCustomElements } from 'weg-shared-layout/loader';
76
42
  import { appConfig } from './app/app.config';
77
43
  import { App } from './app/app';
78
44
 
79
- // MUST run before bootstrapApplication so the browser recognises <weg-footer>
80
- // by the time Angular's renderer touches the DOM.
81
45
  defineCustomElements();
82
46
 
83
47
  bootstrapApplication(App, appConfig).catch((err) => console.error(err));
84
48
  ```
85
49
 
86
- > If you don't see your footer in the page, 9 times out of 10 it's because this step was skipped. Verify by typing `customElements.get('weg-footer')` in the browser DevTools console — it should return a class, not `undefined`.
50
+ ### 3. Allow custom elements in templates
87
51
 
88
- ### 3. Provide `HttpClient`
52
+ Add `schemas: [CUSTOM_ELEMENTS_SCHEMA]` to every `@Component` whose template uses `<weg-footer>` (it does not cascade from the root through `router-outlet` children).
89
53
 
90
- You need `HttpClient` to call the layout API. In `src/app/app.config.ts`:
54
+ ### 4. Pass data with property binding
91
55
 
92
- ```ts
93
- import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
94
- import { provideHttpClient } from '@angular/common/http';
95
- import { provideRouter } from '@angular/router';
96
-
97
- import { routes } from './app.routes';
98
-
99
- export const appConfig: ApplicationConfig = {
100
- providers: [
101
- provideBrowserGlobalErrorListeners(),
102
- provideRouter(routes),
103
- provideHttpClient(),
104
- ],
105
- };
106
- ```
56
+ Use **`[data]="..."`** so Angular sets the element’s JavaScript `data` property (Stencil `@Prop()`), not an HTML attribute.
107
57
 
108
- ### 4. Tell Angular to allow unknown elements
109
-
110
- Without this, Angular's template compiler throws:
111
-
112
- > `'weg-footer' is not a known element: ... If 'weg-footer' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@Component.schemas' of this component to suppress this message.`
113
-
114
- In **standalone components** (default in Angular 17+), add `schemas: [CUSTOM_ELEMENTS_SCHEMA]` to the `@Component` decorator of **every component that uses `<weg-footer>` in its template**. Easiest is to add it to your root `App` component:
58
+ Example with the bundled sample payload:
115
59
 
116
60
  ```ts
117
61
  // src/app/app.ts
118
- import { Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit, signal } from '@angular/core';
119
- import { HttpClient } from '@angular/common/http';
62
+ import { Component, CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';
120
63
  import { RouterOutlet } from '@angular/router';
121
-
122
- const LAYOUT_API = 'https://weg-payload-test.vercel.app/api/layout';
64
+ import layoutFixture from 'weg-shared-layout/dummy-data.json';
123
65
 
124
66
  @Component({
125
67
  selector: 'app-root',
@@ -128,201 +70,87 @@ const LAYOUT_API = 'https://weg-payload-test.vercel.app/api/layout';
128
70
  templateUrl: './app.html',
129
71
  styleUrl: './app.css',
130
72
  })
131
- export class App implements OnInit {
132
- protected readonly layoutData = signal<unknown>(null);
133
-
134
- private readonly http = inject(HttpClient);
135
-
136
- ngOnInit(): void {
137
- this.http.get(LAYOUT_API).subscribe({
138
- next: (data) => this.layoutData.set(data),
139
- error: (err) => console.error('Failed to load layout data', err),
140
- });
141
- }
73
+ export class App {
74
+ protected readonly layoutData = signal(layoutFixture);
142
75
  }
143
76
  ```
144
77
 
145
- > `CUSTOM_ELEMENTS_SCHEMA` is **per-component** in standalone Angular. If you use `<weg-footer>` inside a child component's template, that child must declare `schemas: [CUSTOM_ELEMENTS_SCHEMA]` too. It does **not** cascade through `<router-outlet />`.
146
-
147
- ### 5. Pass the data into `<weg-footer>` via the `[data]` binding
148
-
149
- In `src/app/app.html`:
150
-
151
78
  ```html
79
+ <!-- src/app/app.html -->
152
80
  <router-outlet />
153
-
154
81
  <weg-footer [data]="layoutData()"></weg-footer>
155
82
  ```
156
83
 
157
- That's the whole integration. `<weg-footer>` watches its `data` prop and re-renders when the signal value changes (e.g. when the HTTP response arrives), so an initial `null` is fine — the footer will simply render empty until the data lands.
158
-
159
- #### Why `[data]` and not `data-src` or `[attr.data]`
160
-
161
- - `[data]="..."` is Angular's **property binding** — it sets the JS property `data` on the underlying DOM element. Stencil exposes `@Prop()` values as JS properties, so this passes the object straight through. **This is what you want.**
162
- - `[attr.data]="..."` would set an HTML attribute, which is always a string — Angular would call `JSON.stringify(layoutData)` for you, the component would have to parse it back, and you'd lose type fidelity. Avoid unless you have a reason.
163
- - Plain `data="..."` only works for a static string literal and would have the same parsing cost — fine for testing, not for real data.
164
-
165
- ### 6. Verify it works
166
-
167
- After running `ng serve`, open the page and check:
168
-
169
- 1. The dark footer bar renders at the bottom of the page.
170
- 2. DevTools → Network tab shows a `200 OK` to `https://weg-payload-test.vercel.app/api/layout`.
171
- 3. DevTools → Elements → click `<weg-footer>` → in the Console, type `$0.data` — you should see the JSON object you fetched.
172
- 4. Expand `<weg-footer>` in the Elements panel — you should see a `#shadow-root (open)` containing the actual `<footer>` markup.
84
+ In production, replace `layoutFixture` with data from your own services; keep the same object shape as `dummy-data.json`.
173
85
 
174
86
  ### Troubleshooting
175
87
 
176
88
  | Symptom | Cause / fix |
177
89
  | --- | --- |
178
- | `'weg-footer' is not a known element` build error | Add `schemas: [CUSTOM_ELEMENTS_SCHEMA]` to the `@Component` decorator of the component whose template uses `<weg-footer>`. |
179
- | Element renders as plain inline text / empty box | `defineCustomElements()` was never called. Add it to `main.ts` **before** `bootstrapApplication`. |
180
- | Footer is in the DOM but blank | The HTTP request failed or returned the wrong shape. Check DevTools → Network → confirm the response matches the [API shape](#layout-api). Also inspect `$0.data` in the console to confirm the binding made it onto the element. |
181
- | `NullInjectorError: No provider for HttpClient` | You forgot `provideHttpClient()` in `app.config.ts`. |
182
- | CORS error in the console | The API host must allow your origin. Either fix CORS on the backend, or proxy through Angular's dev proxy (`proxy.conf.json`) so the call becomes same-origin. |
183
- | Footer doesn't update after data changes | Make sure you're using `[data]="layoutData()"` with a signal (or `[data]="layoutData$ | async"` with an Observable) so Angular pushes new values into the element. |
184
- | Works locally, fails in SSR (`document is not defined`) | `defineCustomElements()` touches `window` / `customElements`. Wrap in `if (typeof window !== 'undefined') { ... }` or use `isPlatformBrowser(platformId)` in `main.ts`. |
185
- | TypeScript: `Property 'weg-footer' does not exist on type 'HTMLElementTagNameMap'` | See the typings section below. |
186
-
187
- ### TypeScript typings (Angular)
90
+ | `'weg-footer' is not a known element` | Add `schemas: [CUSTOM_ELEMENTS_SCHEMA]` on the component whose template contains `<weg-footer>`. |
91
+ | Footer missing or empty box | `defineCustomElements()` not called before bootstrap, or `data` not set / wrong shape — compare with `dummy-data.json`. |
92
+ | SSR: `document is not defined` | Guard `defineCustomElements()` with `typeof window !== 'undefined'` or `isPlatformBrowser`. |
188
93
 
189
- If your editor / TS service complains about the unknown element or the `[data]` binding, add this to `src/global.d.ts` (creating the file if needed) and make sure it's included by `tsconfig.json`:
94
+ ### TypeScript typings
190
95
 
191
96
  ```ts
192
97
  /// <reference types="weg-shared-layout/dist/types/components" />
193
98
  ```
194
99
 
195
- ### Legacy: using with `NgModule`
100
+ ### Legacy `NgModule`
196
101
 
197
- If your app is still using `NgModule` (pre-Angular 17 default), add `CUSTOM_ELEMENTS_SCHEMA` once at the module level instead of per component:
198
-
199
- ```ts
200
- // src/app/app.module.ts
201
- import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
202
- import { HttpClientModule } from '@angular/common/http';
203
-
204
- @NgModule({
205
- imports: [HttpClientModule /* ... */],
206
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
207
- })
208
- export class AppModule {}
209
- ```
210
-
211
- `defineCustomElements()` in `main.ts` is still required either way. Inject `HttpClient` in your component the same way as the standalone example above.
102
+ Add `CUSTOM_ELEMENTS_SCHEMA` once on the module that declares components using `<weg-footer>`. `defineCustomElements()` in `main.ts` is still required.
212
103
 
213
104
  ## Using in React
214
105
 
215
- Install:
216
-
217
- ```bash
218
- npm i weg-shared-layout
219
- ```
220
-
221
- Register custom elements (pick one):
106
+ Register once (for example in your app entry):
222
107
 
223
108
  ```ts
224
- // Option A (recommended): loader registers all components
225
109
  import { defineCustomElements } from 'weg-shared-layout/loader';
226
110
 
227
111
  defineCustomElements();
228
112
  ```
229
113
 
230
- Or:
231
-
232
- ```ts
233
- // Option B: import package bundle (also registers, depending on output target configuration)
234
- import 'weg-shared-layout';
235
- ```
236
-
237
- Then fetch the data in your component and pass it in via a ref (because the `data` prop is an object, not a string attribute):
238
-
239
- ```tsx
240
- import { useEffect, useRef, useState } from 'react';
241
- import 'weg-shared-layout/weg-footer';
242
-
243
- const LAYOUT_API = 'https://weg-payload-test.vercel.app/api/layout';
244
-
245
- export function SiteFooter() {
246
- const ref = useRef<HTMLElement>(null);
247
- const [layout, setLayout] = useState<unknown>(null);
248
-
249
- useEffect(() => {
250
- fetch(LAYOUT_API).then((r) => r.json()).then(setLayout);
251
- }, []);
252
-
253
- useEffect(() => {
254
- if (ref.current) (ref.current as any).data = layout;
255
- }, [layout]);
256
-
257
- return <weg-footer ref={ref} />;
258
- }
259
- ```
260
-
261
- > React (pre-19) sets unknown JSX attributes as HTML attributes, which would stringify your object. Assigning via a ref guarantees you set the JS property. React 19+ handles this correctly without the ref dance.
262
-
263
- ### Next.js (App Router) note
264
-
265
- Stencil custom elements must be registered **in the browser**. In Next.js `app/` (App Router), don't import/register Stencil components from a Server Component.
266
-
267
- Instead, register them in a Client Component, for example:
114
+ Pass an object via a **ref** (or use React 19+ property handling) so you set the DOM property, not a string attribute:
268
115
 
269
116
  ```tsx
270
- // app/components/SiteFooter.tsx
271
- "use client";
272
-
273
117
  import { useEffect, useRef, useState } from 'react';
274
118
  import 'weg-shared-layout/weg-footer';
119
+ import layoutFixture from 'weg-shared-layout/dummy-data.json';
275
120
 
276
121
  export function SiteFooter() {
277
- const ref = useRef<HTMLElement>(null);
278
- const [layout, setLayout] = useState<unknown>(null);
122
+ const ref = useRef<HTMLElement & { data?: unknown }>(null);
123
+ const [layout, setLayout] = useState(layoutFixture);
279
124
 
280
125
  useEffect(() => {
281
- fetch('/api/layout').then((r) => r.json()).then(setLayout);
282
- }, []);
283
-
284
- useEffect(() => {
285
- if (ref.current) (ref.current as any).data = layout;
126
+ if (ref.current) ref.current.data = layout;
286
127
  }, [layout]);
287
128
 
288
129
  return <weg-footer ref={ref} />;
289
130
  }
290
131
  ```
291
132
 
292
- (You can also fetch in a Server Component and pass `layout` down as a prop to the Client Component but the actual `data` assignment still has to happen in the browser.)
293
-
294
- ### React TypeScript typings
133
+ Swap `layoutFixture` / `setLayout` for your own data loading. **Next.js App Router:** register and assign `data` only in a Client Component (`"use client"`).
295
134
 
296
- Stencil generates `components.d.ts`, but React doesn't pick up custom element typings automatically. Add a `global.d.ts` that references the generated types:
135
+ ### React TypeScript
297
136
 
298
137
  ```ts
299
138
  /// <reference types="weg-shared-layout/dist/types/components" />
300
139
  ```
301
140
 
302
- If your app still complains about JSX intrinsic elements, you can also augment `JSX.IntrinsicElements` in that same file (varies by React/TS setup).
303
-
304
141
  ## Plain HTML / vanilla JS
305
142
 
306
- For a quick smoke test outside of a framework, fetch the JSON and assign it to the element's `data` property:
143
+ With a bundler that resolves `node_modules` imports:
307
144
 
308
145
  ```html
309
146
  <weg-footer id="footer"></weg-footer>
310
-
311
147
  <script type="module">
312
- const res = await fetch('https://weg-payload-test.vercel.app/api/layout');
313
- document.getElementById('footer').data = await res.json();
314
- </script>
315
- ```
316
-
317
- You can also pass the JSON as a string attribute — useful for SSR or static HTML where you don't want a runtime fetch:
148
+ import { defineCustomElements } from 'weg-shared-layout/loader';
149
+ import layout from 'weg-shared-layout/dummy-data.json';
318
150
 
319
- ```html
320
- <weg-footer data='{"footer":{"social":[],"standardLinks":[],"credits":"","copyright":"Copyright © WEG."}}'></weg-footer>
151
+ defineCustomElements();
152
+ document.getElementById('footer').data = layout;
153
+ </script>
321
154
  ```
322
155
 
323
- ## CORS
324
-
325
- Because the host app calls the API directly, **the API must respond with `Access-Control-Allow-Origin` headers** that include your app's origin. If you hit a CORS error in the browser console, either:
326
-
327
- - Fix CORS on the API host, **or**
328
- - Proxy the call through your own host. Angular: configure `proxy.conf.json` so `/api/layout` is rewritten to the production URL. Next.js: add a route handler that re-fetches and returns the JSON.
156
+ Otherwise, copy `dummy-data.json` to your static assets, `fetch` it, parse JSON, then assign **`element.data`**. You can also pass a JSON string on the **`data` attribute**; the component parses it the same way as an object `data` property.
@@ -0,0 +1,20 @@
1
+ {
2
+ "header": {},
3
+ "footer": {
4
+ "social": [
5
+ { "platform": "LinkedIn", "href": "https://www.linkedin.com/" },
6
+ { "platform": "Instagram", "href": "https://www.instagram.com/" },
7
+ { "platform": "TikTok", "href": "https://www.tiktok.com/" },
8
+ { "platform": "YouTube", "href": "https://www.youtube.com/" }
9
+ ],
10
+ "standardLinks": [
11
+ { "label": "About Us", "href": "/about" },
12
+ { "label": "Privacy Policy", "href": "/privacy" },
13
+ { "label": "Terms of Use", "href": "/terms" },
14
+ { "label": "Cookie Policy", "href": "/cookies" },
15
+ { "label": "Accessibility Statement", "href": "/accessibility" }
16
+ ],
17
+ "credits": "Warwick Employment Group is a department of the Campus and Commercial Services Group at the University of Warwick.",
18
+ "copyright": "Copyright © Warwick Employment Group."
19
+ }
20
+ }