@praxisjs/head 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @praxisjs/head
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2837664: Initial release of `@praxisjs/head` — reactive document head management via `@Head` class decorator. Supports title, description, canonical, arbitrary meta[], og:_, and twitter:_. Stack-based with automatic cleanup on component unmount.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [378cc54]
12
+ - @praxisjs/core@1.7.0
13
+ - @praxisjs/decorators@1.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present Mateus Martins
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=head.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"head.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/head.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,463 @@
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
27
+ };
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
34
+ };
35
+ // @vitest-environment jsdom
36
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
37
+ import { signal } from "@praxisjs/core/internal";
38
+ import { StatefulComponent } from "@praxisjs/core";
39
+ import { Head } from "../index";
40
+ import { pushHead, removeHead, _resetHead, headVersion } from "../head-stack";
41
+ beforeEach(() => {
42
+ document.head.innerHTML = "";
43
+ _resetHead();
44
+ document.title = "initial";
45
+ });
46
+ // ─── head-stack unit tests ────────────────────────────────────────────────────
47
+ describe("pushHead / removeHead", () => {
48
+ it("sets document.title", () => {
49
+ const id = Symbol();
50
+ pushHead(id, { title: "Hello" });
51
+ expect(document.title).toBe("Hello");
52
+ });
53
+ it("adds meta name=description", () => {
54
+ const id = Symbol();
55
+ pushHead(id, { description: "My desc" });
56
+ expect(document.querySelector('meta[name="description"]')?.content).toBe("My desc");
57
+ });
58
+ it("adds og: meta properties", () => {
59
+ const id = Symbol();
60
+ pushHead(id, { og: { title: "OG Title", image: "https://example.com/img.jpg" } });
61
+ expect(document.querySelector('[property="og:title"]')?.getAttribute("content")).toBe("OG Title");
62
+ expect(document.querySelector('[property="og:image"]')?.getAttribute("content")).toBe("https://example.com/img.jpg");
63
+ });
64
+ it("adds twitter: meta tags", () => {
65
+ const id = Symbol();
66
+ pushHead(id, { twitter: { card: "summary_large_image", title: "TW" } });
67
+ expect(document.querySelector('[name="twitter:card"]')?.getAttribute("content")).toBe("summary_large_image");
68
+ expect(document.querySelector('[name="twitter:title"]')?.getAttribute("content")).toBe("TW");
69
+ });
70
+ it("adds canonical link", () => {
71
+ const id = Symbol();
72
+ pushHead(id, { canonical: "https://example.com/page" });
73
+ expect(document.querySelector('link[rel="canonical"]')?.getAttribute("href")).toBe("https://example.com/page");
74
+ });
75
+ it("adds arbitrary meta[] by name and property", () => {
76
+ const id = Symbol();
77
+ pushHead(id, {
78
+ meta: [
79
+ { name: "robots", content: "noindex" },
80
+ { property: "fb:app_id", content: "12345" },
81
+ ],
82
+ });
83
+ expect(document.querySelector('[name="robots"]')?.getAttribute("content")).toBe("noindex");
84
+ expect(document.querySelector('[property="fb:app_id"]')?.getAttribute("content")).toBe("12345");
85
+ });
86
+ it("updates an existing entry (no duplicate push)", () => {
87
+ const id = Symbol();
88
+ pushHead(id, { title: "v1" });
89
+ pushHead(id, { title: "v2" });
90
+ expect(document.title).toBe("v2");
91
+ });
92
+ it("restores initial title and removes managed metas on removeHead", () => {
93
+ const id = Symbol();
94
+ pushHead(id, { title: "Page", description: "Desc" });
95
+ removeHead(id);
96
+ expect(document.title).toBe("initial");
97
+ expect(document.querySelector('meta[name="description"]')).toBeNull();
98
+ });
99
+ it("stack: last entry wins; removing it restores the previous one", () => {
100
+ const a = Symbol();
101
+ const b = Symbol();
102
+ pushHead(a, { title: "A", description: "desc-a" });
103
+ pushHead(b, { title: "B", description: "desc-b" });
104
+ expect(document.title).toBe("B");
105
+ removeHead(b);
106
+ expect(document.title).toBe("A");
107
+ expect(document.querySelector('meta[name="description"]')?.content).toBe("desc-a");
108
+ });
109
+ it("removes all managed metas when stack is empty", () => {
110
+ const id = Symbol();
111
+ pushHead(id, { description: "x", og: { title: "y" } });
112
+ removeHead(id);
113
+ expect(document.querySelectorAll("[data-praxis-head]").length).toBe(0);
114
+ });
115
+ it("removeHead with a non-existent id is a no-op (idx < 0 branch)", () => {
116
+ const id = Symbol("never-pushed");
117
+ // _apply() still runs but the stack stays empty
118
+ expect(() => removeHead(id)).not.toThrow();
119
+ expect(document.querySelectorAll("[data-praxis-head]").length).toBe(0);
120
+ });
121
+ it("_apply restores title to '' when _initialTitle is undefined and stack is empty", () => {
122
+ // _resetHead leaves _initialTitle = undefined; calling removeHead on a missing id
123
+ // triggers _apply with an empty stack and undefined _initialTitle → title = "" fallback.
124
+ const id = Symbol("missing");
125
+ document.title = "should-be-cleared";
126
+ removeHead(id); // idx < 0 → no splice; _apply runs → empty stack, _initialTitle = undefined
127
+ expect(document.title).toBe("");
128
+ });
129
+ it("meta tag with neither name nor property is silently skipped", () => {
130
+ const id = Symbol();
131
+ pushHead(id, { meta: [{ content: "orphan-content" }] });
132
+ // Neither _metaName nor _metaProp should be called
133
+ const all = document.querySelectorAll("[data-praxis-head]");
134
+ const hasOrphan = Array.from(all).some((el) => el.getAttribute("content") === "orphan-content");
135
+ expect(hasOrphan).toBe(false);
136
+ removeHead(id);
137
+ });
138
+ it("omits undefined og fields", () => {
139
+ const id = Symbol();
140
+ pushHead(id, { og: { title: "T" } });
141
+ expect(document.querySelector('[property="og:image"]')).toBeNull();
142
+ expect(document.querySelector('[property="og:title"]')?.getAttribute("content")).toBe("T");
143
+ });
144
+ });
145
+ // ─── SSR guards ───────────────────────────────────────────────────────────────
146
+ describe("SSR guards — no DOM operations when document is undefined", () => {
147
+ afterEach(() => {
148
+ vi.unstubAllGlobals();
149
+ });
150
+ it("pushHead is a no-op when document is undefined", () => {
151
+ const versionBefore = headVersion();
152
+ vi.stubGlobal("document", undefined);
153
+ const id = Symbol();
154
+ expect(() => pushHead(id, { title: "SSR" })).not.toThrow();
155
+ // headVersion must NOT have changed (function returned early)
156
+ expect(headVersion()).toBe(versionBefore);
157
+ });
158
+ it("removeHead is a no-op when document is undefined", () => {
159
+ vi.stubGlobal("document", undefined);
160
+ const id = Symbol();
161
+ expect(() => removeHead(id)).not.toThrow();
162
+ });
163
+ it("_resetHead skips DOM cleanup when document is undefined", () => {
164
+ vi.stubGlobal("document", undefined);
165
+ expect(() => _resetHead()).not.toThrow();
166
+ });
167
+ });
168
+ describe("_resetHead — DOM cleanup", () => {
169
+ it("removes managed elements from document.head when called with elements present", () => {
170
+ const id = Symbol();
171
+ pushHead(id, { title: "Before Reset", description: "desc" });
172
+ expect(document.querySelectorAll("[data-praxis-head]").length).toBeGreaterThan(0);
173
+ // Call _resetHead WITHOUT clearing head.innerHTML first — exercises the forEach
174
+ _resetHead();
175
+ expect(document.querySelectorAll("[data-praxis-head]").length).toBe(0);
176
+ });
177
+ });
178
+ // ─── @Head decorator ──────────────────────────────────────────────────────────
179
+ // All decorated classes at module scope for correct TypeScript inference.
180
+ let StaticComp = (() => {
181
+ let _classDecorators = [Head({ title: "Static Title", description: "Static desc" })];
182
+ let _classDescriptor;
183
+ let _classExtraInitializers = [];
184
+ let _classThis;
185
+ let _classSuper = StatefulComponent;
186
+ var StaticComp = class extends _classSuper {
187
+ static { _classThis = this; }
188
+ static {
189
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
190
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
191
+ StaticComp = _classThis = _classDescriptor.value;
192
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
193
+ }
194
+ static { this.__isComponent = true; }
195
+ static { this.__isStateless = false; }
196
+ render() { return null; }
197
+ static {
198
+ __runInitializers(_classThis, _classExtraInitializers);
199
+ }
200
+ };
201
+ return StaticComp = _classThis;
202
+ })();
203
+ let BeforeComp = (() => {
204
+ let _classDecorators = [Head({ title: "Before" })];
205
+ let _classDescriptor;
206
+ let _classExtraInitializers = [];
207
+ let _classThis;
208
+ let _classSuper = StatefulComponent;
209
+ var BeforeComp = class extends _classSuper {
210
+ static { _classThis = this; }
211
+ static {
212
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
213
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
214
+ BeforeComp = _classThis = _classDescriptor.value;
215
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
216
+ }
217
+ static { this.__isComponent = true; }
218
+ static { this.__isStateless = false; }
219
+ render() { return null; }
220
+ static {
221
+ __runInitializers(_classThis, _classExtraInitializers);
222
+ }
223
+ };
224
+ return BeforeComp = _classThis;
225
+ })();
226
+ describe("@Head decorator — static config", () => {
227
+ it("applies head config on onMount", () => {
228
+ const inst = new StaticComp({});
229
+ inst.onMount?.();
230
+ expect(document.title).toBe("Static Title");
231
+ expect(document.querySelector('meta[name="description"]')?.content).toBe("Static desc");
232
+ inst.onUnmount?.();
233
+ });
234
+ it("removes head config on onUnmount", () => {
235
+ const inst = new BeforeComp({});
236
+ inst.onMount?.();
237
+ expect(document.title).toBe("Before");
238
+ inst.onUnmount?.();
239
+ expect(document.title).toBe("initial");
240
+ });
241
+ });
242
+ describe("@Head decorator — reactive config", () => {
243
+ it("updates document head when signal changes", async () => {
244
+ const count = signal(0);
245
+ let ReactiveComp = (() => {
246
+ let _classDecorators = [Head((self) => ({ title: `Count: ${self.n()}` }))];
247
+ let _classDescriptor;
248
+ let _classExtraInitializers = [];
249
+ let _classThis;
250
+ let _classSuper = StatefulComponent;
251
+ var ReactiveComp = class extends _classSuper {
252
+ static { _classThis = this; }
253
+ constructor() {
254
+ super(...arguments);
255
+ this.n = count;
256
+ }
257
+ static {
258
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
259
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
260
+ ReactiveComp = _classThis = _classDescriptor.value;
261
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
262
+ }
263
+ static { this.__isComponent = true; }
264
+ static { this.__isStateless = false; }
265
+ render() { return null; }
266
+ static {
267
+ __runInitializers(_classThis, _classExtraInitializers);
268
+ }
269
+ };
270
+ return ReactiveComp = _classThis;
271
+ })();
272
+ const inst = new ReactiveComp({});
273
+ inst.onMount?.();
274
+ expect(document.title).toBe("Count: 0");
275
+ count.set(5);
276
+ await Promise.resolve();
277
+ expect(document.title).toBe("Count: 5");
278
+ inst.onUnmount?.();
279
+ });
280
+ it("stops tracking after onUnmount", async () => {
281
+ const val = signal("a");
282
+ let StopComp = (() => {
283
+ let _classDecorators = [Head((self) => ({ title: self.v() }))];
284
+ let _classDescriptor;
285
+ let _classExtraInitializers = [];
286
+ let _classThis;
287
+ let _classSuper = StatefulComponent;
288
+ var StopComp = class extends _classSuper {
289
+ static { _classThis = this; }
290
+ constructor() {
291
+ super(...arguments);
292
+ this.v = val;
293
+ }
294
+ static {
295
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
296
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
297
+ StopComp = _classThis = _classDescriptor.value;
298
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
299
+ }
300
+ static { this.__isComponent = true; }
301
+ static { this.__isStateless = false; }
302
+ render() { return null; }
303
+ static {
304
+ __runInitializers(_classThis, _classExtraInitializers);
305
+ }
306
+ };
307
+ return StopComp = _classThis;
308
+ })();
309
+ const inst = new StopComp({});
310
+ inst.onMount?.();
311
+ inst.onUnmount?.();
312
+ val.set("b");
313
+ await Promise.resolve();
314
+ expect(document.title).not.toBe("b");
315
+ });
316
+ it("multiple instances — last mounted wins, cleanup restores previous", () => {
317
+ const t1 = signal("Page A");
318
+ const t2 = signal("Page B");
319
+ let CompA = (() => {
320
+ let _classDecorators = [Head((self) => ({ title: self.t() }))];
321
+ let _classDescriptor;
322
+ let _classExtraInitializers = [];
323
+ let _classThis;
324
+ let _classSuper = StatefulComponent;
325
+ var CompA = class extends _classSuper {
326
+ static { _classThis = this; }
327
+ constructor() {
328
+ super(...arguments);
329
+ this.t = t1;
330
+ }
331
+ static {
332
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
333
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
334
+ CompA = _classThis = _classDescriptor.value;
335
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
336
+ }
337
+ static { this.__isComponent = true; }
338
+ static { this.__isStateless = false; }
339
+ render() { return null; }
340
+ static {
341
+ __runInitializers(_classThis, _classExtraInitializers);
342
+ }
343
+ };
344
+ return CompA = _classThis;
345
+ })();
346
+ let CompB = (() => {
347
+ let _classDecorators = [Head((self) => ({ title: self.t() }))];
348
+ let _classDescriptor;
349
+ let _classExtraInitializers = [];
350
+ let _classThis;
351
+ let _classSuper = StatefulComponent;
352
+ var CompB = class extends _classSuper {
353
+ static { _classThis = this; }
354
+ constructor() {
355
+ super(...arguments);
356
+ this.t = t2;
357
+ }
358
+ static {
359
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
360
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
361
+ CompB = _classThis = _classDescriptor.value;
362
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
363
+ }
364
+ static { this.__isComponent = true; }
365
+ static { this.__isStateless = false; }
366
+ render() { return null; }
367
+ static {
368
+ __runInitializers(_classThis, _classExtraInitializers);
369
+ }
370
+ };
371
+ return CompB = _classThis;
372
+ })();
373
+ const a = new CompA({});
374
+ const b = new CompB({});
375
+ a.onMount?.();
376
+ expect(document.title).toBe("Page A");
377
+ b.onMount?.();
378
+ expect(document.title).toBe("Page B");
379
+ b.onUnmount?.();
380
+ expect(document.title).toBe("Page A");
381
+ a.onUnmount?.();
382
+ });
383
+ it("full og: config is applied reactively", async () => {
384
+ const slug = signal("hello");
385
+ let OGComp = (() => {
386
+ let _classDecorators = [Head((self) => ({
387
+ og: { title: `Post: ${self.s()}`, url: `https://site.com/${self.s()}` },
388
+ }))];
389
+ let _classDescriptor;
390
+ let _classExtraInitializers = [];
391
+ let _classThis;
392
+ let _classSuper = StatefulComponent;
393
+ var OGComp = class extends _classSuper {
394
+ static { _classThis = this; }
395
+ constructor() {
396
+ super(...arguments);
397
+ this.s = slug;
398
+ }
399
+ static {
400
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
401
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
402
+ OGComp = _classThis = _classDescriptor.value;
403
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
404
+ }
405
+ static { this.__isComponent = true; }
406
+ static { this.__isStateless = false; }
407
+ render() { return null; }
408
+ static {
409
+ __runInitializers(_classThis, _classExtraInitializers);
410
+ }
411
+ };
412
+ return OGComp = _classThis;
413
+ })();
414
+ const inst = new OGComp({});
415
+ inst.onMount?.();
416
+ expect(document.querySelector('[property="og:title"]')?.getAttribute("content")).toBe("Post: hello");
417
+ slug.set("world");
418
+ await Promise.resolve();
419
+ expect(document.querySelector('[property="og:title"]')?.getAttribute("content")).toBe("Post: world");
420
+ inst.onUnmount?.();
421
+ });
422
+ it("getter is called only when deps change, not on every frame", async () => {
423
+ const name = signal("Alice");
424
+ const getter = vi.fn((self) => ({ title: self.n() }));
425
+ let SpyComp = (() => {
426
+ let _classDecorators = [Head(getter)];
427
+ let _classDescriptor;
428
+ let _classExtraInitializers = [];
429
+ let _classThis;
430
+ let _classSuper = StatefulComponent;
431
+ var SpyComp = class extends _classSuper {
432
+ static { _classThis = this; }
433
+ constructor() {
434
+ super(...arguments);
435
+ this.n = name;
436
+ }
437
+ static {
438
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
439
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
440
+ SpyComp = _classThis = _classDescriptor.value;
441
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
442
+ }
443
+ static { this.__isComponent = true; }
444
+ static { this.__isStateless = false; }
445
+ render() { return null; }
446
+ static {
447
+ __runInitializers(_classThis, _classExtraInitializers);
448
+ }
449
+ };
450
+ return SpyComp = _classThis;
451
+ })();
452
+ const inst = new SpyComp({});
453
+ inst.onMount?.();
454
+ expect(getter).toHaveBeenCalledTimes(1);
455
+ name.set("Bob");
456
+ await Promise.resolve();
457
+ expect(getter).toHaveBeenCalledTimes(2);
458
+ await Promise.resolve();
459
+ expect(getter).toHaveBeenCalledTimes(2); // no extra calls
460
+ inst.onUnmount?.();
461
+ });
462
+ });
463
+ //# sourceMappingURL=head.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"head.test.js","sourceRoot":"","sources":["../../src/__tests__/head.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4BAA4B;AAC5B,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE9E,UAAU,CAAC,GAAG,EAAE;IACd,QAAQ,CAAC,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;IAC7B,UAAU,EAAE,CAAC;IACb,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAC;AAC7B,CAAC,CAAC,CAAC;AAEH,iFAAiF;AAEjF,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC;QACzC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAkB,0BAA0B,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACvG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,6BAA6B,EAAE,EAAE,CAAC,CAAC;QAClF,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAClG,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IACvH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACjC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QACxE,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QAC7G,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,wBAAwB,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,0BAA0B,EAAE,CAAC,CAAC;QACxD,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IACjH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,EAAE;YACX,IAAI,EAAE;gBACJ,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE;gBACtC,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE;aAC5C;SACF,CAAC,CAAC;QACH,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,iBAAiB,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC3F,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,wBAAwB,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAClG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9B,QAAQ,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9B,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;QACrD,UAAU,CAAC,EAAE,CAAC,CAAC;QACf,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,0BAA0B,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC;QACnB,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC;QACnB,QAAQ,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnD,QAAQ,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnD,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,UAAU,CAAC,CAAC,CAAC,CAAC;QACd,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAkB,0BAA0B,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACtG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QACvD,UAAU,CAAC,EAAE,CAAC,CAAC;QACf,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,EAAE,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;QAClC,gDAAgD;QAChD,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAC3C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACxF,kFAAkF;QAClF,yFAAyF;QACzF,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QAC7B,QAAQ,CAAC,KAAK,GAAG,mBAAmB,CAAC;QACrC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,4EAA4E;QAC5F,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAC;QACxD,mDAAmD;QACnD,MAAM,GAAG,GAAG,QAAQ,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC;QAC5D,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAC5C,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,KAAK,gBAAgB,CAChD,CAAC;QACF,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9B,UAAU,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QACrC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACnE,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7F,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,iFAAiF;AAEjF,QAAQ,CAAC,2DAA2D,EAAE,GAAG,EAAE;IACzE,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,gBAAgB,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,aAAa,GAAG,WAAW,EAAE,CAAC;QACpC,EAAE,CAAC,UAAU,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QACrC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAC3D,8DAA8D;QAC9D,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,EAAE,CAAC,UAAU,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QACrC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,EAAE,CAAC,UAAU,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QACrC,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,+EAA+E,EAAE,GAAG,EAAE;QACvF,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;QAC7D,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAElF,gFAAgF;QAChF,UAAU,EAAE,CAAC;QACb,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,iFAAiF;AAEjF,0EAA0E;IAGpE,UAAU;4BADf,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC;;;;sBACnC,iBAAiB;0BAAzB,SAAQ,WAAiB;;;;YAA1C,6KAIC;;;;iBAHQ,kBAAa,GAAG,IAAa,AAAhB,CAAiB;iBAC9B,kBAAa,GAAG,KAAK,AAAR,CAAS;QAC7B,MAAM,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC;;YAHrB,uDAAU;;;;;IAOV,UAAU;4BADf,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;;;;sBACD,iBAAiB;0BAAzB,SAAQ,WAAiB;;;;YAA1C,6KAIC;;;;iBAHQ,kBAAa,GAAG,IAAa,AAAhB,CAAiB;iBAC9B,kBAAa,GAAG,KAAK,AAAR,CAAS;QAC7B,MAAM,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC;;YAHrB,uDAAU;;;;;AAMhB,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;QAChC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QACjB,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC5C,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAkB,0BAA0B,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACzG,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;QAChC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QACjB,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;QACnB,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mCAAmC,EAAE,GAAG,EAAE;IACjD,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YAGlB,YAAY;oCADjB,IAAI,CAAC,CAAC,IAAkB,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;;;;8BACrC,iBAAiB;oCAAzB,SAAQ,WAAiB;;;;oBAG1C,MAAC,GAAG,KAAK,CAAC;gBAEZ,CAAC;;;oBALD,6KAKC;;;;yBAJQ,kBAAa,GAAG,IAAa,AAAhB,CAAiB;yBAC9B,kBAAa,GAAG,KAAK,AAAR,CAAS;gBAE7B,MAAM,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC;;oBAJrB,uDAAY;;;;;QAOlB,MAAM,IAAI,GAAG,IAAI,YAAY,CAAC,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QACjB,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACxC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACb,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACxC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;YAGlB,QAAQ;oCADb,IAAI,CAAC,CAAC,IAAc,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;;;;8BACzB,iBAAiB;gCAAzB,SAAQ,WAAiB;;;;oBAGtC,MAAC,GAAG,GAAG,CAAC;gBAEV,CAAC;;;oBALD,6KAKC;;;;yBAJQ,kBAAa,GAAG,IAAa,AAAhB,CAAiB;yBAC9B,kBAAa,GAAG,KAAK,AAAR,CAAS;gBAE7B,MAAM,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC;;oBAJrB,uDAAQ;;;;;QAOd,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QACjB,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;QACnB,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACb,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC5B,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;YAGtB,KAAK;oCADV,IAAI,CAAC,CAAC,IAAW,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;;;;8BACzB,iBAAiB;6BAAzB,SAAQ,WAAiB;;;;oBAGnC,MAAC,GAAG,EAAE,CAAC;gBAET,CAAC;;;oBALD,6KAKC;;;;yBAJQ,kBAAa,GAAG,IAAa,AAAhB,CAAiB;yBAC9B,kBAAa,GAAG,KAAK,AAAR,CAAS;gBAE7B,MAAM,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC;;oBAJrB,uDAAK;;;;;YAQL,KAAK;oCADV,IAAI,CAAC,CAAC,IAAW,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;;;;8BACzB,iBAAiB;6BAAzB,SAAQ,WAAiB;;;;oBAGnC,MAAC,GAAG,EAAE,CAAC;gBAET,CAAC;;;oBALD,6KAKC;;;;yBAJQ,kBAAa,GAAG,IAAa,AAAhB,CAAiB;yBAC9B,kBAAa,GAAG,KAAK,AAAR,CAAS;gBAE7B,MAAM,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC;;oBAJrB,uDAAK;;;;;QAOX,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC;QACxB,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC;QACxB,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QACd,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QACd,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC;QAChB,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;YAKvB,MAAM;oCAHX,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC;oBACvB,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,oBAAoB,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;iBACxE,CAAC,CAAC;;;;8BACkB,iBAAiB;8BAAzB,SAAQ,WAAiB;;;;oBAGpC,MAAC,GAAG,IAAI,CAAC;gBAEX,CAAC;;;oBALD,6KAKC;;;;yBAJQ,kBAAa,GAAG,IAAa,AAAhB,CAAiB;yBAC9B,kBAAa,GAAG,KAAK,AAAR,CAAS;gBAE7B,MAAM,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC;;oBAJrB,uDAAM;;;;;QAOZ,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,EAAE,CAAC,CAAC;QAC5B,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QACjB,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACrG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAClB,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACrG,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7B,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,IAAa,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YAGzD,OAAO;oCADZ,IAAI,CAAC,MAAM,CAAC;;;;8BACS,iBAAiB;+BAAzB,SAAQ,WAAiB;;;;oBAGrC,MAAC,GAAG,IAAI,CAAC;gBAEX,CAAC;;;oBALD,6KAKC;;;;yBAJQ,kBAAa,GAAG,IAAa,AAAhB,CAAiB;yBAC9B,kBAAa,GAAG,KAAK,AAAR,CAAS;gBAE7B,MAAM,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC;;oBAJrB,uDAAO;;;;;QAOb,MAAM,IAAI,GAAG,IAAI,OAAO,CAAC,EAAE,CAAC,CAAC;QAC7B,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QACjB,MAAM,CAAC,MAAM,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAChB,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,MAAM,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,MAAM,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB;QAC1D,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,32 @@
1
+ export interface MetaTag {
2
+ name?: string;
3
+ property?: string;
4
+ content: string;
5
+ }
6
+ export interface HeadConfig {
7
+ title?: string;
8
+ description?: string;
9
+ canonical?: string;
10
+ meta?: MetaTag[];
11
+ og?: {
12
+ title?: string;
13
+ description?: string;
14
+ image?: string;
15
+ url?: string;
16
+ type?: string;
17
+ siteName?: string;
18
+ };
19
+ twitter?: {
20
+ card?: string;
21
+ title?: string;
22
+ description?: string;
23
+ image?: string;
24
+ };
25
+ }
26
+ /** Increments on every head update. Read reactively to subscribe to head changes. */
27
+ export declare const headVersion: import("@praxisjs/shared").Signal<number>;
28
+ export declare function pushHead(id: symbol, config: HeadConfig): void;
29
+ export declare function removeHead(id: symbol): void;
30
+ /** Resets all head state. For use in tests only. */
31
+ export declare function _resetHead(): void;
32
+ //# sourceMappingURL=head-stack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"head-stack.d.ts","sourceRoot":"","sources":["../src/head-stack.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;IACjB,EAAE,CAAC,EAAE;QACH,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAYD,qFAAqF;AACrF,eAAO,MAAM,WAAW,2CAAY,CAAC;AAErC,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAU7D;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAK3C;AAiFD,oDAAoD;AACpD,wBAAgB,UAAU,IAAI,IAAI,CAMjC"}
@@ -0,0 +1,111 @@
1
+ import { signal } from "@praxisjs/core/internal";
2
+ const ATTR = "data-praxis-head";
3
+ const _stack = [];
4
+ let _initialTitle;
5
+ /** Increments on every head update. Read reactively to subscribe to head changes. */
6
+ export const headVersion = signal(0);
7
+ export function pushHead(id, config) {
8
+ if (typeof document === "undefined")
9
+ return;
10
+ _initialTitle ??= document.title;
11
+ const idx = _stack.findIndex((e) => e.id === id);
12
+ if (idx >= 0) {
13
+ _stack[idx].config = config;
14
+ }
15
+ else {
16
+ _stack.push({ id, config });
17
+ }
18
+ _apply();
19
+ }
20
+ export function removeHead(id) {
21
+ if (typeof document === "undefined")
22
+ return;
23
+ const idx = _stack.findIndex((e) => e.id === id);
24
+ if (idx >= 0)
25
+ _stack.splice(idx, 1);
26
+ _apply();
27
+ }
28
+ function _apply() {
29
+ headVersion.update((n) => n + 1);
30
+ document.querySelectorAll(`[${ATTR}]`).forEach((el) => { el.remove(); });
31
+ const top = _stack.at(-1);
32
+ if (top === undefined) {
33
+ document.title = _initialTitle ?? "";
34
+ return;
35
+ }
36
+ const { config } = top;
37
+ if (config.title != null)
38
+ document.title = config.title;
39
+ if (config.description != null)
40
+ _metaName("description", config.description);
41
+ if (config.canonical != null)
42
+ _canonical(config.canonical);
43
+ for (const tag of config.meta ?? []) {
44
+ if (tag.property != null)
45
+ _metaProp(tag.property, tag.content);
46
+ else if (tag.name != null)
47
+ _metaName(tag.name, tag.content);
48
+ }
49
+ if (config.og != null) {
50
+ const og = config.og;
51
+ _applyMap("property", {
52
+ "og:title": og.title,
53
+ "og:description": og.description,
54
+ "og:image": og.image,
55
+ "og:url": og.url,
56
+ "og:type": og.type,
57
+ "og:site_name": og.siteName,
58
+ });
59
+ }
60
+ if (config.twitter != null) {
61
+ const tw = config.twitter;
62
+ _applyMap("name", {
63
+ "twitter:card": tw.card,
64
+ "twitter:title": tw.title,
65
+ "twitter:description": tw.description,
66
+ "twitter:image": tw.image,
67
+ });
68
+ }
69
+ }
70
+ function _applyMap(attr, map) {
71
+ for (const [key, value] of Object.entries(map)) {
72
+ if (value != null) {
73
+ if (attr === "name") {
74
+ _metaName(key, value);
75
+ }
76
+ else {
77
+ _metaProp(key, value);
78
+ }
79
+ }
80
+ }
81
+ }
82
+ function _metaName(name, content) {
83
+ const el = document.createElement("meta");
84
+ el.name = name;
85
+ el.content = content;
86
+ el.setAttribute(ATTR, "");
87
+ document.head.appendChild(el);
88
+ }
89
+ function _metaProp(property, content) {
90
+ const el = document.createElement("meta");
91
+ el.setAttribute("property", property);
92
+ el.content = content;
93
+ el.setAttribute(ATTR, "");
94
+ document.head.appendChild(el);
95
+ }
96
+ function _canonical(href) {
97
+ const el = document.createElement("link");
98
+ el.rel = "canonical";
99
+ el.href = href;
100
+ el.setAttribute(ATTR, "");
101
+ document.head.appendChild(el);
102
+ }
103
+ /** Resets all head state. For use in tests only. */
104
+ export function _resetHead() {
105
+ _stack.length = 0;
106
+ _initialTitle = undefined;
107
+ if (typeof document !== "undefined") {
108
+ document.querySelectorAll(`[${ATTR}]`).forEach((el) => { el.remove(); });
109
+ }
110
+ }
111
+ //# sourceMappingURL=head-stack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"head-stack.js","sourceRoot":"","sources":["../src/head-stack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AA6BjD,MAAM,IAAI,GAAG,kBAAkB,CAAC;AAOhC,MAAM,MAAM,GAAgB,EAAE,CAAC;AAC/B,IAAI,aAAiC,CAAC;AAEtC,qFAAqF;AACrF,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;AAErC,MAAM,UAAU,QAAQ,CAAC,EAAU,EAAE,MAAkB;IACrD,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO;IAC5C,aAAa,KAAK,QAAQ,CAAC,KAAK,CAAC;IACjC,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACjD,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;QACb,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC;IAC9B,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9B,CAAC;IACD,MAAM,EAAE,CAAC;AACX,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,EAAU;IACnC,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO;IAC5C,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACjD,IAAI,GAAG,IAAI,CAAC;QAAE,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACpC,MAAM,EAAE,CAAC;AACX,CAAC;AAED,SAAS,MAAM;IACb,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACjC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAEzE,MAAM,GAAG,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,QAAQ,CAAC,KAAK,GAAG,aAAa,IAAI,EAAE,CAAC;QACrC,OAAO;IACT,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IAEvB,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI;QAAE,QAAQ,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IACxD,IAAI,MAAM,CAAC,WAAW,IAAI,IAAI;QAAE,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IAC7E,IAAI,MAAM,CAAC,SAAS,IAAI,IAAI;QAAE,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAE3D,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;QACpC,IAAI,GAAG,CAAC,QAAQ,IAAI,IAAI;YAAE,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;aAC1D,IAAI,GAAG,CAAC,IAAI,IAAI,IAAI;YAAE,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI,MAAM,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;QACtB,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC;QACrB,SAAS,CAAC,UAAU,EAAE;YACpB,UAAU,EAAE,EAAE,CAAC,KAAK;YACpB,gBAAgB,EAAE,EAAE,CAAC,WAAW;YAChC,UAAU,EAAE,EAAE,CAAC,KAAK;YACpB,QAAQ,EAAE,EAAE,CAAC,GAAG;YAChB,SAAS,EAAE,EAAE,CAAC,IAAI;YAClB,cAAc,EAAE,EAAE,CAAC,QAAQ;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC;QAC1B,SAAS,CAAC,MAAM,EAAE;YAChB,cAAc,EAAE,EAAE,CAAC,IAAI;YACvB,eAAe,EAAE,EAAE,CAAC,KAAK;YACzB,qBAAqB,EAAE,EAAE,CAAC,WAAW;YACrC,eAAe,EAAE,EAAE,CAAC,KAAK;SAC1B,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAChB,IAAyB,EACzB,GAAuC;IAEvC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;YAClB,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;gBAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAAC,CAAC;iBAAM,CAAC;gBAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAAC,CAAC;QACjF,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,IAAY,EAAE,OAAe;IAC9C,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC1C,EAAE,CAAC,IAAI,GAAG,IAAI,CAAC;IACf,EAAE,CAAC,OAAO,GAAG,OAAO,CAAC;IACrB,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,SAAS,CAAC,QAAgB,EAAE,OAAe;IAClD,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC1C,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACtC,EAAE,CAAC,OAAO,GAAG,OAAO,CAAC;IACrB,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC1C,EAAE,CAAC,GAAG,GAAG,WAAW,CAAC;IACrB,EAAE,CAAC,IAAI,GAAG,IAAI,CAAC;IACf,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,UAAU;IACxB,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAClB,aAAa,GAAG,SAAS,CAAC;IAC1B,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { type RootComponent } from "@praxisjs/core/internal";
2
+ import { type HeadConfig, type MetaTag, headVersion } from "./head-stack";
3
+ export type { HeadConfig, MetaTag };
4
+ export { headVersion };
5
+ export declare function Head(config: HeadConfig | ((self: any) => HeadConfig)): <T extends new (...args: any[]) => RootComponent<Record<string, any>>>(constructor: T, _context: ClassDecoratorContext) => T;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,aAAa,EAAG,MAAM,yBAAyB,CAAC;AAOtE,OAAO,EAAwB,KAAK,UAAU,EAAE,KAAK,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhG,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,CAAC;AA4BvB,wBAAgB,IAAI,CAElB,MAAM,EAAE,UAAU,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,KAAK,UAAU,CAAC,mBAnBZ,GAEtC,0GAsBC"}
package/dist/index.js ADDED
@@ -0,0 +1,36 @@
1
+ import { effect } from "@praxisjs/core/internal";
2
+ import { createClassDecorator, ClassBehavior, } from "@praxisjs/decorators";
3
+ import { pushHead, removeHead, headVersion } from "./head-stack";
4
+ export { headVersion };
5
+ class HeadBehavior extends ClassBehavior {
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ constructor(getter) {
8
+ super();
9
+ this.getter = getter;
10
+ }
11
+ create(instance) {
12
+ const id = Symbol();
13
+ const getter = this.getter;
14
+ let stop;
15
+ return {
16
+ onMount() {
17
+ stop = effect(() => {
18
+ pushHead(id, getter(instance));
19
+ });
20
+ },
21
+ onUnmount() {
22
+ stop?.();
23
+ stop = undefined;
24
+ removeHead(id);
25
+ },
26
+ };
27
+ }
28
+ }
29
+ export function Head(
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ config) {
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ const getter = typeof config === "function" ? config : () => config;
34
+ return createClassDecorator(new HeadBehavior(getter));
35
+ }
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAuB,MAAM,yBAAyB,CAAC;AACtE,OAAO,EACL,oBAAoB,EACpB,aAAa,GAEd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAiC,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhG,OAAO,EAAE,WAAW,EAAE,CAAC;AAEvB,MAAM,YAAa,SAAQ,aAAa;IACtC,8DAA8D;IAC9D,YAA6B,MAAiC;QAC5D,KAAK,EAAE,CAAC;QADmB,WAAM,GAAN,MAAM,CAA2B;IAE9D,CAAC;IAED,MAAM,CAAC,QAAuB;QAC5B,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,IAA8B,CAAC;QAEnC,OAAO;YACL,OAAO;gBACL,IAAI,GAAG,MAAM,CAAC,GAAG,EAAE;oBACjB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACjC,CAAC,CAAC,CAAC;YACL,CAAC;YACD,SAAS;gBACP,IAAI,EAAE,EAAE,CAAC;gBACT,IAAI,GAAG,SAAS,CAAC;gBACjB,UAAU,CAAC,EAAE,CAAC,CAAC;YACjB,CAAC;SACF,CAAC;IACJ,CAAC;CACF;AAED,MAAM,UAAU,IAAI;AAClB,8DAA8D;AAC9D,MAAgD;IAEhD,8DAA8D;IAC9D,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAE,MAAoC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC;IACnG,OAAO,oBAAoB,CAAC,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;AACxD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@praxisjs/head",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "devDependencies": {
14
+ "typescript": "^5.9.3"
15
+ },
16
+ "dependencies": {
17
+ "@praxisjs/core": "1.7.0",
18
+ "@praxisjs/decorators": "1.1.0",
19
+ "@praxisjs/shared": "0.2.0"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "dev": "tsc --watch"
24
+ }
25
+ }
@@ -0,0 +1,322 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
3
+ import { signal } from "@praxisjs/core/internal";
4
+ import { StatefulComponent } from "@praxisjs/core";
5
+
6
+ import { Head } from "../index";
7
+ import { pushHead, removeHead, _resetHead, headVersion } from "../head-stack";
8
+
9
+ beforeEach(() => {
10
+ document.head.innerHTML = "";
11
+ _resetHead();
12
+ document.title = "initial";
13
+ });
14
+
15
+ // ─── head-stack unit tests ────────────────────────────────────────────────────
16
+
17
+ describe("pushHead / removeHead", () => {
18
+ it("sets document.title", () => {
19
+ const id = Symbol();
20
+ pushHead(id, { title: "Hello" });
21
+ expect(document.title).toBe("Hello");
22
+ });
23
+
24
+ it("adds meta name=description", () => {
25
+ const id = Symbol();
26
+ pushHead(id, { description: "My desc" });
27
+ expect(document.querySelector<HTMLMetaElement>('meta[name="description"]')?.content).toBe("My desc");
28
+ });
29
+
30
+ it("adds og: meta properties", () => {
31
+ const id = Symbol();
32
+ pushHead(id, { og: { title: "OG Title", image: "https://example.com/img.jpg" } });
33
+ expect(document.querySelector('[property="og:title"]')?.getAttribute("content")).toBe("OG Title");
34
+ expect(document.querySelector('[property="og:image"]')?.getAttribute("content")).toBe("https://example.com/img.jpg");
35
+ });
36
+
37
+ it("adds twitter: meta tags", () => {
38
+ const id = Symbol();
39
+ pushHead(id, { twitter: { card: "summary_large_image", title: "TW" } });
40
+ expect(document.querySelector('[name="twitter:card"]')?.getAttribute("content")).toBe("summary_large_image");
41
+ expect(document.querySelector('[name="twitter:title"]')?.getAttribute("content")).toBe("TW");
42
+ });
43
+
44
+ it("adds canonical link", () => {
45
+ const id = Symbol();
46
+ pushHead(id, { canonical: "https://example.com/page" });
47
+ expect(document.querySelector('link[rel="canonical"]')?.getAttribute("href")).toBe("https://example.com/page");
48
+ });
49
+
50
+ it("adds arbitrary meta[] by name and property", () => {
51
+ const id = Symbol();
52
+ pushHead(id, {
53
+ meta: [
54
+ { name: "robots", content: "noindex" },
55
+ { property: "fb:app_id", content: "12345" },
56
+ ],
57
+ });
58
+ expect(document.querySelector('[name="robots"]')?.getAttribute("content")).toBe("noindex");
59
+ expect(document.querySelector('[property="fb:app_id"]')?.getAttribute("content")).toBe("12345");
60
+ });
61
+
62
+ it("updates an existing entry (no duplicate push)", () => {
63
+ const id = Symbol();
64
+ pushHead(id, { title: "v1" });
65
+ pushHead(id, { title: "v2" });
66
+ expect(document.title).toBe("v2");
67
+ });
68
+
69
+ it("restores initial title and removes managed metas on removeHead", () => {
70
+ const id = Symbol();
71
+ pushHead(id, { title: "Page", description: "Desc" });
72
+ removeHead(id);
73
+ expect(document.title).toBe("initial");
74
+ expect(document.querySelector('meta[name="description"]')).toBeNull();
75
+ });
76
+
77
+ it("stack: last entry wins; removing it restores the previous one", () => {
78
+ const a = Symbol();
79
+ const b = Symbol();
80
+ pushHead(a, { title: "A", description: "desc-a" });
81
+ pushHead(b, { title: "B", description: "desc-b" });
82
+ expect(document.title).toBe("B");
83
+ removeHead(b);
84
+ expect(document.title).toBe("A");
85
+ expect(document.querySelector<HTMLMetaElement>('meta[name="description"]')?.content).toBe("desc-a");
86
+ });
87
+
88
+ it("removes all managed metas when stack is empty", () => {
89
+ const id = Symbol();
90
+ pushHead(id, { description: "x", og: { title: "y" } });
91
+ removeHead(id);
92
+ expect(document.querySelectorAll("[data-praxis-head]").length).toBe(0);
93
+ });
94
+
95
+ it("removeHead with a non-existent id is a no-op (idx < 0 branch)", () => {
96
+ const id = Symbol("never-pushed");
97
+ // _apply() still runs but the stack stays empty
98
+ expect(() => removeHead(id)).not.toThrow();
99
+ expect(document.querySelectorAll("[data-praxis-head]").length).toBe(0);
100
+ });
101
+
102
+ it("_apply restores title to '' when _initialTitle is undefined and stack is empty", () => {
103
+ // _resetHead leaves _initialTitle = undefined; calling removeHead on a missing id
104
+ // triggers _apply with an empty stack and undefined _initialTitle → title = "" fallback.
105
+ const id = Symbol("missing");
106
+ document.title = "should-be-cleared";
107
+ removeHead(id); // idx < 0 → no splice; _apply runs → empty stack, _initialTitle = undefined
108
+ expect(document.title).toBe("");
109
+ });
110
+
111
+ it("meta tag with neither name nor property is silently skipped", () => {
112
+ const id = Symbol();
113
+ pushHead(id, { meta: [{ content: "orphan-content" }] });
114
+ // Neither _metaName nor _metaProp should be called
115
+ const all = document.querySelectorAll("[data-praxis-head]");
116
+ const hasOrphan = Array.from(all).some((el) =>
117
+ el.getAttribute("content") === "orphan-content",
118
+ );
119
+ expect(hasOrphan).toBe(false);
120
+ removeHead(id);
121
+ });
122
+
123
+ it("omits undefined og fields", () => {
124
+ const id = Symbol();
125
+ pushHead(id, { og: { title: "T" } });
126
+ expect(document.querySelector('[property="og:image"]')).toBeNull();
127
+ expect(document.querySelector('[property="og:title"]')?.getAttribute("content")).toBe("T");
128
+ });
129
+ });
130
+
131
+ // ─── SSR guards ───────────────────────────────────────────────────────────────
132
+
133
+ describe("SSR guards — no DOM operations when document is undefined", () => {
134
+ afterEach(() => {
135
+ vi.unstubAllGlobals();
136
+ });
137
+
138
+ it("pushHead is a no-op when document is undefined", () => {
139
+ const versionBefore = headVersion();
140
+ vi.stubGlobal("document", undefined);
141
+ const id = Symbol();
142
+ expect(() => pushHead(id, { title: "SSR" })).not.toThrow();
143
+ // headVersion must NOT have changed (function returned early)
144
+ expect(headVersion()).toBe(versionBefore);
145
+ });
146
+
147
+ it("removeHead is a no-op when document is undefined", () => {
148
+ vi.stubGlobal("document", undefined);
149
+ const id = Symbol();
150
+ expect(() => removeHead(id)).not.toThrow();
151
+ });
152
+
153
+ it("_resetHead skips DOM cleanup when document is undefined", () => {
154
+ vi.stubGlobal("document", undefined);
155
+ expect(() => _resetHead()).not.toThrow();
156
+ });
157
+ });
158
+
159
+ describe("_resetHead — DOM cleanup", () => {
160
+ it("removes managed elements from document.head when called with elements present", () => {
161
+ const id = Symbol();
162
+ pushHead(id, { title: "Before Reset", description: "desc" });
163
+ expect(document.querySelectorAll("[data-praxis-head]").length).toBeGreaterThan(0);
164
+
165
+ // Call _resetHead WITHOUT clearing head.innerHTML first — exercises the forEach
166
+ _resetHead();
167
+ expect(document.querySelectorAll("[data-praxis-head]").length).toBe(0);
168
+ });
169
+ });
170
+
171
+ // ─── @Head decorator ──────────────────────────────────────────────────────────
172
+
173
+ // All decorated classes at module scope for correct TypeScript inference.
174
+
175
+ @Head({ title: "Static Title", description: "Static desc" })
176
+ class StaticComp extends StatefulComponent {
177
+ static __isComponent = true as const;
178
+ static __isStateless = false;
179
+ render() { return null; }
180
+ }
181
+
182
+ @Head({ title: "Before" })
183
+ class BeforeComp extends StatefulComponent {
184
+ static __isComponent = true as const;
185
+ static __isStateless = false;
186
+ render() { return null; }
187
+ }
188
+
189
+ describe("@Head decorator — static config", () => {
190
+ it("applies head config on onMount", () => {
191
+ const inst = new StaticComp({});
192
+ inst.onMount?.();
193
+ expect(document.title).toBe("Static Title");
194
+ expect(document.querySelector<HTMLMetaElement>('meta[name="description"]')?.content).toBe("Static desc");
195
+ inst.onUnmount?.();
196
+ });
197
+
198
+ it("removes head config on onUnmount", () => {
199
+ const inst = new BeforeComp({});
200
+ inst.onMount?.();
201
+ expect(document.title).toBe("Before");
202
+ inst.onUnmount?.();
203
+ expect(document.title).toBe("initial");
204
+ });
205
+ });
206
+
207
+ describe("@Head decorator — reactive config", () => {
208
+ it("updates document head when signal changes", async () => {
209
+ const count = signal(0);
210
+
211
+ @Head((self: ReactiveComp) => ({ title: `Count: ${self.n()}` }))
212
+ class ReactiveComp extends StatefulComponent {
213
+ static __isComponent = true as const;
214
+ static __isStateless = false;
215
+ n = count;
216
+ render() { return null; }
217
+ }
218
+
219
+ const inst = new ReactiveComp({});
220
+ inst.onMount?.();
221
+ expect(document.title).toBe("Count: 0");
222
+ count.set(5);
223
+ await Promise.resolve();
224
+ expect(document.title).toBe("Count: 5");
225
+ inst.onUnmount?.();
226
+ });
227
+
228
+ it("stops tracking after onUnmount", async () => {
229
+ const val = signal("a");
230
+
231
+ @Head((self: StopComp) => ({ title: self.v() }))
232
+ class StopComp extends StatefulComponent {
233
+ static __isComponent = true as const;
234
+ static __isStateless = false;
235
+ v = val;
236
+ render() { return null; }
237
+ }
238
+
239
+ const inst = new StopComp({});
240
+ inst.onMount?.();
241
+ inst.onUnmount?.();
242
+ val.set("b");
243
+ await Promise.resolve();
244
+ expect(document.title).not.toBe("b");
245
+ });
246
+
247
+ it("multiple instances — last mounted wins, cleanup restores previous", () => {
248
+ const t1 = signal("Page A");
249
+ const t2 = signal("Page B");
250
+
251
+ @Head((self: CompA) => ({ title: self.t() }))
252
+ class CompA extends StatefulComponent {
253
+ static __isComponent = true as const;
254
+ static __isStateless = false;
255
+ t = t1;
256
+ render() { return null; }
257
+ }
258
+
259
+ @Head((self: CompB) => ({ title: self.t() }))
260
+ class CompB extends StatefulComponent {
261
+ static __isComponent = true as const;
262
+ static __isStateless = false;
263
+ t = t2;
264
+ render() { return null; }
265
+ }
266
+
267
+ const a = new CompA({});
268
+ const b = new CompB({});
269
+ a.onMount?.();
270
+ expect(document.title).toBe("Page A");
271
+ b.onMount?.();
272
+ expect(document.title).toBe("Page B");
273
+ b.onUnmount?.();
274
+ expect(document.title).toBe("Page A");
275
+ a.onUnmount?.();
276
+ });
277
+
278
+ it("full og: config is applied reactively", async () => {
279
+ const slug = signal("hello");
280
+
281
+ @Head((self: OGComp) => ({
282
+ og: { title: `Post: ${self.s()}`, url: `https://site.com/${self.s()}` },
283
+ }))
284
+ class OGComp extends StatefulComponent {
285
+ static __isComponent = true as const;
286
+ static __isStateless = false;
287
+ s = slug;
288
+ render() { return null; }
289
+ }
290
+
291
+ const inst = new OGComp({});
292
+ inst.onMount?.();
293
+ expect(document.querySelector('[property="og:title"]')?.getAttribute("content")).toBe("Post: hello");
294
+ slug.set("world");
295
+ await Promise.resolve();
296
+ expect(document.querySelector('[property="og:title"]')?.getAttribute("content")).toBe("Post: world");
297
+ inst.onUnmount?.();
298
+ });
299
+
300
+ it("getter is called only when deps change, not on every frame", async () => {
301
+ const name = signal("Alice");
302
+ const getter = vi.fn((self: SpyComp) => ({ title: self.n() }));
303
+
304
+ @Head(getter)
305
+ class SpyComp extends StatefulComponent {
306
+ static __isComponent = true as const;
307
+ static __isStateless = false;
308
+ n = name;
309
+ render() { return null; }
310
+ }
311
+
312
+ const inst = new SpyComp({});
313
+ inst.onMount?.();
314
+ expect(getter).toHaveBeenCalledTimes(1);
315
+ name.set("Bob");
316
+ await Promise.resolve();
317
+ expect(getter).toHaveBeenCalledTimes(2);
318
+ await Promise.resolve();
319
+ expect(getter).toHaveBeenCalledTimes(2); // no extra calls
320
+ inst.onUnmount?.();
321
+ });
322
+ });
@@ -0,0 +1,148 @@
1
+ import { signal } from "@praxisjs/core/internal";
2
+
3
+ export interface MetaTag {
4
+ name?: string;
5
+ property?: string;
6
+ content: string;
7
+ }
8
+
9
+ export interface HeadConfig {
10
+ title?: string;
11
+ description?: string;
12
+ canonical?: string;
13
+ meta?: MetaTag[];
14
+ og?: {
15
+ title?: string;
16
+ description?: string;
17
+ image?: string;
18
+ url?: string;
19
+ type?: string;
20
+ siteName?: string;
21
+ };
22
+ twitter?: {
23
+ card?: string;
24
+ title?: string;
25
+ description?: string;
26
+ image?: string;
27
+ };
28
+ }
29
+
30
+ const ATTR = "data-praxis-head";
31
+
32
+ interface HeadEntry {
33
+ id: symbol;
34
+ config: HeadConfig;
35
+ }
36
+
37
+ const _stack: HeadEntry[] = [];
38
+ let _initialTitle: string | undefined;
39
+
40
+ /** Increments on every head update. Read reactively to subscribe to head changes. */
41
+ export const headVersion = signal(0);
42
+
43
+ export function pushHead(id: symbol, config: HeadConfig): void {
44
+ if (typeof document === "undefined") return;
45
+ _initialTitle ??= document.title;
46
+ const idx = _stack.findIndex((e) => e.id === id);
47
+ if (idx >= 0) {
48
+ _stack[idx].config = config;
49
+ } else {
50
+ _stack.push({ id, config });
51
+ }
52
+ _apply();
53
+ }
54
+
55
+ export function removeHead(id: symbol): void {
56
+ if (typeof document === "undefined") return;
57
+ const idx = _stack.findIndex((e) => e.id === id);
58
+ if (idx >= 0) _stack.splice(idx, 1);
59
+ _apply();
60
+ }
61
+
62
+ function _apply(): void {
63
+ headVersion.update((n) => n + 1);
64
+ document.querySelectorAll(`[${ATTR}]`).forEach((el) => { el.remove(); });
65
+
66
+ const top = _stack.at(-1);
67
+ if (top === undefined) {
68
+ document.title = _initialTitle ?? "";
69
+ return;
70
+ }
71
+
72
+ const { config } = top;
73
+
74
+ if (config.title != null) document.title = config.title;
75
+ if (config.description != null) _metaName("description", config.description);
76
+ if (config.canonical != null) _canonical(config.canonical);
77
+
78
+ for (const tag of config.meta ?? []) {
79
+ if (tag.property != null) _metaProp(tag.property, tag.content);
80
+ else if (tag.name != null) _metaName(tag.name, tag.content);
81
+ }
82
+
83
+ if (config.og != null) {
84
+ const og = config.og;
85
+ _applyMap("property", {
86
+ "og:title": og.title,
87
+ "og:description": og.description,
88
+ "og:image": og.image,
89
+ "og:url": og.url,
90
+ "og:type": og.type,
91
+ "og:site_name": og.siteName,
92
+ });
93
+ }
94
+
95
+ if (config.twitter != null) {
96
+ const tw = config.twitter;
97
+ _applyMap("name", {
98
+ "twitter:card": tw.card,
99
+ "twitter:title": tw.title,
100
+ "twitter:description": tw.description,
101
+ "twitter:image": tw.image,
102
+ });
103
+ }
104
+ }
105
+
106
+ function _applyMap(
107
+ attr: "name" | "property",
108
+ map: Record<string, string | undefined>,
109
+ ): void {
110
+ for (const [key, value] of Object.entries(map)) {
111
+ if (value != null) {
112
+ if (attr === "name") { _metaName(key, value); } else { _metaProp(key, value); }
113
+ }
114
+ }
115
+ }
116
+
117
+ function _metaName(name: string, content: string): void {
118
+ const el = document.createElement("meta");
119
+ el.name = name;
120
+ el.content = content;
121
+ el.setAttribute(ATTR, "");
122
+ document.head.appendChild(el);
123
+ }
124
+
125
+ function _metaProp(property: string, content: string): void {
126
+ const el = document.createElement("meta");
127
+ el.setAttribute("property", property);
128
+ el.content = content;
129
+ el.setAttribute(ATTR, "");
130
+ document.head.appendChild(el);
131
+ }
132
+
133
+ function _canonical(href: string): void {
134
+ const el = document.createElement("link");
135
+ el.rel = "canonical";
136
+ el.href = href;
137
+ el.setAttribute(ATTR, "");
138
+ document.head.appendChild(el);
139
+ }
140
+
141
+ /** Resets all head state. For use in tests only. */
142
+ export function _resetHead(): void {
143
+ _stack.length = 0;
144
+ _initialTitle = undefined;
145
+ if (typeof document !== "undefined") {
146
+ document.querySelectorAll(`[${ATTR}]`).forEach((el) => { el.remove(); });
147
+ }
148
+ }
package/src/index.ts ADDED
@@ -0,0 +1,46 @@
1
+ import { effect, type RootComponent } from "@praxisjs/core/internal";
2
+ import {
3
+ createClassDecorator,
4
+ ClassBehavior,
5
+ type ClassEnhancement,
6
+ } from "@praxisjs/decorators";
7
+
8
+ import { pushHead, removeHead, type HeadConfig, type MetaTag, headVersion } from "./head-stack";
9
+
10
+ export type { HeadConfig, MetaTag };
11
+ export { headVersion };
12
+
13
+ class HeadBehavior extends ClassBehavior {
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ constructor(private readonly getter: (self: any) => HeadConfig) {
16
+ super();
17
+ }
18
+
19
+ create(instance: RootComponent): ClassEnhancement {
20
+ const id = Symbol();
21
+ const getter = this.getter;
22
+ let stop: (() => void) | undefined;
23
+
24
+ return {
25
+ onMount() {
26
+ stop = effect(() => {
27
+ pushHead(id, getter(instance));
28
+ });
29
+ },
30
+ onUnmount() {
31
+ stop?.();
32
+ stop = undefined;
33
+ removeHead(id);
34
+ },
35
+ };
36
+ }
37
+ }
38
+
39
+ export function Head(
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ config: HeadConfig | ((self: any) => HeadConfig),
42
+ ) {
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
+ const getter = typeof config === "function" ? (config as (self: any) => HeadConfig) : () => config;
45
+ return createClassDecorator(new HeadBehavior(getter));
46
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src"]
8
+ }