@adaas/are-html 0.0.23 → 0.0.25

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 (81) hide show
  1. package/dist/browser/index.d.mts +18 -2
  2. package/dist/browser/index.mjs +35 -10
  3. package/dist/browser/index.mjs.map +1 -1
  4. package/dist/node/directives/AreDirectiveIf.directive.d.mts +17 -1
  5. package/dist/node/directives/AreDirectiveIf.directive.d.ts +17 -1
  6. package/dist/node/directives/AreDirectiveIf.directive.js +29 -6
  7. package/dist/node/directives/AreDirectiveIf.directive.js.map +1 -1
  8. package/dist/node/directives/AreDirectiveIf.directive.mjs +29 -6
  9. package/dist/node/directives/AreDirectiveIf.directive.mjs.map +1 -1
  10. package/dist/node/engine/AreHTML.compiler.d.mts +3 -1
  11. package/dist/node/engine/AreHTML.compiler.d.ts +3 -1
  12. package/dist/node/engine/AreHTML.compiler.js +7 -4
  13. package/dist/node/engine/AreHTML.compiler.js.map +1 -1
  14. package/dist/node/engine/AreHTML.compiler.mjs +7 -4
  15. package/dist/node/engine/AreHTML.compiler.mjs.map +1 -1
  16. package/examples/for-perf/dist/index.html +1 -1
  17. package/examples/for-perf/dist/{mqj1mpf2-z4aokv.js → mqp8i2py-vltsx0.js} +2488 -2373
  18. package/examples/lazy-loading/README.md +76 -0
  19. package/examples/lazy-loading/concept.ts +55 -0
  20. package/examples/lazy-loading/containers/UI.container.ts +215 -0
  21. package/examples/lazy-loading/dist/app.js +3803 -0
  22. package/examples/{for-perf/dist/mqj1mpff-4fr7mw.js → lazy-loading/dist/chunks/chunk-6K72IBO4.js} +2688 -5897
  23. package/examples/lazy-loading/dist/index.html +36 -0
  24. package/examples/lazy-loading/dist/lazy/about-page.js +59 -0
  25. package/examples/lazy-loading/dist/lazy/reports-page.js +65 -0
  26. package/examples/lazy-loading/dist/lazy/settings-page.js +54 -0
  27. package/examples/lazy-loading/public/index.html +36 -0
  28. package/examples/lazy-loading/src/components/AppShell.component.ts +44 -0
  29. package/examples/lazy-loading/src/components/HomePage.component.ts +59 -0
  30. package/examples/lazy-loading/src/components/LazyOutlet.component.ts +108 -0
  31. package/examples/lazy-loading/src/components/NavBar.component.ts +98 -0
  32. package/examples/lazy-loading/src/concept.ts +116 -0
  33. package/examples/lazy-loading/src/lazy/AboutPage.component.ts +54 -0
  34. package/examples/lazy-loading/src/lazy/ReportsPage.component.ts +56 -0
  35. package/examples/lazy-loading/src/lazy/SettingsPage.component.ts +45 -0
  36. package/examples/lazy-loading/src/runtime/ComponentManifest.fragment.ts +61 -0
  37. package/examples/lazy-loading/src/runtime/LazyComponentResolver.fragment.ts +77 -0
  38. package/examples/os-desktop/README.md +91 -0
  39. package/examples/os-desktop/concept.ts +54 -0
  40. package/examples/os-desktop/containers/OS.container.ts +198 -0
  41. package/examples/os-desktop/containers/apps/AppBackend.ts +29 -0
  42. package/examples/os-desktop/containers/apps/GanttApp.backend.ts +56 -0
  43. package/examples/os-desktop/containers/apps/MarketingApp.backend.ts +68 -0
  44. package/examples/os-desktop/dist/app.js +4410 -0
  45. package/examples/os-desktop/dist/apps/gantt/app.js +271 -0
  46. package/examples/os-desktop/dist/apps/marketing/app.js +346 -0
  47. package/examples/os-desktop/dist/chunks/chunk-6K72IBO4.js +12455 -0
  48. package/examples/os-desktop/dist/chunks/chunk-EIIGUL6N.js +30 -0
  49. package/examples/os-desktop/dist/chunks/chunk-WOH7L5UR.js +30 -0
  50. package/examples/os-desktop/dist/index.html +33 -0
  51. package/examples/os-desktop/public/index.html +33 -0
  52. package/examples/os-desktop/src/apps/gantt/GanttApp.component.ts +41 -0
  53. package/examples/os-desktop/src/apps/gantt/GanttChart.component.ts +126 -0
  54. package/examples/os-desktop/src/apps/gantt/GanttStore.ts +47 -0
  55. package/examples/os-desktop/src/apps/gantt/GanttToolbar.component.ts +73 -0
  56. package/examples/os-desktop/src/apps/gantt/index.ts +13 -0
  57. package/examples/os-desktop/src/apps/marketing/MarketingApp.component.ts +53 -0
  58. package/examples/os-desktop/src/apps/marketing/MarketingStore.ts +34 -0
  59. package/examples/os-desktop/src/apps/marketing/PostEditor.component.ts +153 -0
  60. package/examples/os-desktop/src/apps/marketing/PostPreview.component.ts +110 -0
  61. package/examples/os-desktop/src/apps/marketing/index.ts +16 -0
  62. package/examples/os-desktop/src/concept.ts +126 -0
  63. package/examples/os-desktop/src/os/AppStage.component.ts +112 -0
  64. package/examples/os-desktop/src/os/AppWindow.component.ts +102 -0
  65. package/examples/os-desktop/src/os/Desktop.component.ts +106 -0
  66. package/examples/os-desktop/src/os/Dock.component.ts +174 -0
  67. package/examples/os-desktop/src/os/Hud.component.ts +83 -0
  68. package/examples/os-desktop/src/os/Launchpad.component.ts +191 -0
  69. package/examples/os-desktop/src/os/MenuBar.component.ts +156 -0
  70. package/examples/os-desktop/src/runtime/AppComponentResolver.fragment.ts +121 -0
  71. package/examples/os-desktop/src/runtime/AppRegistry.fragment.ts +104 -0
  72. package/examples/os-desktop/src/signals/MouseState.signal.ts +34 -0
  73. package/examples/os-desktop/src/signals/OSRoute.signal.ts +37 -0
  74. package/examples/os-desktop/src/signals/SelectionState.signal.ts +34 -0
  75. package/examples/signal-routing/dist/index.html +1 -1
  76. package/examples/signal-routing/dist/{mqiwo23h-bhcolu.js → mqp8hgce-4d6rh0.js} +2911 -2708
  77. package/package.json +11 -7
  78. package/src/directives/AreDirectiveIf.directive.ts +33 -4
  79. package/src/engine/AreHTML.compiler.ts +12 -2
  80. package/tests/PropPropagation.test.ts +181 -0
  81. package/tests/jest.setup.ts +11 -0
