@adaas/are-html 0.0.22 → 0.0.24

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 (191) hide show
  1. package/dist/browser/index.d.mts +194 -10
  2. package/dist/browser/index.mjs +696 -245
  3. package/dist/browser/index.mjs.map +1 -1
  4. package/dist/node/{AreBinding.attribute-doUvtOjc.d.mts → AreBinding.attribute-BWzEIw6H.d.mts} +45 -0
  5. package/dist/node/{AreBinding.attribute-Bm5LlOyE.d.ts → AreBinding.attribute-GpT-5Qmf.d.ts} +45 -0
  6. package/dist/node/attributes/AreBinding.attribute.d.mts +1 -1
  7. package/dist/node/attributes/AreBinding.attribute.d.ts +1 -1
  8. package/dist/node/attributes/AreDirective.attribute.d.mts +1 -1
  9. package/dist/node/attributes/AreDirective.attribute.d.ts +1 -1
  10. package/dist/node/attributes/AreEvent.attribute.d.mts +1 -1
  11. package/dist/node/attributes/AreEvent.attribute.d.ts +1 -1
  12. package/dist/node/attributes/AreStatic.attribute.d.mts +1 -1
  13. package/dist/node/attributes/AreStatic.attribute.d.ts +1 -1
  14. package/dist/node/directives/AreDirectiveFor.directive.d.mts +18 -1
  15. package/dist/node/directives/AreDirectiveFor.directive.d.ts +18 -1
  16. package/dist/node/directives/AreDirectiveFor.directive.js +57 -9
  17. package/dist/node/directives/AreDirectiveFor.directive.js.map +1 -1
  18. package/dist/node/directives/AreDirectiveFor.directive.mjs +57 -9
  19. package/dist/node/directives/AreDirectiveFor.directive.mjs.map +1 -1
  20. package/dist/node/directives/AreDirectiveIf.directive.d.mts +18 -2
  21. package/dist/node/directives/AreDirectiveIf.directive.d.ts +18 -2
  22. package/dist/node/directives/AreDirectiveIf.directive.js +29 -6
  23. package/dist/node/directives/AreDirectiveIf.directive.js.map +1 -1
  24. package/dist/node/directives/AreDirectiveIf.directive.mjs +29 -6
  25. package/dist/node/directives/AreDirectiveIf.directive.mjs.map +1 -1
  26. package/dist/node/directives/AreDirectiveShow.directive.d.mts +1 -1
  27. package/dist/node/directives/AreDirectiveShow.directive.d.ts +1 -1
  28. package/dist/node/engine/AreHTML.compiler.d.mts +4 -2
  29. package/dist/node/engine/AreHTML.compiler.d.ts +4 -2
  30. package/dist/node/engine/AreHTML.compiler.js +11 -4
  31. package/dist/node/engine/AreHTML.compiler.js.map +1 -1
  32. package/dist/node/engine/AreHTML.compiler.mjs +11 -4
  33. package/dist/node/engine/AreHTML.compiler.mjs.map +1 -1
  34. package/dist/node/engine/AreHTML.constants.d.mts +33 -1
  35. package/dist/node/engine/AreHTML.constants.d.ts +33 -1
  36. package/dist/node/engine/AreHTML.constants.js +166 -0
  37. package/dist/node/engine/AreHTML.constants.js.map +1 -1
  38. package/dist/node/engine/AreHTML.constants.mjs +165 -1
  39. package/dist/node/engine/AreHTML.constants.mjs.map +1 -1
  40. package/dist/node/engine/AreHTML.context.d.mts +66 -0
  41. package/dist/node/engine/AreHTML.context.d.ts +66 -0
  42. package/dist/node/engine/AreHTML.context.js +98 -0
  43. package/dist/node/engine/AreHTML.context.js.map +1 -1
  44. package/dist/node/engine/AreHTML.context.mjs +98 -0
  45. package/dist/node/engine/AreHTML.context.mjs.map +1 -1
  46. package/dist/node/engine/AreHTML.interpreter.d.mts +3 -0
  47. package/dist/node/engine/AreHTML.interpreter.d.ts +3 -0
  48. package/dist/node/engine/AreHTML.interpreter.js +66 -10
  49. package/dist/node/engine/AreHTML.interpreter.js.map +1 -1
  50. package/dist/node/engine/AreHTML.interpreter.mjs +66 -10
  51. package/dist/node/engine/AreHTML.interpreter.mjs.map +1 -1
  52. package/dist/node/engine/AreHTML.lifecycle.d.mts +1 -8
  53. package/dist/node/engine/AreHTML.lifecycle.d.ts +1 -8
  54. package/dist/node/engine/AreHTML.lifecycle.js +29 -44
  55. package/dist/node/engine/AreHTML.lifecycle.js.map +1 -1
  56. package/dist/node/engine/AreHTML.lifecycle.mjs +29 -44
  57. package/dist/node/engine/AreHTML.lifecycle.mjs.map +1 -1
  58. package/dist/node/engine/AreHTML.tokenizer.d.mts +1 -1
  59. package/dist/node/engine/AreHTML.tokenizer.d.ts +1 -1
  60. package/dist/node/engine/AreHTML.tokenizer.js +7 -1
  61. package/dist/node/engine/AreHTML.tokenizer.js.map +1 -1
  62. package/dist/node/engine/AreHTML.tokenizer.mjs +7 -1
  63. package/dist/node/engine/AreHTML.tokenizer.mjs.map +1 -1
  64. package/dist/node/engine/AreHTML.transformer.d.mts +1 -1
  65. package/dist/node/engine/AreHTML.transformer.d.ts +1 -1
  66. package/dist/node/index.d.mts +4 -3
  67. package/dist/node/index.d.ts +4 -3
  68. package/dist/node/index.js +7 -0
  69. package/dist/node/index.mjs +1 -0
  70. package/dist/node/instructions/AddStaticHTML.instruction.d.mts +8 -0
  71. package/dist/node/instructions/AddStaticHTML.instruction.d.ts +8 -0
  72. package/dist/node/instructions/AddStaticHTML.instruction.js +31 -0
  73. package/dist/node/instructions/AddStaticHTML.instruction.js.map +1 -0
  74. package/dist/node/instructions/AddStaticHTML.instruction.mjs +24 -0
  75. package/dist/node/instructions/AddStaticHTML.instruction.mjs.map +1 -0
  76. package/dist/node/instructions/AreHTML.instructions.constants.d.mts +1 -0
  77. package/dist/node/instructions/AreHTML.instructions.constants.d.ts +1 -0
  78. package/dist/node/instructions/AreHTML.instructions.constants.js +1 -0
  79. package/dist/node/instructions/AreHTML.instructions.constants.js.map +1 -1
  80. package/dist/node/instructions/AreHTML.instructions.constants.mjs +1 -0
  81. package/dist/node/instructions/AreHTML.instructions.constants.mjs.map +1 -1
  82. package/dist/node/instructions/AreHTML.instructions.types.d.mts +9 -1
  83. package/dist/node/instructions/AreHTML.instructions.types.d.ts +9 -1
  84. package/dist/node/lib/AreDirective/AreDirective.component.d.mts +1 -1
  85. package/dist/node/lib/AreDirective/AreDirective.component.d.ts +1 -1
  86. package/dist/node/lib/AreDirective/AreDirective.types.d.mts +1 -1
  87. package/dist/node/lib/AreDirective/AreDirective.types.d.ts +1 -1
  88. package/dist/node/lib/AreHTML/AreHTML.tokenizer.d.mts +1 -1
  89. package/dist/node/lib/AreHTML/AreHTML.tokenizer.d.ts +1 -1
  90. package/dist/node/lib/AreHTMLAttribute/AreHTML.attribute.d.mts +1 -1
  91. package/dist/node/lib/AreHTMLAttribute/AreHTML.attribute.d.ts +1 -1
  92. package/dist/node/lib/AreHTMLNode/AreHTMLNode.d.mts +1 -1
  93. package/dist/node/lib/AreHTMLNode/AreHTMLNode.d.ts +1 -1
  94. package/dist/node/lib/AreHTMLNode/AreHTMLNode.js +51 -0
  95. package/dist/node/lib/AreHTMLNode/AreHTMLNode.js.map +1 -1
  96. package/dist/node/lib/AreHTMLNode/AreHTMLNode.mjs +51 -0
  97. package/dist/node/lib/AreHTMLNode/AreHTMLNode.mjs.map +1 -1
  98. package/dist/node/lib/AreRoot/AreRoot.component.js.map +1 -1
  99. package/dist/node/lib/AreRoot/AreRoot.component.mjs.map +1 -1
  100. package/dist/node/nodes/AreComment.d.mts +1 -1
  101. package/dist/node/nodes/AreComment.d.ts +1 -1
  102. package/dist/node/nodes/AreComponent.d.mts +1 -1
  103. package/dist/node/nodes/AreComponent.d.ts +1 -1
  104. package/dist/node/nodes/AreInterpolation.d.mts +1 -1
  105. package/dist/node/nodes/AreInterpolation.d.ts +1 -1
  106. package/dist/node/nodes/AreRoot.d.mts +1 -1
  107. package/dist/node/nodes/AreRoot.d.ts +1 -1
  108. package/dist/node/nodes/AreText.d.mts +1 -1
  109. package/dist/node/nodes/AreText.d.ts +1 -1
  110. package/examples/dashboard/concept.ts +1 -1
  111. package/examples/dashboard/dist/index.html +1 -1
  112. package/examples/dashboard/dist/{mqh9ryml-xat335.js → mqiw5sqa-ypckmj.js} +403 -57
  113. package/examples/for-perf/dist/index.html +1 -1
  114. package/examples/for-perf/dist/{mqh9ryfo-6a8d0o.js → mqp8i2py-vltsx0.js} +3030 -2474
  115. package/examples/lazy-loading/README.md +76 -0
  116. package/examples/lazy-loading/concept.ts +55 -0
  117. package/examples/lazy-loading/containers/UI.container.ts +215 -0
  118. package/examples/lazy-loading/dist/app.js +3803 -0
  119. package/examples/{for-perf/dist/mqh9ryfq-4pf5cv.js → lazy-loading/dist/chunks/chunk-6K72IBO4.js} +2708 -5476
  120. package/examples/lazy-loading/dist/index.html +36 -0
  121. package/examples/lazy-loading/dist/lazy/about-page.js +59 -0
  122. package/examples/lazy-loading/dist/lazy/reports-page.js +65 -0
  123. package/examples/lazy-loading/dist/lazy/settings-page.js +54 -0
  124. package/examples/lazy-loading/public/index.html +36 -0
  125. package/examples/lazy-loading/src/components/AppShell.component.ts +44 -0
  126. package/examples/lazy-loading/src/components/HomePage.component.ts +59 -0
  127. package/examples/lazy-loading/src/components/LazyOutlet.component.ts +108 -0
  128. package/examples/lazy-loading/src/components/NavBar.component.ts +98 -0
  129. package/examples/lazy-loading/src/concept.ts +116 -0
  130. package/examples/lazy-loading/src/lazy/AboutPage.component.ts +54 -0
  131. package/examples/lazy-loading/src/lazy/ReportsPage.component.ts +56 -0
  132. package/examples/lazy-loading/src/lazy/SettingsPage.component.ts +45 -0
  133. package/examples/lazy-loading/src/runtime/ComponentManifest.fragment.ts +61 -0
  134. package/examples/lazy-loading/src/runtime/LazyComponentResolver.fragment.ts +77 -0
  135. package/examples/os-desktop/README.md +91 -0
  136. package/examples/os-desktop/concept.ts +54 -0
  137. package/examples/os-desktop/containers/OS.container.ts +198 -0
  138. package/examples/os-desktop/containers/apps/AppBackend.ts +29 -0
  139. package/examples/os-desktop/containers/apps/GanttApp.backend.ts +56 -0
  140. package/examples/os-desktop/containers/apps/MarketingApp.backend.ts +68 -0
  141. package/examples/os-desktop/dist/app.js +4410 -0
  142. package/examples/os-desktop/dist/apps/gantt/app.js +271 -0
  143. package/examples/os-desktop/dist/apps/marketing/app.js +346 -0
  144. package/examples/{for-perf/dist/mqh9ryde-m243t8.js → os-desktop/dist/chunks/chunk-6K72IBO4.js} +2708 -5476
  145. package/examples/os-desktop/dist/chunks/chunk-EIIGUL6N.js +30 -0
  146. package/examples/os-desktop/dist/chunks/chunk-WOH7L5UR.js +30 -0
  147. package/examples/os-desktop/dist/index.html +33 -0
  148. package/examples/os-desktop/public/index.html +33 -0
  149. package/examples/os-desktop/src/apps/gantt/GanttApp.component.ts +41 -0
  150. package/examples/os-desktop/src/apps/gantt/GanttChart.component.ts +126 -0
  151. package/examples/os-desktop/src/apps/gantt/GanttStore.ts +47 -0
  152. package/examples/os-desktop/src/apps/gantt/GanttToolbar.component.ts +73 -0
  153. package/examples/os-desktop/src/apps/gantt/index.ts +13 -0
  154. package/examples/os-desktop/src/apps/marketing/MarketingApp.component.ts +53 -0
  155. package/examples/os-desktop/src/apps/marketing/MarketingStore.ts +34 -0
  156. package/examples/os-desktop/src/apps/marketing/PostEditor.component.ts +153 -0
  157. package/examples/os-desktop/src/apps/marketing/PostPreview.component.ts +110 -0
  158. package/examples/os-desktop/src/apps/marketing/index.ts +16 -0
  159. package/examples/os-desktop/src/concept.ts +126 -0
  160. package/examples/os-desktop/src/os/AppStage.component.ts +112 -0
  161. package/examples/os-desktop/src/os/AppWindow.component.ts +102 -0
  162. package/examples/os-desktop/src/os/Desktop.component.ts +106 -0
  163. package/examples/os-desktop/src/os/Dock.component.ts +174 -0
  164. package/examples/os-desktop/src/os/Hud.component.ts +83 -0
  165. package/examples/os-desktop/src/os/Launchpad.component.ts +191 -0
  166. package/examples/os-desktop/src/os/MenuBar.component.ts +156 -0
  167. package/examples/os-desktop/src/runtime/AppComponentResolver.fragment.ts +121 -0
  168. package/examples/os-desktop/src/runtime/AppRegistry.fragment.ts +104 -0
  169. package/examples/os-desktop/src/signals/MouseState.signal.ts +34 -0
  170. package/examples/os-desktop/src/signals/OSRoute.signal.ts +37 -0
  171. package/examples/os-desktop/src/signals/SelectionState.signal.ts +34 -0
  172. package/examples/signal-routing/dist/index.html +1 -1
  173. package/examples/signal-routing/dist/{mqh9ryc9-dkcbkx.js → mqp8hgce-4d6rh0.js} +3196 -2640
  174. package/package.json +13 -9
  175. package/src/directives/AreDirectiveFor.directive.ts +99 -16
  176. package/src/directives/AreDirectiveIf.directive.ts +33 -4
  177. package/src/engine/AreHTML.compiler.ts +25 -2
  178. package/src/engine/AreHTML.constants.ts +142 -0
  179. package/src/engine/AreHTML.context.ts +112 -0
  180. package/src/engine/AreHTML.interpreter.ts +114 -13
  181. package/src/engine/AreHTML.lifecycle.ts +81 -74
  182. package/src/engine/AreHTML.tokenizer.ts +30 -1
  183. package/src/index.ts +1 -0
  184. package/src/instructions/AddStaticHTML.instruction.ts +23 -0
  185. package/src/instructions/AreHTML.instructions.constants.ts +1 -0
  186. package/src/instructions/AreHTML.instructions.types.ts +9 -0
  187. package/src/lib/AreHTMLNode/AreHTMLNode.ts +74 -0
  188. package/src/lib/AreRoot/AreRoot.component.ts +3 -3
  189. package/tests/PropPropagation.test.ts +181 -0
  190. package/tests/StaticIsland.test.ts +115 -0
  191. package/tests/jest.setup.ts +11 -0
