@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,198 @@
1
+ import { A_Concept, A_Inject } from "@adaas/a-concept";
2
+ import { A_Config } from "@adaas/a-utils/a-config";
3
+ import { A_Logger } from "@adaas/a-utils/a-logger";
4
+ import { A_Service } from "@adaas/a-utils/a-service";
5
+ import { build } from "esbuild";
6
+ import fs from "fs";
7
+ import http from "http";
8
+ import path from "path";
9
+ import { AppBackend } from "./apps/AppBackend";
10
+ import { MarketingAppBackend } from "./apps/MarketingApp.backend";
11
+ import { GanttAppBackend } from "./apps/GanttApp.backend";
12
+
13
+
14
+ /**
15
+ * OS.container — the "kernel" of the OS-desktop example.
16
+ *
17
+ * It is a backend service that:
18
+ * 1. builds the OS shell bundle AND every installed app's bundle in ONE
19
+ * code-split esbuild pass (so the shell and all apps share the SAME
20
+ * framework runtime singletons — required for `import()`ed app classes to
21
+ * `extend` the same `Are`/DI context the shell runs),
22
+ * 2. serves the static SPA (with deep-link fallback to index.html),
23
+ * 3. advertises the installable apps at `GET /api/apps`, and
24
+ * 4. delegates `/apps/<id>/api/*` to that app's OWN backend.
25
+ *
26
+ * Apps are registered ONLY by adding their backend to {@link backends} — its
27
+ * descriptor drives the build entry, the `/api/apps` catalogue, and the API
28
+ * routing all at once.
29
+ */
30
+ export class OSContainer extends A_Service {
31
+
32
+ protected server!: http.Server;
33
+
34
+ /** Every installable app = one backend (descriptor + bundle entry + API). */
35
+ protected backends: AppBackend[] = [
36
+ new MarketingAppBackend(),
37
+ new GanttAppBackend(),
38
+ ];
39
+
40
+ @A_Concept.Build()
41
+ async build(
42
+ @A_Inject(A_Logger) logger: A_Logger,
43
+ ): Promise<void> {
44
+ logger.log('Building OS Desktop example...');
45
+
46
+ const distDir = path.resolve(__dirname, "../dist");
47
+ const shellEntry = path.resolve(__dirname, "../src/concept.ts");
48
+
49
+ if (fs.existsSync(distDir)) {
50
+ fs.rmSync(distDir, { recursive: true, force: true });
51
+ }
52
+
53
+ // OS shell + every app bundle, code-split in a single pass. esbuild
54
+ // hoists shared framework code (@adaas/are, @adaas/a-concept, …) into
55
+ // shared chunks that the shell and each app import by URL, so a
56
+ // dynamically-imported app reuses the already-evaluated runtime.
57
+ const entryPoints: Record<string, string> = {
58
+ app: shellEntry,
59
+ };
60
+ for (const backend of this.backends) {
61
+ // bundle URL '/apps/marketing/app.js' → entry key 'apps/marketing/app'
62
+ const key = backend.descriptor.bundle.replace(/^\//, '').replace(/\.js$/, '');
63
+ entryPoints[key] = path.resolve(__dirname, "..", backend.entry);
64
+ }
65
+
66
+ await build({
67
+ entryPoints,
68
+ outdir: distDir,
69
+ bundle: true,
70
+ splitting: true,
71
+ format: "esm",
72
+ target: "es2020",
73
+ keepNames: true,
74
+ minify: false,
75
+ sourcemap: false,
76
+ entryNames: '[dir]/[name]',
77
+ chunkNames: 'chunks/[name]-[hash]',
78
+ });
79
+
80
+ logger.log('green', `OS shell + ${this.backends.length} app bundles built (code-split).`);
81
+
82
+ const indexHtml = await fs.promises.readFile(
83
+ path.resolve(__dirname, "../public/index.html"), 'utf-8'
84
+ );
85
+ await fs.promises.writeFile(path.join(distDir, "index.html"), indexHtml);
86
+
87
+ const publicDir = path.resolve(__dirname, "../public");
88
+ const entries = await fs.promises.readdir(publicDir, { withFileTypes: true });
89
+ for (const entry of entries) {
90
+ if (entry.isFile() && entry.name !== 'index.html') {
91
+ await fs.promises.copyFile(
92
+ path.join(publicDir, entry.name),
93
+ path.join(distDir, entry.name),
94
+ );
95
+ }
96
+ }
97
+
98
+ logger.log('green', 'Static assets copied.');
99
+ }
100
+
101
+ @A_Concept.Load()
102
+ async preLoadBuild(
103
+ @A_Inject(A_Logger) logger: A_Logger,
104
+ ) {
105
+ await this.build(logger);
106
+ }
107
+
108
+ @A_Concept.Start()
109
+ async startServer(
110
+ @A_Inject(A_Logger) logger: A_Logger,
111
+ @A_Inject(A_Config) config: A_Config,
112
+ ) {
113
+ this.server = http.createServer(this.handleRequest.bind(this));
114
+ const PORT = config.get('PORT') || 8084;
115
+ this.server.listen(PORT, () => {
116
+ logger.log('green', `OS Desktop example running at http://localhost:${PORT}`);
117
+ logger.log('blue', `App catalogue at http://localhost:${PORT}/api/apps`);
118
+ });
119
+ }
120
+
121
+ /**
122
+ * Routes:
123
+ * - GET /api/apps → the installable-app catalogue (descriptors)
124
+ * - /apps/<id>/api/* → delegated to that app's own backend
125
+ * - everything else → static files (SPA fallback to index.html)
126
+ */
127
+ protected handleRequest(
128
+ req: http.IncomingMessage,
129
+ res: http.ServerResponse,
130
+ ) {
131
+ const parsed = new URL(req.url || '/', 'http://localhost');
132
+ const pathname = parsed.pathname;
133
+
134
+ if (pathname === '/api/apps') {
135
+ res.writeHead(200, { 'Content-Type': 'application/json' });
136
+ res.end(JSON.stringify(this.backends.map(b => b.descriptor)));
137
+ return;
138
+ }
139
+
140
+ // Delegate app API calls to the owning app's backend.
141
+ const backend = this.backends.find(b => pathname.startsWith(`${b.descriptor.api}/`));
142
+ if (backend) {
143
+ const handled = backend.handle(pathname, parsed.searchParams, req, res);
144
+ if (!handled) {
145
+ res.writeHead(404, { 'Content-Type': 'application/json' });
146
+ res.end(JSON.stringify({ error: `No handler for ${pathname}` }));
147
+ }
148
+ return;
149
+ }
150
+
151
+ this.serveStaticFile(pathname, res);
152
+ }
153
+
154
+ protected serveStaticFile(
155
+ url: string,
156
+ res: http.ServerResponse,
157
+ ) {
158
+ const distDir = path.resolve(__dirname, "../dist");
159
+ let filePath = path.join(distDir, url === '/' ? 'index.html' : url);
160
+
161
+ const logger = this.scope.resolve<A_Logger>(A_Logger)!;
162
+
163
+ const mimeTypes: Record<string, string> = {
164
+ '.html': 'text/html',
165
+ '.js': 'text/javascript',
166
+ '.css': 'text/css',
167
+ '.json': 'application/json',
168
+ '.png': 'image/png',
169
+ '.svg': 'image/svg+xml',
170
+ };
171
+ const ext = path.extname(filePath).toLowerCase();
172
+
173
+ if (!fs.existsSync(filePath)) {
174
+ // SPA fallback for deep links (e.g. /app/marketing). Asset misses
175
+ // (.js) 404 honestly so loading bugs stay visible.
176
+ if (ext && ext !== '.html') {
177
+ logger.log('red', `404: ${filePath}`);
178
+ res.writeHead(404);
179
+ res.end(`Not Found: ${url}`);
180
+ return;
181
+ }
182
+ filePath = path.join(distDir, 'index.html');
183
+ }
184
+
185
+ const servedExt = path.extname(filePath).toLowerCase();
186
+ const contentType = mimeTypes[servedExt] || 'application/octet-stream';
187
+
188
+ fs.readFile(filePath, (err, content) => {
189
+ if (err) {
190
+ res.writeHead(500);
191
+ res.end(`Server Error: ${err.code}`);
192
+ } else {
193
+ res.writeHead(200, { 'Content-Type': contentType });
194
+ res.end(content);
195
+ }
196
+ });
197
+ }
198
+ }
@@ -0,0 +1,29 @@
1
+ import http from "http";
2
+ import type { AppDescriptor } from "../../src/runtime/AppRegistry.fragment";
3
+
4
+
5
+ /**
6
+ * AppBackend — the server-side half of an installable application.
7
+ *
8
+ * Each app owns BOTH a frontend bundle and a backend. The OS kernel
9
+ * (OS.container.ts) never looks inside an app: it only asks each backend for
10
+ * its {@link descriptor} (advertised at `GET /api/apps`), builds the bundle
11
+ * named by {@link entry}, and forwards any request under the app's API base to
12
+ * {@link handle}. This keeps every app fully self-contained.
13
+ */
14
+ export interface AppBackend {
15
+ /** Public metadata + bundle URL + component manifest for this app. */
16
+ readonly descriptor: AppDescriptor;
17
+ /** esbuild source entry for this app's single code-split bundle. */
18
+ readonly entry: string;
19
+ /**
20
+ * Handle a request addressed to this app's API base (`descriptor.api/*`).
21
+ * Return `true` if the request was served, `false` to let the kernel 404.
22
+ */
23
+ handle(
24
+ pathname: string,
25
+ query: URLSearchParams,
26
+ req: http.IncomingMessage,
27
+ res: http.ServerResponse,
28
+ ): boolean;
29
+ }
@@ -0,0 +1,56 @@
1
+ import http from "http";
2
+ import type { AppDescriptor } from "../../src/runtime/AppRegistry.fragment";
3
+ import { AppBackend } from "./AppBackend";
4
+
5
+
6
+ /** Seed project plan served by the Gantt app's backend. */
7
+ const SEED_TASKS = [
8
+ { id: 's1', name: 'Discovery', start: 0, end: 5, color: '#5b8def', track: 0 },
9
+ { id: 's2', name: 'Design', start: 4, end: 11, color: '#34c759', track: 1 },
10
+ { id: 's3', name: 'Build', start: 9, end: 22, color: '#ff9f0a', track: 2 },
11
+ { id: 's4', name: 'QA', start: 18, end: 26, color: '#bf5af2', track: 3 },
12
+ { id: 's5', name: 'Launch', start: 25, end: 30, color: '#ff375f', track: 4 },
13
+ ];
14
+
15
+
16
+ /**
17
+ * GanttAppBackend — the Gantt app's OWN server.
18
+ *
19
+ * Frontend: gantt-app / gantt-toolbar / gantt-chart (the gantt bundle).
20
+ * Backend: a single `/apps/gantt/api/tasks` endpoint that serves the project
21
+ * plan the chart renders.
22
+ */
23
+ export class GanttAppBackend implements AppBackend {
24
+
25
+ readonly entry = 'src/apps/gantt/index.ts';
26
+
27
+ readonly descriptor: AppDescriptor = {
28
+ id: 'gantt',
29
+ name: 'Timeline',
30
+ icon: '📊',
31
+ accent: '#34c759',
32
+ tagline: 'A project Gantt chart whose tasks are served by its own backend.',
33
+ rootTag: 'gantt-app',
34
+ bundle: '/apps/gantt/app.js',
35
+ api: '/apps/gantt/api',
36
+ components: [
37
+ { tag: 'gantt-app', export: 'GanttApp' },
38
+ { tag: 'gantt-toolbar', export: 'GanttToolbar' },
39
+ { tag: 'gantt-chart', export: 'GanttChart' },
40
+ ],
41
+ };
42
+
43
+ handle(
44
+ pathname: string,
45
+ _query: URLSearchParams,
46
+ _req: http.IncomingMessage,
47
+ res: http.ServerResponse,
48
+ ): boolean {
49
+ if (pathname === '/apps/gantt/api/tasks') {
50
+ res.writeHead(200, { 'Content-Type': 'application/json' });
51
+ res.end(JSON.stringify({ tasks: SEED_TASKS }));
52
+ return true;
53
+ }
54
+ return false;
55
+ }
56
+ }
@@ -0,0 +1,68 @@
1
+ import http from "http";
2
+ import type { AppDescriptor } from "../../src/runtime/AppRegistry.fragment";
3
+ import { AppBackend } from "./AppBackend";
4
+
5
+
6
+ /** Tiny themed hashtag generator — stands in for a real marketing service. */
7
+ const HASHTAG_BANK: Record<string, string[]> = {
8
+ launch: ['ProductLaunch', 'ShipIt', 'NowLive', 'BuildInPublic'],
9
+ runtime: ['RuntimeLoading', 'WebPerf', 'LazyLoad', 'Microfrontends'],
10
+ team: ['TeamWork', 'Hiring', 'Culture', 'RemoteWork'],
11
+ growth: ['GrowthMindset', 'Startup', 'SaaS', 'GoToMarket'],
12
+ };
13
+
14
+ const DEFAULT_TAGS = ['Engineering', 'Innovation', 'TechLeadership', 'OpenSource'];
15
+
16
+
17
+ /**
18
+ * MarketingAppBackend — the Marketing app's OWN server.
19
+ *
20
+ * Frontend: marketing-app / post-editor / post-preview (the marketing bundle).
21
+ * Backend: a single `/apps/marketing/api/hashtags` endpoint that returns topic
22
+ * hashtag suggestions, proving the app ships with its own server logic.
23
+ */
24
+ export class MarketingAppBackend implements AppBackend {
25
+
26
+ readonly entry = 'src/apps/marketing/index.ts';
27
+
28
+ readonly descriptor: AppDescriptor = {
29
+ id: 'marketing',
30
+ name: 'Marketing',
31
+ icon: '📣',
32
+ accent: '#2f6df6',
33
+ tagline: 'Draft a LinkedIn post and pull hashtag ideas from the app backend.',
34
+ rootTag: 'marketing-app',
35
+ bundle: '/apps/marketing/app.js',
36
+ api: '/apps/marketing/api',
37
+ components: [
38
+ { tag: 'marketing-app', export: 'MarketingApp' },
39
+ { tag: 'post-editor', export: 'PostEditor' },
40
+ { tag: 'post-preview', export: 'PostPreview' },
41
+ ],
42
+ };
43
+
44
+ handle(
45
+ pathname: string,
46
+ query: URLSearchParams,
47
+ _req: http.IncomingMessage,
48
+ res: http.ServerResponse,
49
+ ): boolean {
50
+ if (pathname === '/apps/marketing/api/hashtags') {
51
+ const topic = (query.get('topic') || '').toLowerCase();
52
+ res.writeHead(200, { 'Content-Type': 'application/json' });
53
+ res.end(JSON.stringify({ hashtags: this.suggest(topic) }));
54
+ return true;
55
+ }
56
+ return false;
57
+ }
58
+
59
+ protected suggest(topic: string): string[] {
60
+ const matched = Object.keys(HASHTAG_BANK)
61
+ .filter(key => topic.includes(key))
62
+ .flatMap(key => HASHTAG_BANK[key]);
63
+
64
+ const tags = matched.length ? matched : DEFAULT_TAGS;
65
+ // De-dupe + cap.
66
+ return Array.from(new Set(tags)).slice(0, 6);
67
+ }
68
+ }