@tinyfx/runtime 0.1.6 → 0.1.9

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.
Files changed (49) hide show
  1. package/README.md +48 -121
  2. package/dist/__tests__/mount-state.test.d.ts +1 -0
  3. package/dist/__tests__/mount-state.test.js +30 -0
  4. package/dist/__tests__/page-registry.test.d.ts +1 -0
  5. package/dist/__tests__/page-registry.test.js +38 -0
  6. package/dist/__tests__/path-matcher.test.d.ts +1 -0
  7. package/dist/__tests__/path-matcher.test.js +43 -0
  8. package/dist/__tests__/registry.test.d.ts +1 -0
  9. package/dist/__tests__/registry.test.js +67 -0
  10. package/dist/context.d.ts +19 -0
  11. package/dist/context.js +1 -0
  12. package/dist/dom.d.ts +35 -0
  13. package/dist/dom.js +35 -0
  14. package/dist/each.d.ts +10 -0
  15. package/dist/each.js +24 -0
  16. package/dist/http/data.d.ts +76 -0
  17. package/dist/http/data.js +36 -0
  18. package/dist/http/helper.d.ts +20 -0
  19. package/dist/http/helper.js +20 -0
  20. package/dist/http/http.d.ts +13 -0
  21. package/dist/http/http.js +13 -0
  22. package/dist/http.d.ts +2 -10
  23. package/dist/http.js +1 -38
  24. package/dist/index.d.ts +12 -8
  25. package/dist/index.js +11 -7
  26. package/dist/init.d.ts +12 -0
  27. package/dist/init.js +40 -0
  28. package/dist/mount-state.d.ts +17 -0
  29. package/dist/mount-state.js +27 -0
  30. package/dist/page-registry.d.ts +31 -0
  31. package/dist/page-registry.js +38 -0
  32. package/dist/registry.d.ts +31 -0
  33. package/dist/registry.js +49 -0
  34. package/dist/router/active-links.d.ts +3 -0
  35. package/dist/router/active-links.js +3 -0
  36. package/dist/router/index.d.ts +4 -0
  37. package/dist/router/index.js +4 -1
  38. package/dist/router/lifecycle.d.ts +26 -0
  39. package/dist/router/lifecycle.js +31 -1
  40. package/dist/router/navigate.d.ts +8 -0
  41. package/dist/router/navigate.js +8 -0
  42. package/dist/router/params.d.ts +20 -0
  43. package/dist/router/params.js +22 -0
  44. package/dist/router/path-matcher.d.ts +30 -0
  45. package/dist/router/path-matcher.js +45 -0
  46. package/dist/router/types.d.ts +15 -2
  47. package/dist/signals.d.ts +41 -0
  48. package/dist/signals.js +41 -0
  49. package/package.json +6 -3
package/README.md CHANGED
@@ -1,16 +1,15 @@
1
1
  # @tinyfx/runtime
2
2
 
3
- > The minimal, browser-side runtime for tinyfx.
4
-
5
- A lightweight collection of helpers for reactivity, DOM manipulation, and typed HTTP communication. Designed to be used standalone or as part of the `tinyfx` build pipeline.
3
+ The TinyFX browser runtime: signals, DOM helpers, typed HTTP, and router lifecycle utilities.
6
4
 
7
5
  ## Features
8
6
 
9
- - **Signals** Explicit reactivity without Proxies.
10
- - **DOM Helpers** One-way bindings for text, attributes, and classes.
11
- - **Typed HTTP** Minimal wrapper over `fetch` with response typing.
12
- - **DI Container** — Simple `provide()`/`inject()` registry for app services.
13
- - **Tiny** No dependencies, tree-shakeable, and extremely fast.
7
+ - Signals (`signal`, `effect`)
8
+ - DOM binding helpers (`bindText`, `bindAttr`, `bindClass`)
9
+ - Typed HTTP client (`createHttp`)
10
+ - Router helpers (`initRouter`, `navigate`, `goBack`, `getParam`)
11
+ - Lifecycle hooks (`onMount`, `onDestroy`)
12
+ - Lightweight `TinyFxContext` (`params`, `navigate`)
14
13
 
15
14
  ## Installation