@@ -0,0 +1,174 @@
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 { AppRegistry } from "../runtime/AppRegistry.fragment";
6
+ import { OSRoute } from "../signals/OSRoute.signal";
7
+
8
+
9
+ /**
10
+ * Dock — the bottom app launcher.
11
+ *
12
+ * Shows the currently installed apps plus a Launchpad (+) button. Clicking an
13
+ * app dispatches an {@link OSRoute} to focus it; the Dock itself also listens
14
+ * for OSRoute so a freshly installed app reveals its icon and the running app's
15
+ * running-dot lights up.
16
+ *
17
+ * Every available app gets a (hidden) button wired up front, so the engine
18
+ * binds its `@click` handler once at mount. Reacting to a route signal is then
19
+ * a cheap imperative DOM toggle — no subtree teardown, no re-binding.
20
+ */
21
+ export class OsDock extends Are {
22
+
23
+ protected _route: string = document.location.pathname || '/';
24
+
25
+ @Are.Template
26
+ template(
27
+ @A_Inject(A_Caller) node: AreNode,
28
+ @A_Inject(AppRegistry) registry: AppRegistry,
29
+ ) {
30
+ node.setContent(this.build(registry));
31
+ }
32
+
33
+ @Are.Signal(OSRoute)
34
+ onRoute(
35
+ @A_Inject(OSRoute) signal: OSRoute,
36
+ @A_Inject(AppRegistry) registry: AppRegistry,
37
+ ) {
38
+ this._route = signal.path;
39
+ const activeId = this._route.match(/^\/app\/([^/]+)/)?.[1];
40
+
41
+ let anyVisible = false;
42
+ for (const app of registry.available()) {
43
+ const btn = document.getElementById(`dock-app-${app.id}`);
44
+ if (!btn) continue;
45
+ if (registry.isInstalled(app.id)) {
46
+ btn.hidden = false;
47
+ anyVisible = true;
48
+ }
49
+ btn.classList.toggle('running', app.id === activeId);
50
+ }
51
+
52
+ const sep = document.getElementById('dock-sep');
53
+ if (sep) sep.hidden = !anyVisible;
54
+
55
+ const lp = document.getElementById('dock-launchpad');
56
+ if (lp) lp.classList.toggle('running', this._route === '/launchpad');
57
+ }
58
+
59
+ @Are.EventHandler
60
+ open(
61
+ @A_Inject(AreEvent) event: AreEvent,
62
+ @A_Inject(A_SignalBus) bus: A_SignalBus,
63
+ ) {
64
+ (event.get('native') as MouseEvent)?.preventDefault();
65
+ const id = event.get('args')?.[0] as string;
66
+ if (!id) return;
67
+ const path = `/app/${id}`;
68
+ history.pushState({}, '', path);
69
+ bus.next(new OSRoute(path));
70
+ }
71
+
72
+ @Are.EventHandler
73
+ launchpad(
74
+ @A_Inject(AreEvent) event: AreEvent,
75
+ @A_Inject(A_SignalBus) bus: A_SignalBus,
76
+ ) {
77
+ (event.get('native') as MouseEvent)?.preventDefault();
78
+ history.pushState({}, '', '/launchpad');
79
+ bus.next(new OSRoute('/launchpad'));
80
+ }
81
+
82
+ protected build(registry: AppRegistry): string {
83
+ const activeId = this._route.match(/^\/app\/([^/]+)/)?.[1];
84
+
85
+ const apps = registry.available().map(app => {
86
+ const installed = registry.isInstalled(app.id);
87
+ const running = app.id === activeId ? 'running' : '';
88
+ return `
89
+ <button id="dock-app-${app.id}" class="dock-item ${running}" @click="$open('${app.id}')" title="${app.name}"${installed ? '' : ' hidden'}>
90
+ <span class="dock-glyph" style="--accent:${app.accent}">${app.icon}</span>
91
+ <span class="dock-dot"></span>
92
+ </button>
93
+ `;
94
+ }).join('');
95
+
96
+ const anyInstalled = registry.installed().length > 0;
97
+ const launchpadActive = this._route === '/launchpad' ? 'running' : '';
98
+
99
+ return `
100
+ <div class="dock">
101
+ ${apps}
102
+ <span class="dock-sep" id="dock-sep"${anyInstalled ? '' : ' hidden'}></span>
103
+ <button id="dock-launchpad" class="dock-item ${launchpadActive}" @click="$launchpad()" title="Launchpad — install apps">
104
+ <span class="dock-glyph dock-plus">+</span>
105
+ <span class="dock-dot"></span>
106
+ </button>
107
+ </div>
108
+ `;
109
+ }
110
+
111
+ @Are.Styles
112
+ styles(@A_Inject(A_Caller) node: AreHTMLNode) {
113
+ node.setStyles(`
114
+ .dock {
115
+ position: absolute;
116
+ left: 50%;
117
+ bottom: 14px;
118
+ transform: translateX(-50%);
119
+ display: flex;
120
+ align-items: flex-end;
121
+ gap: 10px;
122
+ padding: 8px 12px;
123
+ border-radius: 22px;
124
+ background: rgba(30, 24, 48, 0.45);
125
+ backdrop-filter: blur(24px) saturate(180%);
126
+ -webkit-backdrop-filter: blur(24px) saturate(180%);
127
+ border: 1px solid rgba(255,255,255,0.12);
128
+ box-shadow: 0 18px 50px rgba(0,0,0,0.45);
129
+ z-index: 95;
130
+ }
131
+ .dock-item {
132
+ position: relative;
133
+ display: flex;
134
+ flex-direction: column;
135
+ align-items: center;
136
+ gap: 4px;
137
+ border: none;
138
+ background: none;
139
+ cursor: pointer;
140
+ padding: 0;
141
+ }
142
+ /* The HTML 'hidden' attribute must win over 'display: flex' so an
143
+ uninstalled app icon (and the separator) stays out of the dock. */
144
+ .dock-item[hidden], #dock-sep[hidden] { display: none !important; }
145
+ .dock-glyph {
146
+ width: 52px; height: 52px;
147
+ display: flex; align-items: center; justify-content: center;
148
+ font-size: 28px;
149
+ border-radius: 14px;
150
+ background: linear-gradient(160deg, color-mix(in srgb, var(--accent, #7c6fd6) 85%, white 0%), color-mix(in srgb, var(--accent, #7c6fd6) 70%, black 18%));
151
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.35), 0 6px 14px rgba(0,0,0,0.35);
152
+ transition: transform 0.18s ease;
153
+ }
154
+ .dock-plus {
155
+ background: rgba(255,255,255,0.12);
156
+ color: #f5f5f7;
157
+ font-size: 26px;
158
+ font-weight: 300;
159
+ }
160
+ .dock-item:hover .dock-glyph { transform: translateY(-10px) scale(1.12); }
161
+ .dock-dot {
162
+ width: 4px; height: 4px; border-radius: 50%;
163
+ background: transparent;
164
+ }
165
+ .dock-item.running .dock-dot { background: rgba(245,245,247,0.85); }
166
+ .dock-sep {
167
+ width: 1px;
168
+ align-self: stretch;
169
+ margin: 6px 2px;
170
+ background: rgba(255,255,255,0.16);
171
+ }
172
+ `);
173
+ }
174
+ }
@@ -0,0 +1,83 @@
1
+ import { A_Caller, A_Inject } from "@adaas/a-concept";
2
+ import { Are, AreNode } from "@adaas/are";
3
+ import { AreHTMLNode } from "src";
4
+ import { MouseState } from "../signals/MouseState.signal";
5
+ import { SelectionState } from "../signals/SelectionState.signal";
6
+
7
+
8
+ /**
9
+ * Hud — a small translucent read-out pinned to the bottom-left corner.
10
+ *
11
+ * It exists purely to visualise that the bus carries more than route signals:
12
+ * - {@link MouseState} → live X / Y coordinates (high-frequency, throttled).
13
+ * - {@link SelectionState} → the number of characters currently selected.
14
+ *
15
+ * Because this is the only component that re-renders on every (throttled) mouse
16
+ * move, the cost of the high-frequency signal is isolated to a tiny subtree.
17
+ */
18
+ export class OsHud extends Are {
19
+
20
+ protected _x: number = 0;
21
+ protected _y: number = 0;
22
+ protected _selection: number = 0;
23
+
24
+ @Are.Template
25
+ template(@A_Inject(A_Caller) node: AreNode) {
26
+ node.setContent(this.build());
27
+ }
28
+
29
+ @Are.Signal(MouseState)
30
+ onMouse(@A_Inject(MouseState) signal: MouseState) {
31
+ this._x = signal.x;
32
+ this._y = signal.y;
33
+ const x = document.getElementById('hud-x');
34
+ const y = document.getElementById('hud-y');
35
+ if (x) x.textContent = String(this._x);
36
+ if (y) y.textContent = String(this._y);
37
+ }
38
+
39
+ @Are.Signal(SelectionState)
40
+ onSelection(@A_Inject(SelectionState) signal: SelectionState) {
41
+ this._selection = signal.length;
42
+ const sel = document.getElementById('hud-sel');
43
+ if (sel) sel.textContent = String(this._selection);
44
+ }
45
+
46
+ protected build(): string {
47
+ return `
48
+ <div class="hud">
49
+ <span class="hud-row"><b>signals</b></span>
50
+ <span class="hud-row">MouseState&nbsp;<i>x</i> <span id="hud-x">${this._x}</span>&nbsp;<i>y</i> <span id="hud-y">${this._y}</span></span>
51
+ <span class="hud-row">SelectionState&nbsp;<i>len</i> <span id="hud-sel">${this._selection}</span></span>
52
+ </div>
53
+ `;
54
+ }
55
+
56
+ @Are.Styles
57
+ styles(@A_Inject(A_Caller) node: AreHTMLNode) {
58
+ node.setStyles(`
59
+ .hud {
60
+ position: absolute;
61
+ left: 16px;
62
+ bottom: 104px;
63
+ display: flex;
64
+ flex-direction: column;
65
+ gap: 2px;
66
+ padding: 10px 14px;
67
+ border-radius: 12px;
68
+ background: rgba(12, 10, 24, 0.5);
69
+ backdrop-filter: blur(14px);
70
+ -webkit-backdrop-filter: blur(14px);
71
+ border: 1px solid rgba(255,255,255,0.08);
72
+ font-size: 11px;
73
+ font-family: ui-monospace, 'SF Mono', Menlo, monospace;
74
+ color: rgba(245,245,247,0.85);
75
+ z-index: 90;
76
+ pointer-events: none;
77
+ }
78
+ .hud b { color: #c9b8ff; text-transform: uppercase; letter-spacing: 0.08em; font-size: 10px; }
79
+ .hud i { color: #8b7fb0; font-style: normal; }
80
+ .hud-row { white-space: nowrap; }
81
+ `);
82
+ }
83
+ }
@@ -0,0 +1,191 @@
1
+ import { A_Inject } from "@adaas/a-concept";
2
+ import { A_SignalBus } from "@adaas/a-utils/a-signal";
3
+ import { A_Logger } from "@adaas/a-utils/a-logger";
4
+ import { Are, AreEvent, AreNode } from "@adaas/are";
5
+ import { A_Caller } from "@adaas/a-concept";
6
+ import { AreHTMLNode } from "src";
7
+ import { AppRegistry } from "../runtime/AppRegistry.fragment";
8
+ import { AppComponentResolver } from "../runtime/AppComponentResolver.fragment";
9
+ import { OSRoute } from "../signals/OSRoute.signal";
10
+
11
+
12
+ /**
13
+ * Launchpad — the OS "App Store" overlay.
14
+ *
15
+ * Lists every app the backend offers. For an app the user has not installed it
16
+ * shows **Install**; installing it:
17
+ * 1. records the app in the {@link AppRegistry} (it now shows up in the dock),
18
+ * 2. warms its bundle via {@link AppComponentResolver.preload} — i.e. "adding
19
+ * the app loads all its components" (one network fetch for the whole app),
20
+ * 3. routes to `/app/<id>`, opening the freshly installed app.
21
+ *
22
+ * Already-installed apps show **Open** and simply route to them. The engine
23
+ * registers each component lazily on first render (instant, since preload
24
+ * already fetched the bundle).
25
+ */
26
+ export class OsLaunchpad extends Are {
27
+
28
+ @Are.Template
29
+ template(
30
+ @A_Inject(A_Caller) node: AreNode,
31
+ @A_Inject(AppRegistry) registry: AppRegistry,
32
+ ) {
33
+ node.setContent(this.build(registry));
34
+ }
35
+
36
+ @Are.EventHandler
37
+ async install(
38
+ @A_Inject(AreEvent) event: AreEvent,
39
+ @A_Inject(A_SignalBus) bus: A_SignalBus,
40
+ @A_Inject(AppRegistry) registry: AppRegistry,
41
+ @A_Inject(AppComponentResolver) resolver: AppComponentResolver,
42
+ @A_Inject(A_Logger) logger: A_Logger,
43
+ ) {
44
+ (event.get('native') as MouseEvent)?.preventDefault();
45
+ const id = event.get('args')?.[0] as string;
46
+ const app = id ? registry.get(id) : undefined;
47
+ if (!app) return;
48
+
49
+ registry.install(app.id);
50
+
51
+ // Fetch the app's entire bundle now — "installing loads all its
52
+ // components". The window will then mount instantly from memory.
53
+ try {
54
+ await resolver.preload(app);
55
+ logger.log('green', `Installed "${app.name}" (${app.components.length} components loaded).`);
56
+ } catch (error) {
57
+ logger.error(error);
58
+ }
59
+
60
+ const path = `/app/${app.id}`;
61
+ history.pushState({}, '', path);
62
+ bus.next(new OSRoute(path));
63
+ }
64
+
65
+ @Are.EventHandler
66
+ open(
67
+ @A_Inject(AreEvent) event: AreEvent,
68
+ @A_Inject(A_SignalBus) bus: A_SignalBus,
69
+ ) {
70
+ (event.get('native') as MouseEvent)?.preventDefault();
71
+ const id = event.get('args')?.[0] as string;
72
+ if (!id) return;
73
+ const path = `/app/${id}`;
74
+ history.pushState({}, '', path);
75
+ bus.next(new OSRoute(path));
76
+ }
77
+
78
+ @Are.EventHandler
79
+ close(
80
+ @A_Inject(AreEvent) event: AreEvent,
81
+ @A_Inject(A_SignalBus) bus: A_SignalBus,
82
+ ) {
83
+ (event.get('native') as MouseEvent)?.preventDefault();
84
+ history.pushState({}, '', '/desktop');
85
+ bus.next(new OSRoute('/desktop'));
86
+ }
87
+
88
+ protected build(registry: AppRegistry): string {
89
+ const tiles = registry.available().map(app => {
90
+ const installed = registry.isInstalled(app.id);
91
+ const action = installed
92
+ ? `<button class="lp-btn lp-open" @click="$open('${app.id}')">Open</button>`
93
+ : `<button class="lp-btn lp-install" @click="$install('${app.id}')">Install</button>`;
94
+
95
+ const chips = app.components
96
+ .map(c => `<code>&lt;${c.tag}&gt;</code>`)
97
+ .join('');
98
+
99
+ return `
100
+ <article class="lp-tile" style="--accent:${app.accent}">
101
+ <div class="lp-icon">${app.icon}</div>
102
+ <h3 class="lp-name">${app.name}</h3>
103
+ <p class="lp-tagline">${app.tagline}</p>
104
+ <div class="lp-meta">
105
+ <span class="lp-bundle">${app.bundle}</span>
106
+ <div class="lp-components">${chips}</div>
107
+ </div>
108
+ ${action}
109
+ </article>
110
+ `;
111
+ }).join('');
112
+
113
+ return `
114
+ <div class="lp-backdrop" @click.self="$close()">
115
+ <div class="lp">
116
+ <header class="lp-head">
117
+ <h1>Launchpad</h1>
118
+ <p>Each app is an independent bundle with its own backend and its own set of components. Install one to add it to your dock and load its code.</p>
119
+ </header>
120
+ <div class="lp-grid">${tiles}</div>
121
+ </div>
122
+ </div>
123
+ `;
124
+ }
125
+
126
+ @Are.Styles
127
+ styles(@A_Inject(A_Caller) node: AreHTMLNode) {
128
+ node.setStyles(`
129
+ .lp-backdrop {
130
+ position: absolute;
131
+ inset: 0;
132
+ display: flex;
133
+ align-items: center;
134
+ justify-content: center;
135
+ background: rgba(8, 6, 18, 0.55);
136
+ backdrop-filter: blur(26px) saturate(160%);
137
+ -webkit-backdrop-filter: blur(26px) saturate(160%);
138
+ animation: lp-fade 0.2s ease;
139
+ }
140
+ @keyframes lp-fade { from { opacity: 0; } to { opacity: 1; } }
141
+ .lp { width: min(900px, 88vw); max-height: 80vh; overflow: auto; }
142
+ .lp-head { text-align: center; margin-bottom: 30px; }
143
+ .lp-head h1 { font-size: 30px; font-weight: 800; letter-spacing: -0.02em; }
144
+ .lp-head p { margin-top: 10px; color: rgba(245,245,247,0.7); font-size: 14px; max-width: 560px; margin-left: auto; margin-right: auto; line-height: 1.6; }
145
+ .lp-grid {
146
+ display: grid;
147
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
148
+ gap: 18px;
149
+ }
150
+ .lp-tile {
151
+ display: flex;
152
+ flex-direction: column;
153
+ align-items: flex-start;
154
+ gap: 8px;
155
+ padding: 22px;
156
+ border-radius: 18px;
157
+ background: rgba(30, 24, 48, 0.5);
158
+ border: 1px solid rgba(255,255,255,0.1);
159
+ }
160
+ .lp-icon {
161
+ width: 56px; height: 56px;
162
+ display: flex; align-items: center; justify-content: center;
163
+ font-size: 30px;
164
+ border-radius: 15px;
165
+ background: linear-gradient(160deg, color-mix(in srgb, var(--accent) 85%, white 0%), color-mix(in srgb, var(--accent) 65%, black 20%));
166
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.35);
167
+ margin-bottom: 4px;
168
+ }
169
+ .lp-name { font-size: 17px; font-weight: 700; }
170
+ .lp-tagline { font-size: 13px; color: rgba(245,245,247,0.7); line-height: 1.5; }
171
+ .lp-meta { width: 100%; margin: 8px 0 6px; }
172
+ .lp-bundle { font-size: 11px; font-family: ui-monospace, monospace; color: #8b7fb0; }
173
+ .lp-components { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px; }
174
+ .lp-components code { font-size: 10px; padding: 2px 6px; border-radius: 6px; background: rgba(255,255,255,0.08); color: #c9b8ff; }
175
+ .lp-btn {
176
+ margin-top: auto;
177
+ align-self: stretch;
178
+ padding: 9px 14px;
179
+ border: none;
180
+ border-radius: 10px;
181
+ font-size: 13px;
182
+ font-weight: 600;
183
+ cursor: pointer;
184
+ transition: filter 0.15s;
185
+ }
186
+ .lp-btn:hover { filter: brightness(1.12); }
187
+ .lp-install { background: var(--accent); color: white; }
188
+ .lp-open { background: rgba(255,255,255,0.14); color: #f5f5f7; }
189
+ `);
190
+ }
191
+ }
@@ -0,0 +1,156 @@
1
+ import { A_Caller, A_Inject } from "@adaas/a-concept";
2
+ import { Are, AreNode } from "@adaas/are";
3
+ import { AreHTMLNode } from "src";
4
+ import { AppRegistry } from "../runtime/AppRegistry.fragment";
5
+ import { OSRoute } from "../signals/OSRoute.signal";
6
+ import { SelectionState } from "../signals/SelectionState.signal";
7
+
8
+
9
+ /**
10
+ * MenuBar — the translucent top bar.
11
+ *
12
+ * Demonstrates a component that reacts to TWO independent signal types via
13
+ * typed `@Are.Signal(...)` handlers:
14
+ * - {@link OSRoute} → the active application's name (left side).
15
+ * - {@link SelectionState} → a "N selected" chip (right side).
16
+ *
17
+ * Each handler patches the rendered DOM imperatively, so reacting to a
18
+ * high-frequency selection or route change never tears down and rebuilds the
19
+ * bar's subtree.
20
+ */
21
+ export class MenuBar extends Are {
22
+
23
+ protected _route: string = document.location.pathname || '/';
24
+ protected _selectionLength: number = 0;
25
+ protected _clockTimer?: ReturnType<typeof setInterval>;
26
+
27
+ @Are.Template
28
+ template(
29
+ @A_Inject(A_Caller) node: AreNode,
30
+ @A_Inject(AppRegistry) registry: AppRegistry,
31
+ ) {
32
+ node.setContent(this.build(registry));
33
+ }
34
+
35
+ @Are.onAfterMount
36
+ onMount() {
37
+ this._clockTimer = setInterval(() => {
38
+ const clock = document.getElementById('mb-clock');
39
+ if (clock) clock.textContent = this.now();
40
+ }, 15000);
41
+ }
42
+
43
+ @Are.onBeforeUnmount
44
+ onUnmount() {
45
+ if (this._clockTimer) clearInterval(this._clockTimer);
46
+ this._clockTimer = undefined;
47
+ }
48
+
49
+ @Are.Signal(OSRoute)
50
+ onRoute(
51
+ @A_Inject(OSRoute) signal: OSRoute,
52
+ @A_Inject(AppRegistry) registry: AppRegistry,
53
+ ) {
54
+ this._route = signal.path;
55
+ const el = document.getElementById('mb-active');
56
+ if (el) {
57
+ const { icon, name } = this.activeTitle(registry);
58
+ el.textContent = `${icon ? icon + ' ' : ''}${name}`;
59
+ }
60
+ }
61
+
62
+ @Are.Signal(SelectionState)
63
+ onSelection(@A_Inject(SelectionState) signal: SelectionState) {
64
+ this._selectionLength = signal.length;
65
+ const chip = document.getElementById('mb-chip');
66
+ if (!chip) return;
67
+ if (this._selectionLength > 0) {
68
+ chip.hidden = false;
69
+ chip.textContent = `\u2702 ${this._selectionLength} selected`;
70
+ } else {
71
+ chip.hidden = true;
72
+ }
73
+ }
74
+
75
+ protected now(): string {
76
+ return new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
77
+ }
78
+
79
+ protected activeTitle(registry: AppRegistry): { icon: string; name: string } {
80
+ const match = this._route.match(/^\/app\/([^/]+)/);
81
+ if (match) {
82
+ const app = registry.get(match[1]);
83
+ if (app) return { icon: app.icon, name: app.name };
84
+ }
85
+ if (this._route === '/launchpad') {
86
+ return { icon: '🚀', name: 'Launchpad' };
87
+ }
88
+ return { icon: '', name: 'Finder' };
89
+ }
90
+
91
+ protected build(registry: AppRegistry): string {
92
+ const { icon, name } = this.activeTitle(registry);
93
+ const hasSel = this._selectionLength > 0;
94
+
95
+ return `
96
+ <div class="menubar">
97
+ <div class="mb-left">
98
+ <span class="mb-logo"></span>
99
+ <span class="mb-active" id="mb-active">${icon ? icon + ' ' : ''}${name}</span>
100
+ <span class="mb-menu">File</span>
101
+ <span class="mb-menu">Edit</span>
102
+ <span class="mb-menu">View</span>
103
+ <span class="mb-menu">Window</span>
104
+ </div>
105
+ <div class="mb-right">
106
+ <span class="mb-chip" id="mb-chip"${hasSel ? '' : ' hidden'}>✂ ${this._selectionLength} selected</span>
107
+ <span class="mb-icon">􀙇</span>
108
+ <span class="mb-clock" id="mb-clock">${this.now()}</span>
109
+ </div>
110
+ </div>
111
+ `;
112
+ }
113
+
114
+ @Are.Styles
115
+ styles(@A_Inject(A_Caller) node: AreHTMLNode) {
116
+ node.setStyles(`
117
+ .menubar {
118
+ position: absolute;
119
+ top: 0; left: 0; right: 0;
120
+ height: 28px;
121
+ display: flex;
122
+ align-items: center;
123
+ justify-content: space-between;
124
+ padding: 0 14px;
125
+ background: rgba(20, 16, 32, 0.55);
126
+ backdrop-filter: blur(18px) saturate(160%);
127
+ -webkit-backdrop-filter: blur(18px) saturate(160%);
128
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
129
+ font-size: 13px;
130
+ z-index: 100;
131
+ }
132
+ .mb-left, .mb-right { display: flex; align-items: center; gap: 16px; }
133
+ .mb-logo {
134
+ width: 14px; height: 14px;
135
+ background: #f5f5f7;
136
+ -webkit-mask: radial-gradient(circle at 70% 22%, transparent 22%, #000 23%) 0 0/100% 100%;
137
+ mask: radial-gradient(circle at 70% 22%, transparent 22%, #000 23%) 0 0/100% 100%;
138
+ clip-path: path('M7 0C3 0 0 3.4 0 7.6c0 3.4 2.2 6.4 5.2 6.4 1 0 1.8-.6 2.8-.6s1.6.6 2.6.6c3 0 5.4-3 5.4-6.4C16 3.4 12.8 0 9 0 8 0 7.6.4 7 .4 6.6.4 8 0 7 0z');
139
+ opacity: 0.9;
140
+ }
141
+ .mb-active { font-weight: 700; }
142
+ .mb-menu { color: rgba(245,245,247,0.82); font-weight: 500; }
143
+ .mb-menu:first-of-type { margin-left: 2px; }
144
+ .mb-icon { opacity: 0.85; }
145
+ .mb-clock { font-variant-numeric: tabular-nums; font-weight: 500; }
146
+ .mb-chip {
147
+ font-size: 11px;
148
+ font-weight: 600;
149
+ padding: 2px 9px;
150
+ border-radius: 999px;
151
+ color: #ffe5a3;
152
+ background: rgba(255, 196, 84, 0.18);
153
+ }
154
+ `);
155
+ }
156
+ }