@@ -0,0 +1,36 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>ARE · Lazy Loading</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+ body { background: #09090b; color: #f4f4f5; }
10
+
11
+ /* The mount root is a real block-level container. */
12
+ are-root { display: block; }
13
+
14
+ /*
15
+ * ARE wraps every component (and interpolated text) in a custom tag:
16
+ * <app-shell><div class="shell">… · <nav-bar><aside class="nav">…
17
+ * Left as default `display: inline`, those wrappers — and the empty
18
+ * whitespace <are-text> nodes between siblings — become the actual
19
+ * grid/flex items, so layout lands on the wrapper instead of the styled
20
+ * inner element and stray <are-text> items break the grid columns.
21
+ *
22
+ * `display: contents` makes each wrapper's box disappear while keeping its
23
+ * children in the parent's flow, so `.shell`'s grid sees `.nav` and `main`
24
+ * directly, and empty <are-text> nodes contribute nothing.
25
+ */
26
+ app-shell, nav-bar, lazy-outlet, are-text,
27
+ home-page, about-page, settings-page, reports-page { display: contents; }
28
+ </style>
29
+ </head>
30
+ <body>
31
+ <!-- Outer ARE root: renders the persistent AppShell (nav + dynamic outlet). -->
32
+ <are-root id="app"><app-shell></app-shell></are-root>
33
+
34
+ <script type="module" src="./app.js"></script>
35
+ </body>
36
+ </html>
@@ -0,0 +1,59 @@
1
+ import {
2
+ Are,
3
+ Yt,
4
+ __decorateClass,
5
+ __decorateParam,
6
+ __name,
7
+ te
8
+ } from "../chunks/chunk-6K72IBO4.js";
9
+
10
+ // examples/lazy-loading/src/lazy/AboutPage.component.ts
11
+ var _AboutPage = class _AboutPage extends Are {
12
+ template(node) {
13
+ node.setContent(`
14
+ <section class="page">
15
+ <span class="eyebrow">Lazy \xB7 fetched on demand</span>
16
+ <h1 class="page-title">About this example</h1>
17
+ <p class="page-lead">
18
+ You just triggered a dynamic <code>import('/lazy/about-page.js')</code>.
19
+ The class was registered into the running scope and rendered into the
20
+ outlet \u2014 no page reload, and it wasn't in the initial bundle.
21
+ </p>
22
+ <div class="grid">
23
+ <div class="card"><h3>Code-split</h3><p>The app and every lazy bundle share framework chunks, so this class <code>extends</code> the same <code>Are</code> runtime.</p></div>
24
+ <div class="card"><h3>Backend-driven</h3><p>The server's <code>/api/components</code> manifest decided this page exists and where to fetch it.</p></div>
25
+ <div class="card"><h3>Cached</h3><p>Revisit /about \u2014 no second network request. The manifest marks it loaded.</p></div>
26
+ </div>
27
+ </section>
28
+ `);
29
+ }
30
+ styles(node) {
31
+ node.setStyles(`
32
+ .page { padding: 48px 56px; max-width: 860px; }
33
+ .eyebrow { display: inline-block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; padding: 4px 10px; border-radius: 999px; margin-bottom: 20px; color: #fcd34d; background: rgba(252, 211, 77, 0.12); }
34
+ .page-title { font-size: 32px; font-weight: 800; color: #f4f4f5; margin-bottom: 14px; letter-spacing: -0.02em; }
35
+ .page-lead { font-size: 16px; color: #a1a1aa; line-height: 1.7; margin-bottom: 28px; }
36
+ code { background: #27272a; padding: 2px 6px; border-radius: 4px; color: #a78bfa; font-size: 13px; }
37
+ .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
38
+ .card { background: #1c1c1f; border: 1px solid #27272a; border-radius: 12px; padding: 20px; }
39
+ .card h3 { font-size: 15px; font-weight: 600; color: #f4f4f5; margin-bottom: 8px; }
40
+ .card p { font-size: 13px; color: #71717a; line-height: 1.6; }
41
+ .card code { font-size: 12px; }
42
+ `);
43
+ }
44
+ };
45
+ __name(_AboutPage, "AboutPage");
46
+ __decorateClass([
47
+ Are.Template,
48
+ __decorateParam(0, Yt(te))
49
+ ], _AboutPage.prototype, "template", 1);
50
+ __decorateClass([
51
+ Are.Styles,
52
+ __decorateParam(0, Yt(te))
53
+ ], _AboutPage.prototype, "styles", 1);
54
+ var AboutPage = _AboutPage;
55
+ var AboutPage_component_default = AboutPage;
56
+ export {
57
+ AboutPage,
58
+ AboutPage_component_default as default
59
+ };
@@ -0,0 +1,65 @@
1
+ import {
2
+ Are,
3
+ Yt,
4
+ __decorateClass,
5
+ __decorateParam,
6
+ __name,
7
+ te
8
+ } from "../chunks/chunk-6K72IBO4.js";
9
+
10
+ // examples/lazy-loading/src/lazy/ReportsPage.component.ts
11
+ var _ReportsPage = class _ReportsPage extends Are {
12
+ template(node) {
13
+ node.setContent(`
14
+ <section class="page">
15
+ <span class="eyebrow">Lazy \xB7 fetched on demand</span>
16
+ <h1 class="page-title">Reports</h1>
17
+ <p class="page-lead">Heavy, rarely-visited views are ideal lazy-load candidates \u2014 they stay out of the critical path.</p>
18
+ <div class="stats">
19
+ <div class="stat"><div class="stat-value">1,284</div><div class="stat-label">Components served</div></div>
20
+ <div class="stat"><div class="stat-value">96 ms</div><div class="stat-label">Avg load time</div></div>
21
+ <div class="stat"><div class="stat-value">3</div><div class="stat-label">Lazy bundles</div></div>
22
+ <div class="stat"><div class="stat-value">100%</div><div class="stat-label">Cache hit on revisit</div></div>
23
+ </div>
24
+ <div class="bars">
25
+ <div class="bar" style="height: 38%"></div>
26
+ <div class="bar" style="height: 62%"></div>
27
+ <div class="bar" style="height: 48%"></div>
28
+ <div class="bar" style="height: 81%"></div>
29
+ <div class="bar" style="height: 55%"></div>
30
+ <div class="bar" style="height: 72%"></div>
31
+ <div class="bar" style="height: 90%"></div>
32
+ </div>
33
+ </section>
34
+ `);
35
+ }
36
+ styles(node) {
37
+ node.setStyles(`
38
+ .page { padding: 48px 56px; max-width: 860px; }
39
+ .eyebrow { display: inline-block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; padding: 4px 10px; border-radius: 999px; margin-bottom: 20px; color: #fcd34d; background: rgba(252, 211, 77, 0.12); }
40
+ .page-title { font-size: 32px; font-weight: 800; color: #f4f4f5; margin-bottom: 14px; letter-spacing: -0.02em; }
41
+ .page-lead { font-size: 16px; color: #a1a1aa; line-height: 1.7; margin-bottom: 28px; }
42
+ .stats { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 16px; margin-bottom: 32px; }
43
+ .stat { background: #1c1c1f; border: 1px solid #27272a; border-radius: 12px; padding: 20px; }
44
+ .stat-value { font-size: 26px; font-weight: 800; color: #a78bfa; }
45
+ .stat-label { font-size: 12px; color: #71717a; margin-top: 6px; }
46
+ .bars { display: flex; align-items: flex-end; gap: 12px; height: 180px; padding: 20px; background: #1c1c1f; border: 1px solid #27272a; border-radius: 12px; }
47
+ .bar { flex: 1; background: linear-gradient(180deg, #a78bfa, #7c3aed); border-radius: 6px 6px 0 0; }
48
+ `);
49
+ }
50
+ };
51
+ __name(_ReportsPage, "ReportsPage");
52
+ __decorateClass([
53
+ Are.Template,
54
+ __decorateParam(0, Yt(te))
55
+ ], _ReportsPage.prototype, "template", 1);
56
+ __decorateClass([
57
+ Are.Styles,
58
+ __decorateParam(0, Yt(te))
59
+ ], _ReportsPage.prototype, "styles", 1);
60
+ var ReportsPage = _ReportsPage;
61
+ var ReportsPage_component_default = ReportsPage;
62
+ export {
63
+ ReportsPage,
64
+ ReportsPage_component_default as default
65
+ };
@@ -0,0 +1,54 @@
1
+ import {
2
+ Are,
3
+ Yt,
4
+ __decorateClass,
5
+ __decorateParam,
6
+ __name,
7
+ te
8
+ } from "../chunks/chunk-6K72IBO4.js";
9
+
10
+ // examples/lazy-loading/src/lazy/SettingsPage.component.ts
11
+ var _SettingsPage = class _SettingsPage extends Are {
12
+ template(node) {
13
+ node.setContent(`
14
+ <section class="page">
15
+ <span class="eyebrow">Lazy \xB7 fetched on demand</span>
16
+ <h1 class="page-title">Settings</h1>
17
+ <p class="page-lead">A second independently-served bundle. It shares the framework runtime with the app via code-split chunks.</p>
18
+ <div class="settings">
19
+ <label class="row"><span>Dark theme</span><input type="checkbox" checked /></label>
20
+ <label class="row"><span>Compact density</span><input type="checkbox" /></label>
21
+ <label class="row"><span>Telemetry</span><input type="checkbox" /></label>
22
+ <label class="row"><span>Auto-update components</span><input type="checkbox" checked /></label>
23
+ </div>
24
+ </section>
25
+ `);
26
+ }
27
+ styles(node) {
28
+ node.setStyles(`
29
+ .page { padding: 48px 56px; max-width: 640px; }
30
+ .eyebrow { display: inline-block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; padding: 4px 10px; border-radius: 999px; margin-bottom: 20px; color: #fcd34d; background: rgba(252, 211, 77, 0.12); }
31
+ .page-title { font-size: 32px; font-weight: 800; color: #f4f4f5; margin-bottom: 14px; letter-spacing: -0.02em; }
32
+ .page-lead { font-size: 16px; color: #a1a1aa; line-height: 1.7; margin-bottom: 28px; }
33
+ .settings { display: flex; flex-direction: column; gap: 2px; border: 1px solid #27272a; border-radius: 12px; overflow: hidden; }
34
+ .row { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; background: #1c1c1f; font-size: 14px; color: #e4e4e7; }
35
+ .row + .row { border-top: 1px solid #27272a; }
36
+ .row input { width: 18px; height: 18px; accent-color: #a78bfa; }
37
+ `);
38
+ }
39
+ };
40
+ __name(_SettingsPage, "SettingsPage");
41
+ __decorateClass([
42
+ Are.Template,
43
+ __decorateParam(0, Yt(te))
44
+ ], _SettingsPage.prototype, "template", 1);
45
+ __decorateClass([
46
+ Are.Styles,
47
+ __decorateParam(0, Yt(te))
48
+ ], _SettingsPage.prototype, "styles", 1);
49
+ var SettingsPage = _SettingsPage;
50
+ var SettingsPage_component_default = SettingsPage;
51
+ export {
52
+ SettingsPage,
53
+ SettingsPage_component_default as default
54
+ };
@@ -0,0 +1,36 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>ARE · Lazy Loading</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+ body { background: #09090b; color: #f4f4f5; }
10
+
11
+ /* The mount root is a real block-level container. */
12
+ are-root { display: block; }
13
+
14
+ /*
15
+ * ARE wraps every component (and interpolated text) in a custom tag:
16
+ * <app-shell><div class="shell">… · <nav-bar><aside class="nav">…
17
+ * Left as default `display: inline`, those wrappers — and the empty
18
+ * whitespace <are-text> nodes between siblings — become the actual
19
+ * grid/flex items, so layout lands on the wrapper instead of the styled
20
+ * inner element and stray <are-text> items break the grid columns.
21
+ *
22
+ * `display: contents` makes each wrapper's box disappear while keeping its
23
+ * children in the parent's flow, so `.shell`'s grid sees `.nav` and `main`
24
+ * directly, and empty <are-text> nodes contribute nothing.
25
+ */
26
+ app-shell, nav-bar, lazy-outlet, are-text,
27
+ home-page, about-page, settings-page, reports-page { display: contents; }
28
+ </style>
29
+ </head>
30
+ <body>
31
+ <!-- Outer ARE root: renders the persistent AppShell (nav + dynamic outlet). -->
32
+ <are-root id="app"><app-shell></app-shell></are-root>
33
+
34
+ <script type="module" src="./app.js"></script>
35
+ </body>
36
+ </html>
@@ -0,0 +1,44 @@
1
+ import { A_Caller, A_Inject } from "@adaas/a-concept";
2
+ import { Are, AreNode } from "@adaas/are";
3
+ import { AreHTMLNode } from "src";
4
+
5
+
6
+ /**
7
+ * Persistent application shell: the sidebar nav + the dynamic `<lazy-outlet>`.
8
+ *
9
+ * `<lazy-outlet>` is this example's dynamic slot. It plays the role `AreRoot`
10
+ * plays in the signal-routing example, but it can also fetch + register a
11
+ * component that wasn't in the initial bundle before rendering it.
12
+ */
13
+ export class AppShell extends Are {
14
+
15
+ @Are.Template
16
+ template(@A_Inject(A_Caller) node: AreNode) {
17
+ node.setContent(`
18
+ <div class="shell">
19
+ <nav-bar></nav-bar>
20
+ <main class="shell-main">
21
+ <lazy-outlet></lazy-outlet>
22
+ </main>
23
+ </div>
24
+ `);
25
+ }
26
+
27
+ @Are.Styles
28
+ styles(@A_Inject(A_Caller) node: AreHTMLNode) {
29
+ node.setStyles(`
30
+ .shell {
31
+ display: grid;
32
+ grid-template-columns: 240px 1fr;
33
+ min-height: 100vh;
34
+ background: #09090b;
35
+ color: #f4f4f5;
36
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
37
+ }
38
+ .shell-main {
39
+ padding: 0;
40
+ overflow: auto;
41
+ }
42
+ `);
43
+ }
44
+ }
@@ -0,0 +1,59 @@
1
+ import { A_Caller, A_Inject } from "@adaas/a-concept";
2
+ import { Are, AreNode } from "@adaas/are";
3
+ import { AreHTMLNode } from "src";
4
+
5
+
6
+ /**
7
+ * Home page — EAGER. It ships in the initial app bundle and is registered at
8
+ * bootstrap, so it paints with no extra network request.
9
+ */
10
+ export class HomePage extends Are {
11
+
12
+ @Are.Template
13
+ template(@A_Inject(A_Caller) node: AreNode) {
14
+ node.setContent(`
15
+ <section class="page">
16
+ <span class="eyebrow eyebrow-eager">Eager · bundled</span>
17
+ <h1 class="page-title">Runtime Lazy Component Loading</h1>
18
+ <p class="page-lead">
19
+ This dashboard ships with only the shell, the navigation and this
20
+ home page. The other pages are <strong>separate JS bundles served by
21
+ the backend</strong> and fetched the first time you open them.
22
+ </p>
23
+ <ol class="steps">
24
+ <li><strong>Discover</strong> — the app fetches <code>/api/components</code> to learn which components exist and where to load them.</li>
25
+ <li><strong>Fetch</strong> — on first navigation the matching bundle is loaded via dynamic <code>import()</code>.</li>
26
+ <li><strong>Register</strong> — the returned class is registered into the live scope (<code>scope.register</code>).</li>
27
+ <li><strong>Render</strong> — the outlet builds + mounts the subtree; the new tag is now recognized.</li>
28
+ </ol>
29
+ <p class="hint">Open DevTools → Network and click a <em>Lazy</em> page to watch its bundle load once.</p>
30
+ </section>
31
+ `);
32
+ }
33
+
34
+ @Are.Styles
35
+ styles(@A_Inject(A_Caller) node: AreHTMLNode) {
36
+ node.setStyles(`
37
+ .page { padding: 48px 56px; max-width: 820px; }
38
+ .eyebrow {
39
+ display: inline-block;
40
+ font-size: 11px;
41
+ font-weight: 700;
42
+ text-transform: uppercase;
43
+ letter-spacing: 0.06em;
44
+ padding: 4px 10px;
45
+ border-radius: 999px;
46
+ margin-bottom: 20px;
47
+ }
48
+ .eyebrow-eager { color: #34d399; background: rgba(52, 211, 153, 0.12); }
49
+ .page-title { font-size: 34px; font-weight: 800; color: #f4f4f5; margin-bottom: 16px; letter-spacing: -0.02em; }
50
+ .page-lead { font-size: 17px; color: #a1a1aa; line-height: 1.7; margin-bottom: 28px; }
51
+ .page-lead strong { color: #f4f4f5; }
52
+ .steps { color: #d4d4d8; font-size: 15px; line-height: 1.8; padding-left: 22px; display: flex; flex-direction: column; gap: 6px; }
53
+ .steps strong { color: #a78bfa; }
54
+ code { background: #27272a; padding: 2px 6px; border-radius: 4px; color: #a78bfa; font-size: 13px; }
55
+ .hint { margin-top: 28px; font-size: 13px; color: #71717a; }
56
+ .hint em { color: #fcd34d; font-style: normal; }
57
+ `);
58
+ }
59
+ }
@@ -0,0 +1,108 @@
1
+ import { A_Caller, A_Inject } from "@adaas/a-concept";
2
+ import { A_Logger } from "@adaas/a-utils/a-logger";
3
+ import { Are, AreNode, AreSignalsContext } from "@adaas/are";
4
+ import { ComponentManifest } from "../runtime/ComponentManifest.fragment";
5
+
6
+
7
+ /**
8
+ * LazyOutlet — the route-driven slot.
9
+ *
10
+ * Thanks to the engine primitives this is now tiny: it only decides WHICH tag
11
+ * to show for the current route and asks the node to (re)render. It does NOT
12
+ * fetch, import or register anything — when the engine hits an unregistered
13
+ * lazy tag during render it consults the app's `LazyComponentResolver`
14
+ * (an `AreComponentResolver`), which imports the bundle; the engine then
15
+ * registers the class globally. Teardown + build are the engine's `clear()` /
16
+ * `render()` primitives, so there is no copy-pasted lifecycle loop and no
17
+ * destroy-ordering hazard here anymore.
18
+ *
19
+ * [!] Production apps should prefer `<are-root>` for routing: it adds
20
+ * signal-aware teardown (unsubscribe + LRU cache of detached subtrees) on top
21
+ * of the same primitives. This outlet reproduces just the unsubscribe step so
22
+ * the swapped-out subtree stops receiving route signals (otherwise a detached
23
+ * subtree keeps reacting to the bus and throws on its torn-down scope).
24
+ */
25
+ export class LazyOutlet extends Are {
26
+
27
+ @Are.Template
28
+ template(
29
+ @A_Inject(A_Caller) node: AreNode,
30
+ @A_Inject(ComponentManifest) manifest: ComponentManifest,
31
+ ) {
32
+ // INITIAL render (supports deep links, e.g. a hard refresh on /about).
33
+ // Only set the content tag — the engine's load pipeline builds this
34
+ // outlet's children right after template() returns and will resolve the
35
+ // (possibly lazy) component via the registered AreComponentResolver.
36
+ const route = document.location.pathname || '/';
37
+ const entry = manifest.match(route) ?? manifest.match('/');
38
+
39
+ if (entry) {
40
+ node.setContent(`<${entry.tag}></${entry.tag}>`);
41
+ }
42
+ }
43
+
44
+ @Are.Signal
45
+ async onSignal(
46
+ @A_Inject(A_Caller) node: AreNode,
47
+ @A_Inject(A_Logger) logger: A_Logger,
48
+ @A_Inject(ComponentManifest) manifest: ComponentManifest,
49
+ @A_Inject(AreSignalsContext) signalsContext?: AreSignalsContext,
50
+ ) {
51
+ // RUNTIME route change. NavBar pushState()s before dispatching the route
52
+ // signal, so the live location already reflects the destination.
53
+ const route = document.location.pathname || '/';
54
+ const entry = manifest.match(route) ?? manifest.match('/');
55
+
56
+ if (!entry) {
57
+ logger.warning(`LazyOutlet: no component mapped to route "${route}".`);
58
+ return;
59
+ }
60
+
61
+ // Already showing the target component — nothing to do.
62
+ if (node.children[0]?.type === entry.tag) {
63
+ return;
64
+ }
65
+
66
+ // Unsubscribe the outgoing subtree from the signal bus BEFORE tearing it
67
+ // down. Without this the detached subtree keeps receiving route signals
68
+ // and reacts on a scope that no longer exists. (AreRoot does this too.)
69
+ if (signalsContext) {
70
+ for (const child of [...node.children]) {
71
+ for (const subscriber of this.collectSubscribers(child, signalsContext)) {
72
+ signalsContext.unsubscribe(subscriber);
73
+ }
74
+ }
75
+ }
76
+
77
+ // Full swap via the engine primitives: tear down the old subtree, set
78
+ // the new tag, build + mount. The engine resolves a lazy class through
79
+ // the AreComponentResolver during render() if it isn't registered yet.
80
+ await node.clear();
81
+ node.setContent(`<${entry.tag}></${entry.tag}>`);
82
+ await node.render();
83
+ }
84
+
85
+ /**
86
+ * Walk a subtree and collect the nodes currently subscribed to the signal
87
+ * bus, so they can be unsubscribed before the subtree is detached.
88
+ */
89
+ protected collectSubscribers(
90
+ node: AreNode,
91
+ signalsContext: AreSignalsContext,
92
+ ): AreNode[] {
93
+ const result: AreNode[] = [];
94
+ const queue: AreNode[] = [node];
95
+
96
+ while (queue.length > 0) {
97
+ const current = queue.shift()!;
98
+
99
+ if (signalsContext.subscribers.has(current)) {
100
+ result.push(current);
101
+ }
102
+
103
+ queue.push(...current.children);
104
+ }
105
+
106
+ return result;
107
+ }
108
+ }
@@ -0,0 +1,98 @@
1
+ import { A_Caller, A_Inject } from "@adaas/a-concept";
2
+ import { A_SignalBus } from "@adaas/a-utils/a-signal";
3
+ import { Are, AreEvent, AreNode } from "@adaas/are";
4
+ import { AreHTMLNode } from "src";
5
+ import { AreRoute } from "@adaas/are-html/signals/AreRoute.signal";
6
+
7
+
8
+ /**
9
+ * Sidebar navigation. Each link dispatches an `AreRoute` signal on the bus;
10
+ * `LazyOutlet` picks it up and swaps (lazy-loading if necessary) the page.
11
+ *
12
+ * The "Lazy" badge marks pages whose code is NOT in the initial bundle — open
13
+ * the network tab and watch the corresponding `.js` request fire the first time
14
+ * you click one.
15
+ */
16
+ export class NavBar extends Are {
17
+
18
+ @Are.Template
19
+ template(@A_Inject(A_Caller) node: AreNode) {
20
+ node.setContent(`
21
+ <aside class="nav">
22
+ <div class="nav-brand">ARE · Lazy Loading</div>
23
+ <nav class="nav-links">
24
+ <a href="/" @click="$navigate('/')">Home</a>
25
+ <a href="/about" @click="$navigate('/about')">About <span class="badge">Lazy</span></a>
26
+ <a href="/settings" @click="$navigate('/settings')">Settings <span class="badge">Lazy</span></a>
27
+ <a href="/reports" @click="$navigate('/reports')">Reports <span class="badge">Lazy</span></a>
28
+ </nav>
29
+ <div class="nav-foot">Pages tagged <span class="badge">Lazy</span> are fetched on first visit.</div>
30
+ </aside>
31
+ `);
32
+ }
33
+
34
+ @Are.Styles
35
+ styles(@A_Inject(A_Caller) node: AreHTMLNode) {
36
+ node.setStyles(`
37
+ .nav {
38
+ display: flex;
39
+ flex-direction: column;
40
+ gap: 6px;
41
+ padding: 24px 16px;
42
+ background: #18181b;
43
+ border-right: 1px solid #27272a;
44
+ }
45
+ .nav-brand {
46
+ font-size: 15px;
47
+ font-weight: 700;
48
+ color: #a78bfa;
49
+ letter-spacing: -0.02em;
50
+ margin-bottom: 16px;
51
+ }
52
+ .nav-links { display: flex; flex-direction: column; gap: 4px; }
53
+ .nav-links a {
54
+ display: flex;
55
+ align-items: center;
56
+ justify-content: space-between;
57
+ padding: 9px 14px;
58
+ border-radius: 8px;
59
+ text-decoration: none;
60
+ color: #a1a1aa;
61
+ font-size: 14px;
62
+ font-weight: 500;
63
+ transition: background 0.15s, color 0.15s;
64
+ }
65
+ .nav-links a:hover { background: #27272a; color: #f4f4f5; }
66
+ .badge {
67
+ font-size: 10px;
68
+ font-weight: 700;
69
+ text-transform: uppercase;
70
+ letter-spacing: 0.04em;
71
+ color: #fcd34d;
72
+ background: rgba(252, 211, 77, 0.12);
73
+ padding: 2px 6px;
74
+ border-radius: 999px;
75
+ }
76
+ .nav-foot {
77
+ margin-top: auto;
78
+ font-size: 11px;
79
+ color: #52525b;
80
+ line-height: 1.5;
81
+ padding: 12px 14px;
82
+ }
83
+ `);
84
+ }
85
+
86
+ @Are.EventHandler
87
+ navigate(
88
+ @A_Inject(AreEvent) event: AreEvent,
89
+ @A_Inject(A_SignalBus) bus: A_SignalBus,
90
+ ) {
91
+ const e = event.get('native') as MouseEvent;
92
+ e?.preventDefault();
93
+
94
+ const path = event.get('args')?.[0] as string ?? '/';
95
+ history.pushState({}, '', path);
96
+ bus.next(new AreRoute(path));
97
+ }
98
+ }