16
15
 
@@ -20,27 +19,21 @@ npm install @tinyfx/runtime
20
19
 
21
20
  ## Usage
22
21
 
23
- ### Signals (Reactivity)
24
-
25
- Signals provide a simple way to manage state that triggers effects when changed.
22
+ ### Signals
26
23
 
27
24
  ```ts
28
25
  import { signal, effect } from "@tinyfx/runtime";
29
26
 
30
27
  const count = signal(0);
31
28
 
32
- // Runs immediately, and re-runs whenever count() changes
33
29
  effect(() => {
34
- console.log("The count is:", count());
30
+ console.log("count:", count());
35
31
  });
36
32
 
37
- count.set(1); // logs: "The count is: 1"
38
- count.set(count() + 1); // logs: "The count is: 2"
33
+ count.set(count() + 1);
39
34
  ```
40
35
 
41
- ### DOM Bindings
42
-
43
- Keep your JS decoupled from the DOM while maintaining reactive updates.
36
+ ### DOM bindings
44
37
 
45
38
  ```ts
46
39
  import { signal, bindText, bindClass, bindAttr } from "@tinyfx/runtime";
@@ -51,132 +44,66 @@ const isActive = signal(false);
51
44
  const el = document.getElementById("counter");
52
45
  const btn = document.querySelector("button");
53
46
 
54
- bindText(el, () => `Count: ${count()}`);
55
- bindClass(btn, "active", isActive);
56
- bindAttr(btn, "disabled", () => count() > 10);
57
- ```
58
-
59
- ### Typed HTTP Client
60
-
61
- A thin wrapper over `fetch` that returns typed responses.
62
-
63
- ```ts
64
- import { createHttp } from "@tinyfx/runtime";
65
-
66
- interface User {
67
- id: number;
68
- name: string;
47
+ if (el && btn) {
48
+ bindText(el, () => `Count: ${count()}`);
49
+ bindClass(btn, "active", isActive);
50
+ bindAttr(btn, "disabled", () => count() > 10);
69
51
  }
70
-
71
- const api = createHttp({ baseUrl: "/api" });
72
-
73
- // Fully typed response
74
- const users = await api.get<User[]>("/users");
75
-
76
- // POST with data
77
- await api.post("/users", { name: "Alice" });
78
52
  ```
79
53
 
80
- ### DI Container (provide / inject)
81
-
82
- TinyFX includes a small dependency injection container to avoid importing singleton instances everywhere.
83
-
84
- Import it from `@tinyfx/runtime/di`:
54
+ ### HTTP client
85
55
 
86
56
  ```ts
87
- import { Container } from "@tinyfx/runtime/di";
88
-
89
- class Config {
90
- constructor(public baseUrl: string) {}
91
- }
92
-
93
- const container = new Container();
94
- container.provide(Config, new Config("/api"));
95
-
96
- const cfg = container.inject(Config);
97
- console.log(cfg.baseUrl);
98
- ```
99
-
100
- #### Using DI with tinyfx components
101
-
102
- When you use the tinyfx compiler, the generated glue calls your component behavior as:
57
+ import { createHttp } from "@tinyfx/runtime";
103
58
 
104
- ```ts
105
- init(el, { container })
59
+ const http = createHttp({ baseUrl: "/api" });
60
+ const users = await http.get<{ id: number; name: string }[]>("/users");
106
61
  ```
107
62
 
108
- So your component can inject services without importing global singletons:
63
+ ### TinyFxContext
109
64
 
110
65
  ```ts
111
- import type { TinyFxContext } from "@tinyfx/runtime/di";
112
- import { HttpService } from "../lib/http.service";
66
+ import type { TinyFxContext } from "@tinyfx/runtime";
113
67
 
114
68
  export function init(el: HTMLElement, ctx: TinyFxContext) {
115
- const http = ctx.container.inject(HttpService);
116
- http.getWeather().then((text) => {
117
- el.textContent = text;
118
- });
69
+ console.log(ctx.params);
70
+ ctx.navigate("/about");
119
71
  }
120
72
  ```
121
73
 
122
- #### Example: service that uses the HTTP helper
74
+ ### Router + lifecycle hooks
123
75
 
124
76
  ```ts
125
- // src/lib/http.service.ts
126
- import { createHttp } from "@tinyfx/runtime";
127
-
128
- export class HttpService {
129
- private readonly http;
77
+ import { initRouter, onMount, onDestroy } from "@tinyfx/runtime";
130
78
 
131
- constructor(baseUrl: string) {
132
- this.http = createHttp({ baseUrl });
133
- }
134
-
135
- getWeather() {
136
- // wttr.in can return plain text; your wrapper returns text when not JSON.
137
- return this.http.get<string>("");
138
- }
139
- }
140
- ```
141
-
142
- ```ts
143
- // src/main.ts
144
- import { Container } from "@tinyfx/runtime/di";
145
- import { setupComponents } from "./tinyfx.gen";
146
- import { HttpService } from "./lib/http.service";
79
+ initRouter({
80
+ "/": { page: "index", path: "/" },
81
+ });
147
82
 
148
- const container = new Container();
149
- container.provide(HttpService, new HttpService("https://wttr.in/tunisa"));
83
+ onMount(() => {
84
+ console.log("mounted");
85
+ });
150
86
 
151
- document.addEventListener("DOMContentLoaded", () => {
152
- setupComponents(container);
87
+ onDestroy(() => {
88
+ console.log("destroyed");
153
89
  });
154
90
  ```
155
91
 
156
- ## API Overview
157
-
158
- ### Signals
159
- - `signal<T>(value: T): Signal<T>`
160
- - `effect(fn: () => void): void`
161
-
162
- ### DOM
163
- - `bindText(el: HTMLElement, source: Signal<any> | (() => any)): void`
164
- - `bindAttr(el: HTMLElement, attr: string, source: Signal<any> | (() => any)): void`
165
- - `bindClass(el: HTMLElement, className: string, source: Signal<boolean> | (() => boolean)): void`
166
-
167
- ### HTTP
168
- - `createHttp(config: HttpConfig): HttpClient`
169
- - `client.get<T>(url: string): Promise<T>`
170
- - `client.post<T>(url: string, data?: any): Promise<T>`
171
- - `client.put<T>(url: string, data?: any): Promise<T>`
172
- - `client.del<T>(url: string): Promise<T>`
173
-
174
- ### DI
175
- - `new Container()`
176
- - `container.provide(token, instance)`
177
- - `container.inject(token)`
178
- - `createToken<T>(description: string): symbol`
179
- - `TinyFxContext` (passed to component init via tinyfx glue)
92
+ ## API overview
93
+
94
+ - `signal<T>(value: T)`
95
+ - `effect(fn)`
96
+ - `bindText(el, source)`
97
+ - `bindAttr(el, attr, source)`
98
+ - `bindClass(el, className, source)`
99
+ - `createHttp(config?)`
100
+ - `initRouter(routes)`
101
+ - `getParam(name)`
102
+ - `navigate(path)`
103
+ - `goBack()`
104
+ - `onMount(fn)`
105
+ - `onDestroy(fn)`
106
+ - `TinyFxContext`
180
107
 
181
108
  ## License
182
109
 
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { markMounted, isMounted } from "../mount-state";
3
+ describe("markMounted", () => {
4
+ it("returns true on first call", () => {
5
+ const el = document.createElement("div");
6
+ expect(markMounted(el)).toBe(true);
7
+ });
8
+ it("returns false on second call for same element", () => {
9
+ const el = document.createElement("div");
10
+ markMounted(el);
11
+ expect(markMounted(el)).toBe(false);
12
+ });
13
+ it("returns true for different elements", () => {
14
+ const el1 = document.createElement("div");
15
+ const el2 = document.createElement("div");
16
+ markMounted(el1);
17
+ expect(markMounted(el2)).toBe(true);
18
+ });
19
+ });
20
+ describe("isMounted", () => {
21
+ it("returns false for unmarked element", () => {
22
+ const el = document.createElement("div");
23
+ expect(isMounted(el)).toBe(false);
24
+ });
25
+ it("returns true after markMounted", () => {
26
+ const el = document.createElement("div");
27
+ markMounted(el);
28
+ expect(isMounted(el)).toBe(true);
29
+ });
30
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { registerPage, getPageModule, runPageInit } from "../page-registry";
3
+ describe("registerPage / getPageModule", () => {
4
+ it("returns undefined for unregistered page", () => {
5
+ expect(getPageModule("/unknown")).toBeUndefined();
6
+ });
7
+ it("returns module after registration", () => {
8
+ const mod = { init: () => { } };
9
+ registerPage("/test", mod);
10
+ expect(getPageModule("/test")).toBe(mod);
11
+ });
12
+ });
13
+ describe("runPageInit", () => {
14
+ it("calls init on page module", () => {
15
+ let called = false;
16
+ let receivedEl;
17
+ let receivedCtx;
18
+ registerPage("/init-test", {
19
+ init: (el, ctx) => {
20
+ called = true;
21
+ receivedEl = el;
22
+ receivedCtx = ctx;
23
+ },
24
+ });
25
+ const ctx = { params: { id: "1" }, navigate: () => { } };
26
+ runPageInit("/init-test", ctx);
27
+ expect(called).toBe(true);
28
+ expect(receivedEl).toBe(document.body);
29
+ expect(receivedCtx).toBe(ctx);
30
+ });
31
+ it("does nothing for unregistered page", () => {
32
+ expect(() => runPageInit("/nonexistent", { params: {}, navigate: () => { } })).not.toThrow();
33
+ });
34
+ it("does nothing for page without init", () => {
35
+ registerPage("/no-init", {});
36
+ expect(() => runPageInit("/no-init", { params: {}, navigate: () => { } })).not.toThrow();
37
+ });
38
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { matchPath, splitPath } from "../router/path-matcher";
3
+ describe("splitPath", () => {
4
+ it("splits root path", () => {
5
+ expect(splitPath("/")).toEqual([]);
6
+ });
7
+ it("splits nested path", () => {
8
+ expect(splitPath("/blog/hello-world")).toEqual(["blog", "hello-world"]);
9
+ });
10
+ it("handles trailing slash", () => {
11
+ expect(splitPath("/blog/")).toEqual(["blog"]);
12
+ });
13
+ });
14
+ describe("matchPath", () => {
15
+ it("matches static route exactly", () => {
16
+ const def = { staticSegments: ["blog"], paramNames: [] };
17
+ expect(matchPath(def, ["blog"])).toEqual({});
18
+ });
19
+ it("returns null for static mismatch", () => {
20
+ const def = { staticSegments: ["blog"], paramNames: [] };
21
+ expect(matchPath(def, ["about"])).toBeNull();
22
+ });
23
+ it("returns null for length mismatch", () => {
24
+ const def = { staticSegments: ["blog"], paramNames: [] };
25
+ expect(matchPath(def, ["blog", "extra"])).toBeNull();
26
+ });
27
+ it("extracts single param", () => {
28
+ const def = { staticSegments: ["blog", null], paramNames: ["slug"] };
29
+ expect(matchPath(def, ["blog", "hello-world"])).toEqual({ slug: "hello-world" });
30
+ });
31
+ it("extracts multiple params", () => {
32
+ const def = { staticSegments: [null, "posts", null], paramNames: ["user", "id"] };
33
+ expect(matchPath(def, ["alice", "posts", "42"])).toEqual({ user: "alice", id: "42" });
34
+ });
35
+ it("decodes URI params", () => {
36
+ const def = { staticSegments: ["blog", null], paramNames: ["slug"] };
37
+ expect(matchPath(def, ["blog", "hello%20world"])).toEqual({ slug: "hello world" });
38
+ });
39
+ it("matches root route", () => {
40
+ const def = { staticSegments: [], paramNames: [] };
41
+ expect(matchPath(def, [])).toEqual({});
42
+ });
43
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { registerComponent, getComponentFactory, mountComponents } from "../registry";
3
+ describe("registerComponent / getComponentFactory", () => {
4
+ beforeEach(() => {
5
+ registerComponent("__test_cleanup__", () => { });
6
+ });
7
+ it("returns undefined for unregistered component", () => {
8
+ expect(getComponentFactory("NonExistent")).toBeUndefined();
9
+ });
10
+ it("returns factory after registration", () => {
11
+ const factory = (el) => ({ mounted: true });
12
+ registerComponent("TestComp", factory);
13
+ expect(getComponentFactory("TestComp")).toBe(factory);
14
+ });
15
+ });
16
+ describe("mountComponents", () => {
17
+ it("mounts components found in root element", () => {
18
+ const root = document.createElement("div");
19
+ const el1 = document.createElement("div");
20
+ el1.setAttribute("data-component", "CompA");
21
+ const el2 = document.createElement("div");
22
+ el2.setAttribute("data-component", "CompB");
23
+ root.appendChild(el1);
24
+ root.appendChild(el2);
25
+ const results = [];
26
+ registerComponent("CompA", () => {
27
+ results.push("CompA");
28
+ return { name: "CompA" };
29
+ });
30
+ registerComponent("CompB", () => {
31
+ results.push("CompB");
32
+ return { name: "CompB" };
33
+ });
34
+ const ctx = { params: {}, navigate: () => { } };
35
+ const instances = mountComponents(ctx, root);
36
+ expect(results).toContain("CompA");
37
+ expect(results).toContain("CompB");
38
+ expect(instances).toHaveLength(2);
39
+ });
40
+ it("skips elements without data-component", () => {
41
+ const root = document.createElement("div");
42
+ const el = document.createElement("div");
43
+ root.appendChild(el);
44
+ const ctx = { params: {}, navigate: () => { } };
45
+ const instances = mountComponents(ctx, root);
46
+ expect(instances).toHaveLength(0);
47
+ });
48
+ it("skips unregistered component names", () => {
49
+ const root = document.createElement("div");
50
+ const el = document.createElement("div");
51
+ el.setAttribute("data-component", "Unknown");
52
+ root.appendChild(el);
53
+ const ctx = { params: {}, navigate: () => { } };
54
+ const instances = mountComponents(ctx, root);
55
+ expect(instances).toHaveLength(0);
56
+ });
57
+ it("does not include undefined returns in instances", () => {
58
+ const root = document.createElement("div");
59
+ const el = document.createElement("div");
60
+ el.setAttribute("data-component", "NoReturn");
61
+ root.appendChild(el);
62
+ registerComponent("NoReturn", () => undefined);
63
+ const ctx = { params: {}, navigate: () => { } };
64
+ const instances = mountComponents(ctx, root);
65
+ expect(instances).toHaveLength(0);
66
+ });
67
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Lightweight page context passed to page and component behavior.
3
+ *
4
+ * TinyFX uses this to expose route params and navigation helpers without a
5
+ * dependency-injection container.
6
+ *
7
+ * @example
8
+ * import type { TinyFxContext } from "@tinyfx/runtime";
9
+ *
10
+ * export function init(el: HTMLElement, ctx: TinyFxContext): void {
11
+ * console.log(ctx.params.slug);
12
+ * ctx.navigate("/");
13
+ * void el;
14
+ * }
15
+ */
16
+ export interface TinyFxContext {
17
+ params: Record<string, string>;
18
+ navigate: (path: string) => void;
19
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/dom.d.ts CHANGED
@@ -1,3 +1,38 @@
1
+ /**
2
+ * Binds an element's text content to a reactive getter.
3
+ *
4
+ * @param el - The DOM element whose text content should be updated
5
+ * @param get - A reactive getter that returns the next text value
6
+ * @returns Nothing
7
+ *
8
+ * @example
9
+ * const count = signal(0);
10
+ * bindText(span, () => `Count: ${count()}`);
11
+ */
1
12
  export declare function bindText(el: HTMLElement, get: () => any): void;
13
+ /**
14
+ * Binds an attribute to a reactive getter.
15
+ *
16
+ * @param el - The DOM element whose attribute should be updated
17
+ * @param attr - The attribute name to write
18
+ * @param get - A reactive getter that returns the attribute value
19
+ * @returns Nothing
20
+ *
21
+ * @example
22
+ * const isDark = signal(false);
23
+ * bindAttr(document.body, "data-theme", () => (isDark() ? "dark" : "light"));
24
+ */
2
25
  export declare function bindAttr(el: HTMLElement, attr: string, get: () => string): void;
26
+ /**
27
+ * Toggles a CSS class from a reactive boolean getter.
28
+ *
29
+ * @param el - The DOM element whose class list should be updated
30
+ * @param cls - The class name to toggle
31
+ * @param get - A reactive getter that returns whether the class should exist
32
+ * @returns Nothing
33
+ *
34
+ * @example
35
+ * const open = signal(false);
36
+ * bindClass(panel, "is-open", () => open());
37
+ */
3
38
  export declare function bindClass(el: HTMLElement, cls: string, get: () => boolean): void;
package/dist/dom.js CHANGED
@@ -1,16 +1,51 @@
1
1
  // @tinyfx/runtime — DOM helpers
2
2
  // Helpers for binding reactive state to the DOM.
3
3
  import { effect } from "./signals";
4
+ /**
5
+ * Binds an element's text content to a reactive getter.
6
+ *
7
+ * @param el - The DOM element whose text content should be updated
8
+ * @param get - A reactive getter that returns the next text value
9
+ * @returns Nothing
10
+ *
11
+ * @example
12
+ * const count = signal(0);
13
+ * bindText(span, () => `Count: ${count()}`);
14
+ */
4
15
  export function bindText(el, get) {
5
16
  effect(() => {
6
17
  el.textContent = String(get());
7
18
  });
8
19
  }
20
+ /**
21
+ * Binds an attribute to a reactive getter.
22
+ *
23
+ * @param el - The DOM element whose attribute should be updated
24
+ * @param attr - The attribute name to write
25
+ * @param get - A reactive getter that returns the attribute value
26
+ * @returns Nothing
27
+ *
28
+ * @example
29
+ * const isDark = signal(false);
30
+ * bindAttr(document.body, "data-theme", () => (isDark() ? "dark" : "light"));
31
+ */
9
32
  export function bindAttr(el, attr, get) {
10
33
  effect(() => {
11
34
  el.setAttribute(attr, get());
12
35
  });
13
36
  }
37
+ /**
38
+ * Toggles a CSS class from a reactive boolean getter.
39
+ *
40
+ * @param el - The DOM element whose class list should be updated
41
+ * @param cls - The class name to toggle
42
+ * @param get - A reactive getter that returns whether the class should exist
43
+ * @returns Nothing
44
+ *
45
+ * @example
46
+ * const open = signal(false);
47
+ * bindClass(panel, "is-open", () => open());
48
+ */
14
49
  export function bindClass(el, cls, get) {
15
50
  effect(() => {
16
51
  el.classList.toggle(cls, get());
package/dist/each.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Register a mounted component instance by root element.
3
+ */
4
+ export declare function registerInstance(el: HTMLElement, instance: {
5
+ destroy?: () => void;
6
+ }): void;
7
+ /**
8
+ * Destroy a mounted component instance on a node and nested component nodes.
9
+ */
10
+ export declare function destroyNode(el: HTMLElement): void;
package/dist/each.js ADDED
@@ -0,0 +1,24 @@
1
+ // @tinyfx/runtime — each lifecycle helpers
2
+ const __componentInstances = new WeakMap();
3
+ /**
4
+ * Register a mounted component instance by root element.
5
+ */
6
+ export function registerInstance(el, instance) {
7
+ __componentInstances.set(el, instance);
8
+ }
9
+ /**
10
+ * Destroy a mounted component instance on a node and nested component nodes.
11
+ */
12
+ export function destroyNode(el) {
13
+ var _a;
14
+ const inst = __componentInstances.get(el);
15
+ (_a = inst === null || inst === void 0 ? void 0 : inst.destroy) === null || _a === void 0 ? void 0 : _a.call(inst);
16
+ __componentInstances.delete(el);
17
+ el.querySelectorAll("[data-component]").forEach((child) => {
18
+ var _a;
19
+ const childEl = child;
20
+ const childInst = __componentInstances.get(childEl);
21
+ (_a = childInst === null || childInst === void 0 ? void 0 : childInst.destroy) === null || _a === void 0 ? void 0 : _a.call(childInst);
22
+ __componentInstances.delete(childEl);
23
+ });
24
+ }