@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.
- package/dist/browser/index.d.mts +194 -10
- package/dist/browser/index.mjs +696 -245
- package/dist/browser/index.mjs.map +1 -1
- package/dist/node/{AreBinding.attribute-doUvtOjc.d.mts → AreBinding.attribute-BWzEIw6H.d.mts} +45 -0
- package/dist/node/{AreBinding.attribute-Bm5LlOyE.d.ts → AreBinding.attribute-GpT-5Qmf.d.ts} +45 -0
- package/dist/node/attributes/AreBinding.attribute.d.mts +1 -1
- package/dist/node/attributes/AreBinding.attribute.d.ts +1 -1
- package/dist/node/attributes/AreDirective.attribute.d.mts +1 -1
- package/dist/node/attributes/AreDirective.attribute.d.ts +1 -1
- package/dist/node/attributes/AreEvent.attribute.d.mts +1 -1
- package/dist/node/attributes/AreEvent.attribute.d.ts +1 -1
- package/dist/node/attributes/AreStatic.attribute.d.mts +1 -1
- package/dist/node/attributes/AreStatic.attribute.d.ts +1 -1
- package/dist/node/directives/AreDirectiveFor.directive.d.mts +18 -1
- package/dist/node/directives/AreDirectiveFor.directive.d.ts +18 -1
- package/dist/node/directives/AreDirectiveFor.directive.js +57 -9
- package/dist/node/directives/AreDirectiveFor.directive.js.map +1 -1
- package/dist/node/directives/AreDirectiveFor.directive.mjs +57 -9
- package/dist/node/directives/AreDirectiveFor.directive.mjs.map +1 -1
- package/dist/node/directives/AreDirectiveIf.directive.d.mts +18 -2
- package/dist/node/directives/AreDirectiveIf.directive.d.ts +18 -2
- package/dist/node/directives/AreDirectiveIf.directive.js +29 -6
- package/dist/node/directives/AreDirectiveIf.directive.js.map +1 -1
- package/dist/node/directives/AreDirectiveIf.directive.mjs +29 -6
- package/dist/node/directives/AreDirectiveIf.directive.mjs.map +1 -1
- package/dist/node/directives/AreDirectiveShow.directive.d.mts +1 -1
- package/dist/node/directives/AreDirectiveShow.directive.d.ts +1 -1
- package/dist/node/engine/AreHTML.compiler.d.mts +4 -2
- package/dist/node/engine/AreHTML.compiler.d.ts +4 -2
- package/dist/node/engine/AreHTML.compiler.js +11 -4
- package/dist/node/engine/AreHTML.compiler.js.map +1 -1
- package/dist/node/engine/AreHTML.compiler.mjs +11 -4
- package/dist/node/engine/AreHTML.compiler.mjs.map +1 -1
- package/dist/node/engine/AreHTML.constants.d.mts +33 -1
- package/dist/node/engine/AreHTML.constants.d.ts +33 -1
- package/dist/node/engine/AreHTML.constants.js +166 -0
- package/dist/node/engine/AreHTML.constants.js.map +1 -1
- package/dist/node/engine/AreHTML.constants.mjs +165 -1
- package/dist/node/engine/AreHTML.constants.mjs.map +1 -1
- package/dist/node/engine/AreHTML.context.d.mts +66 -0
- package/dist/node/engine/AreHTML.context.d.ts +66 -0
- package/dist/node/engine/AreHTML.context.js +98 -0
- package/dist/node/engine/AreHTML.context.js.map +1 -1
- package/dist/node/engine/AreHTML.context.mjs +98 -0
- package/dist/node/engine/AreHTML.context.mjs.map +1 -1
- package/dist/node/engine/AreHTML.interpreter.d.mts +3 -0
- package/dist/node/engine/AreHTML.interpreter.d.ts +3 -0
- package/dist/node/engine/AreHTML.interpreter.js +66 -10
- package/dist/node/engine/AreHTML.interpreter.js.map +1 -1
- package/dist/node/engine/AreHTML.interpreter.mjs +66 -10
- package/dist/node/engine/AreHTML.interpreter.mjs.map +1 -1
- package/dist/node/engine/AreHTML.lifecycle.d.mts +1 -8
- package/dist/node/engine/AreHTML.lifecycle.d.ts +1 -8
- package/dist/node/engine/AreHTML.lifecycle.js +29 -44
- package/dist/node/engine/AreHTML.lifecycle.js.map +1 -1
- package/dist/node/engine/AreHTML.lifecycle.mjs +29 -44
- package/dist/node/engine/AreHTML.lifecycle.mjs.map +1 -1
- package/dist/node/engine/AreHTML.tokenizer.d.mts +1 -1
- package/dist/node/engine/AreHTML.tokenizer.d.ts +1 -1
- package/dist/node/engine/AreHTML.tokenizer.js +7 -1
- package/dist/node/engine/AreHTML.tokenizer.js.map +1 -1
- package/dist/node/engine/AreHTML.tokenizer.mjs +7 -1
- package/dist/node/engine/AreHTML.tokenizer.mjs.map +1 -1
- package/dist/node/engine/AreHTML.transformer.d.mts +1 -1
- package/dist/node/engine/AreHTML.transformer.d.ts +1 -1
- package/dist/node/index.d.mts +4 -3
- package/dist/node/index.d.ts +4 -3
- package/dist/node/index.js +7 -0
- package/dist/node/index.mjs +1 -0
- package/dist/node/instructions/AddStaticHTML.instruction.d.mts +8 -0
- package/dist/node/instructions/AddStaticHTML.instruction.d.ts +8 -0
- package/dist/node/instructions/AddStaticHTML.instruction.js +31 -0
- package/dist/node/instructions/AddStaticHTML.instruction.js.map +1 -0
- package/dist/node/instructions/AddStaticHTML.instruction.mjs +24 -0
- package/dist/node/instructions/AddStaticHTML.instruction.mjs.map +1 -0
- package/dist/node/instructions/AreHTML.instructions.constants.d.mts +1 -0
- package/dist/node/instructions/AreHTML.instructions.constants.d.ts +1 -0
- package/dist/node/instructions/AreHTML.instructions.constants.js +1 -0
- package/dist/node/instructions/AreHTML.instructions.constants.js.map +1 -1
- package/dist/node/instructions/AreHTML.instructions.constants.mjs +1 -0
- package/dist/node/instructions/AreHTML.instructions.constants.mjs.map +1 -1
- package/dist/node/instructions/AreHTML.instructions.types.d.mts +9 -1
- package/dist/node/instructions/AreHTML.instructions.types.d.ts +9 -1
- package/dist/node/lib/AreDirective/AreDirective.component.d.mts +1 -1
- package/dist/node/lib/AreDirective/AreDirective.component.d.ts +1 -1
- package/dist/node/lib/AreDirective/AreDirective.types.d.mts +1 -1
- package/dist/node/lib/AreDirective/AreDirective.types.d.ts +1 -1
- package/dist/node/lib/AreHTML/AreHTML.tokenizer.d.mts +1 -1
- package/dist/node/lib/AreHTML/AreHTML.tokenizer.d.ts +1 -1
- package/dist/node/lib/AreHTMLAttribute/AreHTML.attribute.d.mts +1 -1
- package/dist/node/lib/AreHTMLAttribute/AreHTML.attribute.d.ts +1 -1
- package/dist/node/lib/AreHTMLNode/AreHTMLNode.d.mts +1 -1
- package/dist/node/lib/AreHTMLNode/AreHTMLNode.d.ts +1 -1
- package/dist/node/lib/AreHTMLNode/AreHTMLNode.js +51 -0
- package/dist/node/lib/AreHTMLNode/AreHTMLNode.js.map +1 -1
- package/dist/node/lib/AreHTMLNode/AreHTMLNode.mjs +51 -0
- package/dist/node/lib/AreHTMLNode/AreHTMLNode.mjs.map +1 -1
- package/dist/node/lib/AreRoot/AreRoot.component.js.map +1 -1
- package/dist/node/lib/AreRoot/AreRoot.component.mjs.map +1 -1
- package/dist/node/nodes/AreComment.d.mts +1 -1
- package/dist/node/nodes/AreComment.d.ts +1 -1
- package/dist/node/nodes/AreComponent.d.mts +1 -1
- package/dist/node/nodes/AreComponent.d.ts +1 -1
- package/dist/node/nodes/AreInterpolation.d.mts +1 -1
- package/dist/node/nodes/AreInterpolation.d.ts +1 -1
- package/dist/node/nodes/AreRoot.d.mts +1 -1
- package/dist/node/nodes/AreRoot.d.ts +1 -1
- package/dist/node/nodes/AreText.d.mts +1 -1
- package/dist/node/nodes/AreText.d.ts +1 -1
- package/examples/dashboard/concept.ts +1 -1
- package/examples/dashboard/dist/index.html +1 -1
- package/examples/dashboard/dist/{mqh9ryml-xat335.js → mqiw5sqa-ypckmj.js} +403 -57
- package/examples/for-perf/dist/index.html +1 -1
- package/examples/for-perf/dist/{mqh9ryfo-6a8d0o.js → mqp8i2py-vltsx0.js} +3030 -2474
- package/examples/lazy-loading/README.md +76 -0
- package/examples/lazy-loading/concept.ts +55 -0
- package/examples/lazy-loading/containers/UI.container.ts +215 -0
- package/examples/lazy-loading/dist/app.js +3803 -0
- package/examples/{for-perf/dist/mqh9ryfq-4pf5cv.js → lazy-loading/dist/chunks/chunk-6K72IBO4.js} +2708 -5476
- package/examples/lazy-loading/dist/index.html +36 -0
- package/examples/lazy-loading/dist/lazy/about-page.js +59 -0
- package/examples/lazy-loading/dist/lazy/reports-page.js +65 -0
- package/examples/lazy-loading/dist/lazy/settings-page.js +54 -0
- package/examples/lazy-loading/public/index.html +36 -0
- package/examples/lazy-loading/src/components/AppShell.component.ts +44 -0
- package/examples/lazy-loading/src/components/HomePage.component.ts +59 -0
- package/examples/lazy-loading/src/components/LazyOutlet.component.ts +108 -0
- package/examples/lazy-loading/src/components/NavBar.component.ts +98 -0
- package/examples/lazy-loading/src/concept.ts +116 -0
- package/examples/lazy-loading/src/lazy/AboutPage.component.ts +54 -0
- package/examples/lazy-loading/src/lazy/ReportsPage.component.ts +56 -0
- package/examples/lazy-loading/src/lazy/SettingsPage.component.ts +45 -0
- package/examples/lazy-loading/src/runtime/ComponentManifest.fragment.ts +61 -0
- package/examples/lazy-loading/src/runtime/LazyComponentResolver.fragment.ts +77 -0
- package/examples/os-desktop/README.md +91 -0
- package/examples/os-desktop/concept.ts +54 -0
- package/examples/os-desktop/containers/OS.container.ts +198 -0
- package/examples/os-desktop/containers/apps/AppBackend.ts +29 -0
- package/examples/os-desktop/containers/apps/GanttApp.backend.ts +56 -0
- package/examples/os-desktop/containers/apps/MarketingApp.backend.ts +68 -0
- package/examples/os-desktop/dist/app.js +4410 -0
- package/examples/os-desktop/dist/apps/gantt/app.js +271 -0
- package/examples/os-desktop/dist/apps/marketing/app.js +346 -0
- package/examples/{for-perf/dist/mqh9ryde-m243t8.js → os-desktop/dist/chunks/chunk-6K72IBO4.js} +2708 -5476
- package/examples/os-desktop/dist/chunks/chunk-EIIGUL6N.js +30 -0
- package/examples/os-desktop/dist/chunks/chunk-WOH7L5UR.js +30 -0
- package/examples/os-desktop/dist/index.html +33 -0
- package/examples/os-desktop/public/index.html +33 -0
- package/examples/os-desktop/src/apps/gantt/GanttApp.component.ts +41 -0
- package/examples/os-desktop/src/apps/gantt/GanttChart.component.ts +126 -0
- package/examples/os-desktop/src/apps/gantt/GanttStore.ts +47 -0
- package/examples/os-desktop/src/apps/gantt/GanttToolbar.component.ts +73 -0
- package/examples/os-desktop/src/apps/gantt/index.ts +13 -0
- package/examples/os-desktop/src/apps/marketing/MarketingApp.component.ts +53 -0
- package/examples/os-desktop/src/apps/marketing/MarketingStore.ts +34 -0
- package/examples/os-desktop/src/apps/marketing/PostEditor.component.ts +153 -0
- package/examples/os-desktop/src/apps/marketing/PostPreview.component.ts +110 -0
- package/examples/os-desktop/src/apps/marketing/index.ts +16 -0
- package/examples/os-desktop/src/concept.ts +126 -0
- package/examples/os-desktop/src/os/AppStage.component.ts +112 -0
- package/examples/os-desktop/src/os/AppWindow.component.ts +102 -0
- package/examples/os-desktop/src/os/Desktop.component.ts +106 -0
- package/examples/os-desktop/src/os/Dock.component.ts +174 -0
- package/examples/os-desktop/src/os/Hud.component.ts +83 -0
- package/examples/os-desktop/src/os/Launchpad.component.ts +191 -0
- package/examples/os-desktop/src/os/MenuBar.component.ts +156 -0
- package/examples/os-desktop/src/runtime/AppComponentResolver.fragment.ts +121 -0
- package/examples/os-desktop/src/runtime/AppRegistry.fragment.ts +104 -0
- package/examples/os-desktop/src/signals/MouseState.signal.ts +34 -0
- package/examples/os-desktop/src/signals/OSRoute.signal.ts +37 -0
- package/examples/os-desktop/src/signals/SelectionState.signal.ts +34 -0
- package/examples/signal-routing/dist/index.html +1 -1
- package/examples/signal-routing/dist/{mqh9ryc9-dkcbkx.js → mqp8hgce-4d6rh0.js} +3196 -2640
- package/package.json +13 -9
- package/src/directives/AreDirectiveFor.directive.ts +99 -16
- package/src/directives/AreDirectiveIf.directive.ts +33 -4
- package/src/engine/AreHTML.compiler.ts +25 -2
- package/src/engine/AreHTML.constants.ts +142 -0
- package/src/engine/AreHTML.context.ts +112 -0
- package/src/engine/AreHTML.interpreter.ts +114 -13
- package/src/engine/AreHTML.lifecycle.ts +81 -74
- package/src/engine/AreHTML.tokenizer.ts +30 -1
- package/src/index.ts +1 -0
- package/src/instructions/AddStaticHTML.instruction.ts +23 -0
- package/src/instructions/AreHTML.instructions.constants.ts +1 -0
- package/src/instructions/AreHTML.instructions.types.ts +9 -0
- package/src/lib/AreHTMLNode/AreHTMLNode.ts +74 -0
- package/src/lib/AreRoot/AreRoot.component.ts +3 -3
- package/tests/PropPropagation.test.ts +181 -0
- package/tests/StaticIsland.test.ts +115 -0
- package/tests/jest.setup.ts +11 -0
|
@@ -0,0 +1,4410 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SelectionState
|
|
3
|
+
} from "./chunks/chunk-WOH7L5UR.js";
|
|
4
|
+
import {
|
|
5
|
+
MouseState
|
|
6
|
+
} from "./chunks/chunk-EIIGUL6N.js";
|
|
7
|
+
import {
|
|
8
|
+
A_Config,
|
|
9
|
+
A_ExecutionContext,
|
|
10
|
+
A_LOGGER_ENV_KEYS,
|
|
11
|
+
A_Logger,
|
|
12
|
+
A_Polyfill,
|
|
13
|
+
A_Route,
|
|
14
|
+
A_ServiceFeatures,
|
|
15
|
+
A_SignalBus,
|
|
16
|
+
A_SignalState,
|
|
17
|
+
A_SignalVector,
|
|
18
|
+
Are,
|
|
19
|
+
AreAttribute,
|
|
20
|
+
AreAttributeFeatures,
|
|
21
|
+
AreCompiler,
|
|
22
|
+
AreCompilerError,
|
|
23
|
+
AreComponentResolver,
|
|
24
|
+
AreContainer,
|
|
25
|
+
AreContext,
|
|
26
|
+
AreDeclaration,
|
|
27
|
+
AreEngine,
|
|
28
|
+
AreEvent,
|
|
29
|
+
AreInit,
|
|
30
|
+
AreInstructionDefaultNames,
|
|
31
|
+
AreInterpreter,
|
|
32
|
+
AreInterpreterError,
|
|
33
|
+
AreLifecycle,
|
|
34
|
+
AreMutation,
|
|
35
|
+
AreNode,
|
|
36
|
+
AreNodeFeatures,
|
|
37
|
+
AreScene,
|
|
38
|
+
AreSignal,
|
|
39
|
+
AreSignals,
|
|
40
|
+
AreSignalsContext,
|
|
41
|
+
AreStore,
|
|
42
|
+
AreSyntax,
|
|
43
|
+
AreTokenizer,
|
|
44
|
+
AreTransformer,
|
|
45
|
+
ConfigReader,
|
|
46
|
+
H,
|
|
47
|
+
M,
|
|
48
|
+
N,
|
|
49
|
+
O,
|
|
50
|
+
P,
|
|
51
|
+
R,
|
|
52
|
+
R2,
|
|
53
|
+
Yt,
|
|
54
|
+
_,
|
|
55
|
+
__decorateClass,
|
|
56
|
+
__decorateParam,
|
|
57
|
+
__name,
|
|
58
|
+
ct,
|
|
59
|
+
m,
|
|
60
|
+
te,
|
|
61
|
+
z
|
|
62
|
+
} from "./chunks/chunk-6K72IBO4.js";
|
|
63
|
+
|
|
64
|
+
// src/lib/AreHTMLAttribute/AreHTML.attribute.ts
|
|
65
|
+
var AreHTMLAttribute = class extends AreAttribute {
|
|
66
|
+
get owner() {
|
|
67
|
+
return this.scope.issuer();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
__name(AreHTMLAttribute, "AreHTMLAttribute");
|
|
71
|
+
AreHTMLAttribute = __decorateClass([
|
|
72
|
+
R2.Define({
|
|
73
|
+
namespace: "a-are-html",
|
|
74
|
+
description: "Base class for all typed HTML attributes in the ARE framework. Provides typed access to the owning AreHTMLNode via the scope injector so that attribute subclasses can inspect host-node properties and resolve store bindings during transformation, compilation, and lifecycle phases."
|
|
75
|
+
})
|
|
76
|
+
], AreHTMLAttribute);
|
|
77
|
+
|
|
78
|
+
// src/attributes/AreBinding.attribute.ts
|
|
79
|
+
var AreBindingAttribute = class extends AreHTMLAttribute {
|
|
80
|
+
// get value(): string {
|
|
81
|
+
// const [firstPart, ...pathPart] = this.content.split('.');
|
|
82
|
+
// const primaryObject = this.owner.store.get(firstPart);
|
|
83
|
+
// return AreCommonHelper.extractPropertyByPath(primaryObject, pathPart.join('.')) as string;
|
|
84
|
+
// }
|
|
85
|
+
};
|
|
86
|
+
__name(AreBindingAttribute, "AreBindingAttribute");
|
|
87
|
+
AreBindingAttribute = __decorateClass([
|
|
88
|
+
R2.Define({
|
|
89
|
+
namespace: "a-are-html",
|
|
90
|
+
description: "Attribute type for two-way value bindings (: prefix). Marks that the attribute value should be resolved dynamically from the node store rather than used verbatim, enabling reactive updates whenever the underlying store value changes during a rendering cycle."
|
|
91
|
+
})
|
|
92
|
+
], AreBindingAttribute);
|
|
93
|
+
|
|
94
|
+
// src/attributes/AreDirective.attribute.ts
|
|
95
|
+
var AreDirectiveAttribute = class extends AreHTMLAttribute {
|
|
96
|
+
/**
|
|
97
|
+
* Returns a custom directive component associated with this attribute, if available.
|
|
98
|
+
*
|
|
99
|
+
* The method uses the attribute's name to resolve the corresponding directive component from the scope. It constructs the expected directive name by converting the attribute name to PascalCase and prefixing it with "AreDirective". If a matching directive component is found in the scope, it is returned; otherwise, the method returns undefined.
|
|
100
|
+
*/
|
|
101
|
+
get component() {
|
|
102
|
+
const component = this.scope.resolve(`AreDirective${P.toPascalCase(this.name)}`);
|
|
103
|
+
return component;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
__name(AreDirectiveAttribute, "AreDirectiveAttribute");
|
|
107
|
+
AreDirectiveAttribute = __decorateClass([
|
|
108
|
+
R2.Define({
|
|
109
|
+
namespace: "a-are-html",
|
|
110
|
+
description: "Attribute type for directive invocations ($ prefix). Carries the resolved directive component class and a cloned template node. The associated directive uses these during its Compile phase to emit conditional or repeated instruction groups and to manage per-item or per-condition subscopes."
|
|
111
|
+
})
|
|
112
|
+
], AreDirectiveAttribute);
|
|
113
|
+
|
|
114
|
+
// src/attributes/AreEvent.attribute.ts
|
|
115
|
+
var AreEventAttribute = class extends AreHTMLAttribute {
|
|
116
|
+
};
|
|
117
|
+
__name(AreEventAttribute, "AreEventAttribute");
|
|
118
|
+
AreEventAttribute = __decorateClass([
|
|
119
|
+
R2.Define({
|
|
120
|
+
namespace: "a-are-html",
|
|
121
|
+
description: "Attribute type for DOM event listeners (@ prefix). Marks the attribute as an event binding \u2014 the compiler emits an AddListener instruction that attaches a handler expression resolved from the store to the specified event name on the host element."
|
|
122
|
+
})
|
|
123
|
+
], AreEventAttribute);
|
|
124
|
+
|
|
125
|
+
// src/attributes/AreStatic.attribute.ts
|
|
126
|
+
var AreStaticAttribute = class extends AreHTMLAttribute {
|
|
127
|
+
};
|
|
128
|
+
__name(AreStaticAttribute, "AreStaticAttribute");
|
|
129
|
+
AreStaticAttribute = __decorateClass([
|
|
130
|
+
R2.Define({
|
|
131
|
+
namespace: "a-are-html",
|
|
132
|
+
description: "Attribute type for plain static HTML attributes with no dynamic prefix. Its value is emitted verbatim via an AddAttribute instruction at compile time and does not participate in reactive update cycles."
|
|
133
|
+
})
|
|
134
|
+
], AreStaticAttribute);
|
|
135
|
+
|
|
136
|
+
// src/lib/AreDirective/AreDirective.meta.ts
|
|
137
|
+
var _AreDirectiveMeta = class _AreDirectiveMeta extends z {
|
|
138
|
+
constructor() {
|
|
139
|
+
super(...arguments);
|
|
140
|
+
this.priority = 0;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
__name(_AreDirectiveMeta, "AreDirectiveMeta");
|
|
144
|
+
var AreDirectiveMeta = _AreDirectiveMeta;
|
|
145
|
+
|
|
146
|
+
// src/lib/AreDirective/AreDirective.constants.ts
|
|
147
|
+
var AreDirectiveFeatures = {
|
|
148
|
+
/**
|
|
149
|
+
* Feature that should transform the tree based on the directive attribute. This method is called during the transformation phase of the ARE component and should perform any necessary transformations on the AreNode tree based on the directive's content and context. This can include tasks such as adding or removing nodes, modifying node properties, or restructuring the tree to ensure that the directive is applied correctly during rendering.
|
|
150
|
+
*/
|
|
151
|
+
Transform: "_AreDirective_Transform",
|
|
152
|
+
/**
|
|
153
|
+
* Feature that should convert a directiveAttribute definition into a set of SceneInstructions to be rendered correctly
|
|
154
|
+
*/
|
|
155
|
+
Compile: "_AreDirective_Compile",
|
|
156
|
+
/**
|
|
157
|
+
* Feature that should update the directiveAttribute based on the changes in the store or other dependencies.
|
|
158
|
+
*/
|
|
159
|
+
Update: "_AreDirective_Update"
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// src/lib/AreDirective/AreDirective.component.ts
|
|
163
|
+
var AreDirective = class extends O {
|
|
164
|
+
//==================================================================================
|
|
165
|
+
//======================== LIFECYCLE DECORATORS ====================================
|
|
166
|
+
//==================================================================================
|
|
167
|
+
/**
|
|
168
|
+
* Allows to define a compilation order for directives, which is necessary when we have multiple directives on the same node and we want to control the order of their compilation and application. The directive with the highest priority will be compiled and applied first, and the directive with the lowest priority will be compiled and applied last. This is important because some directives may depend on the output of other directives, so we need to ensure that they are compiled and applied in the correct order to avoid errors and ensure the expected behavior.
|
|
169
|
+
*
|
|
170
|
+
* @param priority
|
|
171
|
+
* @returns
|
|
172
|
+
*/
|
|
173
|
+
static Priority(priority) {
|
|
174
|
+
return function(target) {
|
|
175
|
+
const meta = _.meta(target);
|
|
176
|
+
meta.priority = priority;
|
|
177
|
+
return target;
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Allows to define a custom method for transforming the AreNode tree based on the directive attribute. This method is called during the transformation phase of the ARE component and should perform any necessary transformations on the AreNode tree based on the directive's content and context. This can include tasks such as adding or removing nodes, modifying node properties, or restructuring the tree to ensure that the directive is applied correctly during rendering.
|
|
182
|
+
*/
|
|
183
|
+
static get Transform() {
|
|
184
|
+
return (target, propertyKey, descriptor) => {
|
|
185
|
+
return N.Extend({
|
|
186
|
+
name: AreDirectiveFeatures.Transform,
|
|
187
|
+
scope: [target.constructor]
|
|
188
|
+
})(target, propertyKey, descriptor);
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Allows to define a custom method for compiling a directive attribute into a set of SceneInstructions.
|
|
193
|
+
* Can be used at any component to extend this logic not only for a AreDirective inherited.
|
|
194
|
+
*/
|
|
195
|
+
static get Compile() {
|
|
196
|
+
return (target, propertyKey, descriptor) => {
|
|
197
|
+
return N.Extend({
|
|
198
|
+
name: AreDirectiveFeatures.Compile,
|
|
199
|
+
scope: [target.constructor]
|
|
200
|
+
})(target, propertyKey, descriptor);
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Allows to define a custom method for updating a directive attribute based on changes in the store or other dependencies.
|
|
205
|
+
* Can be used at any component to extend this logic not only for a AreDirective inherited.
|
|
206
|
+
*/
|
|
207
|
+
static get Update() {
|
|
208
|
+
return (target, propertyKey, descriptor) => {
|
|
209
|
+
return N.Extend({
|
|
210
|
+
name: AreDirectiveFeatures.Update,
|
|
211
|
+
scope: [target.constructor]
|
|
212
|
+
})(target, propertyKey, descriptor);
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Default transform method for directives, which can be overridden by specific directive implementations. This method is called during the transformation phase of the ARE component and should perform any necessary transformations on the AreNode tree based on the directive's content and context. This can include tasks such as adding or removing nodes, modifying node properties, or restructuring the tree to ensure that the directive is applied correctly during rendering.
|
|
217
|
+
*
|
|
218
|
+
* @param attribute - The directive attribute to transform, which contains all the information about the directive as defined in the template (e.g. name, raw content, evaluated value, etc.)
|
|
219
|
+
* @param args - Additional arguments that may be required for the transformation process.
|
|
220
|
+
*/
|
|
221
|
+
transform(attribute, ...args) {
|
|
222
|
+
const logger = _.scope(this).resolve(A_Logger);
|
|
223
|
+
if (logger) {
|
|
224
|
+
logger.warning(`No transforming logic defined for directive: ${attribute.name} with content: ${attribute.content}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
compile(attribute, ...args) {
|
|
228
|
+
const logger = _.scope(this).resolve(A_Logger);
|
|
229
|
+
if (logger) {
|
|
230
|
+
logger.warning(`No compiling logic defined for directive: ${attribute.name} with content: ${attribute.content}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
update(attribute, ...args) {
|
|
234
|
+
const logger = _.scope(this).resolve(A_Logger);
|
|
235
|
+
if (logger) {
|
|
236
|
+
logger.warning(`No update logic defined for directive: ${attribute.name} with content: ${attribute.content}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
__name(AreDirective, "AreDirective");
|
|
241
|
+
__decorateClass([
|
|
242
|
+
__decorateParam(0, Yt(te))
|
|
243
|
+
], AreDirective.prototype, "transform", 1);
|
|
244
|
+
__decorateClass([
|
|
245
|
+
N.Extend({
|
|
246
|
+
name: AreDirectiveFeatures.Compile,
|
|
247
|
+
scope: [AreDirective]
|
|
248
|
+
}),
|
|
249
|
+
__decorateParam(0, Yt(te))
|
|
250
|
+
], AreDirective.prototype, "compile", 1);
|
|
251
|
+
__decorateClass([
|
|
252
|
+
N.Extend({
|
|
253
|
+
name: AreDirectiveFeatures.Update,
|
|
254
|
+
scope: [AreDirective]
|
|
255
|
+
}),
|
|
256
|
+
__decorateParam(0, Yt(te))
|
|
257
|
+
], AreDirective.prototype, "update", 1);
|
|
258
|
+
AreDirective = __decorateClass([
|
|
259
|
+
R2.Define({
|
|
260
|
+
namespace: "a-are-html",
|
|
261
|
+
description: "Abstract base component for all ARE directive types. Provides lifecycle decorators (Transform, Compile, Apply, Revert, Priority) that subclasses hook into at each pipeline stage. Subclasses implement Transform to rewrite the attribute or template node, Compile to emit scene instructions, Apply to activate them in the DOM, and Revert to undo them on removal."
|
|
262
|
+
}),
|
|
263
|
+
m.Define(AreDirectiveMeta)
|
|
264
|
+
], AreDirective);
|
|
265
|
+
|
|
266
|
+
// src/instructions/AreHTML.instructions.constants.ts
|
|
267
|
+
var AreHTMLInstructions = {
|
|
268
|
+
AddElement: "_AreHTML_AddElement",
|
|
269
|
+
AddText: "_AreHTML_AddText",
|
|
270
|
+
AddAttribute: "_AreHTML_AddAttribute",
|
|
271
|
+
AddStyle: "_AreHTML_AddStyle",
|
|
272
|
+
AddListener: "_AreHTML_AddListener",
|
|
273
|
+
AddInterpolation: "_AreHTML_AddInterpolation",
|
|
274
|
+
AddComment: "_AreHTML_AddComment",
|
|
275
|
+
AddStaticHTML: "_AreHTML_AddStaticHTML",
|
|
276
|
+
HideElement: "_AreHTML_HideElement"
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// src/instructions/AddComment.instruction.ts
|
|
280
|
+
var AddCommentInstruction = class extends AreDeclaration {
|
|
281
|
+
get content() {
|
|
282
|
+
return this.payload.content;
|
|
283
|
+
}
|
|
284
|
+
constructor(props) {
|
|
285
|
+
if ("aseid" in props) {
|
|
286
|
+
super(props);
|
|
287
|
+
} else {
|
|
288
|
+
super(AreHTMLInstructions.AddComment, props);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
__name(AddCommentInstruction, "AddCommentInstruction");
|
|
293
|
+
AddCommentInstruction = __decorateClass([
|
|
294
|
+
R2.Define({
|
|
295
|
+
namespace: "a-are-html",
|
|
296
|
+
description: "Appends a comment node to an element. Apply creates the comment node; revert removes it. Content can be a static string or a dynamic getter for interpolations."
|
|
297
|
+
})
|
|
298
|
+
], AddCommentInstruction);
|
|
299
|
+
|
|
300
|
+
// src/lib/AreDirective/AreDirective.context.ts
|
|
301
|
+
var _AreDirectiveContext = class _AreDirectiveContext extends A_ExecutionContext {
|
|
302
|
+
constructor(aseid) {
|
|
303
|
+
super(aseid.toString());
|
|
304
|
+
this.scope = {};
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
__name(_AreDirectiveContext, "AreDirectiveContext");
|
|
308
|
+
var AreDirectiveContext = _AreDirectiveContext;
|
|
309
|
+
|
|
310
|
+
// src/helpers/AreScheduler.helper.ts
|
|
311
|
+
var _AreSchedulerHelper = class _AreSchedulerHelper {
|
|
312
|
+
/**
|
|
313
|
+
* High-resolution wall-clock time in milliseconds. Uses `performance.now()`
|
|
314
|
+
* when available (monotonic, sub-millisecond), falling back to `Date.now()`.
|
|
315
|
+
*/
|
|
316
|
+
static now() {
|
|
317
|
+
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Schedule `fn` to run on the next macrotask.
|
|
321
|
+
*
|
|
322
|
+
* `MessageChannel` yields a true macrotask without the ~4ms clamp that nested
|
|
323
|
+
* `setTimeout(0)` calls incur, so the browser can paint between chunks with
|
|
324
|
+
* minimal scheduling overhead. Falls back to `setTimeout` in non-DOM
|
|
325
|
+
* environments (e.g. tests / SSR).
|
|
326
|
+
*/
|
|
327
|
+
static scheduleMacrotask(fn) {
|
|
328
|
+
if (typeof MessageChannel === "undefined") {
|
|
329
|
+
setTimeout(fn, 0);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (!this._channel) {
|
|
333
|
+
this._channel = new MessageChannel();
|
|
334
|
+
this._channel.port1.onmessage = () => {
|
|
335
|
+
const next = this._queue.shift();
|
|
336
|
+
if (next) next();
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
this._queue.push(fn);
|
|
340
|
+
this._channel.port2.postMessage(null);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
__name(_AreSchedulerHelper, "AreSchedulerHelper");
|
|
344
|
+
/** FIFO queue of callbacks waiting for their posted macrotask to fire. */
|
|
345
|
+
_AreSchedulerHelper._queue = [];
|
|
346
|
+
var AreSchedulerHelper = _AreSchedulerHelper;
|
|
347
|
+
|
|
348
|
+
// src/engine/AreHTML.context.ts
|
|
349
|
+
var AreHTMLEngineContext = class extends AreContext {
|
|
350
|
+
constructor(props) {
|
|
351
|
+
super(props.container?.body.innerHTML || props.source || "");
|
|
352
|
+
/**
|
|
353
|
+
* Index structure mapping:
|
|
354
|
+
*
|
|
355
|
+
* Node -> Group ID -> Element
|
|
356
|
+
* -----------------------------------------------------------------------------------
|
|
357
|
+
* | - Attribute | group: string | Node
|
|
358
|
+
* | - Directive (e.g. for) | | Node
|
|
359
|
+
*/
|
|
360
|
+
this.index = {
|
|
361
|
+
/**
|
|
362
|
+
* 1 AreNode = 1 Dom Node
|
|
363
|
+
*
|
|
364
|
+
* uses ASEID
|
|
365
|
+
*/
|
|
366
|
+
nodeToHostElements: /* @__PURE__ */ new Map(),
|
|
367
|
+
/**
|
|
368
|
+
* 1 Group Instruction = MANY Dom Nodes (e.g. for loop)
|
|
369
|
+
*
|
|
370
|
+
* uses ASEID
|
|
371
|
+
*/
|
|
372
|
+
groupToElements: /* @__PURE__ */ new Map(),
|
|
373
|
+
/**
|
|
374
|
+
* 1 Dom Node = 1 Instruction
|
|
375
|
+
*
|
|
376
|
+
* uses ASEID
|
|
377
|
+
*/
|
|
378
|
+
elementToInstruction: /* @__PURE__ */ new WeakMap(),
|
|
379
|
+
/**
|
|
380
|
+
* 1 Instruction = 1 Dom Node (for CreateElement instructions, for example)
|
|
381
|
+
*
|
|
382
|
+
* uses ASEID
|
|
383
|
+
*/
|
|
384
|
+
instructionToElement: /* @__PURE__ */ new Map(),
|
|
385
|
+
/**
|
|
386
|
+
* Event listeners attached to elements, used for proper cleanup when reverting instructions. Maps a DOM element to a map of event names and their corresponding listeners, allowing the engine to track which listeners are attached to which elements and remove them when necessary (e.g., when an instruction is reverted).
|
|
387
|
+
*/
|
|
388
|
+
elementListeners: /* @__PURE__ */ new WeakMap()
|
|
389
|
+
};
|
|
390
|
+
/**
|
|
391
|
+
* Parsed-fragment cache for static islands (see AddStaticHTMLInstruction).
|
|
392
|
+
*
|
|
393
|
+
* Keyed by `hostTag\u0000markup`, each entry holds a `DocumentFragment` whose
|
|
394
|
+
* children were parsed by the browser exactly once — in the *correct element
|
|
395
|
+
* context* (the host tag), so table fragments (`<tr>`, `<td>`, …) and other
|
|
396
|
+
* context-sensitive content parse correctly. Repeated static islands with
|
|
397
|
+
* identical markup (e.g. list rows, reused components) clone the pre-parsed
|
|
398
|
+
* fragment instead of re-parsing the HTML string on every mount — turning an
|
|
399
|
+
* O(parse) operation into an O(clone) one.
|
|
400
|
+
*/
|
|
401
|
+
this._staticFragmentCache = /* @__PURE__ */ new Map();
|
|
402
|
+
/**
|
|
403
|
+
* Live-DOM attachments deferred while a mount pass is batching.
|
|
404
|
+
*
|
|
405
|
+
* A freshly-mounted subtree is built inside a *detached* root element, so
|
|
406
|
+
* every descendant `appendChild`/`insertBefore` happens off-document and
|
|
407
|
+
* triggers zero layout/paint invalidation. The single mutation that actually
|
|
408
|
+
* connects the built subtree to the live document is deferred and collected
|
|
409
|
+
* here, then flushed once when the batch closes — collapsing O(nodes) reflows
|
|
410
|
+
* into O(1) per mount root.
|
|
411
|
+
*/
|
|
412
|
+
this._pendingAttachments = [];
|
|
413
|
+
/**
|
|
414
|
+
* Depth of the currently open batching scopes. Re-entrant so that nested
|
|
415
|
+
* `beginBatch`/`endBatch` pairs flush exactly once, when the outermost scope
|
|
416
|
+
* closes.
|
|
417
|
+
*/
|
|
418
|
+
this._batchDepth = 0;
|
|
419
|
+
this._container = props.container;
|
|
420
|
+
}
|
|
421
|
+
get container() {
|
|
422
|
+
return this._container;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* `true` while a synchronous mount pass is batching live-DOM attachments.
|
|
426
|
+
* Interpreter handlers consult this to decide whether to attach an element
|
|
427
|
+
* immediately or hand the attachment to {@link deferAttach}.
|
|
428
|
+
*/
|
|
429
|
+
get isBatching() {
|
|
430
|
+
return this._batchDepth > 0;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Opens a batching scope. Re-entrant: only the outermost matching
|
|
434
|
+
* {@link endBatch} flushes the deferred attachments, so a single mount pass
|
|
435
|
+
* connects its built subtree to the live DOM exactly once.
|
|
436
|
+
*/
|
|
437
|
+
beginBatch() {
|
|
438
|
+
this._batchDepth++;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Registers a live-DOM attachment to run when the current batch flushes. If
|
|
442
|
+
* no batch is active the attachment runs immediately, preserving the original
|
|
443
|
+
* synchronous behaviour for updates that mount outside a batch.
|
|
444
|
+
*
|
|
445
|
+
* @param attach the DOM mutation that connects a built subtree to the document
|
|
446
|
+
*/
|
|
447
|
+
deferAttach(attach) {
|
|
448
|
+
if (this._batchDepth > 0) {
|
|
449
|
+
this._pendingAttachments.push(attach);
|
|
450
|
+
} else {
|
|
451
|
+
attach();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Closes a batching scope. When the outermost scope closes, every deferred
|
|
456
|
+
* attachment runs in registration (document) order, connecting the built
|
|
457
|
+
* subtrees to the live DOM in a single pass.
|
|
458
|
+
*/
|
|
459
|
+
endBatch() {
|
|
460
|
+
if (this._batchDepth === 0) return;
|
|
461
|
+
this._batchDepth--;
|
|
462
|
+
if (this._batchDepth > 0) return;
|
|
463
|
+
const pending = this._pendingAttachments;
|
|
464
|
+
this._pendingAttachments = [];
|
|
465
|
+
for (let i = 0; i < pending.length; i++) {
|
|
466
|
+
pending[i]();
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Returns a `DocumentFragment` containing the parsed form of `html`, parsed
|
|
471
|
+
* once in the context of `hostTag` (so context-sensitive content such as
|
|
472
|
+
* table rows/cells parses correctly) and cached thereafter. Callers should
|
|
473
|
+
* `cloneNode(true)` the returned fragment rather than mutating it, so the
|
|
474
|
+
* cache stays reusable.
|
|
475
|
+
*
|
|
476
|
+
* @param hostTag the tag name of the element the markup will be injected into
|
|
477
|
+
* @param html verbatim static-island inner markup
|
|
478
|
+
*/
|
|
479
|
+
getStaticFragment(hostTag, html) {
|
|
480
|
+
const key = `${hostTag}\0${html}`;
|
|
481
|
+
let fragment = this._staticFragmentCache.get(key);
|
|
482
|
+
if (!fragment) {
|
|
483
|
+
const container = this._container.createElement(hostTag);
|
|
484
|
+
container.innerHTML = html;
|
|
485
|
+
fragment = this._container.createDocumentFragment();
|
|
486
|
+
while (container.firstChild) {
|
|
487
|
+
fragment.appendChild(container.firstChild);
|
|
488
|
+
}
|
|
489
|
+
this._staticFragmentCache.set(key, fragment);
|
|
490
|
+
}
|
|
491
|
+
return fragment;
|
|
492
|
+
}
|
|
493
|
+
getNodeElement(node) {
|
|
494
|
+
if (typeof node === "string") {
|
|
495
|
+
return this.index.nodeToHostElements.get(node);
|
|
496
|
+
} else {
|
|
497
|
+
return this.index.nodeToHostElements.get(node.aseid.toString());
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Associates a DOM element with a given instruction and its owner node. This method updates the context's index to map the instruction's ASEID to the provided DOM element, and also maps the element back to the instruction's ASEID for reverse lookup. If the instruction has an owner node, it also maps the node's ASEID to the element. Additionally, if the instruction belongs to a group, it adds the element to the set of elements associated with that group. This indexing allows the engine to efficiently manage and update DOM elements based on instructions and their corresponding nodes, enabling dynamic rendering and interaction in response to application state changes.
|
|
502
|
+
*
|
|
503
|
+
* @param instruction
|
|
504
|
+
* @param element
|
|
505
|
+
*/
|
|
506
|
+
setInstructionElement(instruction, element) {
|
|
507
|
+
const node = instruction.owner;
|
|
508
|
+
this.index.instructionToElement.set(instruction.aseid.toString(), element);
|
|
509
|
+
this.index.elementToInstruction.set(element, instruction.aseid.toString());
|
|
510
|
+
if (node && instruction instanceof AreDeclaration) {
|
|
511
|
+
this.index.nodeToHostElements.set(node.aseid.toString(), element);
|
|
512
|
+
}
|
|
513
|
+
if (instruction.group) {
|
|
514
|
+
const groupId = instruction.group;
|
|
515
|
+
if (!this.index.groupToElements.has(groupId)) {
|
|
516
|
+
this.index.groupToElements.set(groupId, /* @__PURE__ */ new Set());
|
|
517
|
+
}
|
|
518
|
+
this.index.groupToElements.get(groupId).add(element);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
getElementByInstruction(instruction) {
|
|
522
|
+
if (typeof instruction === "string") {
|
|
523
|
+
return this.index.instructionToElement.get(instruction);
|
|
524
|
+
} else {
|
|
525
|
+
return this.index.instructionToElement.get(instruction.aseid.toString());
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Removes the association between a given instruction and its corresponding DOM element. This method looks up the instruction's ASEID to find the associated DOM element, and if found, it deletes the mapping from both instructionToElement and elementToInstruction. If the instruction has an owner node, it also removes the mapping from nodeToHostElements. Additionally, if the instruction belongs to a group, it removes the element from the set of elements associated with that group, and if the group has no more elements, it deletes the group from the index. This cleanup is essential for maintaining an accurate and efficient mapping of instructions to DOM elements, especially when instructions are reverted or when nodes are removed from the DOM.
|
|
530
|
+
*
|
|
531
|
+
* @param instruction
|
|
532
|
+
*/
|
|
533
|
+
removeInstructionElement(instruction) {
|
|
534
|
+
const element = this.index.instructionToElement.get(instruction.aseid.toString());
|
|
535
|
+
if (element) {
|
|
536
|
+
this.index.instructionToElement.delete(instruction.aseid.toString());
|
|
537
|
+
this.index.elementToInstruction.delete(element);
|
|
538
|
+
const node = instruction.owner;
|
|
539
|
+
if (node && instruction instanceof AreDeclaration) {
|
|
540
|
+
this.index.nodeToHostElements.delete(node.aseid.toString());
|
|
541
|
+
}
|
|
542
|
+
if (instruction.group) {
|
|
543
|
+
const groupId = instruction.group;
|
|
544
|
+
const groupElements = this.index.groupToElements.get(groupId);
|
|
545
|
+
if (groupElements) {
|
|
546
|
+
groupElements.delete(element);
|
|
547
|
+
if (groupElements.size === 0) {
|
|
548
|
+
this.index.groupToElements.delete(groupId);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
getElementsByGroup(instruction) {
|
|
555
|
+
if (typeof instruction === "string") {
|
|
556
|
+
return this.index.groupToElements.get(instruction);
|
|
557
|
+
} else {
|
|
558
|
+
return this.index.groupToElements.get(instruction.aseid.toString());
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Adds an event listener to a specific DOM element and keeps track of it in the context's index for proper cleanup later. This method takes a DOM element, an event name, and a listener function or object, and stores this information in the elementListeners map. This allows the engine to efficiently manage event listeners attached to dynamically created elements, ensuring that they can be removed when the associated instructions are reverted or when nodes are removed from the DOM, preventing memory leaks and unintended behavior.
|
|
563
|
+
*
|
|
564
|
+
* @param element
|
|
565
|
+
* @param eventName
|
|
566
|
+
* @param listener
|
|
567
|
+
*/
|
|
568
|
+
addListener(element, eventName, listener) {
|
|
569
|
+
if (!this.index.elementListeners.has(element)) {
|
|
570
|
+
this.index.elementListeners.set(element, /* @__PURE__ */ new Map());
|
|
571
|
+
}
|
|
572
|
+
const byEvent = this.index.elementListeners.get(element);
|
|
573
|
+
if (!byEvent.has(eventName)) {
|
|
574
|
+
byEvent.set(eventName, /* @__PURE__ */ new Set());
|
|
575
|
+
}
|
|
576
|
+
byEvent.get(eventName).add(listener);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Retrieves the event listener associated with a specific DOM element and event name from the context's index. This method looks up the element in the elementListeners map and then retrieves the listener for the specified event name. If no listener is found for the given element and event, it returns undefined. This allows the engine to efficiently access and manage event listeners that have been attached to dynamically created elements, enabling proper cleanup when instructions are reverted or when nodes are removed from the DOM.
|
|
580
|
+
*
|
|
581
|
+
* @param element
|
|
582
|
+
* @param eventName
|
|
583
|
+
* @returns
|
|
584
|
+
*/
|
|
585
|
+
getListener(element, eventName) {
|
|
586
|
+
const set = this.index.elementListeners.get(element)?.get(eventName);
|
|
587
|
+
if (!set || set.size === 0) return void 0;
|
|
588
|
+
return set.values().next().value;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Returns all listeners registered for a given element + event name.
|
|
592
|
+
*/
|
|
593
|
+
getListeners(element, eventName) {
|
|
594
|
+
return this.index.elementListeners.get(element)?.get(eventName);
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Removes an event listener from a specific DOM element and updates the context's index accordingly. This method looks up the element in the elementListeners map and deletes the listener for the specified event name. This is typically called when an instruction is reverted or when a node is removed from the DOM, ensuring that any attached event listeners are properly cleaned up to prevent memory leaks and unintended behavior.
|
|
598
|
+
*
|
|
599
|
+
* @param element
|
|
600
|
+
* @param eventName
|
|
601
|
+
*/
|
|
602
|
+
removeListener(element, eventName, listener) {
|
|
603
|
+
const byEvent = this.index.elementListeners.get(element);
|
|
604
|
+
if (!byEvent) return;
|
|
605
|
+
if (listener) {
|
|
606
|
+
const set = byEvent.get(eventName);
|
|
607
|
+
if (set) {
|
|
608
|
+
set.delete(listener);
|
|
609
|
+
if (set.size === 0) byEvent.delete(eventName);
|
|
610
|
+
}
|
|
611
|
+
} else {
|
|
612
|
+
byEvent.delete(eventName);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
__name(AreHTMLEngineContext, "AreHTMLEngineContext");
|
|
617
|
+
AreHTMLEngineContext = __decorateClass([
|
|
618
|
+
R2.Define({
|
|
619
|
+
namespace: "a-are-html",
|
|
620
|
+
description: "Runtime index for the HTML rendering engine. Maps each AreNode and instruction ASEID to its corresponding DOM element so that apply and revert handlers on interpreter instructions can look up their DOM node in O(1). Tracks root-element mounts and maintains the group-level index used by structural directives."
|
|
621
|
+
})
|
|
622
|
+
], AreHTMLEngineContext);
|
|
623
|
+
|
|
624
|
+
// src/directives/AreDirectiveFor.directive.ts
|
|
625
|
+
var AreDirectiveFor = class extends AreDirective {
|
|
626
|
+
transform(attribute, scope, store, scene, logger, ...args) {
|
|
627
|
+
logger.debug(`[Transform] directive $FOR for <${attribute.owner.aseid.toString()}>`);
|
|
628
|
+
const node = attribute.owner;
|
|
629
|
+
const forTemplate = node.cloneWithScope();
|
|
630
|
+
const forAttr = forTemplate.attributes.find((d) => d.name === attribute.name);
|
|
631
|
+
if (forAttr) {
|
|
632
|
+
forTemplate.scope.deregister(forAttr);
|
|
633
|
+
node.scope.register(forAttr);
|
|
634
|
+
}
|
|
635
|
+
node.init();
|
|
636
|
+
attribute.template = forTemplate;
|
|
637
|
+
const { key, index, arrayExpr } = this.parseExpression(attribute.content);
|
|
638
|
+
const contextScope = attribute.owner.scope.resolve(AreDirectiveContext)?.scope || {};
|
|
639
|
+
const array = this.resolveArray(store, arrayExpr, attribute.content, contextScope);
|
|
640
|
+
attribute.value = array;
|
|
641
|
+
for (let i = 0; i < array.length; i++) {
|
|
642
|
+
this.spawnItemNode(attribute.template, attribute.owner, key, index, array[i], i);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
compile(attribute, store, scene, ...args) {
|
|
646
|
+
const hostInstruction = scene.host;
|
|
647
|
+
const commentIdentifier = ` --- for: ${attribute.template.id} --- `;
|
|
648
|
+
const declaration = new AddCommentInstruction({ content: commentIdentifier });
|
|
649
|
+
scene.setHost(declaration);
|
|
650
|
+
scene.planBefore(declaration, hostInstruction);
|
|
651
|
+
scene.unPlan(hostInstruction);
|
|
652
|
+
}
|
|
653
|
+
update(attribute, store, scene, ...args) {
|
|
654
|
+
let state = AreDirectiveFor.renderState.get(attribute);
|
|
655
|
+
if (!state) {
|
|
656
|
+
state = { running: false, pending: false };
|
|
657
|
+
AreDirectiveFor.renderState.set(attribute, state);
|
|
658
|
+
}
|
|
659
|
+
if (state.running) {
|
|
660
|
+
state.pending = true;
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
return this.performUpdate(attribute, store, scene, state);
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Core of the `$for` update: re-diff the source array against the current
|
|
667
|
+
* children, reconcile reused/removed items, then mount the new ones (small
|
|
668
|
+
* lists synchronously, large lists time-sliced). Never called while another
|
|
669
|
+
* pass for the same `$for` is in flight (see `update`).
|
|
670
|
+
*/
|
|
671
|
+
performUpdate(attribute, store, scene, state) {
|
|
672
|
+
const { key, index, arrayExpr, trackExpr } = this.parseExpression(attribute.content);
|
|
673
|
+
const owner = attribute.owner;
|
|
674
|
+
const contextScope = owner.scope.resolve(AreDirectiveContext)?.scope || {};
|
|
675
|
+
const newArray = this.resolveArray(store, arrayExpr, attribute.content, contextScope);
|
|
676
|
+
const currentChildren = [...owner.children];
|
|
677
|
+
attribute.value = newArray;
|
|
678
|
+
const attached = this.isAttached(owner);
|
|
679
|
+
const computeKey = this.makeKeyFn(key, index, trackExpr);
|
|
680
|
+
const childByKey = /* @__PURE__ */ new Map();
|
|
681
|
+
const remaining = /* @__PURE__ */ new Set();
|
|
682
|
+
for (let i = 0; i < currentChildren.length; i++) {
|
|
683
|
+
const child = currentChildren[i];
|
|
684
|
+
const ctx = child.scope.resolveFlat(AreDirectiveContext);
|
|
685
|
+
const k = ctx ? computeKey(ctx.scope[key], ctx.scope[index || "index"]) : /* @__PURE__ */ Symbol("orphan");
|
|
686
|
+
childByKey.set(k, child);
|
|
687
|
+
remaining.add(child);
|
|
688
|
+
}
|
|
689
|
+
const toCreate = [];
|
|
690
|
+
const finalByKey = /* @__PURE__ */ new Map();
|
|
691
|
+
const orderedKeys = new Array(newArray.length);
|
|
692
|
+
for (let i = 0; i < newArray.length; i++) {
|
|
693
|
+
const item = newArray[i];
|
|
694
|
+
const k = computeKey(item, i);
|
|
695
|
+
orderedKeys[i] = k;
|
|
696
|
+
const existing = childByKey.get(k);
|
|
697
|
+
if (existing) {
|
|
698
|
+
remaining.delete(existing);
|
|
699
|
+
finalByKey.set(k, existing);
|
|
700
|
+
let directiveContext = existing.scope.resolveFlat(AreDirectiveContext);
|
|
701
|
+
if (!directiveContext) {
|
|
702
|
+
directiveContext = new AreDirectiveContext(existing.aseid);
|
|
703
|
+
existing.scope.register(directiveContext);
|
|
704
|
+
}
|
|
705
|
+
directiveContext.scope = {
|
|
706
|
+
...directiveContext.scope,
|
|
707
|
+
[key]: item,
|
|
708
|
+
[index || "index"]: i
|
|
709
|
+
};
|
|
710
|
+
} else {
|
|
711
|
+
toCreate.push({ item, idx: i, key: k });
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
for (const child of remaining) {
|
|
715
|
+
if (attached) child.unmount();
|
|
716
|
+
owner.removeChild(child);
|
|
717
|
+
}
|
|
718
|
+
const createItem = /* @__PURE__ */ __name((desc) => {
|
|
719
|
+
const child = this.spawnItemNode(attribute.template, owner, key, index, desc.item, desc.idx);
|
|
720
|
+
finalByKey.set(desc.key, child);
|
|
721
|
+
child.transform();
|
|
722
|
+
child.compile();
|
|
723
|
+
if (attached) child.mount();
|
|
724
|
+
}, "createItem");
|
|
725
|
+
if (toCreate.length <= AreDirectiveFor.SYNC_THRESHOLD) {
|
|
726
|
+
for (const desc of toCreate) createItem(desc);
|
|
727
|
+
if (attached) this.reconcileOrder(owner, orderedKeys, finalByKey);
|
|
728
|
+
return this.finishUpdate(attribute, store, scene, state);
|
|
729
|
+
}
|
|
730
|
+
state.running = true;
|
|
731
|
+
let cursor = 0;
|
|
732
|
+
const processChunk = /* @__PURE__ */ __name(() => {
|
|
733
|
+
try {
|
|
734
|
+
const start = AreSchedulerHelper.now();
|
|
735
|
+
while (cursor < toCreate.length) {
|
|
736
|
+
createItem(toCreate[cursor]);
|
|
737
|
+
cursor++;
|
|
738
|
+
if (AreSchedulerHelper.now() - start >= AreDirectiveFor.CHUNK_BUDGET_MS) break;
|
|
739
|
+
}
|
|
740
|
+
} catch (error) {
|
|
741
|
+
state.running = false;
|
|
742
|
+
state.pending = false;
|
|
743
|
+
throw error;
|
|
744
|
+
}
|
|
745
|
+
if (cursor < toCreate.length) {
|
|
746
|
+
return new Promise((resolve) => {
|
|
747
|
+
AreSchedulerHelper.scheduleMacrotask(() => resolve(processChunk()));
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
if (attached) this.reconcileOrder(owner, orderedKeys, finalByKey);
|
|
751
|
+
return this.finishUpdate(attribute, store, scene, state);
|
|
752
|
+
}, "processChunk");
|
|
753
|
+
return processChunk();
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Repositions the item nodes' DOM elements so the rendered order matches the
|
|
757
|
+
* source array order. The keyed diff (steps 1–4) reuses existing nodes in
|
|
758
|
+
* place and mounts new ones at the end; without this pass a `prepend` or
|
|
759
|
+
* `shuffle` would leave reused rows where they were and pile new rows at the
|
|
760
|
+
* bottom. We walk the desired order RIGHT-TO-LEFT, keeping a `ref` pointer to
|
|
761
|
+
* the element each item must precede (starting at the `$for` anchor comment),
|
|
762
|
+
* and only call `insertBefore` when an element is not already in position —
|
|
763
|
+
* so a plain `append` (already-correct order) performs ZERO DOM moves.
|
|
764
|
+
*/
|
|
765
|
+
reconcileOrder(owner, orderedKeys, finalByKey) {
|
|
766
|
+
const context = owner.scope.resolve(AreHTMLEngineContext);
|
|
767
|
+
if (!context) return;
|
|
768
|
+
const anchor = context.getNodeElement(owner);
|
|
769
|
+
if (!anchor || !anchor.parentNode) return;
|
|
770
|
+
const parent = anchor.parentNode;
|
|
771
|
+
let ref = anchor;
|
|
772
|
+
for (let i = orderedKeys.length - 1; i >= 0; i--) {
|
|
773
|
+
const node = finalByKey.get(orderedKeys[i]);
|
|
774
|
+
if (!node) continue;
|
|
775
|
+
const element = context.getNodeElement(node);
|
|
776
|
+
if (!element || element.parentNode !== parent) continue;
|
|
777
|
+
if (element.nextSibling !== ref) {
|
|
778
|
+
parent.insertBefore(element, ref);
|
|
779
|
+
}
|
|
780
|
+
ref = element;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Completes an update pass. If another update() arrived while a chunked
|
|
785
|
+
* render was streaming, run exactly one more pass now from the latest store
|
|
786
|
+
* value so the final DOM always reflects the most recent data.
|
|
787
|
+
*/
|
|
788
|
+
finishUpdate(attribute, store, scene, state) {
|
|
789
|
+
state.running = false;
|
|
790
|
+
if (state.pending) {
|
|
791
|
+
state.pending = false;
|
|
792
|
+
return this.performUpdate(attribute, store, scene, state);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Walks the node's ancestor chain (inclusive) and reports whether the
|
|
797
|
+
* whole path is currently active — i.e. the subtree is actually rendered
|
|
798
|
+
* into the DOM. A single inactive ancestor scene (e.g. a `$if` whose
|
|
799
|
+
* condition is false) means the subtree is detached.
|
|
800
|
+
*/
|
|
801
|
+
isAttached(node) {
|
|
802
|
+
let current = node;
|
|
803
|
+
while (current) {
|
|
804
|
+
if (current.scene?.isInactive) return false;
|
|
805
|
+
current = current.parent;
|
|
806
|
+
}
|
|
807
|
+
return true;
|
|
808
|
+
}
|
|
809
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
810
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
811
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
812
|
+
/**
|
|
813
|
+
* Build a key-function that derives a stable identity from each item.
|
|
814
|
+
* If the user provided a `track <expr>` clause, evaluate it as a path on
|
|
815
|
+
* the item; otherwise fall back to the item identity (reference equality).
|
|
816
|
+
*/
|
|
817
|
+
makeKeyFn(key, index, trackExpr) {
|
|
818
|
+
if (!trackExpr) {
|
|
819
|
+
return (item, i) => item ?? i;
|
|
820
|
+
}
|
|
821
|
+
const path = trackExpr.startsWith(key + ".") ? trackExpr.slice(key.length + 1) : trackExpr;
|
|
822
|
+
return (item, i) => {
|
|
823
|
+
if (item == null) return i;
|
|
824
|
+
if (path === key || path === "$index") return path === "$index" ? i : item;
|
|
825
|
+
const parts = path.split(".");
|
|
826
|
+
let v = item;
|
|
827
|
+
for (const p of parts) {
|
|
828
|
+
if (v == null) return i;
|
|
829
|
+
v = v[p];
|
|
830
|
+
}
|
|
831
|
+
return v ?? i;
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Parses the $for expression string into its constituent parts.
|
|
836
|
+
*
|
|
837
|
+
* Supported formats:
|
|
838
|
+
* item in items
|
|
839
|
+
* item, index in items
|
|
840
|
+
* (item, index) in items
|
|
841
|
+
* item in filter(items)
|
|
842
|
+
* item, index in filter(items, 'active')
|
|
843
|
+
* item in items track item.id
|
|
844
|
+
* (item, i) in items track item.id
|
|
845
|
+
*/
|
|
846
|
+
parseExpression(content) {
|
|
847
|
+
let trackExpr;
|
|
848
|
+
const trackIdx = content.search(/\s+track\s+/);
|
|
849
|
+
let body = content;
|
|
850
|
+
if (trackIdx !== -1) {
|
|
851
|
+
const m2 = content.slice(trackIdx).match(/\s+track\s+(.+)$/);
|
|
852
|
+
if (m2) {
|
|
853
|
+
trackExpr = m2[1].trim();
|
|
854
|
+
body = content.slice(0, trackIdx).trim();
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
const inIndex = body.lastIndexOf(" in ");
|
|
858
|
+
const keyAndIndex = body.slice(0, inIndex).trim().replace(/^\(|\)$/g, "");
|
|
859
|
+
const arrayExpr = body.slice(inIndex + 4).trim();
|
|
860
|
+
const keyParts = keyAndIndex.split(",").map((p) => p.trim());
|
|
861
|
+
return {
|
|
862
|
+
key: keyParts[0],
|
|
863
|
+
index: keyParts[1] || void 0,
|
|
864
|
+
arrayExpr,
|
|
865
|
+
trackExpr
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Resolves the array expression against the store.
|
|
870
|
+
* Supports both plain key lookups and function-call expressions:
|
|
871
|
+
* items → store.get('items')
|
|
872
|
+
* filter(items) → store.get('filter')(store.get('items'))
|
|
873
|
+
*
|
|
874
|
+
* `contextScope` carries item-scoped variables introduced by an enclosing
|
|
875
|
+
* directive (e.g. the `row` of an outer `$for`). It is consulted BEFORE the
|
|
876
|
+
* store so a nested `$for="cell in row.cells"` resolves `row` from the
|
|
877
|
+
* parent iteration instead of looking for a (non-existent) top-level store
|
|
878
|
+
* key. Leading identifiers not present in the context fall back to the store.
|
|
879
|
+
*/
|
|
880
|
+
resolveArray(store, arrayExpr, fullContent, contextScope = {}) {
|
|
881
|
+
const getRoot = /* @__PURE__ */ __name((rawKey) => {
|
|
882
|
+
const k = rawKey.replace(/\?$/, "");
|
|
883
|
+
return k in contextScope ? contextScope[k] : store.get(k);
|
|
884
|
+
}, "getRoot");
|
|
885
|
+
let result;
|
|
886
|
+
const callMatch = arrayExpr.match(/^([^(]+)\((.+)\)$/);
|
|
887
|
+
if (callMatch) {
|
|
888
|
+
const fnName = callMatch[1].trim();
|
|
889
|
+
const fn = getRoot(fnName);
|
|
890
|
+
if (typeof fn !== "function")
|
|
891
|
+
throw new AreCompilerError({
|
|
892
|
+
title: 'Invalid "for" Directive Function',
|
|
893
|
+
description: `The expression "${fnName}" in the "for" directive does not resolve to a function in the store. Received: ${typeof fn}`
|
|
894
|
+
});
|
|
895
|
+
const rawArgs = callMatch[2].split(",").map((a) => a.trim());
|
|
896
|
+
const resolvedArgs = rawArgs.map((arg) => {
|
|
897
|
+
if (arg.startsWith("'") && arg.endsWith("'")) return arg.slice(1, -1);
|
|
898
|
+
if (arg.startsWith('"') && arg.endsWith('"')) return arg.slice(1, -1);
|
|
899
|
+
if (!isNaN(Number(arg))) return Number(arg);
|
|
900
|
+
const stripped = arg.replace(/\?$/, "");
|
|
901
|
+
if (stripped.includes(".")) {
|
|
902
|
+
const parts = stripped.split(".").map((p) => p.replace(/\?$/, ""));
|
|
903
|
+
let val = getRoot(parts[0]);
|
|
904
|
+
for (let j = 1; j < parts.length; j++) {
|
|
905
|
+
if (val == null) return void 0;
|
|
906
|
+
val = val[parts[j]];
|
|
907
|
+
}
|
|
908
|
+
return val ?? void 0;
|
|
909
|
+
}
|
|
910
|
+
return getRoot(stripped);
|
|
911
|
+
});
|
|
912
|
+
result = fn(...resolvedArgs);
|
|
913
|
+
} else if (arrayExpr.includes(".")) {
|
|
914
|
+
const parts = arrayExpr.split(".").map((p) => p.replace(/\?$/, ""));
|
|
915
|
+
result = getRoot(parts[0]);
|
|
916
|
+
for (let i = 1; i < parts.length; i++) {
|
|
917
|
+
if (result == null) break;
|
|
918
|
+
result = result[parts[i]];
|
|
919
|
+
}
|
|
920
|
+
} else {
|
|
921
|
+
result = getRoot(arrayExpr);
|
|
922
|
+
}
|
|
923
|
+
if (result == null) return [];
|
|
924
|
+
if (!Array.isArray(result))
|
|
925
|
+
throw new AreCompilerError({
|
|
926
|
+
title: 'Invalid "for" Directive Value',
|
|
927
|
+
description: `The "for" directive expects an array but got ${typeof result}. Expression: "${fullContent}". Received: ${JSON.stringify(result)}`
|
|
928
|
+
});
|
|
929
|
+
return result;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Creates a single item node from the template, registers it as a child of
|
|
933
|
+
* the owner, initialises it, injects item-scoped store values, and activates
|
|
934
|
+
* its scene so the mount/compile cycle will include it.
|
|
935
|
+
*
|
|
936
|
+
* NOTE: This method does NOT call compile() or mount() — the caller is
|
|
937
|
+
* responsible for doing so when the main lifecycle cycle won't cover it
|
|
938
|
+
* (i.e. during update, but not during the initial compile phase).
|
|
939
|
+
*/
|
|
940
|
+
spawnItemNode(template, owner, key, index, item, i) {
|
|
941
|
+
const itemNode = template.clone();
|
|
942
|
+
owner.addChild(itemNode);
|
|
943
|
+
const queue = [itemNode];
|
|
944
|
+
while (queue.length > 0) {
|
|
945
|
+
const current = queue.shift();
|
|
946
|
+
current.init();
|
|
947
|
+
queue.push(...current.children);
|
|
948
|
+
}
|
|
949
|
+
let directiveContext = itemNode.scope.resolveFlat(AreDirectiveContext);
|
|
950
|
+
if (!directiveContext) {
|
|
951
|
+
directiveContext = new AreDirectiveContext(itemNode.aseid);
|
|
952
|
+
itemNode.scope.register(directiveContext);
|
|
953
|
+
}
|
|
954
|
+
directiveContext.scope = {
|
|
955
|
+
...directiveContext.scope,
|
|
956
|
+
[key]: item,
|
|
957
|
+
[index || "index"]: i
|
|
958
|
+
};
|
|
959
|
+
itemNode.scene.activate();
|
|
960
|
+
return itemNode;
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
__name(AreDirectiveFor, "AreDirectiveFor");
|
|
964
|
+
/**
|
|
965
|
+
* Lists whose number of NEW item nodes is at or below this threshold render
|
|
966
|
+
* fully synchronously — byte-for-byte the previous behavior. Typical UIs
|
|
967
|
+
* (menus, small tables) are therefore completely unaffected; only genuinely
|
|
968
|
+
* large lists pay the (tiny) scheduling cost to keep the main thread responsive.
|
|
969
|
+
*/
|
|
970
|
+
AreDirectiveFor.SYNC_THRESHOLD = 100;
|
|
971
|
+
/**
|
|
972
|
+
* Per-chunk time budget (ms). During a large-list render we mount item nodes
|
|
973
|
+
* until this much time has elapsed, then yield to the browser so it can paint
|
|
974
|
+
* and process input before the next chunk. ~16ms targets one animation frame.
|
|
975
|
+
*/
|
|
976
|
+
AreDirectiveFor.CHUNK_BUDGET_MS = 16;
|
|
977
|
+
/**
|
|
978
|
+
* Per-attribute serialization state. A new update() that arrives while a
|
|
979
|
+
* chunked render of the SAME `$for` is still in flight does NOT start a second
|
|
980
|
+
* concurrent pass (which could interleave mutations on the shared children
|
|
981
|
+
* list); instead it marks `pending` and the in-flight run re-runs once more
|
|
982
|
+
* with the latest data when it finishes. This guarantees the children list is
|
|
983
|
+
* only ever mutated by one pass at a time and the final state always reflects
|
|
984
|
+
* the most recent store value.
|
|
985
|
+
*/
|
|
986
|
+
AreDirectiveFor.renderState = /* @__PURE__ */ new WeakMap();
|
|
987
|
+
__decorateClass([
|
|
988
|
+
AreDirective.Transform,
|
|
989
|
+
__decorateParam(0, Yt(te)),
|
|
990
|
+
__decorateParam(1, Yt(R)),
|
|
991
|
+
__decorateParam(2, Yt(AreStore)),
|
|
992
|
+
__decorateParam(3, Yt(AreScene)),
|
|
993
|
+
__decorateParam(4, Yt(A_Logger))
|
|
994
|
+
], AreDirectiveFor.prototype, "transform", 1);
|
|
995
|
+
__decorateClass([
|
|
996
|
+
AreDirective.Compile,
|
|
997
|
+
__decorateParam(0, Yt(te)),
|
|
998
|
+
__decorateParam(1, Yt(AreStore)),
|
|
999
|
+
__decorateParam(2, Yt(AreScene))
|
|
1000
|
+
], AreDirectiveFor.prototype, "compile", 1);
|
|
1001
|
+
__decorateClass([
|
|
1002
|
+
AreDirective.Update,
|
|
1003
|
+
__decorateParam(0, Yt(te)),
|
|
1004
|
+
__decorateParam(1, Yt(AreStore)),
|
|
1005
|
+
__decorateParam(2, Yt(AreScene))
|
|
1006
|
+
], AreDirectiveFor.prototype, "update", 1);
|
|
1007
|
+
AreDirectiveFor = __decorateClass([
|
|
1008
|
+
R2.Define({
|
|
1009
|
+
namespace: "a-are-html",
|
|
1010
|
+
description: "Built-in $for directive. Iterates over an array expression resolved from the store and renders a cloned template fragment per item, managing per-item subscopes and comment-node anchors. Supports keyed diffing via an optional track clause to minimise DOM mutations on collection updates."
|
|
1011
|
+
}),
|
|
1012
|
+
AreDirective.Priority(1)
|
|
1013
|
+
], AreDirectiveFor);
|
|
1014
|
+
|
|
1015
|
+
// src/directives/AreDirectiveIf.directive.ts
|
|
1016
|
+
var AreDirectiveIf = class extends AreDirective {
|
|
1017
|
+
transform(attribute, scope, store, scene, logger, ...args) {
|
|
1018
|
+
logger.debug(`[Transform] directive $IF for <${attribute.owner.aseid.toString()}>`);
|
|
1019
|
+
const node = attribute.owner;
|
|
1020
|
+
const ifTemplate = node.cloneWithScope();
|
|
1021
|
+
const ifAttr = ifTemplate.attributes.find((d) => d.name === attribute.name);
|
|
1022
|
+
if (ifAttr) {
|
|
1023
|
+
ifTemplate.scope.deregister(ifAttr);
|
|
1024
|
+
node.scope.register(ifAttr);
|
|
1025
|
+
}
|
|
1026
|
+
node.init();
|
|
1027
|
+
node.addChild(ifTemplate);
|
|
1028
|
+
ifTemplate.scene.deactivate();
|
|
1029
|
+
attribute.template = ifTemplate;
|
|
1030
|
+
}
|
|
1031
|
+
compile(attribute, store, scene, syntax, directiveContext, ...args) {
|
|
1032
|
+
attribute.value = this.evaluateCondition(syntax, attribute, store, directiveContext);
|
|
1033
|
+
const hostInstruction = scene.host;
|
|
1034
|
+
const commentIdentifier = ` --- if: ${attribute.template.id} --- `;
|
|
1035
|
+
const declaration = new AddCommentInstruction({ content: commentIdentifier });
|
|
1036
|
+
scene.setHost(declaration);
|
|
1037
|
+
scene.planBefore(declaration, hostInstruction);
|
|
1038
|
+
scene.unPlan(hostInstruction);
|
|
1039
|
+
if (attribute.value)
|
|
1040
|
+
attribute.template.scene.activate();
|
|
1041
|
+
else
|
|
1042
|
+
attribute.template.scene.deactivate();
|
|
1043
|
+
}
|
|
1044
|
+
update(attribute, store, scope, syntax, scene, directiveContext, ...args) {
|
|
1045
|
+
const previous = !!attribute.value;
|
|
1046
|
+
const next = this.evaluateCondition(syntax, attribute, store, directiveContext);
|
|
1047
|
+
attribute.value = next;
|
|
1048
|
+
if (previous === next) return;
|
|
1049
|
+
if (next) {
|
|
1050
|
+
attribute.template.scene.activate();
|
|
1051
|
+
attribute.template.mount();
|
|
1052
|
+
} else {
|
|
1053
|
+
attribute.template.unmount();
|
|
1054
|
+
attribute.template.scene.deactivate();
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Evaluates the `$if` condition defensively.
|
|
1059
|
+
*
|
|
1060
|
+
* A condition can reference data that is momentarily unavailable — most
|
|
1061
|
+
* commonly a nested `$if` (e.g. `$if="selected.fields.length"`) living
|
|
1062
|
+
* inside a parent `$if="selected"` whose object has just become `null`.
|
|
1063
|
+
* Because the nested directive is still subscribed to the store, its
|
|
1064
|
+
* update fires on that same change and the raw expression would throw
|
|
1065
|
+
* `Cannot read properties of null`, crashing the whole update pipeline.
|
|
1066
|
+
*
|
|
1067
|
+
* Treating an evaluation error as `false` is the correct contract for a
|
|
1068
|
+
* conditional: if the condition cannot be resolved, the subtree simply
|
|
1069
|
+
* stays hidden until the referenced data is present again (at which point
|
|
1070
|
+
* the parent `$if` re-activates and re-evaluates this one).
|
|
1071
|
+
*/
|
|
1072
|
+
evaluateCondition(syntax, attribute, store, directiveContext) {
|
|
1073
|
+
try {
|
|
1074
|
+
return !!syntax.evaluate(attribute.content, store, {
|
|
1075
|
+
...directiveContext?.scope || {}
|
|
1076
|
+
});
|
|
1077
|
+
} catch {
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
__name(AreDirectiveIf, "AreDirectiveIf");
|
|
1083
|
+
__decorateClass([
|
|
1084
|
+
AreDirective.Transform,
|
|
1085
|
+
__decorateParam(0, Yt(te)),
|
|
1086
|
+
__decorateParam(1, Yt(R)),
|
|
1087
|
+
__decorateParam(2, Yt(AreStore)),
|
|
1088
|
+
__decorateParam(3, Yt(AreScene)),
|
|
1089
|
+
__decorateParam(4, Yt(A_Logger))
|
|
1090
|
+
], AreDirectiveIf.prototype, "transform", 1);
|
|
1091
|
+
__decorateClass([
|
|
1092
|
+
AreDirective.Compile,
|
|
1093
|
+
__decorateParam(0, Yt(te)),
|
|
1094
|
+
__decorateParam(1, Yt(AreStore)),
|
|
1095
|
+
__decorateParam(2, Yt(AreScene)),
|
|
1096
|
+
__decorateParam(3, Yt(AreSyntax)),
|
|
1097
|
+
__decorateParam(4, Yt(AreDirectiveContext))
|
|
1098
|
+
], AreDirectiveIf.prototype, "compile", 1);
|
|
1099
|
+
__decorateClass([
|
|
1100
|
+
AreDirective.Update,
|
|
1101
|
+
__decorateParam(0, Yt(te)),
|
|
1102
|
+
__decorateParam(1, Yt(AreStore)),
|
|
1103
|
+
__decorateParam(2, Yt(R)),
|
|
1104
|
+
__decorateParam(3, Yt(AreSyntax)),
|
|
1105
|
+
__decorateParam(4, Yt(AreScene)),
|
|
1106
|
+
__decorateParam(5, Yt(AreDirectiveContext))
|
|
1107
|
+
], AreDirectiveIf.prototype, "update", 1);
|
|
1108
|
+
AreDirectiveIf = __decorateClass([
|
|
1109
|
+
R2.Define({
|
|
1110
|
+
namespace: "a-are-html",
|
|
1111
|
+
description: "Built-in $if directive. Conditionally renders a subtree based on a store expression. Replaces the target element with a stable comment anchor when the condition is false and restores the fully rendered subtree when it becomes true, preventing any leaking of the host element between states."
|
|
1112
|
+
}),
|
|
1113
|
+
AreDirective.Priority(2)
|
|
1114
|
+
], AreDirectiveIf);
|
|
1115
|
+
|
|
1116
|
+
// src/instructions/HideElement.instruction.ts
|
|
1117
|
+
var HideElementInstruction = class extends AreMutation {
|
|
1118
|
+
constructor(parent, props) {
|
|
1119
|
+
if ("aseid" in props) {
|
|
1120
|
+
super(props);
|
|
1121
|
+
} else {
|
|
1122
|
+
super(AreHTMLInstructions.HideElement, parent, props);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
__name(HideElementInstruction, "HideElementInstruction");
|
|
1127
|
+
HideElementInstruction = __decorateClass([
|
|
1128
|
+
R2.Define({
|
|
1129
|
+
namespace: "a-are-html",
|
|
1130
|
+
description: 'Toggles the visibility of an existing element by setting its inline display to "none" on apply and restoring the previous inline display on revert. Used by the $show directive to hide/show an element without unmounting it, preserving its subtree, listeners and scene state.'
|
|
1131
|
+
})
|
|
1132
|
+
], HideElementInstruction);
|
|
1133
|
+
|
|
1134
|
+
// src/directives/AreDirectiveShow.directive.ts
|
|
1135
|
+
var AreDirectiveShow = class extends AreDirective {
|
|
1136
|
+
transform(attribute, logger, ...args) {
|
|
1137
|
+
logger.debug(`[Transform] directive $SHOW for <${attribute.owner.aseid.toString()}> (no structural change)`);
|
|
1138
|
+
}
|
|
1139
|
+
compile(attribute, store, scene, syntax, directiveContext, ...args) {
|
|
1140
|
+
const visible = !!syntax.evaluate(attribute.content, store, {
|
|
1141
|
+
...directiveContext?.scope || {}
|
|
1142
|
+
});
|
|
1143
|
+
attribute.value = visible;
|
|
1144
|
+
const hide = new HideElementInstruction(scene.host, {});
|
|
1145
|
+
attribute.cache = hide;
|
|
1146
|
+
if (!visible)
|
|
1147
|
+
scene.plan(hide);
|
|
1148
|
+
}
|
|
1149
|
+
update(attribute, store, scene, syntax, directiveContext, ...args) {
|
|
1150
|
+
const previous = !!attribute.value;
|
|
1151
|
+
const next = !!syntax.evaluate(attribute.content, store, {
|
|
1152
|
+
...directiveContext?.scope || {}
|
|
1153
|
+
});
|
|
1154
|
+
attribute.value = next;
|
|
1155
|
+
if (previous === next) return;
|
|
1156
|
+
const hide = attribute.cache;
|
|
1157
|
+
if (!hide) return;
|
|
1158
|
+
if (next)
|
|
1159
|
+
scene.unPlan(hide);
|
|
1160
|
+
else
|
|
1161
|
+
scene.plan(hide);
|
|
1162
|
+
attribute.owner.interpret();
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
__name(AreDirectiveShow, "AreDirectiveShow");
|
|
1166
|
+
__decorateClass([
|
|
1167
|
+
AreDirective.Transform,
|
|
1168
|
+
__decorateParam(0, Yt(te)),
|
|
1169
|
+
__decorateParam(1, Yt(A_Logger))
|
|
1170
|
+
], AreDirectiveShow.prototype, "transform", 1);
|
|
1171
|
+
__decorateClass([
|
|
1172
|
+
AreDirective.Compile,
|
|
1173
|
+
__decorateParam(0, Yt(te)),
|
|
1174
|
+
__decorateParam(1, Yt(AreStore)),
|
|
1175
|
+
__decorateParam(2, Yt(AreScene)),
|
|
1176
|
+
__decorateParam(3, Yt(AreSyntax)),
|
|
1177
|
+
__decorateParam(4, Yt(AreDirectiveContext))
|
|
1178
|
+
], AreDirectiveShow.prototype, "compile", 1);
|
|
1179
|
+
__decorateClass([
|
|
1180
|
+
AreDirective.Update,
|
|
1181
|
+
__decorateParam(0, Yt(te)),
|
|
1182
|
+
__decorateParam(1, Yt(AreStore)),
|
|
1183
|
+
__decorateParam(2, Yt(AreScene)),
|
|
1184
|
+
__decorateParam(3, Yt(AreSyntax)),
|
|
1185
|
+
__decorateParam(4, Yt(AreDirectiveContext))
|
|
1186
|
+
], AreDirectiveShow.prototype, "update", 1);
|
|
1187
|
+
AreDirectiveShow = __decorateClass([
|
|
1188
|
+
R2.Define({
|
|
1189
|
+
namespace: "a-are-html",
|
|
1190
|
+
description: "Built-in $show directive. Toggles an element's visibility by flipping its inline display value based on a store expression, keeping the element mounted (subtree, listeners and scene state preserved) instead of unmounting it like $if."
|
|
1191
|
+
}),
|
|
1192
|
+
AreDirective.Priority(3)
|
|
1193
|
+
], AreDirectiveShow);
|
|
1194
|
+
|
|
1195
|
+
// src/instructions/AddAttribute.instruction.ts
|
|
1196
|
+
var AddAttributeInstruction = class extends AreMutation {
|
|
1197
|
+
constructor(parent, props) {
|
|
1198
|
+
if ("aseid" in props) {
|
|
1199
|
+
super(props);
|
|
1200
|
+
} else {
|
|
1201
|
+
super(AreHTMLInstructions.AddAttribute, parent, props);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
__name(AddAttributeInstruction, "AddAttributeInstruction");
|
|
1206
|
+
AddAttributeInstruction = __decorateClass([
|
|
1207
|
+
R2.Define({
|
|
1208
|
+
namespace: "a-are-html",
|
|
1209
|
+
description: "Sets an attribute on an HTML element. Apply calls setAttribute; revert calls removeAttribute."
|
|
1210
|
+
})
|
|
1211
|
+
], AddAttributeInstruction);
|
|
1212
|
+
|
|
1213
|
+
// src/instructions/AddListener.instruction.ts
|
|
1214
|
+
var AddListenerInstruction = class extends AreMutation {
|
|
1215
|
+
constructor(parent, props) {
|
|
1216
|
+
if ("aseid" in props) {
|
|
1217
|
+
super(props);
|
|
1218
|
+
} else {
|
|
1219
|
+
super(AreHTMLInstructions.AddListener, parent, props);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
__name(AddListenerInstruction, "AddListenerInstruction");
|
|
1224
|
+
AddListenerInstruction = __decorateClass([
|
|
1225
|
+
R2.Define({
|
|
1226
|
+
namespace: "a-are-html",
|
|
1227
|
+
description: "Attaches a DOM event listener to an element. Apply calls addEventListener; revert calls removeEventListener."
|
|
1228
|
+
})
|
|
1229
|
+
], AddListenerInstruction);
|
|
1230
|
+
|
|
1231
|
+
// src/instructions/AddStaticHTML.instruction.ts
|
|
1232
|
+
var AddStaticHTMLInstruction = class extends AreMutation {
|
|
1233
|
+
constructor(parent, props) {
|
|
1234
|
+
if ("aseid" in props) {
|
|
1235
|
+
super(props);
|
|
1236
|
+
} else {
|
|
1237
|
+
super(AreHTMLInstructions.AddStaticHTML, parent, props);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
__name(AddStaticHTMLInstruction, "AddStaticHTMLInstruction");
|
|
1242
|
+
AddStaticHTMLInstruction = __decorateClass([
|
|
1243
|
+
R2.Define({
|
|
1244
|
+
namespace: "a-are-html",
|
|
1245
|
+
description: 'Materialises a fully static subtree (a "static island") onto its parent element in a single pass via browser-parsed innerHTML / a cached <template> clone. Apply injects the markup; revert clears it. Decodes HTML entities (e.g. ) for free.'
|
|
1246
|
+
})
|
|
1247
|
+
], AddStaticHTMLInstruction);
|
|
1248
|
+
|
|
1249
|
+
// src/instructions/AddStyle.instruction.ts
|
|
1250
|
+
var AddStyleInstruction = class extends AreMutation {
|
|
1251
|
+
constructor(parent, props) {
|
|
1252
|
+
if ("aseid" in props) {
|
|
1253
|
+
super(props);
|
|
1254
|
+
} else {
|
|
1255
|
+
super(AreHTMLInstructions.AddStyle, parent, props);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
__name(AddStyleInstruction, "AddStyleInstruction");
|
|
1260
|
+
AddStyleInstruction = __decorateClass([
|
|
1261
|
+
R2.Define({
|
|
1262
|
+
namespace: "a-are-html",
|
|
1263
|
+
description: "Sets an inline CSS style property on an element. Apply sets the property; revert removes it."
|
|
1264
|
+
})
|
|
1265
|
+
], AddStyleInstruction);
|
|
1266
|
+
|
|
1267
|
+
// src/instructions/AddText.instruction.ts
|
|
1268
|
+
var AddTextInstruction = class extends AreDeclaration {
|
|
1269
|
+
constructor(props) {
|
|
1270
|
+
if ("aseid" in props) {
|
|
1271
|
+
super(props);
|
|
1272
|
+
} else {
|
|
1273
|
+
super(AreHTMLInstructions.AddText, props);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
__name(AddTextInstruction, "AddTextInstruction");
|
|
1278
|
+
AddTextInstruction = __decorateClass([
|
|
1279
|
+
R2.Define({
|
|
1280
|
+
namespace: "a-are-html",
|
|
1281
|
+
description: "Appends a text node to an element. Apply creates the text node; revert removes it. Content can be a static string or a dynamic getter for interpolations."
|
|
1282
|
+
})
|
|
1283
|
+
], AddTextInstruction);
|
|
1284
|
+
|
|
1285
|
+
// src/lib/AreStyle/AreStyle.context.ts
|
|
1286
|
+
var AreStyle = class extends H {
|
|
1287
|
+
constructor(styles, aseid) {
|
|
1288
|
+
super({
|
|
1289
|
+
name: aseid ? aseid.toString() : "default-style"
|
|
1290
|
+
});
|
|
1291
|
+
this.styles = styles;
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
__name(AreStyle, "AreStyle");
|
|
1295
|
+
AreStyle = __decorateClass([
|
|
1296
|
+
R2.Define({
|
|
1297
|
+
namespace: "a-are-html",
|
|
1298
|
+
description: "Context fragment that holds the resolved CSS style rules string for a component scope. Populated during lifecycle initialisation and read by the compiler when emitting AddStyle instructions for inline styles declared on the component host element."
|
|
1299
|
+
})
|
|
1300
|
+
], AreStyle);
|
|
1301
|
+
|
|
1302
|
+
// src/lib/AreHTMLNode/AreHTMLNode.ts
|
|
1303
|
+
var AreHTMLNode = class extends AreNode {
|
|
1304
|
+
/**
|
|
1305
|
+
* Actual node type.
|
|
1306
|
+
* By default it's a tag name
|
|
1307
|
+
*/
|
|
1308
|
+
get tag() {
|
|
1309
|
+
return this.aseid.entity;
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* The verbatim inner markup captured when this node was identified as a
|
|
1313
|
+
* static island, or `undefined` for ordinary (per-node) nodes.
|
|
1314
|
+
*/
|
|
1315
|
+
get staticInnerHTML() {
|
|
1316
|
+
return this._staticInnerHTML;
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Whether this node is a static-island root (see `_staticInnerHTML`).
|
|
1320
|
+
*/
|
|
1321
|
+
get isStaticIsland() {
|
|
1322
|
+
return this._staticInnerHTML !== void 0;
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Marks this node as a static-island root, capturing the verbatim inner
|
|
1326
|
+
* markup to be materialised in one shot by the interpreter. Called by the
|
|
1327
|
+
* tokenizer when the node's inner content is detected to be fully static.
|
|
1328
|
+
*/
|
|
1329
|
+
markStatic(innerHTML) {
|
|
1330
|
+
this._staticInnerHTML = innerHTML;
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Deep-clone the node. Overridden to carry over the static-island marker
|
|
1334
|
+
* (`_staticInnerHTML`), which lives on AreHTMLNode and is therefore NOT
|
|
1335
|
+
* copied by the base AreNode.clone(). Without this, cloning a directive
|
|
1336
|
+
* template ($if/$for) that wraps a static island (e.g. `<span $if>★</span>`)
|
|
1337
|
+
* would drop the captured inner markup and render an empty element. The
|
|
1338
|
+
* base clone() recurses via each child's polymorphic clone(), so nested
|
|
1339
|
+
* island children are preserved automatically through this override.
|
|
1340
|
+
*/
|
|
1341
|
+
clone() {
|
|
1342
|
+
const cloned = super.clone();
|
|
1343
|
+
const self = this;
|
|
1344
|
+
if (self._staticInnerHTML !== void 0)
|
|
1345
|
+
cloned.markStatic(self._staticInnerHTML);
|
|
1346
|
+
return cloned;
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Clone the node while transferring its existing scope to the clone (used by
|
|
1350
|
+
* the $if/$for directives to turn the original node into a lightweight group
|
|
1351
|
+
* container). Overridden for the same reason as `clone()`: the static-island
|
|
1352
|
+
* marker must survive so a directive applied to an island root keeps its
|
|
1353
|
+
* inner markup.
|
|
1354
|
+
*/
|
|
1355
|
+
cloneWithScope() {
|
|
1356
|
+
const cloned = super.cloneWithScope();
|
|
1357
|
+
const self = this;
|
|
1358
|
+
if (self._staticInnerHTML !== void 0)
|
|
1359
|
+
cloned.markStatic(self._staticInnerHTML);
|
|
1360
|
+
return cloned;
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* The static attributes defined for the node, which are typically used to represent static properties or characteristics of the node that do not change based on the context or state. These attributes are usually defined in the template and are not reactive.
|
|
1364
|
+
*
|
|
1365
|
+
* Example: For a node defined as `<div class="static-class">`, the static attribute would be `class="static-class"`.
|
|
1366
|
+
*/
|
|
1367
|
+
get staticAttributes() {
|
|
1368
|
+
return this.scope.resolveFlatAll(AreStaticAttribute);
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* The binding attributes defined for the node, which are typically used to represent dynamic properties or characteristics of the node that can change based on the context or state. These attributes are usually defined in the template with a specific syntax (e.g., `:prop="value"` or `v-bind:prop="value"`) and are reactive, meaning that they will update automatically when the underlying data changes.
|
|
1372
|
+
*
|
|
1373
|
+
* Example: For a node defined as `<div :class="dynamicClass">`, the binding attribute would be `:class="dynamicClass"`.
|
|
1374
|
+
*/
|
|
1375
|
+
get bindings() {
|
|
1376
|
+
return this.scope.resolveFlatAll(AreBindingAttribute);
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* The directive attributes defined for the node, which are typically used to represent special instructions or behaviors that should be applied to the node. These attributes are usually defined in the template with a specific syntax (e.g., `v-if="condition"` or `v-for="item in list"`) and are processed by the rendering engine to apply the corresponding logic or behavior to the node.
|
|
1380
|
+
*
|
|
1381
|
+
* Example: For a node defined as `<div v-if="isVisible">`, the directive attribute would be `v-if="isVisible"`.
|
|
1382
|
+
*/
|
|
1383
|
+
get directives() {
|
|
1384
|
+
const directives = this.scope.resolveFlatAll(AreDirectiveAttribute);
|
|
1385
|
+
return directives.filter((d) => d.component).sort((a, b) => {
|
|
1386
|
+
const aMeta = _.meta(a.component);
|
|
1387
|
+
const bMeta = _.meta(b.component);
|
|
1388
|
+
const aPriority = aMeta.priority ?? 0;
|
|
1389
|
+
const bPriority = bMeta.priority ?? 0;
|
|
1390
|
+
return bPriority - aPriority;
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* The event attributes defined for the node, which are typically used to represent event listeners or handlers that should be attached to the node. These attributes are usually defined in the template with a specific syntax (e.g., `@click="handleClick"` or `v-on:click="handleClick"`) and are processed by the rendering engine to attach the corresponding event listeners to the node.
|
|
1395
|
+
*
|
|
1396
|
+
* Example: For a node defined as `<button @click="handleClick">`, the event attribute would be `@click="handleClick"`.
|
|
1397
|
+
*/
|
|
1398
|
+
get events() {
|
|
1399
|
+
return this.scope.resolveFlatAll(AreEventAttribute);
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* The styles defined for the node, which can include inline styles or styles defined in a separate stylesheet that are applied to the node. These styles can be used to control the visual appearance of the node and can be defined using standard CSS syntax.
|
|
1403
|
+
*/
|
|
1404
|
+
get styles() {
|
|
1405
|
+
return this.scope.resolveFlat(AreStyle);
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Registers or updates the component-scoped CSS string for this node.
|
|
1409
|
+
* Called by the @Are.Styles-decorated method on the associated component.
|
|
1410
|
+
* A new AreStyle fragment is registered in scope on first call; subsequent
|
|
1411
|
+
* calls update the existing fragment in-place.
|
|
1412
|
+
*/
|
|
1413
|
+
setStyles(css) {
|
|
1414
|
+
const existing = this.scope.resolveFlat(AreStyle);
|
|
1415
|
+
if (existing) {
|
|
1416
|
+
existing.styles = css;
|
|
1417
|
+
} else {
|
|
1418
|
+
this.scope.register(new AreStyle(css, this.aseid.toString()));
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
__name(AreHTMLNode, "AreHTMLNode");
|
|
1423
|
+
AreHTMLNode = __decorateClass([
|
|
1424
|
+
R2.Define({
|
|
1425
|
+
namespace: "a-are-html",
|
|
1426
|
+
description: "AreHTMLNode represents a node in the HTML structure. It extends the base AreNode and includes properties and methods specific to HTML nodes, such as handling attributes, directives, events, and styles."
|
|
1427
|
+
})
|
|
1428
|
+
], AreHTMLNode);
|
|
1429
|
+
|
|
1430
|
+
// src/nodes/AreComment.ts
|
|
1431
|
+
var AreComment = class extends AreHTMLNode {
|
|
1432
|
+
fromNew(newEntity) {
|
|
1433
|
+
super.fromNew({
|
|
1434
|
+
...newEntity,
|
|
1435
|
+
payload: {
|
|
1436
|
+
...newEntity.payload || {},
|
|
1437
|
+
entity: "are-comment"
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
__name(AreComment, "AreComment");
|
|
1443
|
+
AreComment = __decorateClass([
|
|
1444
|
+
R2.Define({
|
|
1445
|
+
namespace: "a-are-html",
|
|
1446
|
+
description: "Node type representing a comment node in the AreHTMLNode tree. Used as a stable DOM anchor by structural directives such as $if and $for that swap rendered content in and out, ensuring the parent container always has a consistent insertion point."
|
|
1447
|
+
})
|
|
1448
|
+
], AreComment);
|
|
1449
|
+
|
|
1450
|
+
// src/nodes/AreComponent.ts
|
|
1451
|
+
var AreComponentNode = class extends AreHTMLNode {
|
|
1452
|
+
/**
|
|
1453
|
+
* A custom component associated with this node, which can be used to provide custom logic and behavior for the node. This component is typically defined in the context and can be resolved based on the node's type or other identifying information. The component can include its own template, markup, styles, and features that are specific to the functionality it provides.
|
|
1454
|
+
*
|
|
1455
|
+
* Example: If the node type is "custom-component", the corresponding component would be resolved from the context and can be used to provide custom rendering and behavior for nodes of that type.
|
|
1456
|
+
*
|
|
1457
|
+
* [!] Note: The component is optional and may not be defined for all nodes. If no component is associated with the node, it will be treated as a standard HTML element or a basic node without custom logic.
|
|
1458
|
+
*/
|
|
1459
|
+
get component() {
|
|
1460
|
+
return this.scope.resolve(P.toPascalCase(this.aseid.entity));
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1463
|
+
__name(AreComponentNode, "AreComponentNode");
|
|
1464
|
+
AreComponentNode = __decorateClass([
|
|
1465
|
+
R2.Define({
|
|
1466
|
+
namespace: "a-are-html",
|
|
1467
|
+
description: "AreComponentNode represents a node in the scene graph that corresponds to a component. It extends the base AreNode and includes additional properties and methods specific to component nodes, such as handling attributes, bindings, directives, events, styles, and interpolations associated with the component."
|
|
1468
|
+
})
|
|
1469
|
+
], AreComponentNode);
|
|
1470
|
+
|
|
1471
|
+
// src/nodes/AreInterpolation.ts
|
|
1472
|
+
var AreInterpolation = class extends AreHTMLNode {
|
|
1473
|
+
fromNew(newEntity) {
|
|
1474
|
+
super.fromNew({
|
|
1475
|
+
...newEntity,
|
|
1476
|
+
payload: {
|
|
1477
|
+
...newEntity.payload || {},
|
|
1478
|
+
entity: "are-interpolation"
|
|
1479
|
+
}
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
__name(AreInterpolation, "AreInterpolation");
|
|
1484
|
+
AreInterpolation = __decorateClass([
|
|
1485
|
+
R2.Define({
|
|
1486
|
+
namespace: "a-are-html",
|
|
1487
|
+
description: "Node type representing a reactive inline expression in the AreHTMLNode tree. Its content expression is resolved from the store at render time and kept live via an AddInterpolation instruction that updates the corresponding text node on each reactive cycle."
|
|
1488
|
+
})
|
|
1489
|
+
], AreInterpolation);
|
|
1490
|
+
|
|
1491
|
+
// src/nodes/AreRoot.ts
|
|
1492
|
+
var AreRootNode = class extends AreHTMLNode {
|
|
1493
|
+
/**
|
|
1494
|
+
* For the root node, we can default to a generic container element like <div> since it serves as the root of the component tree and does not correspond to a specific HTML tag defined in the markup. The actual content and structure of the root node will be determined by the child nodes and components that are rendered within it, allowing for flexibility in how the root node is used and what it contains.
|
|
1495
|
+
*/
|
|
1496
|
+
get tag() {
|
|
1497
|
+
return "div";
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* A custom component associated with this node, which can be used to provide custom logic and behavior for the node. This component is typically defined in the context and can be resolved based on the node's type or other identifying information. The component can include its own template, markup, styles, and features that are specific to the functionality it provides.
|
|
1501
|
+
*
|
|
1502
|
+
* Example: If the node type is "custom-component", the corresponding component would be resolved from the context and can be used to provide custom rendering and behavior for nodes of that type.
|
|
1503
|
+
*
|
|
1504
|
+
* [!] Note: The component is optional and may not be defined for all nodes. If no component is associated with the node, it will be treated as a standard HTML element or a basic node without custom logic.
|
|
1505
|
+
*/
|
|
1506
|
+
get component() {
|
|
1507
|
+
return this.scope.resolve(P.toPascalCase(this.aseid.entity));
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
__name(AreRootNode, "AreRootNode");
|
|
1511
|
+
AreRootNode = __decorateClass([
|
|
1512
|
+
R2.Define({
|
|
1513
|
+
namespace: "a-are-html",
|
|
1514
|
+
description: "AreRootNode represents the root node in the scene graph. It extends the base AreHTMLNode and includes additional properties and methods specific to the root node, such as handling the root element and its associated component."
|
|
1515
|
+
})
|
|
1516
|
+
], AreRootNode);
|
|
1517
|
+
|
|
1518
|
+
// src/nodes/AreText.ts
|
|
1519
|
+
var AreText = class extends AreHTMLNode {
|
|
1520
|
+
fromNew(newEntity) {
|
|
1521
|
+
super.fromNew({
|
|
1522
|
+
...newEntity,
|
|
1523
|
+
payload: {
|
|
1524
|
+
...newEntity.payload || {},
|
|
1525
|
+
entity: "are-text"
|
|
1526
|
+
}
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
__name(AreText, "AreText");
|
|
1531
|
+
AreText = __decorateClass([
|
|
1532
|
+
R2.Define({
|
|
1533
|
+
namespace: "a-are-html",
|
|
1534
|
+
description: "Node type representing a plain or partially-dynamic text segment in the AreHTMLNode tree. Emits an AddText instruction that sets or updates the corresponding DOM text node; the content may carry a store getter for any dynamic portion."
|
|
1535
|
+
})
|
|
1536
|
+
], AreText);
|
|
1537
|
+
|
|
1538
|
+
// src/signals/AreRoute.signal.ts
|
|
1539
|
+
var AreRoute = class extends AreSignal {
|
|
1540
|
+
constructor(path) {
|
|
1541
|
+
super({
|
|
1542
|
+
data: new A_Route(path)
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
get route() {
|
|
1546
|
+
return this.data;
|
|
1547
|
+
}
|
|
1548
|
+
static default() {
|
|
1549
|
+
return new AreRoute(document.location.pathname || "/");
|
|
1550
|
+
}
|
|
1551
|
+
compare(other) {
|
|
1552
|
+
return this.route.toRegExp().test(other.data.toString());
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
__name(AreRoute, "AreRoute");
|
|
1556
|
+
AreRoute = __decorateClass([
|
|
1557
|
+
R2.Define({
|
|
1558
|
+
namespace: "a-are-html",
|
|
1559
|
+
description: "ARE signal that carries an A_Route value. Dispatched by AreRouteWatcher on client-side navigation events (pushState, replaceState, popstate). The signal bus delivers it to all subscribed root nodes, triggering route-based conditional rendering across the component tree."
|
|
1560
|
+
})
|
|
1561
|
+
], AreRoute);
|
|
1562
|
+
|
|
1563
|
+
// src/engine/AreHTML.constants.ts
|
|
1564
|
+
var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
1565
|
+
var SVG_ATTRIBUTE_NS = {
|
|
1566
|
+
xlink: "http://www.w3.org/1999/xlink",
|
|
1567
|
+
xml: "http://www.w3.org/XML/1998/namespace",
|
|
1568
|
+
xmlns: "http://www.w3.org/2000/xmlns/"
|
|
1569
|
+
};
|
|
1570
|
+
var VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
1571
|
+
"area",
|
|
1572
|
+
"base",
|
|
1573
|
+
"br",
|
|
1574
|
+
"col",
|
|
1575
|
+
"embed",
|
|
1576
|
+
"hr",
|
|
1577
|
+
"img",
|
|
1578
|
+
"input",
|
|
1579
|
+
"link",
|
|
1580
|
+
"meta",
|
|
1581
|
+
"param",
|
|
1582
|
+
"source",
|
|
1583
|
+
"track",
|
|
1584
|
+
"wbr"
|
|
1585
|
+
]);
|
|
1586
|
+
function isVoidElement(tagName) {
|
|
1587
|
+
return VOID_ELEMENTS.has(tagName.toLowerCase());
|
|
1588
|
+
}
|
|
1589
|
+
__name(isVoidElement, "isVoidElement");
|
|
1590
|
+
var BOOLEAN_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
1591
|
+
"allowfullscreen",
|
|
1592
|
+
"async",
|
|
1593
|
+
"autofocus",
|
|
1594
|
+
"autoplay",
|
|
1595
|
+
"checked",
|
|
1596
|
+
"controls",
|
|
1597
|
+
"default",
|
|
1598
|
+
"defer",
|
|
1599
|
+
"disabled",
|
|
1600
|
+
"formnovalidate",
|
|
1601
|
+
"hidden",
|
|
1602
|
+
"inert",
|
|
1603
|
+
"ismap",
|
|
1604
|
+
"itemscope",
|
|
1605
|
+
"loop",
|
|
1606
|
+
"multiple",
|
|
1607
|
+
"muted",
|
|
1608
|
+
"nomodule",
|
|
1609
|
+
"novalidate",
|
|
1610
|
+
"open",
|
|
1611
|
+
"playsinline",
|
|
1612
|
+
"readonly",
|
|
1613
|
+
"required",
|
|
1614
|
+
"reversed",
|
|
1615
|
+
"selected"
|
|
1616
|
+
]);
|
|
1617
|
+
function isBooleanAttribute(name) {
|
|
1618
|
+
return BOOLEAN_ATTRIBUTES.has(name.toLowerCase());
|
|
1619
|
+
}
|
|
1620
|
+
__name(isBooleanAttribute, "isBooleanAttribute");
|
|
1621
|
+
var IDL_FORM_PROPERTIES = {
|
|
1622
|
+
INPUT: /* @__PURE__ */ new Set(["value", "checked", "indeterminate"]),
|
|
1623
|
+
TEXTAREA: /* @__PURE__ */ new Set(["value"]),
|
|
1624
|
+
SELECT: /* @__PURE__ */ new Set(["value"]),
|
|
1625
|
+
OPTION: /* @__PURE__ */ new Set(["selected"])
|
|
1626
|
+
};
|
|
1627
|
+
function isIDLFormProperty(tagName, attrName) {
|
|
1628
|
+
const set = IDL_FORM_PROPERTIES[tagName.toUpperCase()];
|
|
1629
|
+
return !!set && set.has(attrName);
|
|
1630
|
+
}
|
|
1631
|
+
__name(isIDLFormProperty, "isIDLFormProperty");
|
|
1632
|
+
function normalizeClassValue(value) {
|
|
1633
|
+
if (value === null || value === void 0 || value === false) return "";
|
|
1634
|
+
if (typeof value === "string") return value;
|
|
1635
|
+
if (typeof value === "number") return String(value);
|
|
1636
|
+
if (Array.isArray(value)) {
|
|
1637
|
+
return value.map(normalizeClassValue).filter(Boolean).join(" ");
|
|
1638
|
+
}
|
|
1639
|
+
if (typeof value === "object") {
|
|
1640
|
+
const parts = [];
|
|
1641
|
+
for (const key of Object.keys(value)) {
|
|
1642
|
+
if (value[key]) parts.push(key);
|
|
1643
|
+
}
|
|
1644
|
+
return parts.join(" ");
|
|
1645
|
+
}
|
|
1646
|
+
return "";
|
|
1647
|
+
}
|
|
1648
|
+
__name(normalizeClassValue, "normalizeClassValue");
|
|
1649
|
+
function normalizeStyleValue(value) {
|
|
1650
|
+
if (value === null || value === void 0 || value === false) return "";
|
|
1651
|
+
if (typeof value === "string") return value;
|
|
1652
|
+
if (typeof value === "number") return String(value);
|
|
1653
|
+
if (Array.isArray(value)) {
|
|
1654
|
+
return value.map(normalizeStyleValue).filter(Boolean).join("; ");
|
|
1655
|
+
}
|
|
1656
|
+
if (typeof value === "object") {
|
|
1657
|
+
const parts = [];
|
|
1658
|
+
for (const key of Object.keys(value)) {
|
|
1659
|
+
const v = value[key];
|
|
1660
|
+
if (v === null || v === void 0 || v === false) continue;
|
|
1661
|
+
const kebab = key.replace(/[A-Z]/g, (m2) => "-" + m2.toLowerCase());
|
|
1662
|
+
parts.push(`${kebab}: ${v}`);
|
|
1663
|
+
}
|
|
1664
|
+
return parts.join("; ");
|
|
1665
|
+
}
|
|
1666
|
+
return "";
|
|
1667
|
+
}
|
|
1668
|
+
__name(normalizeStyleValue, "normalizeStyleValue");
|
|
1669
|
+
function parseEventName(raw) {
|
|
1670
|
+
const [event, ...modifiers] = raw.split(".");
|
|
1671
|
+
return { event, modifiers: new Set(modifiers) };
|
|
1672
|
+
}
|
|
1673
|
+
__name(parseEventName, "parseEventName");
|
|
1674
|
+
function toDOMString(value) {
|
|
1675
|
+
if (value === null || value === void 0) return "";
|
|
1676
|
+
if (typeof value === "string") return value;
|
|
1677
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
1678
|
+
try {
|
|
1679
|
+
return JSON.stringify(value);
|
|
1680
|
+
} catch {
|
|
1681
|
+
return "";
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
__name(toDOMString, "toDOMString");
|
|
1685
|
+
var STANDARD_HTML_TAGS = /* @__PURE__ */ new Set([
|
|
1686
|
+
// root / sections
|
|
1687
|
+
"html",
|
|
1688
|
+
"body",
|
|
1689
|
+
"header",
|
|
1690
|
+
"footer",
|
|
1691
|
+
"main",
|
|
1692
|
+
"nav",
|
|
1693
|
+
"section",
|
|
1694
|
+
"article",
|
|
1695
|
+
"aside",
|
|
1696
|
+
"address",
|
|
1697
|
+
"hgroup",
|
|
1698
|
+
// headings
|
|
1699
|
+
"h1",
|
|
1700
|
+
"h2",
|
|
1701
|
+
"h3",
|
|
1702
|
+
"h4",
|
|
1703
|
+
"h5",
|
|
1704
|
+
"h6",
|
|
1705
|
+
// grouping
|
|
1706
|
+
"div",
|
|
1707
|
+
"p",
|
|
1708
|
+
"span",
|
|
1709
|
+
"pre",
|
|
1710
|
+
"blockquote",
|
|
1711
|
+
"figure",
|
|
1712
|
+
"figcaption",
|
|
1713
|
+
"hr",
|
|
1714
|
+
"br",
|
|
1715
|
+
"wbr",
|
|
1716
|
+
// lists
|
|
1717
|
+
"ul",
|
|
1718
|
+
"ol",
|
|
1719
|
+
"li",
|
|
1720
|
+
"dl",
|
|
1721
|
+
"dt",
|
|
1722
|
+
"dd",
|
|
1723
|
+
"menu",
|
|
1724
|
+
// text-level / phrasing
|
|
1725
|
+
"a",
|
|
1726
|
+
"b",
|
|
1727
|
+
"i",
|
|
1728
|
+
"u",
|
|
1729
|
+
"s",
|
|
1730
|
+
"em",
|
|
1731
|
+
"strong",
|
|
1732
|
+
"small",
|
|
1733
|
+
"mark",
|
|
1734
|
+
"abbr",
|
|
1735
|
+
"cite",
|
|
1736
|
+
"q",
|
|
1737
|
+
"code",
|
|
1738
|
+
"kbd",
|
|
1739
|
+
"samp",
|
|
1740
|
+
"var",
|
|
1741
|
+
"sub",
|
|
1742
|
+
"sup",
|
|
1743
|
+
"time",
|
|
1744
|
+
"data",
|
|
1745
|
+
"dfn",
|
|
1746
|
+
"bdi",
|
|
1747
|
+
"bdo",
|
|
1748
|
+
"ruby",
|
|
1749
|
+
"rt",
|
|
1750
|
+
"rp",
|
|
1751
|
+
"del",
|
|
1752
|
+
"ins",
|
|
1753
|
+
// media / embedded (no special namespace handling needed)
|
|
1754
|
+
"img",
|
|
1755
|
+
"picture",
|
|
1756
|
+
"source",
|
|
1757
|
+
"figure",
|
|
1758
|
+
"audio",
|
|
1759
|
+
"video",
|
|
1760
|
+
"track",
|
|
1761
|
+
// tables
|
|
1762
|
+
"table",
|
|
1763
|
+
"caption",
|
|
1764
|
+
"colgroup",
|
|
1765
|
+
"col",
|
|
1766
|
+
"thead",
|
|
1767
|
+
"tbody",
|
|
1768
|
+
"tfoot",
|
|
1769
|
+
"tr",
|
|
1770
|
+
"th",
|
|
1771
|
+
"td",
|
|
1772
|
+
// forms (display only — these still render fine from innerHTML)
|
|
1773
|
+
"label",
|
|
1774
|
+
"fieldset",
|
|
1775
|
+
"legend",
|
|
1776
|
+
"datalist",
|
|
1777
|
+
"option",
|
|
1778
|
+
"optgroup",
|
|
1779
|
+
"output",
|
|
1780
|
+
"progress",
|
|
1781
|
+
"meter",
|
|
1782
|
+
// interactive
|
|
1783
|
+
"details",
|
|
1784
|
+
"summary",
|
|
1785
|
+
"dialog"
|
|
1786
|
+
]);
|
|
1787
|
+
function isStaticMarkup(inner) {
|
|
1788
|
+
if (!inner) return false;
|
|
1789
|
+
if (inner.indexOf("{{") !== -1) return false;
|
|
1790
|
+
const n = inner.length;
|
|
1791
|
+
let i = 0;
|
|
1792
|
+
while (i < n) {
|
|
1793
|
+
const lt = inner.indexOf("<", i);
|
|
1794
|
+
if (lt === -1) break;
|
|
1795
|
+
if (inner.startsWith("<!--", lt)) {
|
|
1796
|
+
const end = inner.indexOf("-->", lt + 4);
|
|
1797
|
+
if (end === -1) return false;
|
|
1798
|
+
i = end + 3;
|
|
1799
|
+
continue;
|
|
1800
|
+
}
|
|
1801
|
+
if (inner[lt + 1] === "/" || inner[lt + 1] === "!" || inner[lt + 1] === "?") {
|
|
1802
|
+
const gt = inner.indexOf(">", lt);
|
|
1803
|
+
if (gt === -1) return false;
|
|
1804
|
+
i = gt + 1;
|
|
1805
|
+
continue;
|
|
1806
|
+
}
|
|
1807
|
+
const nameMatch = /^<([a-zA-Z][a-zA-Z0-9-]*)/.exec(inner.slice(lt));
|
|
1808
|
+
if (!nameMatch) {
|
|
1809
|
+
i = lt + 1;
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
const tag = nameMatch[1].toLowerCase();
|
|
1813
|
+
if (tag.indexOf("-") !== -1 || !STANDARD_HTML_TAGS.has(tag)) return false;
|
|
1814
|
+
let j = lt + nameMatch[0].length;
|
|
1815
|
+
let inSingle = false;
|
|
1816
|
+
let inDouble = false;
|
|
1817
|
+
let atNameBoundary = true;
|
|
1818
|
+
let tagEnd = -1;
|
|
1819
|
+
while (j < n) {
|
|
1820
|
+
const ch = inner[j];
|
|
1821
|
+
if (inDouble) {
|
|
1822
|
+
if (ch === '"') inDouble = false;
|
|
1823
|
+
} else if (inSingle) {
|
|
1824
|
+
if (ch === "'") inSingle = false;
|
|
1825
|
+
} else if (ch === '"') {
|
|
1826
|
+
inDouble = true;
|
|
1827
|
+
atNameBoundary = false;
|
|
1828
|
+
} else if (ch === "'") {
|
|
1829
|
+
inSingle = true;
|
|
1830
|
+
atNameBoundary = false;
|
|
1831
|
+
} else if (ch === ">") {
|
|
1832
|
+
tagEnd = j;
|
|
1833
|
+
break;
|
|
1834
|
+
} else if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "/") {
|
|
1835
|
+
atNameBoundary = true;
|
|
1836
|
+
} else {
|
|
1837
|
+
if (atNameBoundary && (ch === "$" || ch === ":" || ch === "@")) {
|
|
1838
|
+
return false;
|
|
1839
|
+
}
|
|
1840
|
+
atNameBoundary = false;
|
|
1841
|
+
}
|
|
1842
|
+
j++;
|
|
1843
|
+
}
|
|
1844
|
+
if (tagEnd === -1) return false;
|
|
1845
|
+
i = tagEnd + 1;
|
|
1846
|
+
}
|
|
1847
|
+
return true;
|
|
1848
|
+
}
|
|
1849
|
+
__name(isStaticMarkup, "isStaticMarkup");
|
|
1850
|
+
|
|
1851
|
+
// src/engine/AreHTML.compiler.ts
|
|
1852
|
+
var AreHTMLCompiler = class extends AreCompiler {
|
|
1853
|
+
compileHTMLNode(node, scene, logger, ...args) {
|
|
1854
|
+
super.compile(node, scene, logger, ...args);
|
|
1855
|
+
if (node.isStaticIsland && scene.host) {
|
|
1856
|
+
scene.plan(new AddStaticHTMLInstruction(scene.host, { html: node.staticInnerHTML }));
|
|
1857
|
+
}
|
|
1858
|
+
if (node.styles?.styles) {
|
|
1859
|
+
const host = scene.host;
|
|
1860
|
+
if (host) {
|
|
1861
|
+
scene.plan(new AddStyleInstruction(host, { styles: node.styles.styles }));
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
compileInterpolation(interpolation, scene, store, logger, ...args) {
|
|
1866
|
+
scene.plan(new AddTextInstruction({ content: interpolation.content, evaluate: true }));
|
|
1867
|
+
}
|
|
1868
|
+
compileText(text, scene, logger, ...args) {
|
|
1869
|
+
logger?.debug("cyan", `AreHTMLCompiler: compile text node <${text.aseid.toString()}> with content: "${text.content}"`);
|
|
1870
|
+
if (scene.host)
|
|
1871
|
+
scene.unPlan(scene.host);
|
|
1872
|
+
scene.plan(new AddTextInstruction({ content: text.content }));
|
|
1873
|
+
}
|
|
1874
|
+
compileStaticAttribute(attribute, scene, ...args) {
|
|
1875
|
+
if (!scene.host)
|
|
1876
|
+
throw new AreCompilerError({
|
|
1877
|
+
title: "Scene Host Not Found",
|
|
1878
|
+
description: `No host found for the scene with id: ${scene.id}. Please ensure that the scene is properly initialized and has a host before compiling binding attributes.`
|
|
1879
|
+
});
|
|
1880
|
+
const content = attribute.content;
|
|
1881
|
+
if (content.includes("{{")) {
|
|
1882
|
+
const transformed = '"' + content.replace(/\{\{([^}]+)\}\}/g, '"+($1)+"') + '"';
|
|
1883
|
+
scene.plan(new AddAttributeInstruction(scene.host, {
|
|
1884
|
+
name: attribute.name,
|
|
1885
|
+
content: transformed,
|
|
1886
|
+
evaluate: true
|
|
1887
|
+
}));
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
scene.plan(new AddAttributeInstruction(scene.host, {
|
|
1891
|
+
name: attribute.name,
|
|
1892
|
+
content: attribute.content
|
|
1893
|
+
}));
|
|
1894
|
+
}
|
|
1895
|
+
compileDirectiveAttribute(directive, store, feature, logger, ...args) {
|
|
1896
|
+
store.watch(directive);
|
|
1897
|
+
if (directive.component) {
|
|
1898
|
+
feature.chain(directive.component, AreDirectiveFeatures.Compile, directive.owner.scope);
|
|
1899
|
+
} else {
|
|
1900
|
+
logger?.warning(`Directive handler component not found for directive: ${directive.name}. Make sure to create a component named "AreDirective${P.toPascalCase(directive.name)}" to handle this directive.`);
|
|
1901
|
+
}
|
|
1902
|
+
store.unwatch(directive);
|
|
1903
|
+
}
|
|
1904
|
+
compileEventAttribute(attribute, scene, ...args) {
|
|
1905
|
+
if (!scene.host)
|
|
1906
|
+
throw new AreCompilerError({
|
|
1907
|
+
title: "Scene Host Not Found",
|
|
1908
|
+
description: `No host found for the scene with id: ${scene.id}. Please ensure that the scene is properly initialized and has a host before compiling binding attributes.`
|
|
1909
|
+
});
|
|
1910
|
+
scene.plan(new AddListenerInstruction(scene.host, {
|
|
1911
|
+
name: attribute.name,
|
|
1912
|
+
handler: attribute.content
|
|
1913
|
+
}));
|
|
1914
|
+
}
|
|
1915
|
+
compileBindingAttribute(attribute, scene, parentStore, store, syntax, directiveContext, ...args) {
|
|
1916
|
+
if (!scene.host)
|
|
1917
|
+
throw new AreCompilerError({
|
|
1918
|
+
title: "Scene Host Not Found",
|
|
1919
|
+
description: `No host found for the scene with id: ${scene.id}. Please ensure that the scene is properly initialized and has a host before compiling binding attributes.`
|
|
1920
|
+
});
|
|
1921
|
+
const node = attribute.owner;
|
|
1922
|
+
const props = node.component?.props;
|
|
1923
|
+
let propName;
|
|
1924
|
+
if (props) {
|
|
1925
|
+
if (props[attribute.name]) {
|
|
1926
|
+
propName = attribute.name;
|
|
1927
|
+
} else {
|
|
1928
|
+
const camel = P.toCamelCase(attribute.name);
|
|
1929
|
+
if (props[camel]) propName = camel;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
if (propName && props) {
|
|
1933
|
+
const propDefinition = props[propName];
|
|
1934
|
+
const coerce = /* @__PURE__ */ __name((raw) => {
|
|
1935
|
+
let value = raw;
|
|
1936
|
+
if (propDefinition.type) {
|
|
1937
|
+
switch (propDefinition.type) {
|
|
1938
|
+
case "string":
|
|
1939
|
+
value = value === void 0 || value === null ? "" : String(value);
|
|
1940
|
+
break;
|
|
1941
|
+
case "number":
|
|
1942
|
+
value = Number(value);
|
|
1943
|
+
break;
|
|
1944
|
+
case "boolean":
|
|
1945
|
+
value = Boolean(value);
|
|
1946
|
+
break;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
return value;
|
|
1950
|
+
}, "coerce");
|
|
1951
|
+
const directiveScope = /* @__PURE__ */ __name(() => directiveContext?.scope ?? {}, "directiveScope");
|
|
1952
|
+
const watcher = {
|
|
1953
|
+
update: /* @__PURE__ */ __name(() => {
|
|
1954
|
+
try {
|
|
1955
|
+
parentStore.watch(watcher);
|
|
1956
|
+
const next = coerce(syntax.evaluate(attribute.content, parentStore, directiveScope()));
|
|
1957
|
+
parentStore.unwatch(watcher);
|
|
1958
|
+
store.set(propName, next);
|
|
1959
|
+
} catch (e) {
|
|
1960
|
+
parentStore.unwatch(watcher);
|
|
1961
|
+
}
|
|
1962
|
+
}, "update")
|
|
1963
|
+
};
|
|
1964
|
+
parentStore.watch(watcher);
|
|
1965
|
+
const initial = coerce(syntax.evaluate(attribute.content, parentStore, directiveScope()));
|
|
1966
|
+
parentStore.unwatch(watcher);
|
|
1967
|
+
store.set(propName, initial);
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
const instruction = new AddAttributeInstruction(scene.host, {
|
|
1971
|
+
name: attribute.name,
|
|
1972
|
+
content: attribute.content,
|
|
1973
|
+
evaluate: true
|
|
1974
|
+
});
|
|
1975
|
+
scene.plan(instruction);
|
|
1976
|
+
}
|
|
1977
|
+
};
|
|
1978
|
+
__name(AreHTMLCompiler, "AreHTMLCompiler");
|
|
1979
|
+
__decorateClass([
|
|
1980
|
+
AreCompiler.Compile(AreHTMLNode),
|
|
1981
|
+
__decorateParam(0, Yt(te)),
|
|
1982
|
+
__decorateParam(1, Yt(AreScene)),
|
|
1983
|
+
__decorateParam(2, Yt(A_Logger))
|
|
1984
|
+
], AreHTMLCompiler.prototype, "compileHTMLNode", 1);
|
|
1985
|
+
__decorateClass([
|
|
1986
|
+
AreCompiler.Compile(AreInterpolation),
|
|
1987
|
+
__decorateParam(0, Yt(te)),
|
|
1988
|
+
__decorateParam(1, Yt(AreScene)),
|
|
1989
|
+
__decorateParam(2, Yt(AreStore)),
|
|
1990
|
+
__decorateParam(3, Yt(A_Logger))
|
|
1991
|
+
], AreHTMLCompiler.prototype, "compileInterpolation", 1);
|
|
1992
|
+
__decorateClass([
|
|
1993
|
+
AreCompiler.Compile(AreText),
|
|
1994
|
+
__decorateParam(0, Yt(te)),
|
|
1995
|
+
__decorateParam(1, Yt(AreScene)),
|
|
1996
|
+
__decorateParam(2, Yt(A_Logger))
|
|
1997
|
+
], AreHTMLCompiler.prototype, "compileText", 1);
|
|
1998
|
+
__decorateClass([
|
|
1999
|
+
AreCompiler.Compile(AreStaticAttribute),
|
|
2000
|
+
__decorateParam(0, Yt(te)),
|
|
2001
|
+
__decorateParam(1, Yt(AreScene))
|
|
2002
|
+
], AreHTMLCompiler.prototype, "compileStaticAttribute", 1);
|
|
2003
|
+
__decorateClass([
|
|
2004
|
+
AreCompiler.Compile(AreDirectiveAttribute),
|
|
2005
|
+
__decorateParam(0, Yt(te)),
|
|
2006
|
+
__decorateParam(1, Yt(AreStore)),
|
|
2007
|
+
__decorateParam(2, Yt(N)),
|
|
2008
|
+
__decorateParam(3, Yt(A_Logger))
|
|
2009
|
+
], AreHTMLCompiler.prototype, "compileDirectiveAttribute", 1);
|
|
2010
|
+
__decorateClass([
|
|
2011
|
+
AreCompiler.Compile(AreEventAttribute),
|
|
2012
|
+
__decorateParam(0, Yt(te)),
|
|
2013
|
+
__decorateParam(1, Yt(AreScene))
|
|
2014
|
+
], AreHTMLCompiler.prototype, "compileEventAttribute", 1);
|
|
2015
|
+
__decorateClass([
|
|
2016
|
+
AreCompiler.Compile(AreBindingAttribute),
|
|
2017
|
+
__decorateParam(0, Yt(te)),
|
|
2018
|
+
__decorateParam(1, Yt(AreScene)),
|
|
2019
|
+
__decorateParam(2, M.Parent()),
|
|
2020
|
+
__decorateParam(2, Yt(AreStore)),
|
|
2021
|
+
__decorateParam(3, Yt(AreStore)),
|
|
2022
|
+
__decorateParam(4, Yt(AreSyntax)),
|
|
2023
|
+
__decorateParam(5, Yt(AreDirectiveContext))
|
|
2024
|
+
], AreHTMLCompiler.prototype, "compileBindingAttribute", 1);
|
|
2025
|
+
AreHTMLCompiler = __decorateClass([
|
|
2026
|
+
R2.Define({
|
|
2027
|
+
namespace: "a-are-html",
|
|
2028
|
+
description: "HTML-specific compiler for A-Concept Rendering Engine (ARE) components, extending the base AreCompiler to handle HTML templates, styles, and rendering logic tailored for web environments."
|
|
2029
|
+
})
|
|
2030
|
+
], AreHTMLCompiler);
|
|
2031
|
+
|
|
2032
|
+
// src/engine/AreHTML.interpreter.ts
|
|
2033
|
+
var AreHTMLInterpreter = class extends AreInterpreter {
|
|
2034
|
+
addElement(declaration, context, logger) {
|
|
2035
|
+
try {
|
|
2036
|
+
const node = declaration.owner;
|
|
2037
|
+
let currentNode = node;
|
|
2038
|
+
let parent = node.parent;
|
|
2039
|
+
while (parent) {
|
|
2040
|
+
if (context.getNodeElement(parent)) {
|
|
2041
|
+
break;
|
|
2042
|
+
}
|
|
2043
|
+
currentNode = parent;
|
|
2044
|
+
parent = parent.parent;
|
|
2045
|
+
}
|
|
2046
|
+
const tag = node.tag;
|
|
2047
|
+
const isSVG = tag === "svg" || this.isInSVGContext(node);
|
|
2048
|
+
if (parent) {
|
|
2049
|
+
const mountPoint = context.getNodeElement(parent);
|
|
2050
|
+
if (!mountPoint) {
|
|
2051
|
+
throw new AreInterpreterError({
|
|
2052
|
+
title: "Mount Point Not Found",
|
|
2053
|
+
description: `Could not find a mount point for the node with id "${node.id}". Ensure that the parent node is rendered before its children, or that a valid root element with the corresponding id exists in the DOM.`
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
const element = isSVG ? context.container.createElementNS(SVG_NAMESPACE, tag) : context.container.createElement(tag);
|
|
2057
|
+
context.setInstructionElement(declaration, element);
|
|
2058
|
+
const attach = mountPoint.nodeType === Node.ELEMENT_NODE ? () => mountPoint.appendChild(element) : () => {
|
|
2059
|
+
mountPoint.parentNode?.insertBefore(element, mountPoint);
|
|
2060
|
+
};
|
|
2061
|
+
if (context.isBatching && mountPoint.isConnected) {
|
|
2062
|
+
context.deferAttach(attach);
|
|
2063
|
+
} else {
|
|
2064
|
+
attach();
|
|
2065
|
+
}
|
|
2066
|
+
} else {
|
|
2067
|
+
const mountPoint = context.container.getElementById(node.id);
|
|
2068
|
+
if (!mountPoint) {
|
|
2069
|
+
throw new AreInterpreterError({
|
|
2070
|
+
title: "Mount Point Not Found",
|
|
2071
|
+
description: `Could not find a mount point for the node with id "${node.id}". Ensure that the parent node is rendered before its children, or that a valid root element with the corresponding id exists in the DOM.`
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
const element = isSVG ? context.container.createElementNS(SVG_NAMESPACE, tag) : context.container.createElement(tag);
|
|
2075
|
+
context.setInstructionElement(declaration, element);
|
|
2076
|
+
const attach = /* @__PURE__ */ __name(() => {
|
|
2077
|
+
mountPoint.parentNode?.replaceChild(element, mountPoint);
|
|
2078
|
+
}, "attach");
|
|
2079
|
+
if (context.isBatching && mountPoint.isConnected) {
|
|
2080
|
+
context.deferAttach(attach);
|
|
2081
|
+
} else {
|
|
2082
|
+
attach();
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
logger?.debug("green", `Element ${node.aseid.toString()} added to Context:`);
|
|
2086
|
+
} catch (error) {
|
|
2087
|
+
logger?.error(error);
|
|
2088
|
+
throw error;
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
removeElement(declaration, context) {
|
|
2092
|
+
const element = context.getElementByInstruction(declaration);
|
|
2093
|
+
if (element && element.parentNode && element.isConnected) {
|
|
2094
|
+
element.parentNode.removeChild(element);
|
|
2095
|
+
}
|
|
2096
|
+
context.removeInstructionElement(declaration);
|
|
2097
|
+
}
|
|
2098
|
+
addAttribute(mutation, context, store, syntax, directiveContext, logger) {
|
|
2099
|
+
const element = context.getElementByInstruction(mutation.parent);
|
|
2100
|
+
if (!element) {
|
|
2101
|
+
throw new AreInterpreterError({
|
|
2102
|
+
title: "Element Not Found",
|
|
2103
|
+
description: `Could not find a DOM element associated with the instruction ASEID "${mutation.parent}". Ensure that the parent instruction is properly rendered and associated with a DOM element before applying attribute mutations.`
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
2106
|
+
const { name, content, evaluate } = mutation.payload;
|
|
2107
|
+
const rawValue = evaluate ? syntax.evaluate(content, store, {
|
|
2108
|
+
...directiveContext?.scope || {}
|
|
2109
|
+
}) : content;
|
|
2110
|
+
const el = element;
|
|
2111
|
+
const lowerName = name.toLowerCase();
|
|
2112
|
+
const colonIdx = name.indexOf(":");
|
|
2113
|
+
if (colonIdx > 0) {
|
|
2114
|
+
const ns = SVG_ATTRIBUTE_NS[name.slice(0, colonIdx)];
|
|
2115
|
+
if (ns) {
|
|
2116
|
+
el.setAttributeNS(ns, name, toDOMString(rawValue));
|
|
2117
|
+
mutation.cache = toDOMString(rawValue);
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
if (isBooleanAttribute(lowerName)) {
|
|
2122
|
+
if (rawValue) {
|
|
2123
|
+
el.setAttribute(lowerName, "");
|
|
2124
|
+
try {
|
|
2125
|
+
el[lowerName] = true;
|
|
2126
|
+
} catch {
|
|
2127
|
+
}
|
|
2128
|
+
} else {
|
|
2129
|
+
el.removeAttribute(lowerName);
|
|
2130
|
+
try {
|
|
2131
|
+
el[lowerName] = false;
|
|
2132
|
+
} catch {
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
mutation.cache = rawValue ? "true" : "";
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
if (isIDLFormProperty(el.tagName, name)) {
|
|
2139
|
+
const propName = name === "value" ? "value" : name === "checked" ? "checked" : name === "selected" ? "selected" : name === "indeterminate" ? "indeterminate" : name;
|
|
2140
|
+
try {
|
|
2141
|
+
if (propName === "checked" || propName === "selected" || propName === "indeterminate") {
|
|
2142
|
+
el[propName] = !!rawValue;
|
|
2143
|
+
} else {
|
|
2144
|
+
el[propName] = toDOMString(rawValue);
|
|
2145
|
+
}
|
|
2146
|
+
} catch {
|
|
2147
|
+
}
|
|
2148
|
+
if (propName !== "value") {
|
|
2149
|
+
if (rawValue) el.setAttribute(name, "");
|
|
2150
|
+
else el.removeAttribute(name);
|
|
2151
|
+
} else {
|
|
2152
|
+
el.setAttribute(name, toDOMString(rawValue));
|
|
2153
|
+
}
|
|
2154
|
+
mutation.cache = toDOMString(rawValue);
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
2157
|
+
if (lowerName === "class") {
|
|
2158
|
+
const newValue = normalizeClassValue(rawValue);
|
|
2159
|
+
if (mutation.cache === void 0) {
|
|
2160
|
+
const existingValue = el.getAttribute("class");
|
|
2161
|
+
const merged = existingValue ? `${existingValue} ${newValue}`.trim() : newValue;
|
|
2162
|
+
if (merged) el.setAttribute("class", merged);
|
|
2163
|
+
else el.removeAttribute("class");
|
|
2164
|
+
} else {
|
|
2165
|
+
const existingValue = el.getAttribute("class");
|
|
2166
|
+
const existingParts = existingValue ? existingValue.split(/\s+/).filter(Boolean) : [];
|
|
2167
|
+
const oldParts = new Set(mutation.cache.split(/\s+/).filter(Boolean));
|
|
2168
|
+
const newParts = newValue ? newValue.split(/\s+/).filter(Boolean) : [];
|
|
2169
|
+
const merged = [...existingParts.filter((p) => !oldParts.has(p)), ...newParts].join(" ");
|
|
2170
|
+
if (merged) el.setAttribute("class", merged);
|
|
2171
|
+
else el.removeAttribute("class");
|
|
2172
|
+
}
|
|
2173
|
+
mutation.cache = newValue;
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
if (lowerName === "style") {
|
|
2177
|
+
const newValue = normalizeStyleValue(rawValue);
|
|
2178
|
+
if (newValue) el.setAttribute("style", newValue);
|
|
2179
|
+
else el.removeAttribute("style");
|
|
2180
|
+
mutation.cache = newValue;
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
const stringValue = toDOMString(rawValue);
|
|
2184
|
+
if (stringValue === "" && evaluate && (rawValue === false || rawValue === null || rawValue === void 0)) {
|
|
2185
|
+
el.removeAttribute(name);
|
|
2186
|
+
} else {
|
|
2187
|
+
el.setAttribute(name, stringValue);
|
|
2188
|
+
}
|
|
2189
|
+
mutation.cache = stringValue;
|
|
2190
|
+
}
|
|
2191
|
+
removeAttribute(mutation, context) {
|
|
2192
|
+
try {
|
|
2193
|
+
const element = context.getElementByInstruction(mutation.parent);
|
|
2194
|
+
if (!element) return;
|
|
2195
|
+
const { name } = mutation.payload;
|
|
2196
|
+
if (name && element.nodeType === Node.ELEMENT_NODE && element.isConnected) {
|
|
2197
|
+
const colonIdx = name.indexOf(":");
|
|
2198
|
+
if (colonIdx > 0) {
|
|
2199
|
+
const ns = SVG_ATTRIBUTE_NS[name.slice(0, colonIdx)];
|
|
2200
|
+
if (ns) {
|
|
2201
|
+
element.removeAttributeNS(ns, name.slice(colonIdx + 1));
|
|
2202
|
+
} else {
|
|
2203
|
+
element.removeAttribute(name);
|
|
2204
|
+
}
|
|
2205
|
+
} else {
|
|
2206
|
+
element.removeAttribute(name);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
} catch (error) {
|
|
2210
|
+
console.log("Error removing attribute:", error);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
hideElement(mutation, context) {
|
|
2214
|
+
const element = context.getElementByInstruction(mutation.parent);
|
|
2215
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) return;
|
|
2216
|
+
const el = element;
|
|
2217
|
+
mutation.cache = el.style.display;
|
|
2218
|
+
el.style.display = "none";
|
|
2219
|
+
}
|
|
2220
|
+
showElement(mutation, context) {
|
|
2221
|
+
const element = context.getElementByInstruction(mutation.parent);
|
|
2222
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) return;
|
|
2223
|
+
if (!element.isConnected) return;
|
|
2224
|
+
const el = element;
|
|
2225
|
+
el.style.display = mutation.payload?.display ?? mutation.cache ?? "";
|
|
2226
|
+
}
|
|
2227
|
+
addEventListener(mutation, context, store, syntax, directiveContext, logger) {
|
|
2228
|
+
const element = context.getElementByInstruction(mutation.parent);
|
|
2229
|
+
if (!element) {
|
|
2230
|
+
throw new AreInterpreterError({
|
|
2231
|
+
title: "Element Not Found",
|
|
2232
|
+
description: `Could not find a DOM element associated with the instruction ASEID "${mutation.parent}". Ensure that the parent instruction is properly rendered and associated with a DOM element before adding event listeners.`
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
const { event: eventName, modifiers } = parseEventName(mutation.payload.name);
|
|
2236
|
+
const listenerOptions = {};
|
|
2237
|
+
if (modifiers.has("capture")) listenerOptions.capture = true;
|
|
2238
|
+
if (modifiers.has("once")) listenerOptions.once = true;
|
|
2239
|
+
if (modifiers.has("passive")) listenerOptions.passive = true;
|
|
2240
|
+
const handlers = syntax.extractEmitHandlers(mutation.payload.handler);
|
|
2241
|
+
let liveEvent = null;
|
|
2242
|
+
const handlerScope = {};
|
|
2243
|
+
for (const handler of handlers) {
|
|
2244
|
+
const handlerFn = /* @__PURE__ */ __name((...args) => {
|
|
2245
|
+
const event = new AreEvent(handler);
|
|
2246
|
+
const effectiveArgs = args.length === 0 && liveEvent ? [liveEvent] : liveEvent ? [...args, liveEvent] : args;
|
|
2247
|
+
event.set("args", effectiveArgs);
|
|
2248
|
+
event.set("element", element);
|
|
2249
|
+
event.set("instruction", mutation);
|
|
2250
|
+
if (liveEvent) event.set("native", liveEvent);
|
|
2251
|
+
mutation.owner.emit(event);
|
|
2252
|
+
}, "handlerFn");
|
|
2253
|
+
handlerScope[`$${handler}`] = handlerFn;
|
|
2254
|
+
}
|
|
2255
|
+
const callback = /* @__PURE__ */ __name((e) => {
|
|
2256
|
+
try {
|
|
2257
|
+
liveEvent = e;
|
|
2258
|
+
if (modifiers.has("self") && e.target !== element) return;
|
|
2259
|
+
if (modifiers.has("stop")) e.stopPropagation();
|
|
2260
|
+
if (modifiers.has("prevent")) e.preventDefault();
|
|
2261
|
+
if (e instanceof KeyboardEvent && modifiers.size > 0) {
|
|
2262
|
+
const key = (e.key || "").toLowerCase();
|
|
2263
|
+
const KEY_ALIASES = {
|
|
2264
|
+
enter: ["enter"],
|
|
2265
|
+
esc: ["escape"],
|
|
2266
|
+
escape: ["escape"],
|
|
2267
|
+
tab: ["tab"],
|
|
2268
|
+
space: [" ", "spacebar"],
|
|
2269
|
+
up: ["arrowup"],
|
|
2270
|
+
down: ["arrowdown"],
|
|
2271
|
+
left: ["arrowleft"],
|
|
2272
|
+
right: ["arrowright"],
|
|
2273
|
+
delete: ["delete", "backspace"]
|
|
2274
|
+
};
|
|
2275
|
+
const keyMods = [...modifiers].filter((m2) => m2 in KEY_ALIASES || m2 === "ctrl" || m2 === "alt" || m2 === "shift" || m2 === "meta");
|
|
2276
|
+
if (keyMods.length > 0) {
|
|
2277
|
+
const keyMatch = keyMods.some((m2) => {
|
|
2278
|
+
if (m2 === "ctrl") return e.ctrlKey;
|
|
2279
|
+
if (m2 === "alt") return e.altKey;
|
|
2280
|
+
if (m2 === "shift") return e.shiftKey;
|
|
2281
|
+
if (m2 === "meta") return e.metaKey;
|
|
2282
|
+
const aliases = KEY_ALIASES[m2];
|
|
2283
|
+
return aliases && aliases.includes(key);
|
|
2284
|
+
});
|
|
2285
|
+
if (!keyMatch) return;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
context.startPerformance("event:" + eventName);
|
|
2289
|
+
const result = syntax.evaluate(mutation.payload.handler, store, {
|
|
2290
|
+
...handlerScope,
|
|
2291
|
+
$event: e,
|
|
2292
|
+
...directiveContext?.scope || {}
|
|
2293
|
+
});
|
|
2294
|
+
if (typeof result === "function") result(e);
|
|
2295
|
+
context.endPerformance("event:" + eventName);
|
|
2296
|
+
} catch (err) {
|
|
2297
|
+
logger?.error(err);
|
|
2298
|
+
} finally {
|
|
2299
|
+
liveEvent = null;
|
|
2300
|
+
}
|
|
2301
|
+
}, "callback");
|
|
2302
|
+
const useOptions = listenerOptions.capture || listenerOptions.once || listenerOptions.passive;
|
|
2303
|
+
if (useOptions) {
|
|
2304
|
+
element.addEventListener(eventName, callback, listenerOptions);
|
|
2305
|
+
} else {
|
|
2306
|
+
element.addEventListener(eventName, callback);
|
|
2307
|
+
}
|
|
2308
|
+
mutation.payload._callback = callback;
|
|
2309
|
+
context.addListener(element, mutation.payload.name, callback);
|
|
2310
|
+
}
|
|
2311
|
+
removeEventListener(mutation, context) {
|
|
2312
|
+
const element = context.getElementByInstruction(mutation.parent);
|
|
2313
|
+
if (!element) return;
|
|
2314
|
+
const { name } = mutation.payload;
|
|
2315
|
+
const { event: eventName } = parseEventName(name);
|
|
2316
|
+
const listener = mutation.payload._callback;
|
|
2317
|
+
if (listener) {
|
|
2318
|
+
if (element.isConnected) {
|
|
2319
|
+
element.removeEventListener(eventName, listener);
|
|
2320
|
+
}
|
|
2321
|
+
context.removeListener(element, name, listener);
|
|
2322
|
+
mutation.payload._callback = void 0;
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
addText(declaration, context, store, syntax, directiveContext, logger) {
|
|
2326
|
+
const node = declaration.owner.parent;
|
|
2327
|
+
const { content, evaluate } = declaration.payload;
|
|
2328
|
+
const rawValue = evaluate ? syntax.evaluate(content, store, {
|
|
2329
|
+
...directiveContext?.scope || {}
|
|
2330
|
+
}) : content;
|
|
2331
|
+
const value = toDOMString(rawValue);
|
|
2332
|
+
if (!node) {
|
|
2333
|
+
const textNode = context.container.createTextNode(value);
|
|
2334
|
+
context.container.body.appendChild(textNode);
|
|
2335
|
+
context.setInstructionElement(declaration, textNode);
|
|
2336
|
+
} else {
|
|
2337
|
+
const element = context.getNodeElement(node);
|
|
2338
|
+
if (!element) {
|
|
2339
|
+
throw new AreInterpreterError({
|
|
2340
|
+
title: "Element Not Found",
|
|
2341
|
+
description: `Could not find a DOM element associated with the instruction ASEID "${declaration.owner.parent.aseid}". Ensure that the parent instruction is properly rendered and associated with a DOM element before applying attribute mutations.`
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
const existingNode = context.getElementByInstruction(declaration);
|
|
2345
|
+
if (existingNode) {
|
|
2346
|
+
existingNode.textContent = value;
|
|
2347
|
+
} else {
|
|
2348
|
+
const textNode = context.container.createTextNode(value);
|
|
2349
|
+
element.appendChild(textNode);
|
|
2350
|
+
context.setInstructionElement(declaration, textNode);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
logger?.debug("green", `Text ${node?.aseid.toString()} added to Context:`);
|
|
2354
|
+
}
|
|
2355
|
+
removeText(declaration, context) {
|
|
2356
|
+
const element = context.getElementByInstruction(declaration);
|
|
2357
|
+
if (!element) return;
|
|
2358
|
+
if (element.isConnected) {
|
|
2359
|
+
element.parentNode?.removeChild(element);
|
|
2360
|
+
}
|
|
2361
|
+
context.removeInstructionElement(declaration);
|
|
2362
|
+
}
|
|
2363
|
+
addStaticHTML(mutation, context, logger) {
|
|
2364
|
+
const element = context.getElementByInstruction(mutation.parent);
|
|
2365
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
|
|
2366
|
+
throw new AreInterpreterError({
|
|
2367
|
+
title: "Element Not Found",
|
|
2368
|
+
description: `Could not find a DOM element associated with the instruction ASEID "${mutation.parent}". Ensure the host element is rendered before materialising its static island.`
|
|
2369
|
+
});
|
|
2370
|
+
}
|
|
2371
|
+
const el = element;
|
|
2372
|
+
const { html } = mutation.payload;
|
|
2373
|
+
el.textContent = "";
|
|
2374
|
+
const fragment = context.getStaticFragment(el.tagName.toLowerCase(), html);
|
|
2375
|
+
el.appendChild(fragment.cloneNode(true));
|
|
2376
|
+
logger?.debug("green", `Static island materialised onto <${(mutation.owner.parent ?? mutation.owner)?.aseid?.toString?.()}>`);
|
|
2377
|
+
}
|
|
2378
|
+
removeStaticHTML(mutation, context) {
|
|
2379
|
+
const element = context.getElementByInstruction(mutation.parent);
|
|
2380
|
+
if (element && element.nodeType === Node.ELEMENT_NODE && element.isConnected) {
|
|
2381
|
+
element.textContent = "";
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
addComment(declaration, context, store, syntax, directiveContext, logger) {
|
|
2385
|
+
const node = declaration.owner.parent;
|
|
2386
|
+
const { content, evaluate } = declaration.payload;
|
|
2387
|
+
const rawValue = evaluate ? syntax.evaluate(content, store, {
|
|
2388
|
+
...directiveContext?.scope || {}
|
|
2389
|
+
}) : content;
|
|
2390
|
+
const value = toDOMString(rawValue);
|
|
2391
|
+
if (!node) {
|
|
2392
|
+
const commentNode = context.container.createComment(value);
|
|
2393
|
+
context.container.body.appendChild(commentNode);
|
|
2394
|
+
context.setInstructionElement(declaration, commentNode);
|
|
2395
|
+
} else {
|
|
2396
|
+
const element = context.getNodeElement(node);
|
|
2397
|
+
if (!element) {
|
|
2398
|
+
throw new AreInterpreterError({
|
|
2399
|
+
title: "Element Not Found",
|
|
2400
|
+
description: `Could not find a DOM element associated with the instruction ASEID "${declaration.owner.parent.aseid}". Ensure that the parent instruction is properly rendered and associated with a DOM element before applying attribute mutations.`
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
const existingNode = context.getElementByInstruction(declaration);
|
|
2404
|
+
if (existingNode) {
|
|
2405
|
+
existingNode.textContent = value;
|
|
2406
|
+
} else {
|
|
2407
|
+
const commentNode = context.container.createComment(value);
|
|
2408
|
+
element.appendChild(commentNode);
|
|
2409
|
+
context.setInstructionElement(declaration, commentNode);
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
logger?.debug("green", `Comment ${node?.aseid.toString()} added to Context:`);
|
|
2413
|
+
}
|
|
2414
|
+
removeComment(declaration, context) {
|
|
2415
|
+
const element = context.getElementByInstruction(declaration);
|
|
2416
|
+
if (!element) return;
|
|
2417
|
+
if (element.isConnected) {
|
|
2418
|
+
element.parentNode?.removeChild(element);
|
|
2419
|
+
}
|
|
2420
|
+
context.removeInstructionElement(declaration);
|
|
2421
|
+
}
|
|
2422
|
+
addStyle(mutation, context, logger) {
|
|
2423
|
+
try {
|
|
2424
|
+
const { styles } = mutation.payload;
|
|
2425
|
+
const styleId = `are-style-${String(mutation.aseid)}`;
|
|
2426
|
+
const existing = context.getElementByInstruction(mutation);
|
|
2427
|
+
if (existing) {
|
|
2428
|
+
existing.textContent = styles;
|
|
2429
|
+
} else {
|
|
2430
|
+
const styleEl = context.container.createElement("style");
|
|
2431
|
+
styleEl.setAttribute("data-are-id", styleId);
|
|
2432
|
+
styleEl.textContent = styles;
|
|
2433
|
+
(context.container.head ?? context.container.body).appendChild(styleEl);
|
|
2434
|
+
context.setInstructionElement(mutation, styleEl);
|
|
2435
|
+
logger?.debug("green", `Style injected for ${String(mutation.aseid)}`);
|
|
2436
|
+
}
|
|
2437
|
+
} catch (error) {
|
|
2438
|
+
logger?.error(error);
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
removeStyle(mutation, context) {
|
|
2442
|
+
const styleEl = context.getElementByInstruction(mutation);
|
|
2443
|
+
if (styleEl?.parentNode) {
|
|
2444
|
+
styleEl.parentNode.removeChild(styleEl);
|
|
2445
|
+
}
|
|
2446
|
+
context.removeInstructionElement(mutation);
|
|
2447
|
+
}
|
|
2448
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2449
|
+
// ── SVG helpers ───────────────────────────────────────────────────────────────
|
|
2450
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2451
|
+
/**
|
|
2452
|
+
* Returns true when any ancestor of the given node has the tag `svg`,
|
|
2453
|
+
* meaning the node lives inside an SVG subtree and its DOM element must be
|
|
2454
|
+
* created via createElementNS(SVG_NAMESPACE, tag).
|
|
2455
|
+
*/
|
|
2456
|
+
isInSVGContext(node) {
|
|
2457
|
+
let current = node.parent;
|
|
2458
|
+
while (current) {
|
|
2459
|
+
if (current.tag === "svg") return true;
|
|
2460
|
+
if (current.tag === "foreignobject") return false;
|
|
2461
|
+
current = current.parent;
|
|
2462
|
+
}
|
|
2463
|
+
return false;
|
|
2464
|
+
}
|
|
2465
|
+
};
|
|
2466
|
+
__name(AreHTMLInterpreter, "AreHTMLInterpreter");
|
|
2467
|
+
__decorateClass([
|
|
2468
|
+
R2.Define({
|
|
2469
|
+
description: "Create an HTML element based on the provided declaration instruction. Handles both root-level mounting and child element creation based on the structural parent hierarchy."
|
|
2470
|
+
}),
|
|
2471
|
+
AreInterpreter.Apply(AreInstructionDefaultNames.Default),
|
|
2472
|
+
AreInterpreter.Apply(AreHTMLInstructions.AddElement),
|
|
2473
|
+
__decorateParam(0, Yt(te)),
|
|
2474
|
+
__decorateParam(1, Yt(AreHTMLEngineContext)),
|
|
2475
|
+
__decorateParam(2, Yt(A_Logger))
|
|
2476
|
+
], AreHTMLInterpreter.prototype, "addElement", 1);
|
|
2477
|
+
__decorateClass([
|
|
2478
|
+
R2.Define({
|
|
2479
|
+
description: "Remove an HTML element that was created by a CreateElement declaration. Cleans up the DOM and the context index."
|
|
2480
|
+
}),
|
|
2481
|
+
AreInterpreter.Revert(AreInstructionDefaultNames.Default),
|
|
2482
|
+
AreInterpreter.Revert(AreHTMLInstructions.AddElement),
|
|
2483
|
+
__decorateParam(0, Yt(te)),
|
|
2484
|
+
__decorateParam(1, Yt(AreHTMLEngineContext))
|
|
2485
|
+
], AreHTMLInterpreter.prototype, "removeElement", 1);
|
|
2486
|
+
__decorateClass([
|
|
2487
|
+
R2.Define({
|
|
2488
|
+
description: "Add an attribute to an HTML element based on the provided mutation instruction."
|
|
2489
|
+
}),
|
|
2490
|
+
AreInterpreter.Apply(AreHTMLInstructions.AddAttribute),
|
|
2491
|
+
AreInterpreter.Update(AreHTMLInstructions.AddAttribute),
|
|
2492
|
+
__decorateParam(0, Yt(te)),
|
|
2493
|
+
__decorateParam(1, Yt(AreHTMLEngineContext)),
|
|
2494
|
+
__decorateParam(2, Yt(AreStore)),
|
|
2495
|
+
__decorateParam(3, Yt(AreSyntax)),
|
|
2496
|
+
__decorateParam(4, Yt(AreDirectiveContext)),
|
|
2497
|
+
__decorateParam(5, Yt(A_Logger))
|
|
2498
|
+
], AreHTMLInterpreter.prototype, "addAttribute", 1);
|
|
2499
|
+
__decorateClass([
|
|
2500
|
+
R2.Define({
|
|
2501
|
+
description: "Remove an attribute from an HTML element based on the provided mutation instruction."
|
|
2502
|
+
}),
|
|
2503
|
+
AreInterpreter.Revert(AreHTMLInstructions.AddAttribute),
|
|
2504
|
+
__decorateParam(0, Yt(te)),
|
|
2505
|
+
__decorateParam(1, Yt(AreHTMLEngineContext))
|
|
2506
|
+
], AreHTMLInterpreter.prototype, "removeAttribute", 1);
|
|
2507
|
+
__decorateClass([
|
|
2508
|
+
R2.Define({
|
|
2509
|
+
description: "Hide an element by setting inline display:none, caching its previous inline display value for restoration on revert."
|
|
2510
|
+
}),
|
|
2511
|
+
AreInterpreter.Apply(AreHTMLInstructions.HideElement),
|
|
2512
|
+
__decorateParam(0, Yt(te)),
|
|
2513
|
+
__decorateParam(1, Yt(AreHTMLEngineContext))
|
|
2514
|
+
], AreHTMLInterpreter.prototype, "hideElement", 1);
|
|
2515
|
+
__decorateClass([
|
|
2516
|
+
R2.Define({
|
|
2517
|
+
description: "Restore an element hidden by a HideElement instruction back to its previous inline display value."
|
|
2518
|
+
}),
|
|
2519
|
+
AreInterpreter.Revert(AreHTMLInstructions.HideElement),
|
|
2520
|
+
__decorateParam(0, Yt(te)),
|
|
2521
|
+
__decorateParam(1, Yt(AreHTMLEngineContext))
|
|
2522
|
+
], AreHTMLInterpreter.prototype, "showElement", 1);
|
|
2523
|
+
__decorateClass([
|
|
2524
|
+
R2.Define({
|
|
2525
|
+
description: "Add an event listener to an HTML element based on the provided mutation instruction."
|
|
2526
|
+
}),
|
|
2527
|
+
AreInterpreter.Apply(AreHTMLInstructions.AddListener),
|
|
2528
|
+
__decorateParam(0, Yt(te)),
|
|
2529
|
+
__decorateParam(1, Yt(AreHTMLEngineContext)),
|
|
2530
|
+
__decorateParam(2, Yt(AreStore)),
|
|
2531
|
+
__decorateParam(3, Yt(AreSyntax)),
|
|
2532
|
+
__decorateParam(4, Yt(AreDirectiveContext)),
|
|
2533
|
+
__decorateParam(5, Yt(A_Logger))
|
|
2534
|
+
], AreHTMLInterpreter.prototype, "addEventListener", 1);
|
|
2535
|
+
__decorateClass([
|
|
2536
|
+
R2.Define({
|
|
2537
|
+
description: "Remove an event listener from an HTML element based on the provided mutation instruction."
|
|
2538
|
+
}),
|
|
2539
|
+
AreInterpreter.Revert(AreHTMLInstructions.AddListener),
|
|
2540
|
+
__decorateParam(0, Yt(te)),
|
|
2541
|
+
__decorateParam(1, Yt(AreHTMLEngineContext))
|
|
2542
|
+
], AreHTMLInterpreter.prototype, "removeEventListener", 1);
|
|
2543
|
+
__decorateClass([
|
|
2544
|
+
R2.Define({
|
|
2545
|
+
description: "Add text content to an HTML element based on the provided declaration instruction."
|
|
2546
|
+
}),
|
|
2547
|
+
AreInterpreter.Apply(AreHTMLInstructions.AddText),
|
|
2548
|
+
AreInterpreter.Update(AreHTMLInstructions.AddText),
|
|
2549
|
+
__decorateParam(0, Yt(te)),
|
|
2550
|
+
__decorateParam(1, Yt(AreHTMLEngineContext)),
|
|
2551
|
+
__decorateParam(2, Yt(AreStore)),
|
|
2552
|
+
__decorateParam(3, Yt(AreSyntax)),
|
|
2553
|
+
__decorateParam(4, Yt(AreDirectiveContext)),
|
|
2554
|
+
__decorateParam(5, Yt(A_Logger))
|
|
2555
|
+
], AreHTMLInterpreter.prototype, "addText", 1);
|
|
2556
|
+
__decorateClass([
|
|
2557
|
+
R2.Define({
|
|
2558
|
+
description: "Remove text content from an HTML element based on the provided declaration instruction."
|
|
2559
|
+
}),
|
|
2560
|
+
AreInterpreter.Revert(AreHTMLInstructions.AddText),
|
|
2561
|
+
__decorateParam(0, Yt(te)),
|
|
2562
|
+
__decorateParam(1, Yt(AreHTMLEngineContext))
|
|
2563
|
+
], AreHTMLInterpreter.prototype, "removeText", 1);
|
|
2564
|
+
__decorateClass([
|
|
2565
|
+
R2.Define({
|
|
2566
|
+
description: "Inject a static island's inner markup onto its host element in one pass via a cached, browser-parsed <template> clone. Decodes HTML entities natively."
|
|
2567
|
+
}),
|
|
2568
|
+
AreInterpreter.Apply(AreHTMLInstructions.AddStaticHTML),
|
|
2569
|
+
AreInterpreter.Update(AreHTMLInstructions.AddStaticHTML),
|
|
2570
|
+
__decorateParam(0, Yt(te)),
|
|
2571
|
+
__decorateParam(1, Yt(AreHTMLEngineContext)),
|
|
2572
|
+
__decorateParam(2, Yt(A_Logger))
|
|
2573
|
+
], AreHTMLInterpreter.prototype, "addStaticHTML", 1);
|
|
2574
|
+
__decorateClass([
|
|
2575
|
+
R2.Define({
|
|
2576
|
+
description: "Clear a static island's injected markup from its host element on revert."
|
|
2577
|
+
}),
|
|
2578
|
+
AreInterpreter.Revert(AreHTMLInstructions.AddStaticHTML),
|
|
2579
|
+
__decorateParam(0, Yt(te)),
|
|
2580
|
+
__decorateParam(1, Yt(AreHTMLEngineContext))
|
|
2581
|
+
], AreHTMLInterpreter.prototype, "removeStaticHTML", 1);
|
|
2582
|
+
__decorateClass([
|
|
2583
|
+
R2.Define({
|
|
2584
|
+
description: "Add a comment node to the DOM based on the provided declaration instruction."
|
|
2585
|
+
}),
|
|
2586
|
+
AreInterpreter.Apply(AreHTMLInstructions.AddComment),
|
|
2587
|
+
AreInterpreter.Update(AreHTMLInstructions.AddComment),
|
|
2588
|
+
__decorateParam(0, Yt(te)),
|
|
2589
|
+
__decorateParam(1, Yt(AreHTMLEngineContext)),
|
|
2590
|
+
__decorateParam(2, Yt(AreStore)),
|
|
2591
|
+
__decorateParam(3, Yt(AreSyntax)),
|
|
2592
|
+
__decorateParam(4, Yt(AreDirectiveContext)),
|
|
2593
|
+
__decorateParam(5, Yt(A_Logger))
|
|
2594
|
+
], AreHTMLInterpreter.prototype, "addComment", 1);
|
|
2595
|
+
__decorateClass([
|
|
2596
|
+
R2.Define({
|
|
2597
|
+
description: "Remove a comment node from the DOM based on the provided declaration instruction."
|
|
2598
|
+
}),
|
|
2599
|
+
AreInterpreter.Revert(AreHTMLInstructions.AddComment),
|
|
2600
|
+
__decorateParam(0, Yt(te)),
|
|
2601
|
+
__decorateParam(1, Yt(AreHTMLEngineContext))
|
|
2602
|
+
], AreHTMLInterpreter.prototype, "removeComment", 1);
|
|
2603
|
+
__decorateClass([
|
|
2604
|
+
R2.Define({
|
|
2605
|
+
description: "Inject a <style> element into the document <head> carrying the component CSS. Keyed by instruction ASEID so multiple components with styles do not collide. Subsequent Update calls refresh the textContent in-place."
|
|
2606
|
+
}),
|
|
2607
|
+
AreInterpreter.Apply(AreHTMLInstructions.AddStyle),
|
|
2608
|
+
AreInterpreter.Update(AreHTMLInstructions.AddStyle),
|
|
2609
|
+
__decorateParam(0, Yt(te)),
|
|
2610
|
+
__decorateParam(1, Yt(AreHTMLEngineContext)),
|
|
2611
|
+
__decorateParam(2, Yt(A_Logger))
|
|
2612
|
+
], AreHTMLInterpreter.prototype, "addStyle", 1);
|
|
2613
|
+
__decorateClass([
|
|
2614
|
+
R2.Define({
|
|
2615
|
+
description: "Remove the <style> element that was injected by addStyle, cleaning up the document head."
|
|
2616
|
+
}),
|
|
2617
|
+
AreInterpreter.Revert(AreHTMLInstructions.AddStyle),
|
|
2618
|
+
__decorateParam(0, Yt(te)),
|
|
2619
|
+
__decorateParam(1, Yt(AreHTMLEngineContext))
|
|
2620
|
+
], AreHTMLInterpreter.prototype, "removeStyle", 1);
|
|
2621
|
+
AreHTMLInterpreter = __decorateClass([
|
|
2622
|
+
R2.Define({
|
|
2623
|
+
namespace: "a-are-html",
|
|
2624
|
+
description: "DOM interpreter for the HTML rendering pipeline. Extends AreInterpreter to apply and revert each ARE instruction type directly against the browser DOM \u2014 creating and removing elements, setting and removing attributes and event listeners, managing inline styles, and inserting text and comment nodes. Driven by the scene diff computed per render cycle."
|
|
2625
|
+
})
|
|
2626
|
+
], AreHTMLInterpreter);
|
|
2627
|
+
|
|
2628
|
+
// src/engine/AreHTML.tokenizer.ts
|
|
2629
|
+
var AreHTMLTokenizer = class extends AreTokenizer {
|
|
2630
|
+
constructor() {
|
|
2631
|
+
super(...arguments);
|
|
2632
|
+
this.ATTR_PATTERN = /([$:@]?[\w.-]+(?::[\w.-]+)?)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>/"'=]+)))?/g;
|
|
2633
|
+
}
|
|
2634
|
+
tokenize(node, context, logger) {
|
|
2635
|
+
const isStaticIsland = node instanceof AreComponentNode && !!node.content && isStaticMarkup(node.content);
|
|
2636
|
+
if (isStaticIsland) {
|
|
2637
|
+
node.markStatic(node.content);
|
|
2638
|
+
} else {
|
|
2639
|
+
super.tokenize(node, context, logger);
|
|
2640
|
+
}
|
|
2641
|
+
context.startPerformance("attributeExtraction");
|
|
2642
|
+
const attributes = this.extractAttributes(node.markup);
|
|
2643
|
+
for (const attr of attributes) {
|
|
2644
|
+
node.scope.register(attr);
|
|
2645
|
+
}
|
|
2646
|
+
context.endPerformance("attributeExtraction");
|
|
2647
|
+
}
|
|
2648
|
+
extractAttributes(markup) {
|
|
2649
|
+
const withoutTag = markup.replace(/^<[a-zA-Z][a-zA-Z0-9-]*\s*/, "");
|
|
2650
|
+
let inSingle = false;
|
|
2651
|
+
let inDouble = false;
|
|
2652
|
+
let endIdx = withoutTag.length;
|
|
2653
|
+
for (let i = 0; i < withoutTag.length; i++) {
|
|
2654
|
+
const ch = withoutTag[i];
|
|
2655
|
+
if (ch === '"' && !inSingle) inDouble = !inDouble;
|
|
2656
|
+
else if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
2657
|
+
else if (ch === ">" && !inSingle && !inDouble) {
|
|
2658
|
+
endIdx = i;
|
|
2659
|
+
break;
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
const attrString = withoutTag.slice(0, endIdx).replace(/\s*\/?\s*$/, "").trim();
|
|
2663
|
+
const results = [];
|
|
2664
|
+
for (const match of attrString.matchAll(this.ATTR_PATTERN)) {
|
|
2665
|
+
const raw = match[0];
|
|
2666
|
+
const full = match[1];
|
|
2667
|
+
if (!full) continue;
|
|
2668
|
+
const value = match[2] ?? match[3] ?? match[4] ?? "true";
|
|
2669
|
+
const prefix = full[0];
|
|
2670
|
+
const isSpecial = prefix === ":" || prefix === "@" || prefix === "$";
|
|
2671
|
+
const name = isSpecial ? full.slice(1) : full;
|
|
2672
|
+
const meta = { name, content: value, raw, prefix: isSpecial ? prefix : "" };
|
|
2673
|
+
if (prefix === ":") results.push(new AreBindingAttribute(meta));
|
|
2674
|
+
else if (prefix === "@") results.push(new AreEventAttribute(meta));
|
|
2675
|
+
else if (prefix === "$") results.push(new AreDirectiveAttribute(meta));
|
|
2676
|
+
else results.push(new AreStaticAttribute(meta));
|
|
2677
|
+
}
|
|
2678
|
+
return results;
|
|
2679
|
+
}
|
|
2680
|
+
};
|
|
2681
|
+
__name(AreHTMLTokenizer, "AreHTMLTokenizer");
|
|
2682
|
+
__decorateClass([
|
|
2683
|
+
N.Extend({
|
|
2684
|
+
name: AreNodeFeatures.onTokenize,
|
|
2685
|
+
scope: [AreComponentNode, AreRootNode]
|
|
2686
|
+
}),
|
|
2687
|
+
__decorateParam(0, Yt(te)),
|
|
2688
|
+
__decorateParam(1, Yt(AreContext)),
|
|
2689
|
+
__decorateParam(2, Yt(A_Logger))
|
|
2690
|
+
], AreHTMLTokenizer.prototype, "tokenize", 1);
|
|
2691
|
+
AreHTMLTokenizer = __decorateClass([
|
|
2692
|
+
R2.Define({
|
|
2693
|
+
namespace: "a-are-html",
|
|
2694
|
+
description: "HTML-specific tokenizer extending AreTokenizer. Parses raw HTML template strings into AreHTMLNode trees by scanning element tags and resolving directive ($), event (@), binding (:), and static attributes to their typed attribute classes, constructing AreComponentNode and AreRootNode instances where required."
|
|
2695
|
+
})
|
|
2696
|
+
], AreHTMLTokenizer);
|
|
2697
|
+
|
|
2698
|
+
// src/engine/AreHTML.lifecycle.ts
|
|
2699
|
+
var AreHTMLLifecycle = class extends AreLifecycle {
|
|
2700
|
+
initComponent(node, scope, context, signalsContext, logger, ...args) {
|
|
2701
|
+
if (node.component)
|
|
2702
|
+
signalsContext?.subscribe(node);
|
|
2703
|
+
super.init(node, scope, context, logger, ...args);
|
|
2704
|
+
}
|
|
2705
|
+
initRoot(node, scope, context, signalsContext, logger, ...args) {
|
|
2706
|
+
signalsContext?.subscribe(node);
|
|
2707
|
+
super.init(node, scope, context, logger, ...args);
|
|
2708
|
+
}
|
|
2709
|
+
initText(node, scope, context, logger, ...args) {
|
|
2710
|
+
const scene = new AreScene(node.aseid);
|
|
2711
|
+
scope.register(scene);
|
|
2712
|
+
}
|
|
2713
|
+
initInterpolation(node, scope, context, logger, ...args) {
|
|
2714
|
+
const scene = new AreScene(node.aseid);
|
|
2715
|
+
scope.register(scene);
|
|
2716
|
+
}
|
|
2717
|
+
mount(node, scene, logger, ...args) {
|
|
2718
|
+
logger?.debug(`[Mount] Component Trigger for <${node.aseid.entity}> with aseid :{${node.aseid.toString()}}`);
|
|
2719
|
+
if (scene.isInactive) return;
|
|
2720
|
+
const context = node.scope.resolve(AreHTMLEngineContext);
|
|
2721
|
+
context?.beginBatch();
|
|
2722
|
+
const afterMountQueue = [];
|
|
2723
|
+
try {
|
|
2724
|
+
node.interpret();
|
|
2725
|
+
const stack = [];
|
|
2726
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
2727
|
+
stack.push({ node: node.children[i], entered: false });
|
|
2728
|
+
}
|
|
2729
|
+
while (stack.length > 0) {
|
|
2730
|
+
const frame = stack[stack.length - 1];
|
|
2731
|
+
const current = frame.node;
|
|
2732
|
+
if (frame.entered) {
|
|
2733
|
+
stack.pop();
|
|
2734
|
+
afterMountQueue.push(current);
|
|
2735
|
+
continue;
|
|
2736
|
+
}
|
|
2737
|
+
frame.entered = true;
|
|
2738
|
+
current.call(AreNodeFeatures.onBeforeMount, current.scope);
|
|
2739
|
+
if (!current.scene.isInactive) {
|
|
2740
|
+
current.interpret();
|
|
2741
|
+
for (let i = current.children.length - 1; i >= 0; i--) {
|
|
2742
|
+
stack.push({ node: current.children[i], entered: false });
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
} finally {
|
|
2747
|
+
context?.endBatch();
|
|
2748
|
+
}
|
|
2749
|
+
for (let i = 0; i < afterMountQueue.length; i++) {
|
|
2750
|
+
const mounted = afterMountQueue[i];
|
|
2751
|
+
mounted.call(AreNodeFeatures.onAfterMount, mounted.scope);
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
updateDirectiveAttribute(directive, scope, feature, logger, ...args) {
|
|
2755
|
+
if (directive.component) {
|
|
2756
|
+
feature.chain(directive.component, AreDirectiveFeatures.Update, directive.owner.scope);
|
|
2757
|
+
} else {
|
|
2758
|
+
logger?.warning(`Directive handler component not found for directive: ${directive.name}. Make sure to create a component named "AreDirective${P.toPascalCase(directive.name)}" to handle this directive.`);
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
};
|
|
2762
|
+
__name(AreHTMLLifecycle, "AreHTMLLifecycle");
|
|
2763
|
+
__decorateClass([
|
|
2764
|
+
AreLifecycle.Init(AreComponentNode),
|
|
2765
|
+
__decorateParam(0, Yt(te)),
|
|
2766
|
+
__decorateParam(1, Yt(R)),
|
|
2767
|
+
__decorateParam(2, Yt(AreHTMLEngineContext)),
|
|
2768
|
+
__decorateParam(3, Yt(AreSignalsContext)),
|
|
2769
|
+
__decorateParam(4, Yt(A_Logger))
|
|
2770
|
+
], AreHTMLLifecycle.prototype, "initComponent", 1);
|
|
2771
|
+
__decorateClass([
|
|
2772
|
+
AreLifecycle.Init(AreRootNode),
|
|
2773
|
+
__decorateParam(0, Yt(te)),
|
|
2774
|
+
__decorateParam(1, Yt(R)),
|
|
2775
|
+
__decorateParam(2, Yt(AreHTMLEngineContext)),
|
|
2776
|
+
__decorateParam(3, Yt(AreSignalsContext)),
|
|
2777
|
+
__decorateParam(4, Yt(A_Logger))
|
|
2778
|
+
], AreHTMLLifecycle.prototype, "initRoot", 1);
|
|
2779
|
+
__decorateClass([
|
|
2780
|
+
AreLifecycle.Init(AreText),
|
|
2781
|
+
__decorateParam(0, Yt(te)),
|
|
2782
|
+
__decorateParam(1, Yt(R)),
|
|
2783
|
+
__decorateParam(2, Yt(AreHTMLEngineContext)),
|
|
2784
|
+
__decorateParam(3, Yt(A_Logger))
|
|
2785
|
+
], AreHTMLLifecycle.prototype, "initText", 1);
|
|
2786
|
+
__decorateClass([
|
|
2787
|
+
AreLifecycle.Init(AreInterpolation),
|
|
2788
|
+
__decorateParam(0, Yt(te)),
|
|
2789
|
+
__decorateParam(1, Yt(R)),
|
|
2790
|
+
__decorateParam(2, Yt(AreHTMLEngineContext)),
|
|
2791
|
+
__decorateParam(3, Yt(A_Logger))
|
|
2792
|
+
], AreHTMLLifecycle.prototype, "initInterpolation", 1);
|
|
2793
|
+
__decorateClass([
|
|
2794
|
+
N.Extend({
|
|
2795
|
+
name: AreNodeFeatures.onMount,
|
|
2796
|
+
scope: [AreHTMLNode]
|
|
2797
|
+
}),
|
|
2798
|
+
__decorateParam(0, Yt(te)),
|
|
2799
|
+
__decorateParam(1, Yt(AreScene)),
|
|
2800
|
+
__decorateParam(2, Yt(A_Logger))
|
|
2801
|
+
], AreHTMLLifecycle.prototype, "mount", 1);
|
|
2802
|
+
__decorateClass([
|
|
2803
|
+
N.Extend({
|
|
2804
|
+
name: AreAttributeFeatures.Update,
|
|
2805
|
+
scope: [AreDirectiveAttribute]
|
|
2806
|
+
}),
|
|
2807
|
+
__decorateParam(0, Yt(te)),
|
|
2808
|
+
__decorateParam(1, Yt(R)),
|
|
2809
|
+
__decorateParam(2, Yt(N)),
|
|
2810
|
+
__decorateParam(3, Yt(A_Logger))
|
|
2811
|
+
], AreHTMLLifecycle.prototype, "updateDirectiveAttribute", 1);
|
|
2812
|
+
AreHTMLLifecycle = __decorateClass([
|
|
2813
|
+
R2.Define({
|
|
2814
|
+
namespace: "a-are-html",
|
|
2815
|
+
description: "HTML-specific lifecycle handler extending AreLifecycle. Wires DOM-aware init hooks for component nodes, root nodes, interpolations, text nodes, and directive attributes to the ARE rendering pipeline, connecting each entity to its HTML engine context and priming the scene for subsequent compilation and interpretation."
|
|
2816
|
+
})
|
|
2817
|
+
], AreHTMLLifecycle);
|
|
2818
|
+
|
|
2819
|
+
// src/engine/AreHTML.transformer.ts
|
|
2820
|
+
var AreHTMLTransformer = class extends AreTransformer {
|
|
2821
|
+
transformDirectiveAttribute(directive, store, feature, logger, ...args) {
|
|
2822
|
+
store.watch(directive);
|
|
2823
|
+
if (directive.component) {
|
|
2824
|
+
feature.chain(directive.component, AreDirectiveFeatures.Transform, directive.owner.scope);
|
|
2825
|
+
} else {
|
|
2826
|
+
logger?.warning(`Directive handler component not found for directive: ${directive.name}. Make sure to create a component named "AreDirective${P.toPascalCase(directive.name)}" to handle this directive.`);
|
|
2827
|
+
}
|
|
2828
|
+
store.unwatch(directive);
|
|
2829
|
+
}
|
|
2830
|
+
};
|
|
2831
|
+
__name(AreHTMLTransformer, "AreHTMLTransformer");
|
|
2832
|
+
__decorateClass([
|
|
2833
|
+
N.Extend({
|
|
2834
|
+
name: AreAttributeFeatures.Transform,
|
|
2835
|
+
scope: [AreDirectiveAttribute]
|
|
2836
|
+
}),
|
|
2837
|
+
__decorateParam(0, Yt(te)),
|
|
2838
|
+
__decorateParam(1, Yt(AreStore)),
|
|
2839
|
+
__decorateParam(2, Yt(N)),
|
|
2840
|
+
__decorateParam(3, Yt(A_Logger))
|
|
2841
|
+
], AreHTMLTransformer.prototype, "transformDirectiveAttribute", 1);
|
|
2842
|
+
AreHTMLTransformer = __decorateClass([
|
|
2843
|
+
R2.Define({
|
|
2844
|
+
namespace: "a-are-html",
|
|
2845
|
+
description: "HTML-specific transformer extending AreTransformer. Handles directive-attribute structural rewrites before compilation \u2014 sorting directives by declared priority and expanding compound directive expressions \u2014 so the compiler receives a clean, ordered AreHTMLNode tree ready for instruction emission."
|
|
2846
|
+
})
|
|
2847
|
+
], AreHTMLTransformer);
|
|
2848
|
+
|
|
2849
|
+
// src/lib/AreRoot/AreRootCache.context.ts
|
|
2850
|
+
var AreRootCache = class extends H {
|
|
2851
|
+
constructor(limit = 10) {
|
|
2852
|
+
super({ name: "AreRootCache" });
|
|
2853
|
+
/**
|
|
2854
|
+
* rootId -> (component tag -> cache entry). The inner Map preserves
|
|
2855
|
+
* insertion order which is used as the LRU recency order: the first key is
|
|
2856
|
+
* the least-recently-used entry, the last key the most-recently-used.
|
|
2857
|
+
*/
|
|
2858
|
+
this._cache = /* @__PURE__ */ new Map();
|
|
2859
|
+
this._limit = Math.max(0, Math.floor(limit));
|
|
2860
|
+
}
|
|
2861
|
+
/**
|
|
2862
|
+
* Maximum number of cached subtrees kept per root.
|
|
2863
|
+
*/
|
|
2864
|
+
get limit() {
|
|
2865
|
+
return this._limit;
|
|
2866
|
+
}
|
|
2867
|
+
bucket(rootId) {
|
|
2868
|
+
let bucket = this._cache.get(rootId);
|
|
2869
|
+
if (!bucket) {
|
|
2870
|
+
bucket = /* @__PURE__ */ new Map();
|
|
2871
|
+
this._cache.set(rootId, bucket);
|
|
2872
|
+
}
|
|
2873
|
+
return bucket;
|
|
2874
|
+
}
|
|
2875
|
+
/**
|
|
2876
|
+
* Whether a subtree for the given component tag is currently cached.
|
|
2877
|
+
*/
|
|
2878
|
+
has(rootId, tag) {
|
|
2879
|
+
return this.bucket(rootId).has(tag);
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Retrieve AND remove a cached subtree so it can become live again. Returns
|
|
2883
|
+
* `undefined` on a cache miss.
|
|
2884
|
+
*/
|
|
2885
|
+
take(rootId, tag) {
|
|
2886
|
+
const bucket = this.bucket(rootId);
|
|
2887
|
+
const entry = bucket.get(tag);
|
|
2888
|
+
if (entry) {
|
|
2889
|
+
bucket.delete(tag);
|
|
2890
|
+
}
|
|
2891
|
+
return entry;
|
|
2892
|
+
}
|
|
2893
|
+
/**
|
|
2894
|
+
* Stash a detached subtree under the given component tag. Returns any entries
|
|
2895
|
+
* that were evicted to honour the LRU limit (or replaced for the same tag) so
|
|
2896
|
+
* the caller can `destroy()` them.
|
|
2897
|
+
*/
|
|
2898
|
+
put(rootId, tag, entry) {
|
|
2899
|
+
const bucket = this.bucket(rootId);
|
|
2900
|
+
const evicted = [];
|
|
2901
|
+
const existing = bucket.get(tag);
|
|
2902
|
+
if (existing) {
|
|
2903
|
+
bucket.delete(tag);
|
|
2904
|
+
if (existing.node !== entry.node) {
|
|
2905
|
+
evicted.push(existing);
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
bucket.set(tag, entry);
|
|
2909
|
+
while (bucket.size > this._limit) {
|
|
2910
|
+
const oldestKey = bucket.keys().next().value;
|
|
2911
|
+
if (oldestKey === void 0) {
|
|
2912
|
+
break;
|
|
2913
|
+
}
|
|
2914
|
+
const oldest = bucket.get(oldestKey);
|
|
2915
|
+
bucket.delete(oldestKey);
|
|
2916
|
+
evicted.push(oldest);
|
|
2917
|
+
}
|
|
2918
|
+
return evicted;
|
|
2919
|
+
}
|
|
2920
|
+
/**
|
|
2921
|
+
* Remove and return every cached entry for a root (e.g. on teardown) so the
|
|
2922
|
+
* caller can destroy them.
|
|
2923
|
+
*/
|
|
2924
|
+
clear(rootId) {
|
|
2925
|
+
const bucket = this._cache.get(rootId);
|
|
2926
|
+
if (!bucket) {
|
|
2927
|
+
return [];
|
|
2928
|
+
}
|
|
2929
|
+
const entries = [...bucket.values()];
|
|
2930
|
+
bucket.clear();
|
|
2931
|
+
this._cache.delete(rootId);
|
|
2932
|
+
return entries;
|
|
2933
|
+
}
|
|
2934
|
+
};
|
|
2935
|
+
__name(AreRootCache, "AreRootCache");
|
|
2936
|
+
AreRootCache = __decorateClass([
|
|
2937
|
+
R2.Define({
|
|
2938
|
+
namespace: "a-are-html",
|
|
2939
|
+
description: "AreRootCache is a fragment that keeps a small per-root LRU of previously rendered are-root subtrees. When an are-root swaps the component it displays, the outgoing subtree is stashed here (unmounted + detached, but not destroyed) so that routing back to it can re-inject the preserved scene instantly instead of rebuilding from scratch."
|
|
2940
|
+
})
|
|
2941
|
+
], AreRootCache);
|
|
2942
|
+
|
|
2943
|
+
// src/engine/AreHTML.engine.ts
|
|
2944
|
+
var AreHTMLEngine = class extends AreEngine {
|
|
2945
|
+
get DefaultSyntax() {
|
|
2946
|
+
return new AreSyntax({
|
|
2947
|
+
trimWhitespace: true,
|
|
2948
|
+
strictMode: true,
|
|
2949
|
+
rules: [
|
|
2950
|
+
// HTML comments
|
|
2951
|
+
{
|
|
2952
|
+
opening: "<!--",
|
|
2953
|
+
closing: "-->",
|
|
2954
|
+
component: AreComment,
|
|
2955
|
+
priority: 10,
|
|
2956
|
+
nested: false,
|
|
2957
|
+
extract: /* @__PURE__ */ __name((raw) => ({ content: raw.slice(4, -3).trim() }), "extract")
|
|
2958
|
+
},
|
|
2959
|
+
// interpolations
|
|
2960
|
+
{
|
|
2961
|
+
opening: "{{",
|
|
2962
|
+
closing: "}}",
|
|
2963
|
+
component: AreInterpolation,
|
|
2964
|
+
priority: 9,
|
|
2965
|
+
nested: false,
|
|
2966
|
+
extract: /* @__PURE__ */ __name((_2, match) => ({ key: match.content }), "extract")
|
|
2967
|
+
},
|
|
2968
|
+
// are-root — matched before generic elements, produces AreRootNode
|
|
2969
|
+
{
|
|
2970
|
+
matcher: this.rootElementMatcher.bind(this),
|
|
2971
|
+
component: AreRootNode,
|
|
2972
|
+
priority: 5
|
|
2973
|
+
},
|
|
2974
|
+
// generic HTML elements
|
|
2975
|
+
{
|
|
2976
|
+
matcher: this.htmlElementMatcher.bind(this),
|
|
2977
|
+
component: AreComponentNode,
|
|
2978
|
+
priority: 4
|
|
2979
|
+
},
|
|
2980
|
+
// plain text fallback
|
|
2981
|
+
{
|
|
2982
|
+
component: AreText,
|
|
2983
|
+
priority: 0
|
|
2984
|
+
}
|
|
2985
|
+
]
|
|
2986
|
+
});
|
|
2987
|
+
}
|
|
2988
|
+
async init(scope, signalContext, rootCache) {
|
|
2989
|
+
this.package(scope, {
|
|
2990
|
+
context: new AreHTMLEngineContext({}),
|
|
2991
|
+
syntax: this.DefaultSyntax,
|
|
2992
|
+
compiler: AreHTMLCompiler,
|
|
2993
|
+
interpreter: AreHTMLInterpreter,
|
|
2994
|
+
tokenizer: AreHTMLTokenizer,
|
|
2995
|
+
lifecycle: AreHTMLLifecycle,
|
|
2996
|
+
transformer: AreHTMLTransformer
|
|
2997
|
+
});
|
|
2998
|
+
if (!signalContext) {
|
|
2999
|
+
signalContext = new AreSignalsContext();
|
|
3000
|
+
scope.register(signalContext);
|
|
3001
|
+
}
|
|
3002
|
+
if (!rootCache) {
|
|
3003
|
+
rootCache = new AreRootCache();
|
|
3004
|
+
scope.register(rootCache);
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
rootElementMatcher(source, from, to, build) {
|
|
3008
|
+
const rootTag = "are-root";
|
|
3009
|
+
const tagStart = source.indexOf("<", from);
|
|
3010
|
+
if (tagStart === -1 || tagStart >= to) return null;
|
|
3011
|
+
const tagNameMatch = source.slice(tagStart).match(/^<([a-zA-Z][a-zA-Z0-9-]*)/);
|
|
3012
|
+
if (!tagNameMatch || tagNameMatch[1].toLowerCase() !== rootTag) return null;
|
|
3013
|
+
return this.htmlElementMatcher(source, from, to, build);
|
|
3014
|
+
}
|
|
3015
|
+
htmlElementMatcher(source, from, to, build) {
|
|
3016
|
+
let index = from;
|
|
3017
|
+
while (index < to) {
|
|
3018
|
+
const tagStart = source.indexOf("<", index);
|
|
3019
|
+
if (tagStart === -1 || tagStart >= to) return null;
|
|
3020
|
+
if (source.startsWith("<!--", tagStart)) {
|
|
3021
|
+
index = tagStart + 1;
|
|
3022
|
+
continue;
|
|
3023
|
+
}
|
|
3024
|
+
if (source[tagStart + 1] === "/") {
|
|
3025
|
+
index = tagStart + 1;
|
|
3026
|
+
continue;
|
|
3027
|
+
}
|
|
3028
|
+
if (source[tagStart + 1] === "!" || source[tagStart + 1] === "?") {
|
|
3029
|
+
index = tagStart + 1;
|
|
3030
|
+
continue;
|
|
3031
|
+
}
|
|
3032
|
+
const tagNameMatch = source.slice(tagStart).match(/^<([a-zA-Z][a-zA-Z0-9-]*)/);
|
|
3033
|
+
if (!tagNameMatch) {
|
|
3034
|
+
index = tagStart + 1;
|
|
3035
|
+
continue;
|
|
3036
|
+
}
|
|
3037
|
+
const tagName = tagNameMatch[1];
|
|
3038
|
+
const openingTagEnd = AreHTMLEngine.findTagClose(source, tagStart);
|
|
3039
|
+
if (openingTagEnd === -1) return null;
|
|
3040
|
+
const openingTagStr = source.slice(tagStart, openingTagEnd + 1);
|
|
3041
|
+
const idMatch = openingTagStr.match(/\bid=["']([^"']*)["']/);
|
|
3042
|
+
const id = idMatch ? idMatch[1] : void 0;
|
|
3043
|
+
if (source[openingTagEnd - 1] === "/") {
|
|
3044
|
+
const raw = source.slice(tagStart, openingTagEnd + 1);
|
|
3045
|
+
const content2 = source.slice(tagStart + tagNameMatch[0].length, openingTagEnd - 1);
|
|
3046
|
+
const match2 = build(raw, content2, tagStart, "/>");
|
|
3047
|
+
match2.payload = { entity: tagName, selfClose: true, id };
|
|
3048
|
+
return match2;
|
|
3049
|
+
}
|
|
3050
|
+
if (isVoidElement(tagName)) {
|
|
3051
|
+
const raw = source.slice(tagStart, openingTagEnd + 1);
|
|
3052
|
+
const content2 = source.slice(tagStart + tagNameMatch[0].length, openingTagEnd);
|
|
3053
|
+
const match2 = build(raw, content2, tagStart, ">");
|
|
3054
|
+
match2.payload = { entity: tagName, selfClose: true, id };
|
|
3055
|
+
return match2;
|
|
3056
|
+
}
|
|
3057
|
+
const closingTag = `</${tagName}>`;
|
|
3058
|
+
let level = 0;
|
|
3059
|
+
let searchIndex = openingTagEnd + 1;
|
|
3060
|
+
let closingStart = -1;
|
|
3061
|
+
while (searchIndex < to) {
|
|
3062
|
+
const nextOpen = source.indexOf(`<${tagName}`, searchIndex);
|
|
3063
|
+
const nextClose = source.indexOf(closingTag, searchIndex);
|
|
3064
|
+
if (nextClose === -1) break;
|
|
3065
|
+
if (nextOpen !== -1 && nextOpen < nextClose) {
|
|
3066
|
+
const charAfter = source[nextOpen + tagName.length + 1];
|
|
3067
|
+
if (charAfter === " " || charAfter === ">" || charAfter === "/") {
|
|
3068
|
+
const innerEnd = AreHTMLEngine.findTagClose(source, nextOpen);
|
|
3069
|
+
const isSelfClose = innerEnd !== -1 && source[innerEnd - 1] === "/";
|
|
3070
|
+
if (!isSelfClose) {
|
|
3071
|
+
level++;
|
|
3072
|
+
}
|
|
3073
|
+
searchIndex = innerEnd === -1 ? nextOpen + tagName.length + 1 : innerEnd + 1;
|
|
3074
|
+
continue;
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
if (level === 0) {
|
|
3078
|
+
closingStart = nextClose;
|
|
3079
|
+
break;
|
|
3080
|
+
}
|
|
3081
|
+
level--;
|
|
3082
|
+
searchIndex = nextClose + closingTag.length;
|
|
3083
|
+
}
|
|
3084
|
+
if (closingStart === -1) return null;
|
|
3085
|
+
const fullTag = source.slice(tagStart, closingStart + closingTag.length);
|
|
3086
|
+
const content = source.slice(openingTagEnd + 1, closingStart);
|
|
3087
|
+
const match = build(fullTag, content, tagStart, closingTag);
|
|
3088
|
+
match.payload = { entity: tagName, selfClose: false, id };
|
|
3089
|
+
return match;
|
|
3090
|
+
}
|
|
3091
|
+
return null;
|
|
3092
|
+
}
|
|
3093
|
+
/**
|
|
3094
|
+
* Find the index of the closing `>` of an opening tag, skipping over
|
|
3095
|
+
* `>` characters that appear inside quoted attribute values.
|
|
3096
|
+
*/
|
|
3097
|
+
static findTagClose(source, from) {
|
|
3098
|
+
let inSingle = false;
|
|
3099
|
+
let inDouble = false;
|
|
3100
|
+
for (let i = from; i < source.length; i++) {
|
|
3101
|
+
const ch = source[i];
|
|
3102
|
+
if (ch === '"' && !inSingle) inDouble = !inDouble;
|
|
3103
|
+
else if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
3104
|
+
else if (ch === ">" && !inSingle && !inDouble) return i;
|
|
3105
|
+
}
|
|
3106
|
+
return -1;
|
|
3107
|
+
}
|
|
3108
|
+
};
|
|
3109
|
+
__name(AreHTMLEngine, "AreHTMLEngine");
|
|
3110
|
+
__decorateClass([
|
|
3111
|
+
N.Extend({
|
|
3112
|
+
name: A_ServiceFeatures.onBeforeLoad,
|
|
3113
|
+
before: /.*/
|
|
3114
|
+
}),
|
|
3115
|
+
__decorateParam(0, Yt(R)),
|
|
3116
|
+
__decorateParam(1, Yt(AreSignalsContext)),
|
|
3117
|
+
__decorateParam(2, Yt(AreRootCache))
|
|
3118
|
+
], AreHTMLEngine.prototype, "init", 1);
|
|
3119
|
+
AreHTMLEngine = __decorateClass([
|
|
3120
|
+
R2.Define({
|
|
3121
|
+
namespace: "a-are-html",
|
|
3122
|
+
description: "Concrete HTML rendering engine that assembles the full ARE pipeline for web environments. Bootstraps and wires AreHTMLTokenizer, AreHTMLTransformer, AreHTMLCompiler, AreHTMLInterpreter, and AreHTMLLifecycle; mounts root nodes from inline or fetched templates; and drives reactive re-renders via the AreSignals bus."
|
|
3123
|
+
})
|
|
3124
|
+
], AreHTMLEngine);
|
|
3125
|
+
|
|
3126
|
+
// src/lib/AreRoot/AreRoot.component.ts
|
|
3127
|
+
var AreRoot = class extends Are {
|
|
3128
|
+
async template(root, logger, signalsContext, signalState) {
|
|
3129
|
+
const rootId = root.id;
|
|
3130
|
+
if (signalsContext && !signalsContext.hasRoot(rootId)) {
|
|
3131
|
+
if (!root.content?.trim()) {
|
|
3132
|
+
const defaultMatch = root.markup?.match(/\bdefault=["']([^"']*)["']/);
|
|
3133
|
+
const defaultComponent = defaultMatch?.[1];
|
|
3134
|
+
if (defaultComponent) {
|
|
3135
|
+
root.setContent(`<${defaultComponent}></${defaultComponent}>`);
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
return;
|
|
3139
|
+
}
|
|
3140
|
+
const initialVector = this.buildInitialVector(signalState);
|
|
3141
|
+
const renderTarget = this.matchComponent(rootId, initialVector, signalsContext);
|
|
3142
|
+
let componentName = renderTarget?.name ? P.toKebabCase(renderTarget.name) : void 0;
|
|
3143
|
+
if (!componentName) {
|
|
3144
|
+
if (root.content?.trim()) {
|
|
3145
|
+
return;
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
if (!componentName) {
|
|
3149
|
+
const defaultComp = signalsContext?.getDefault(rootId);
|
|
3150
|
+
if (defaultComp?.name) {
|
|
3151
|
+
componentName = P.toKebabCase(defaultComp.name);
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
if (!componentName) {
|
|
3155
|
+
const defaultMatch = root.markup?.match(/\bdefault=["']([^"']*)["']/);
|
|
3156
|
+
componentName = defaultMatch?.[1];
|
|
3157
|
+
}
|
|
3158
|
+
if (!componentName) {
|
|
3159
|
+
logger.warning('AreRoot: No component found for initial render. Provide body content, a route condition, or a "default" attribute.');
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
root.setContent(`<${componentName}></${componentName}>`);
|
|
3163
|
+
}
|
|
3164
|
+
async onSignal(root, vector, logger, signalsContext, cache) {
|
|
3165
|
+
const rootId = root.id;
|
|
3166
|
+
if (signalsContext && !signalsContext.hasRoot(rootId)) {
|
|
3167
|
+
return;
|
|
3168
|
+
}
|
|
3169
|
+
const renderTarget = this.matchComponent(rootId, vector, signalsContext);
|
|
3170
|
+
const def = signalsContext?.getDefault(rootId);
|
|
3171
|
+
const componentName = renderTarget?.name ? P.toKebabCase(renderTarget.name) : def?.name ? P.toKebabCase(def.name) : void 0;
|
|
3172
|
+
if (!componentName) {
|
|
3173
|
+
for (const child of [...root.children]) {
|
|
3174
|
+
this.stashChild(root, child, signalsContext, cache);
|
|
3175
|
+
}
|
|
3176
|
+
root.setContent("");
|
|
3177
|
+
return;
|
|
3178
|
+
}
|
|
3179
|
+
const currentChild = root.children[0];
|
|
3180
|
+
if (currentChild?.type === componentName) {
|
|
3181
|
+
return;
|
|
3182
|
+
}
|
|
3183
|
+
for (const child of [...root.children]) {
|
|
3184
|
+
this.stashChild(root, child, signalsContext, cache);
|
|
3185
|
+
}
|
|
3186
|
+
root.setContent(`<${componentName}></${componentName}>`);
|
|
3187
|
+
const cached = cache?.take(root.id, componentName);
|
|
3188
|
+
if (cached) {
|
|
3189
|
+
this.restoreChild(root, cached, signalsContext);
|
|
3190
|
+
return;
|
|
3191
|
+
}
|
|
3192
|
+
root.tokenize();
|
|
3193
|
+
for (let i = 0; i < root.children.length; i++) {
|
|
3194
|
+
const child = root.children[i];
|
|
3195
|
+
child.init();
|
|
3196
|
+
const res = child.load();
|
|
3197
|
+
if (res instanceof Promise) {
|
|
3198
|
+
await res;
|
|
3199
|
+
}
|
|
3200
|
+
child.transform();
|
|
3201
|
+
child.compile();
|
|
3202
|
+
await child.mount();
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
/**
|
|
3206
|
+
* Resolves the component a vector should render for the given root, mirroring
|
|
3207
|
+
* the priority used everywhere in the routing system:
|
|
3208
|
+
* 1. Root-specific conditions registered on AreSignalsContext.
|
|
3209
|
+
* 2. The global AreSignalsMeta map, restricted to this outlet's pool.
|
|
3210
|
+
*
|
|
3211
|
+
* Passing the pool *into* the meta lookup is critical: without it, the first
|
|
3212
|
+
* globally matching component wins and may belong to a different outlet
|
|
3213
|
+
* (e.g. AisRequirementsPanel for the meta-outlet matching
|
|
3214
|
+
* AisEditorCursorScope) — the pool check would then reject it and the outlet
|
|
3215
|
+
* would fall back to its default, hiding a valid in-pool match (e.g.
|
|
3216
|
+
* AisDiagramTab matching AisSetPrimaryDisplay).
|
|
3217
|
+
*
|
|
3218
|
+
* Returns `undefined` when nothing matches — callers decide whether to use a
|
|
3219
|
+
* configured default, body content, or clear the outlet.
|
|
3220
|
+
*/
|
|
3221
|
+
matchComponent(rootId, vector, signalsContext) {
|
|
3222
|
+
if (!vector) return void 0;
|
|
3223
|
+
let renderTarget = signalsContext?.findComponentByVector(rootId, vector);
|
|
3224
|
+
if (!renderTarget) {
|
|
3225
|
+
const signalsMeta = _.meta(AreSignals);
|
|
3226
|
+
const pool = signalsContext?.getComponentById(rootId);
|
|
3227
|
+
const metaTarget = signalsMeta?.findComponentByVector(
|
|
3228
|
+
vector,
|
|
3229
|
+
pool?.length ? pool : void 0,
|
|
3230
|
+
rootId
|
|
3231
|
+
);
|
|
3232
|
+
if (metaTarget && (!pool?.length || pool.includes(metaTarget))) {
|
|
3233
|
+
renderTarget = metaTarget;
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
return renderTarget;
|
|
3237
|
+
}
|
|
3238
|
+
/**
|
|
3239
|
+
* Builds the vector used for the INITIAL render. It is seeded from the
|
|
3240
|
+
* accumulated signal state (every signal dispatched on the bus so far) so a
|
|
3241
|
+
* freshly-mounted outlet reflects the live application state immediately,
|
|
3242
|
+
* not just on the next signal tick. The current URL route is appended when
|
|
3243
|
+
* no AreRoute is already present in the state, so route-driven outlets still
|
|
3244
|
+
* resolve on the very first paint (before AreRouteWatcher has dispatched).
|
|
3245
|
+
*/
|
|
3246
|
+
buildInitialVector(signalState) {
|
|
3247
|
+
const signals = [];
|
|
3248
|
+
if (signalState) {
|
|
3249
|
+
for (const signal of signalState.toVector()) {
|
|
3250
|
+
if (signal) signals.push(signal);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
if (!signals.some((signal) => signal instanceof AreRoute)) {
|
|
3254
|
+
try {
|
|
3255
|
+
const currentRoute = AreRoute.default();
|
|
3256
|
+
if (currentRoute) signals.push(currentRoute);
|
|
3257
|
+
} catch {
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
return new A_SignalVector(signals);
|
|
3261
|
+
}
|
|
3262
|
+
/**
|
|
3263
|
+
* Detach a displayed child subtree from the outlet and stash it in the cache
|
|
3264
|
+
* for fast re-injection later. The subtree is unmounted (its scene plan is
|
|
3265
|
+
* preserved) and deregistered from the root scope, but NOT destroyed. The
|
|
3266
|
+
* nodes that were subscribed to the signal bus are unsubscribed while cached
|
|
3267
|
+
* so the detached DOM never reacts to signals, and recorded so they can be
|
|
3268
|
+
* re-subscribed verbatim on restore.
|
|
3269
|
+
*
|
|
3270
|
+
* When no cache is available, or the LRU evicts an entry, the affected
|
|
3271
|
+
* subtree is fully destroyed.
|
|
3272
|
+
*/
|
|
3273
|
+
stashChild(root, child, signalsContext, cache) {
|
|
3274
|
+
const tag = child.type;
|
|
3275
|
+
child.unmount();
|
|
3276
|
+
const subscribers = signalsContext ? this.collectSubscribers(child, signalsContext) : [];
|
|
3277
|
+
for (const node of subscribers) {
|
|
3278
|
+
signalsContext?.unsubscribe(node);
|
|
3279
|
+
}
|
|
3280
|
+
root.removeChild(child);
|
|
3281
|
+
if (!cache) {
|
|
3282
|
+
void child.destroy();
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
const evicted = cache.put(root.id, tag, { node: child, subscribers });
|
|
3286
|
+
for (const entry of evicted) {
|
|
3287
|
+
void entry.node.destroy();
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
/**
|
|
3291
|
+
* Re-attach a cached subtree to the outlet and re-mount it from its preserved
|
|
3292
|
+
* scene plan, re-subscribing exactly the nodes that were subscribed before it
|
|
3293
|
+
* was cached.
|
|
3294
|
+
*/
|
|
3295
|
+
restoreChild(root, entry, signalsContext) {
|
|
3296
|
+
const child = entry.node;
|
|
3297
|
+
root.addChild(child);
|
|
3298
|
+
for (const node of entry.subscribers) {
|
|
3299
|
+
signalsContext?.subscribe(node);
|
|
3300
|
+
}
|
|
3301
|
+
child.mount();
|
|
3302
|
+
}
|
|
3303
|
+
/**
|
|
3304
|
+
* Walk a subtree and collect the nodes currently registered as signal
|
|
3305
|
+
* subscribers. Mirrors the subscription performed at init time in
|
|
3306
|
+
* AreHTMLLifecycle (component nodes and root nodes) without depending on the
|
|
3307
|
+
* concrete node classes — it simply intersects the subtree with the live
|
|
3308
|
+
* subscriber registry.
|
|
3309
|
+
*/
|
|
3310
|
+
collectSubscribers(node, signalsContext) {
|
|
3311
|
+
const result = [];
|
|
3312
|
+
const queue = [node];
|
|
3313
|
+
while (queue.length > 0) {
|
|
3314
|
+
const current = queue.shift();
|
|
3315
|
+
if (signalsContext.subscribers.has(current)) {
|
|
3316
|
+
result.push(current);
|
|
3317
|
+
}
|
|
3318
|
+
queue.push(...current.children);
|
|
3319
|
+
}
|
|
3320
|
+
return result;
|
|
3321
|
+
}
|
|
3322
|
+
};
|
|
3323
|
+
__name(AreRoot, "AreRoot");
|
|
3324
|
+
__decorateClass([
|
|
3325
|
+
Are.Template,
|
|
3326
|
+
__decorateParam(0, Yt(te)),
|
|
3327
|
+
__decorateParam(1, Yt(A_Logger)),
|
|
3328
|
+
__decorateParam(2, Yt(AreSignalsContext)),
|
|
3329
|
+
__decorateParam(3, Yt(A_SignalState))
|
|
3330
|
+
], AreRoot.prototype, "template", 1);
|
|
3331
|
+
__decorateClass([
|
|
3332
|
+
Are.Signal,
|
|
3333
|
+
__decorateParam(0, Yt(te)),
|
|
3334
|
+
__decorateParam(1, Yt(A_SignalVector)),
|
|
3335
|
+
__decorateParam(2, Yt(A_Logger)),
|
|
3336
|
+
__decorateParam(3, Yt(AreSignalsContext)),
|
|
3337
|
+
__decorateParam(4, Yt(AreRootCache))
|
|
3338
|
+
], AreRoot.prototype, "onSignal", 1);
|
|
3339
|
+
AreRoot = __decorateClass([
|
|
3340
|
+
R2.Define({
|
|
3341
|
+
namespace: "a-are-html",
|
|
3342
|
+
description: "The AreRoot component serves as the foundational entry point for the A-Concept Rendering Engine (ARE). It is responsible for initializing the rendering process, managing the root node of the component tree, and handling signal-based rendering logic. The AreRoot component processes incoming signals to determine which child components to render, allowing for dynamic and responsive UI updates based on application state and user interactions."
|
|
3343
|
+
})
|
|
3344
|
+
], AreRoot);
|
|
3345
|
+
|
|
3346
|
+
// src/lib/AreRouteWatcher/AreRouteWatcher.component.ts
|
|
3347
|
+
var AreRouteWatcher = class extends O {
|
|
3348
|
+
constructor() {
|
|
3349
|
+
super();
|
|
3350
|
+
this.handlers = /* @__PURE__ */ new Set();
|
|
3351
|
+
this.current = new URL(window.location.href);
|
|
3352
|
+
// ── Listeners ─────────────────────────────────────────────────────────────
|
|
3353
|
+
this.onPopState = /* @__PURE__ */ __name(() => {
|
|
3354
|
+
this.notify();
|
|
3355
|
+
}, "onPopState");
|
|
3356
|
+
this.onHashChange = /* @__PURE__ */ __name(() => {
|
|
3357
|
+
this.notify();
|
|
3358
|
+
}, "onHashChange");
|
|
3359
|
+
this.onURLChange = /* @__PURE__ */ __name(() => {
|
|
3360
|
+
this.notify();
|
|
3361
|
+
}, "onURLChange");
|
|
3362
|
+
this.patchHistory();
|
|
3363
|
+
this.attachListeners();
|
|
3364
|
+
}
|
|
3365
|
+
// ── Public ────────────────────────────────────────────────────────────────
|
|
3366
|
+
onChange(handler) {
|
|
3367
|
+
this.handlers.add(handler);
|
|
3368
|
+
return () => this.handlers.delete(handler);
|
|
3369
|
+
}
|
|
3370
|
+
get url() {
|
|
3371
|
+
return this.current;
|
|
3372
|
+
}
|
|
3373
|
+
destroy() {
|
|
3374
|
+
window.removeEventListener("popstate", this.onPopState);
|
|
3375
|
+
window.removeEventListener("hashchange", this.onHashChange);
|
|
3376
|
+
window.removeEventListener("urlchange", this.onURLChange);
|
|
3377
|
+
this.handlers.clear();
|
|
3378
|
+
}
|
|
3379
|
+
attachListeners() {
|
|
3380
|
+
window.addEventListener("popstate", this.onPopState);
|
|
3381
|
+
window.addEventListener("hashchange", this.onHashChange);
|
|
3382
|
+
window.addEventListener("urlchange", this.onURLChange);
|
|
3383
|
+
}
|
|
3384
|
+
// ── Patch pushState / replaceState ────────────────────────────────────────
|
|
3385
|
+
patchHistory() {
|
|
3386
|
+
const patch = /* @__PURE__ */ __name((original) => function(...args) {
|
|
3387
|
+
original.apply(this, args);
|
|
3388
|
+
window.dispatchEvent(new Event("urlchange"));
|
|
3389
|
+
}, "patch");
|
|
3390
|
+
history.pushState = patch(history.pushState);
|
|
3391
|
+
history.replaceState = patch(history.replaceState);
|
|
3392
|
+
}
|
|
3393
|
+
// ── Notify ────────────────────────────────────────────────────────────────
|
|
3394
|
+
notify() {
|
|
3395
|
+
const next = new URL(window.location.href);
|
|
3396
|
+
if (next.href === this.current.href) return;
|
|
3397
|
+
this.current = next;
|
|
3398
|
+
for (const handler of this.handlers) {
|
|
3399
|
+
handler(this.current);
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
};
|
|
3403
|
+
__name(AreRouteWatcher, "AreRouteWatcher");
|
|
3404
|
+
AreRouteWatcher = __decorateClass([
|
|
3405
|
+
R2.Define({
|
|
3406
|
+
namespace: "a-are-html",
|
|
3407
|
+
description: "AreRouteWatcher is a component that observes browser navigation events (history pushState, replaceState, and popstate) and notifies registered handlers when the URL changes, enabling client-side routing and reactive route-based rendering within the ARE framework."
|
|
3408
|
+
})
|
|
3409
|
+
], AreRouteWatcher);
|
|
3410
|
+
|
|
3411
|
+
// examples/os-desktop/src/signals/OSRoute.signal.ts
|
|
3412
|
+
var OSRoute = class extends AreSignal {
|
|
3413
|
+
constructor(path) {
|
|
3414
|
+
super({ data: { path } });
|
|
3415
|
+
}
|
|
3416
|
+
get path() {
|
|
3417
|
+
return this.data.path;
|
|
3418
|
+
}
|
|
3419
|
+
compare(other) {
|
|
3420
|
+
return this.data.path === other.data.path;
|
|
3421
|
+
}
|
|
3422
|
+
};
|
|
3423
|
+
__name(OSRoute, "OSRoute");
|
|
3424
|
+
OSRoute = __decorateClass([
|
|
3425
|
+
R2.Define({
|
|
3426
|
+
namespace: "a-are-os-desktop",
|
|
3427
|
+
description: "OS routing signal carrying the active route (/desktop, /launchpad, /app/<id>). Drives which surface the desktop shows and is consumed independently by the AppStage, MenuBar and Dock."
|
|
3428
|
+
})
|
|
3429
|
+
], OSRoute);
|
|
3430
|
+
|
|
3431
|
+
// examples/os-desktop/src/os/Desktop.component.ts
|
|
3432
|
+
var _OsDesktop = class _OsDesktop extends Are {
|
|
3433
|
+
constructor() {
|
|
3434
|
+
super(...arguments);
|
|
3435
|
+
this._lastMouse = 0;
|
|
3436
|
+
}
|
|
3437
|
+
template(node) {
|
|
3438
|
+
node.setContent(`
|
|
3439
|
+
<div class="os">
|
|
3440
|
+
<menu-bar></menu-bar>
|
|
3441
|
+
<div class="os-stage">
|
|
3442
|
+
<app-stage></app-stage>
|
|
3443
|
+
</div>
|
|
3444
|
+
<os-hud></os-hud>
|
|
3445
|
+
<os-dock></os-dock>
|
|
3446
|
+
</div>
|
|
3447
|
+
`);
|
|
3448
|
+
}
|
|
3449
|
+
onMount(bus) {
|
|
3450
|
+
requestAnimationFrame(() => {
|
|
3451
|
+
window.addEventListener("mousemove", (event) => {
|
|
3452
|
+
const now = Date.now();
|
|
3453
|
+
if (now - this._lastMouse < 100) return;
|
|
3454
|
+
this._lastMouse = now;
|
|
3455
|
+
bus.next(new MouseState(event.clientX, event.clientY));
|
|
3456
|
+
});
|
|
3457
|
+
document.addEventListener("selectionchange", () => {
|
|
3458
|
+
const text = window.getSelection()?.toString() || "";
|
|
3459
|
+
bus.next(new SelectionState(text));
|
|
3460
|
+
});
|
|
3461
|
+
window.addEventListener("popstate", () => {
|
|
3462
|
+
bus.next(new OSRoute(window.location.pathname || "/"));
|
|
3463
|
+
});
|
|
3464
|
+
});
|
|
3465
|
+
}
|
|
3466
|
+
styles(node) {
|
|
3467
|
+
node.setStyles(`
|
|
3468
|
+
.os {
|
|
3469
|
+
position: relative;
|
|
3470
|
+
width: 100vw;
|
|
3471
|
+
height: 100vh;
|
|
3472
|
+
overflow: hidden;
|
|
3473
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
3474
|
+
color: #f5f5f7;
|
|
3475
|
+
background:
|
|
3476
|
+
radial-gradient(120% 120% at 20% 0%, #5b3fb0 0%, transparent 55%),
|
|
3477
|
+
radial-gradient(120% 120% at 100% 30%, #b03f8a 0%, transparent 50%),
|
|
3478
|
+
linear-gradient(160deg, #1d1033 0%, #0c0a1a 60%, #050410 100%);
|
|
3479
|
+
user-select: none;
|
|
3480
|
+
}
|
|
3481
|
+
.os-stage {
|
|
3482
|
+
position: absolute;
|
|
3483
|
+
inset: 28px 0 0 0;
|
|
3484
|
+
display: flex;
|
|
3485
|
+
align-items: center;
|
|
3486
|
+
justify-content: center;
|
|
3487
|
+
}
|
|
3488
|
+
`);
|
|
3489
|
+
}
|
|
3490
|
+
};
|
|
3491
|
+
__name(_OsDesktop, "OsDesktop");
|
|
3492
|
+
__decorateClass([
|
|
3493
|
+
Are.Template,
|
|
3494
|
+
__decorateParam(0, Yt(te))
|
|
3495
|
+
], _OsDesktop.prototype, "template", 1);
|
|
3496
|
+
__decorateClass([
|
|
3497
|
+
Are.onAfterMount,
|
|
3498
|
+
__decorateParam(0, Yt(A_SignalBus))
|
|
3499
|
+
], _OsDesktop.prototype, "onMount", 1);
|
|
3500
|
+
__decorateClass([
|
|
3501
|
+
Are.Styles,
|
|
3502
|
+
__decorateParam(0, Yt(te))
|
|
3503
|
+
], _OsDesktop.prototype, "styles", 1);
|
|
3504
|
+
var OsDesktop = _OsDesktop;
|
|
3505
|
+
|
|
3506
|
+
// examples/os-desktop/src/runtime/AppRegistry.fragment.ts
|
|
3507
|
+
var AppRegistry = class extends H {
|
|
3508
|
+
constructor(data) {
|
|
3509
|
+
super({ name: "AppRegistry" });
|
|
3510
|
+
this._available = [];
|
|
3511
|
+
this._installed = /* @__PURE__ */ new Set();
|
|
3512
|
+
this._available = data.available ?? [];
|
|
3513
|
+
for (const id of data.installed ?? []) {
|
|
3514
|
+
this._installed.add(id);
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
/** Every app the backend offers. */
|
|
3518
|
+
available() {
|
|
3519
|
+
return this._available;
|
|
3520
|
+
}
|
|
3521
|
+
/** Apps the user has installed, in catalogue order. */
|
|
3522
|
+
installed() {
|
|
3523
|
+
return this._available.filter((app) => this._installed.has(app.id));
|
|
3524
|
+
}
|
|
3525
|
+
isInstalled(id) {
|
|
3526
|
+
return this._installed.has(id);
|
|
3527
|
+
}
|
|
3528
|
+
install(id) {
|
|
3529
|
+
if (this._available.some((app) => app.id === id)) {
|
|
3530
|
+
this._installed.add(id);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
uninstall(id) {
|
|
3534
|
+
this._installed.delete(id);
|
|
3535
|
+
}
|
|
3536
|
+
/** Look up an app by its id. */
|
|
3537
|
+
get(id) {
|
|
3538
|
+
return this._available.find((app) => app.id === id);
|
|
3539
|
+
}
|
|
3540
|
+
/** Find the installed app that owns a given component tag (root or child). */
|
|
3541
|
+
appByComponentTag(tag) {
|
|
3542
|
+
return this.installed().find(
|
|
3543
|
+
(app) => app.rootTag === tag || app.components.some((c) => c.tag === tag)
|
|
3544
|
+
);
|
|
3545
|
+
}
|
|
3546
|
+
};
|
|
3547
|
+
__name(AppRegistry, "AppRegistry");
|
|
3548
|
+
AppRegistry = __decorateClass([
|
|
3549
|
+
R2.Define({
|
|
3550
|
+
namespace: "a-are-os-desktop",
|
|
3551
|
+
description: "Holds the catalogue of available applications (from GET /api/apps) and the set the user has installed. Shared by the OS shell components and the AppComponentResolver to map component tags to their owning app bundle."
|
|
3552
|
+
})
|
|
3553
|
+
], AppRegistry);
|
|
3554
|
+
|
|
3555
|
+
// examples/os-desktop/src/os/MenuBar.component.ts
|
|
3556
|
+
var _MenuBar = class _MenuBar extends Are {
|
|
3557
|
+
constructor() {
|
|
3558
|
+
super(...arguments);
|
|
3559
|
+
this._route = document.location.pathname || "/";
|
|
3560
|
+
this._selectionLength = 0;
|
|
3561
|
+
}
|
|
3562
|
+
template(node, registry) {
|
|
3563
|
+
node.setContent(this.build(registry));
|
|
3564
|
+
}
|
|
3565
|
+
onMount() {
|
|
3566
|
+
this._clockTimer = setInterval(() => {
|
|
3567
|
+
const clock = document.getElementById("mb-clock");
|
|
3568
|
+
if (clock) clock.textContent = this.now();
|
|
3569
|
+
}, 15e3);
|
|
3570
|
+
}
|
|
3571
|
+
onUnmount() {
|
|
3572
|
+
if (this._clockTimer) clearInterval(this._clockTimer);
|
|
3573
|
+
this._clockTimer = void 0;
|
|
3574
|
+
}
|
|
3575
|
+
onRoute(signal, registry) {
|
|
3576
|
+
this._route = signal.path;
|
|
3577
|
+
const el = document.getElementById("mb-active");
|
|
3578
|
+
if (el) {
|
|
3579
|
+
const { icon, name } = this.activeTitle(registry);
|
|
3580
|
+
el.textContent = `${icon ? icon + " " : ""}${name}`;
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
onSelection(signal) {
|
|
3584
|
+
this._selectionLength = signal.length;
|
|
3585
|
+
const chip = document.getElementById("mb-chip");
|
|
3586
|
+
if (!chip) return;
|
|
3587
|
+
if (this._selectionLength > 0) {
|
|
3588
|
+
chip.hidden = false;
|
|
3589
|
+
chip.textContent = `\u2702 ${this._selectionLength} selected`;
|
|
3590
|
+
} else {
|
|
3591
|
+
chip.hidden = true;
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
now() {
|
|
3595
|
+
return (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
3596
|
+
}
|
|
3597
|
+
activeTitle(registry) {
|
|
3598
|
+
const match = this._route.match(/^\/app\/([^/]+)/);
|
|
3599
|
+
if (match) {
|
|
3600
|
+
const app = registry.get(match[1]);
|
|
3601
|
+
if (app) return { icon: app.icon, name: app.name };
|
|
3602
|
+
}
|
|
3603
|
+
if (this._route === "/launchpad") {
|
|
3604
|
+
return { icon: "\u{1F680}", name: "Launchpad" };
|
|
3605
|
+
}
|
|
3606
|
+
return { icon: "", name: "Finder" };
|
|
3607
|
+
}
|
|
3608
|
+
build(registry) {
|
|
3609
|
+
const { icon, name } = this.activeTitle(registry);
|
|
3610
|
+
const hasSel = this._selectionLength > 0;
|
|
3611
|
+
return `
|
|
3612
|
+
<div class="menubar">
|
|
3613
|
+
<div class="mb-left">
|
|
3614
|
+
<span class="mb-logo"></span>
|
|
3615
|
+
<span class="mb-active" id="mb-active">${icon ? icon + " " : ""}${name}</span>
|
|
3616
|
+
<span class="mb-menu">File</span>
|
|
3617
|
+
<span class="mb-menu">Edit</span>
|
|
3618
|
+
<span class="mb-menu">View</span>
|
|
3619
|
+
<span class="mb-menu">Window</span>
|
|
3620
|
+
</div>
|
|
3621
|
+
<div class="mb-right">
|
|
3622
|
+
<span class="mb-chip" id="mb-chip"${hasSel ? "" : " hidden"}>\u2702 ${this._selectionLength} selected</span>
|
|
3623
|
+
<span class="mb-icon">\u{100647}</span>
|
|
3624
|
+
<span class="mb-clock" id="mb-clock">${this.now()}</span>
|
|
3625
|
+
</div>
|
|
3626
|
+
</div>
|
|
3627
|
+
`;
|
|
3628
|
+
}
|
|
3629
|
+
styles(node) {
|
|
3630
|
+
node.setStyles(`
|
|
3631
|
+
.menubar {
|
|
3632
|
+
position: absolute;
|
|
3633
|
+
top: 0; left: 0; right: 0;
|
|
3634
|
+
height: 28px;
|
|
3635
|
+
display: flex;
|
|
3636
|
+
align-items: center;
|
|
3637
|
+
justify-content: space-between;
|
|
3638
|
+
padding: 0 14px;
|
|
3639
|
+
background: rgba(20, 16, 32, 0.55);
|
|
3640
|
+
backdrop-filter: blur(18px) saturate(160%);
|
|
3641
|
+
-webkit-backdrop-filter: blur(18px) saturate(160%);
|
|
3642
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
3643
|
+
font-size: 13px;
|
|
3644
|
+
z-index: 100;
|
|
3645
|
+
}
|
|
3646
|
+
.mb-left, .mb-right { display: flex; align-items: center; gap: 16px; }
|
|
3647
|
+
.mb-logo {
|
|
3648
|
+
width: 14px; height: 14px;
|
|
3649
|
+
background: #f5f5f7;
|
|
3650
|
+
-webkit-mask: radial-gradient(circle at 70% 22%, transparent 22%, #000 23%) 0 0/100% 100%;
|
|
3651
|
+
mask: radial-gradient(circle at 70% 22%, transparent 22%, #000 23%) 0 0/100% 100%;
|
|
3652
|
+
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');
|
|
3653
|
+
opacity: 0.9;
|
|
3654
|
+
}
|
|
3655
|
+
.mb-active { font-weight: 700; }
|
|
3656
|
+
.mb-menu { color: rgba(245,245,247,0.82); font-weight: 500; }
|
|
3657
|
+
.mb-menu:first-of-type { margin-left: 2px; }
|
|
3658
|
+
.mb-icon { opacity: 0.85; }
|
|
3659
|
+
.mb-clock { font-variant-numeric: tabular-nums; font-weight: 500; }
|
|
3660
|
+
.mb-chip {
|
|
3661
|
+
font-size: 11px;
|
|
3662
|
+
font-weight: 600;
|
|
3663
|
+
padding: 2px 9px;
|
|
3664
|
+
border-radius: 999px;
|
|
3665
|
+
color: #ffe5a3;
|
|
3666
|
+
background: rgba(255, 196, 84, 0.18);
|
|
3667
|
+
}
|
|
3668
|
+
`);
|
|
3669
|
+
}
|
|
3670
|
+
};
|
|
3671
|
+
__name(_MenuBar, "MenuBar");
|
|
3672
|
+
__decorateClass([
|
|
3673
|
+
Are.Template,
|
|
3674
|
+
__decorateParam(0, Yt(te)),
|
|
3675
|
+
__decorateParam(1, Yt(AppRegistry))
|
|
3676
|
+
], _MenuBar.prototype, "template", 1);
|
|
3677
|
+
__decorateClass([
|
|
3678
|
+
Are.onAfterMount
|
|
3679
|
+
], _MenuBar.prototype, "onMount", 1);
|
|
3680
|
+
__decorateClass([
|
|
3681
|
+
Are.onBeforeUnmount
|
|
3682
|
+
], _MenuBar.prototype, "onUnmount", 1);
|
|
3683
|
+
__decorateClass([
|
|
3684
|
+
Are.Signal(OSRoute),
|
|
3685
|
+
__decorateParam(0, Yt(OSRoute)),
|
|
3686
|
+
__decorateParam(1, Yt(AppRegistry))
|
|
3687
|
+
], _MenuBar.prototype, "onRoute", 1);
|
|
3688
|
+
__decorateClass([
|
|
3689
|
+
Are.Signal(SelectionState),
|
|
3690
|
+
__decorateParam(0, Yt(SelectionState))
|
|
3691
|
+
], _MenuBar.prototype, "onSelection", 1);
|
|
3692
|
+
__decorateClass([
|
|
3693
|
+
Are.Styles,
|
|
3694
|
+
__decorateParam(0, Yt(te))
|
|
3695
|
+
], _MenuBar.prototype, "styles", 1);
|
|
3696
|
+
var MenuBar = _MenuBar;
|
|
3697
|
+
|
|
3698
|
+
// examples/os-desktop/src/os/Hud.component.ts
|
|
3699
|
+
var _OsHud = class _OsHud extends Are {
|
|
3700
|
+
constructor() {
|
|
3701
|
+
super(...arguments);
|
|
3702
|
+
this._x = 0;
|
|
3703
|
+
this._y = 0;
|
|
3704
|
+
this._selection = 0;
|
|
3705
|
+
}
|
|
3706
|
+
template(node) {
|
|
3707
|
+
node.setContent(this.build());
|
|
3708
|
+
}
|
|
3709
|
+
onMouse(signal) {
|
|
3710
|
+
this._x = signal.x;
|
|
3711
|
+
this._y = signal.y;
|
|
3712
|
+
const x = document.getElementById("hud-x");
|
|
3713
|
+
const y = document.getElementById("hud-y");
|
|
3714
|
+
if (x) x.textContent = String(this._x);
|
|
3715
|
+
if (y) y.textContent = String(this._y);
|
|
3716
|
+
}
|
|
3717
|
+
onSelection(signal) {
|
|
3718
|
+
this._selection = signal.length;
|
|
3719
|
+
const sel = document.getElementById("hud-sel");
|
|
3720
|
+
if (sel) sel.textContent = String(this._selection);
|
|
3721
|
+
}
|
|
3722
|
+
build() {
|
|
3723
|
+
return `
|
|
3724
|
+
<div class="hud">
|
|
3725
|
+
<span class="hud-row"><b>signals</b></span>
|
|
3726
|
+
<span class="hud-row">MouseState <i>x</i> <span id="hud-x">${this._x}</span> <i>y</i> <span id="hud-y">${this._y}</span></span>
|
|
3727
|
+
<span class="hud-row">SelectionState <i>len</i> <span id="hud-sel">${this._selection}</span></span>
|
|
3728
|
+
</div>
|
|
3729
|
+
`;
|
|
3730
|
+
}
|
|
3731
|
+
styles(node) {
|
|
3732
|
+
node.setStyles(`
|
|
3733
|
+
.hud {
|
|
3734
|
+
position: absolute;
|
|
3735
|
+
left: 16px;
|
|
3736
|
+
bottom: 104px;
|
|
3737
|
+
display: flex;
|
|
3738
|
+
flex-direction: column;
|
|
3739
|
+
gap: 2px;
|
|
3740
|
+
padding: 10px 14px;
|
|
3741
|
+
border-radius: 12px;
|
|
3742
|
+
background: rgba(12, 10, 24, 0.5);
|
|
3743
|
+
backdrop-filter: blur(14px);
|
|
3744
|
+
-webkit-backdrop-filter: blur(14px);
|
|
3745
|
+
border: 1px solid rgba(255,255,255,0.08);
|
|
3746
|
+
font-size: 11px;
|
|
3747
|
+
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
|
|
3748
|
+
color: rgba(245,245,247,0.85);
|
|
3749
|
+
z-index: 90;
|
|
3750
|
+
pointer-events: none;
|
|
3751
|
+
}
|
|
3752
|
+
.hud b { color: #c9b8ff; text-transform: uppercase; letter-spacing: 0.08em; font-size: 10px; }
|
|
3753
|
+
.hud i { color: #8b7fb0; font-style: normal; }
|
|
3754
|
+
.hud-row { white-space: nowrap; }
|
|
3755
|
+
`);
|
|
3756
|
+
}
|
|
3757
|
+
};
|
|
3758
|
+
__name(_OsHud, "OsHud");
|
|
3759
|
+
__decorateClass([
|
|
3760
|
+
Are.Template,
|
|
3761
|
+
__decorateParam(0, Yt(te))
|
|
3762
|
+
], _OsHud.prototype, "template", 1);
|
|
3763
|
+
__decorateClass([
|
|
3764
|
+
Are.Signal(MouseState),
|
|
3765
|
+
__decorateParam(0, Yt(MouseState))
|
|
3766
|
+
], _OsHud.prototype, "onMouse", 1);
|
|
3767
|
+
__decorateClass([
|
|
3768
|
+
Are.Signal(SelectionState),
|
|
3769
|
+
__decorateParam(0, Yt(SelectionState))
|
|
3770
|
+
], _OsHud.prototype, "onSelection", 1);
|
|
3771
|
+
__decorateClass([
|
|
3772
|
+
Are.Styles,
|
|
3773
|
+
__decorateParam(0, Yt(te))
|
|
3774
|
+
], _OsHud.prototype, "styles", 1);
|
|
3775
|
+
var OsHud = _OsHud;
|
|
3776
|
+
|
|
3777
|
+
// examples/os-desktop/src/os/Dock.component.ts
|
|
3778
|
+
var _OsDock = class _OsDock extends Are {
|
|
3779
|
+
constructor() {
|
|
3780
|
+
super(...arguments);
|
|
3781
|
+
this._route = document.location.pathname || "/";
|
|
3782
|
+
}
|
|
3783
|
+
template(node, registry) {
|
|
3784
|
+
node.setContent(this.build(registry));
|
|
3785
|
+
}
|
|
3786
|
+
onRoute(signal, registry) {
|
|
3787
|
+
this._route = signal.path;
|
|
3788
|
+
const activeId = this._route.match(/^\/app\/([^/]+)/)?.[1];
|
|
3789
|
+
let anyVisible = false;
|
|
3790
|
+
for (const app of registry.available()) {
|
|
3791
|
+
const btn = document.getElementById(`dock-app-${app.id}`);
|
|
3792
|
+
if (!btn) continue;
|
|
3793
|
+
if (registry.isInstalled(app.id)) {
|
|
3794
|
+
btn.hidden = false;
|
|
3795
|
+
anyVisible = true;
|
|
3796
|
+
}
|
|
3797
|
+
btn.classList.toggle("running", app.id === activeId);
|
|
3798
|
+
}
|
|
3799
|
+
const sep = document.getElementById("dock-sep");
|
|
3800
|
+
if (sep) sep.hidden = !anyVisible;
|
|
3801
|
+
const lp = document.getElementById("dock-launchpad");
|
|
3802
|
+
if (lp) lp.classList.toggle("running", this._route === "/launchpad");
|
|
3803
|
+
}
|
|
3804
|
+
open(event, bus) {
|
|
3805
|
+
event.get("native")?.preventDefault();
|
|
3806
|
+
const id = event.get("args")?.[0];
|
|
3807
|
+
if (!id) return;
|
|
3808
|
+
const path = `/app/${id}`;
|
|
3809
|
+
history.pushState({}, "", path);
|
|
3810
|
+
bus.next(new OSRoute(path));
|
|
3811
|
+
}
|
|
3812
|
+
launchpad(event, bus) {
|
|
3813
|
+
event.get("native")?.preventDefault();
|
|
3814
|
+
history.pushState({}, "", "/launchpad");
|
|
3815
|
+
bus.next(new OSRoute("/launchpad"));
|
|
3816
|
+
}
|
|
3817
|
+
build(registry) {
|
|
3818
|
+
const activeId = this._route.match(/^\/app\/([^/]+)/)?.[1];
|
|
3819
|
+
const apps = registry.available().map((app) => {
|
|
3820
|
+
const installed = registry.isInstalled(app.id);
|
|
3821
|
+
const running = app.id === activeId ? "running" : "";
|
|
3822
|
+
return `
|
|
3823
|
+
<button id="dock-app-${app.id}" class="dock-item ${running}" @click="$open('${app.id}')" title="${app.name}"${installed ? "" : " hidden"}>
|
|
3824
|
+
<span class="dock-glyph" style="--accent:${app.accent}">${app.icon}</span>
|
|
3825
|
+
<span class="dock-dot"></span>
|
|
3826
|
+
</button>
|
|
3827
|
+
`;
|
|
3828
|
+
}).join("");
|
|
3829
|
+
const anyInstalled = registry.installed().length > 0;
|
|
3830
|
+
const launchpadActive = this._route === "/launchpad" ? "running" : "";
|
|
3831
|
+
return `
|
|
3832
|
+
<div class="dock">
|
|
3833
|
+
${apps}
|
|
3834
|
+
<span class="dock-sep" id="dock-sep"${anyInstalled ? "" : " hidden"}></span>
|
|
3835
|
+
<button id="dock-launchpad" class="dock-item ${launchpadActive}" @click="$launchpad()" title="Launchpad \u2014 install apps">
|
|
3836
|
+
<span class="dock-glyph dock-plus">\uFF0B</span>
|
|
3837
|
+
<span class="dock-dot"></span>
|
|
3838
|
+
</button>
|
|
3839
|
+
</div>
|
|
3840
|
+
`;
|
|
3841
|
+
}
|
|
3842
|
+
styles(node) {
|
|
3843
|
+
node.setStyles(`
|
|
3844
|
+
.dock {
|
|
3845
|
+
position: absolute;
|
|
3846
|
+
left: 50%;
|
|
3847
|
+
bottom: 14px;
|
|
3848
|
+
transform: translateX(-50%);
|
|
3849
|
+
display: flex;
|
|
3850
|
+
align-items: flex-end;
|
|
3851
|
+
gap: 10px;
|
|
3852
|
+
padding: 8px 12px;
|
|
3853
|
+
border-radius: 22px;
|
|
3854
|
+
background: rgba(30, 24, 48, 0.45);
|
|
3855
|
+
backdrop-filter: blur(24px) saturate(180%);
|
|
3856
|
+
-webkit-backdrop-filter: blur(24px) saturate(180%);
|
|
3857
|
+
border: 1px solid rgba(255,255,255,0.12);
|
|
3858
|
+
box-shadow: 0 18px 50px rgba(0,0,0,0.45);
|
|
3859
|
+
z-index: 95;
|
|
3860
|
+
}
|
|
3861
|
+
.dock-item {
|
|
3862
|
+
position: relative;
|
|
3863
|
+
display: flex;
|
|
3864
|
+
flex-direction: column;
|
|
3865
|
+
align-items: center;
|
|
3866
|
+
gap: 4px;
|
|
3867
|
+
border: none;
|
|
3868
|
+
background: none;
|
|
3869
|
+
cursor: pointer;
|
|
3870
|
+
padding: 0;
|
|
3871
|
+
}
|
|
3872
|
+
/* The HTML 'hidden' attribute must win over 'display: flex' so an
|
|
3873
|
+
uninstalled app icon (and the separator) stays out of the dock. */
|
|
3874
|
+
.dock-item[hidden], #dock-sep[hidden] { display: none !important; }
|
|
3875
|
+
.dock-glyph {
|
|
3876
|
+
width: 52px; height: 52px;
|
|
3877
|
+
display: flex; align-items: center; justify-content: center;
|
|
3878
|
+
font-size: 28px;
|
|
3879
|
+
border-radius: 14px;
|
|
3880
|
+
background: linear-gradient(160deg, color-mix(in srgb, var(--accent, #7c6fd6) 85%, white 0%), color-mix(in srgb, var(--accent, #7c6fd6) 70%, black 18%));
|
|
3881
|
+
box-shadow: inset 0 1px 0 rgba(255,255,255,0.35), 0 6px 14px rgba(0,0,0,0.35);
|
|
3882
|
+
transition: transform 0.18s ease;
|
|
3883
|
+
}
|
|
3884
|
+
.dock-plus {
|
|
3885
|
+
background: rgba(255,255,255,0.12);
|
|
3886
|
+
color: #f5f5f7;
|
|
3887
|
+
font-size: 26px;
|
|
3888
|
+
font-weight: 300;
|
|
3889
|
+
}
|
|
3890
|
+
.dock-item:hover .dock-glyph { transform: translateY(-10px) scale(1.12); }
|
|
3891
|
+
.dock-dot {
|
|
3892
|
+
width: 4px; height: 4px; border-radius: 50%;
|
|
3893
|
+
background: transparent;
|
|
3894
|
+
}
|
|
3895
|
+
.dock-item.running .dock-dot { background: rgba(245,245,247,0.85); }
|
|
3896
|
+
.dock-sep {
|
|
3897
|
+
width: 1px;
|
|
3898
|
+
align-self: stretch;
|
|
3899
|
+
margin: 6px 2px;
|
|
3900
|
+
background: rgba(255,255,255,0.16);
|
|
3901
|
+
}
|
|
3902
|
+
`);
|
|
3903
|
+
}
|
|
3904
|
+
};
|
|
3905
|
+
__name(_OsDock, "OsDock");
|
|
3906
|
+
__decorateClass([
|
|
3907
|
+
Are.Template,
|
|
3908
|
+
__decorateParam(0, Yt(te)),
|
|
3909
|
+
__decorateParam(1, Yt(AppRegistry))
|
|
3910
|
+
], _OsDock.prototype, "template", 1);
|
|
3911
|
+
__decorateClass([
|
|
3912
|
+
Are.Signal(OSRoute),
|
|
3913
|
+
__decorateParam(0, Yt(OSRoute)),
|
|
3914
|
+
__decorateParam(1, Yt(AppRegistry))
|
|
3915
|
+
], _OsDock.prototype, "onRoute", 1);
|
|
3916
|
+
__decorateClass([
|
|
3917
|
+
Are.EventHandler,
|
|
3918
|
+
__decorateParam(0, Yt(AreEvent)),
|
|
3919
|
+
__decorateParam(1, Yt(A_SignalBus))
|
|
3920
|
+
], _OsDock.prototype, "open", 1);
|
|
3921
|
+
__decorateClass([
|
|
3922
|
+
Are.EventHandler,
|
|
3923
|
+
__decorateParam(0, Yt(AreEvent)),
|
|
3924
|
+
__decorateParam(1, Yt(A_SignalBus))
|
|
3925
|
+
], _OsDock.prototype, "launchpad", 1);
|
|
3926
|
+
__decorateClass([
|
|
3927
|
+
Are.Styles,
|
|
3928
|
+
__decorateParam(0, Yt(te))
|
|
3929
|
+
], _OsDock.prototype, "styles", 1);
|
|
3930
|
+
var OsDock = _OsDock;
|
|
3931
|
+
|
|
3932
|
+
// examples/os-desktop/src/os/AppStage.component.ts
|
|
3933
|
+
var _AppStage = class _AppStage extends Are {
|
|
3934
|
+
constructor() {
|
|
3935
|
+
super(...arguments);
|
|
3936
|
+
this._renderedKey = "";
|
|
3937
|
+
}
|
|
3938
|
+
template(node) {
|
|
3939
|
+
const route = document.location.pathname || "/";
|
|
3940
|
+
this._renderedKey = this.surfaceKey(route);
|
|
3941
|
+
const tag = this.surfaceTag(route);
|
|
3942
|
+
if (tag) {
|
|
3943
|
+
node.setContent(`<${tag}></${tag}>`);
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
async onRoute(node, signal, signalsContext) {
|
|
3947
|
+
const route = signal.path;
|
|
3948
|
+
const key = this.surfaceKey(route);
|
|
3949
|
+
if (key === this._renderedKey) {
|
|
3950
|
+
return;
|
|
3951
|
+
}
|
|
3952
|
+
if (signalsContext) {
|
|
3953
|
+
for (const child of [...node.children]) {
|
|
3954
|
+
for (const subscriber of this.collectSubscribers(child, signalsContext)) {
|
|
3955
|
+
signalsContext.unsubscribe(subscriber);
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
await node.clear();
|
|
3960
|
+
const tag = this.surfaceTag(route);
|
|
3961
|
+
if (tag) {
|
|
3962
|
+
node.setContent(`<${tag}></${tag}>`);
|
|
3963
|
+
}
|
|
3964
|
+
this._renderedKey = key;
|
|
3965
|
+
await node.render();
|
|
3966
|
+
}
|
|
3967
|
+
/** A stable key per visible surface (distinct apps must differ). */
|
|
3968
|
+
surfaceKey(route) {
|
|
3969
|
+
if (route.startsWith("/app/")) return route;
|
|
3970
|
+
if (route === "/launchpad") return "/launchpad";
|
|
3971
|
+
return "/desktop";
|
|
3972
|
+
}
|
|
3973
|
+
/** The component tag to mount for a route ('' = empty desktop). */
|
|
3974
|
+
surfaceTag(route) {
|
|
3975
|
+
if (route.startsWith("/app/")) return "app-window";
|
|
3976
|
+
if (route === "/launchpad") return "os-launchpad";
|
|
3977
|
+
return "";
|
|
3978
|
+
}
|
|
3979
|
+
collectSubscribers(node, signalsContext) {
|
|
3980
|
+
const result = [];
|
|
3981
|
+
const queue = [node];
|
|
3982
|
+
while (queue.length > 0) {
|
|
3983
|
+
const current = queue.shift();
|
|
3984
|
+
if (signalsContext.subscribers.has(current)) {
|
|
3985
|
+
result.push(current);
|
|
3986
|
+
}
|
|
3987
|
+
queue.push(...current.children);
|
|
3988
|
+
}
|
|
3989
|
+
return result;
|
|
3990
|
+
}
|
|
3991
|
+
styles(node) {
|
|
3992
|
+
node.setStyles(`
|
|
3993
|
+
app-stage { display: contents; }
|
|
3994
|
+
`);
|
|
3995
|
+
}
|
|
3996
|
+
};
|
|
3997
|
+
__name(_AppStage, "AppStage");
|
|
3998
|
+
__decorateClass([
|
|
3999
|
+
Are.Template,
|
|
4000
|
+
__decorateParam(0, Yt(te))
|
|
4001
|
+
], _AppStage.prototype, "template", 1);
|
|
4002
|
+
__decorateClass([
|
|
4003
|
+
Are.Signal(OSRoute),
|
|
4004
|
+
__decorateParam(0, Yt(te)),
|
|
4005
|
+
__decorateParam(1, Yt(OSRoute)),
|
|
4006
|
+
__decorateParam(2, Yt(AreSignalsContext))
|
|
4007
|
+
], _AppStage.prototype, "onRoute", 1);
|
|
4008
|
+
__decorateClass([
|
|
4009
|
+
Are.Styles,
|
|
4010
|
+
__decorateParam(0, Yt(te))
|
|
4011
|
+
], _AppStage.prototype, "styles", 1);
|
|
4012
|
+
var AppStage = _AppStage;
|
|
4013
|
+
|
|
4014
|
+
// examples/os-desktop/src/os/AppWindow.component.ts
|
|
4015
|
+
var _AppWindow = class _AppWindow extends Are {
|
|
4016
|
+
template(node, registry) {
|
|
4017
|
+
const id = (document.location.pathname.match(/^\/app\/([^/]+)/) || [])[1];
|
|
4018
|
+
const app = id ? registry.get(id) : void 0;
|
|
4019
|
+
if (!app) {
|
|
4020
|
+
node.setContent(`<div class="win win-missing">Application not found.</div>`);
|
|
4021
|
+
return;
|
|
4022
|
+
}
|
|
4023
|
+
node.setContent(`
|
|
4024
|
+
<div class="win" style="--accent:${app.accent}">
|
|
4025
|
+
<header class="win-bar">
|
|
4026
|
+
<div class="traffic">
|
|
4027
|
+
<button class="tl tl-red" @click="$close()" title="Close"></button>
|
|
4028
|
+
<span class="tl tl-amber"></span>
|
|
4029
|
+
<span class="tl tl-green"></span>
|
|
4030
|
+
</div>
|
|
4031
|
+
<div class="win-title">${app.icon} ${app.name}</div>
|
|
4032
|
+
<div class="win-spacer"></div>
|
|
4033
|
+
</header>
|
|
4034
|
+
<div class="win-body">
|
|
4035
|
+
<${app.rootTag}></${app.rootTag}>
|
|
4036
|
+
</div>
|
|
4037
|
+
</div>
|
|
4038
|
+
`);
|
|
4039
|
+
}
|
|
4040
|
+
close(event, bus) {
|
|
4041
|
+
event.get("native")?.preventDefault();
|
|
4042
|
+
history.pushState({}, "", "/desktop");
|
|
4043
|
+
bus.next(new OSRoute("/desktop"));
|
|
4044
|
+
}
|
|
4045
|
+
styles(node) {
|
|
4046
|
+
node.setStyles(`
|
|
4047
|
+
.win {
|
|
4048
|
+
width: min(960px, 86vw);
|
|
4049
|
+
height: min(620px, 80vh);
|
|
4050
|
+
display: flex;
|
|
4051
|
+
flex-direction: column;
|
|
4052
|
+
border-radius: 14px;
|
|
4053
|
+
overflow: hidden;
|
|
4054
|
+
background: #16131f;
|
|
4055
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
4056
|
+
box-shadow: 0 40px 120px rgba(0,0,0,0.6), 0 0 0 1px rgba(0,0,0,0.4);
|
|
4057
|
+
animation: win-in 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
4058
|
+
}
|
|
4059
|
+
@keyframes win-in {
|
|
4060
|
+
from { opacity: 0; transform: translateY(14px) scale(0.97); }
|
|
4061
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
4062
|
+
}
|
|
4063
|
+
.win-bar {
|
|
4064
|
+
display: flex;
|
|
4065
|
+
align-items: center;
|
|
4066
|
+
height: 40px;
|
|
4067
|
+
padding: 0 14px;
|
|
4068
|
+
background: linear-gradient(180deg, color-mix(in srgb, var(--accent, #6d5fd0) 26%, #221b33), #1a1526);
|
|
4069
|
+
border-bottom: 1px solid rgba(255,255,255,0.06);
|
|
4070
|
+
}
|
|
4071
|
+
.traffic { display: flex; gap: 8px; width: 80px; }
|
|
4072
|
+
.tl { width: 12px; height: 12px; border-radius: 50%; border: none; padding: 0; cursor: default; }
|
|
4073
|
+
.tl-red { background: #ff5f57; cursor: pointer; }
|
|
4074
|
+
.tl-amber { background: #febc2e; }
|
|
4075
|
+
.tl-green { background: #28c840; }
|
|
4076
|
+
.win-title { flex: 1; text-align: center; font-size: 13px; font-weight: 600; color: rgba(245,245,247,0.9); }
|
|
4077
|
+
.win-spacer { width: 80px; }
|
|
4078
|
+
.win-body { flex: 1; overflow: auto; background: #14111d; }
|
|
4079
|
+
.win-missing { padding: 40px; color: #a1a1aa; }
|
|
4080
|
+
`);
|
|
4081
|
+
}
|
|
4082
|
+
};
|
|
4083
|
+
__name(_AppWindow, "AppWindow");
|
|
4084
|
+
__decorateClass([
|
|
4085
|
+
Are.Template,
|
|
4086
|
+
__decorateParam(0, Yt(te)),
|
|
4087
|
+
__decorateParam(1, Yt(AppRegistry))
|
|
4088
|
+
], _AppWindow.prototype, "template", 1);
|
|
4089
|
+
__decorateClass([
|
|
4090
|
+
Are.EventHandler,
|
|
4091
|
+
__decorateParam(0, Yt(AreEvent)),
|
|
4092
|
+
__decorateParam(1, Yt(A_SignalBus))
|
|
4093
|
+
], _AppWindow.prototype, "close", 1);
|
|
4094
|
+
__decorateClass([
|
|
4095
|
+
Are.Styles,
|
|
4096
|
+
__decorateParam(0, Yt(te))
|
|
4097
|
+
], _AppWindow.prototype, "styles", 1);
|
|
4098
|
+
var AppWindow = _AppWindow;
|
|
4099
|
+
|
|
4100
|
+
// examples/os-desktop/src/runtime/AppComponentResolver.fragment.ts
|
|
4101
|
+
var AppComponentResolver = class extends AreComponentResolver {
|
|
4102
|
+
constructor(data) {
|
|
4103
|
+
super({ name: "AppComponentResolver" });
|
|
4104
|
+
/** Cache of in-flight / settled bundle imports, keyed by bundle URL. */
|
|
4105
|
+
this._bundles = /* @__PURE__ */ new Map();
|
|
4106
|
+
/** Tags already handed to the engine (registered) — skip re-resolving. */
|
|
4107
|
+
this._resolved = /* @__PURE__ */ new Set();
|
|
4108
|
+
this._registry = data.registry;
|
|
4109
|
+
}
|
|
4110
|
+
/**
|
|
4111
|
+
* Lazy path: resolve a single tag on a render miss. Returns `undefined` for
|
|
4112
|
+
* tags that do not belong to any installed app (they stay plain elements)
|
|
4113
|
+
* or that the engine already has registered.
|
|
4114
|
+
*/
|
|
4115
|
+
async resolve(entity) {
|
|
4116
|
+
if (this._resolved.has(entity)) {
|
|
4117
|
+
return void 0;
|
|
4118
|
+
}
|
|
4119
|
+
const app = this._registry.appByComponentTag(entity);
|
|
4120
|
+
if (!app) {
|
|
4121
|
+
return void 0;
|
|
4122
|
+
}
|
|
4123
|
+
const exportName = this.exportNameForTag(app, entity);
|
|
4124
|
+
if (!exportName) {
|
|
4125
|
+
return void 0;
|
|
4126
|
+
}
|
|
4127
|
+
const mod = await this.importBundle(app.bundle);
|
|
4128
|
+
const Component = mod[exportName];
|
|
4129
|
+
if (typeof Component !== "function") {
|
|
4130
|
+
return void 0;
|
|
4131
|
+
}
|
|
4132
|
+
this._resolved.add(entity);
|
|
4133
|
+
return Component;
|
|
4134
|
+
}
|
|
4135
|
+
/**
|
|
4136
|
+
* Eager path: fetch an app's WHOLE bundle (all its components) on install,
|
|
4137
|
+
* so "adding an app loads all its necessary components" up front. Called by
|
|
4138
|
+
* the Launchpad. It only *warms* the import cache and returns the classes —
|
|
4139
|
+
* it deliberately does NOT mark the tags resolved, so the engine still does
|
|
4140
|
+
* the actual global registration lazily on first render (now instant,
|
|
4141
|
+
* because the bundle is already in memory). This keeps a single owner of
|
|
4142
|
+
* registration (the engine) while making install the moment the code lands.
|
|
4143
|
+
*/
|
|
4144
|
+
async preload(app) {
|
|
4145
|
+
const mod = await this.importBundle(app.bundle);
|
|
4146
|
+
const classes = [];
|
|
4147
|
+
for (const ref of app.components) {
|
|
4148
|
+
const Component = mod[ref.export];
|
|
4149
|
+
if (typeof Component === "function") {
|
|
4150
|
+
classes.push(Component);
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
return classes;
|
|
4154
|
+
}
|
|
4155
|
+
exportNameForTag(app, tag) {
|
|
4156
|
+
return app.components.find((c) => c.tag === tag)?.export;
|
|
4157
|
+
}
|
|
4158
|
+
/**
|
|
4159
|
+
* Dynamic import of an app bundle, de-duplicated per URL. Because the shell
|
|
4160
|
+
* and every app bundle are code-split against shared framework chunks, the
|
|
4161
|
+
* classes returned here `extend` the SAME `Are`/DI runtime the OS shell runs.
|
|
4162
|
+
*/
|
|
4163
|
+
importBundle(url) {
|
|
4164
|
+
let pending = this._bundles.get(url);
|
|
4165
|
+
if (!pending) {
|
|
4166
|
+
pending = import(
|
|
4167
|
+
/* @vite-ignore */
|
|
4168
|
+
url
|
|
4169
|
+
);
|
|
4170
|
+
this._bundles.set(url, pending);
|
|
4171
|
+
}
|
|
4172
|
+
return pending;
|
|
4173
|
+
}
|
|
4174
|
+
};
|
|
4175
|
+
__name(AppComponentResolver, "AppComponentResolver");
|
|
4176
|
+
AppComponentResolver = __decorateClass([
|
|
4177
|
+
R2.Define({
|
|
4178
|
+
namespace: "a-are-os-desktop",
|
|
4179
|
+
description: "Resolves unregistered component tags to classes from their owning app bundle (one code-split bundle per app, many components). Plugged into the engine via AreComponentResolver; also exposes preload() to eagerly load every component of an app on install."
|
|
4180
|
+
})
|
|
4181
|
+
], AppComponentResolver);
|
|
4182
|
+
|
|
4183
|
+
// examples/os-desktop/src/os/Launchpad.component.ts
|
|
4184
|
+
var _OsLaunchpad = class _OsLaunchpad extends Are {
|
|
4185
|
+
template(node, registry) {
|
|
4186
|
+
node.setContent(this.build(registry));
|
|
4187
|
+
}
|
|
4188
|
+
async install(event, bus, registry, resolver, logger) {
|
|
4189
|
+
event.get("native")?.preventDefault();
|
|
4190
|
+
const id = event.get("args")?.[0];
|
|
4191
|
+
const app = id ? registry.get(id) : void 0;
|
|
4192
|
+
if (!app) return;
|
|
4193
|
+
registry.install(app.id);
|
|
4194
|
+
try {
|
|
4195
|
+
await resolver.preload(app);
|
|
4196
|
+
logger.log("green", `Installed "${app.name}" (${app.components.length} components loaded).`);
|
|
4197
|
+
} catch (error) {
|
|
4198
|
+
logger.error(error);
|
|
4199
|
+
}
|
|
4200
|
+
const path = `/app/${app.id}`;
|
|
4201
|
+
history.pushState({}, "", path);
|
|
4202
|
+
bus.next(new OSRoute(path));
|
|
4203
|
+
}
|
|
4204
|
+
open(event, bus) {
|
|
4205
|
+
event.get("native")?.preventDefault();
|
|
4206
|
+
const id = event.get("args")?.[0];
|
|
4207
|
+
if (!id) return;
|
|
4208
|
+
const path = `/app/${id}`;
|
|
4209
|
+
history.pushState({}, "", path);
|
|
4210
|
+
bus.next(new OSRoute(path));
|
|
4211
|
+
}
|
|
4212
|
+
close(event, bus) {
|
|
4213
|
+
event.get("native")?.preventDefault();
|
|
4214
|
+
history.pushState({}, "", "/desktop");
|
|
4215
|
+
bus.next(new OSRoute("/desktop"));
|
|
4216
|
+
}
|
|
4217
|
+
build(registry) {
|
|
4218
|
+
const tiles = registry.available().map((app) => {
|
|
4219
|
+
const installed = registry.isInstalled(app.id);
|
|
4220
|
+
const action = installed ? `<button class="lp-btn lp-open" @click="$open('${app.id}')">Open</button>` : `<button class="lp-btn lp-install" @click="$install('${app.id}')">Install</button>`;
|
|
4221
|
+
const chips = app.components.map((c) => `<code><${c.tag}></code>`).join("");
|
|
4222
|
+
return `
|
|
4223
|
+
<article class="lp-tile" style="--accent:${app.accent}">
|
|
4224
|
+
<div class="lp-icon">${app.icon}</div>
|
|
4225
|
+
<h3 class="lp-name">${app.name}</h3>
|
|
4226
|
+
<p class="lp-tagline">${app.tagline}</p>
|
|
4227
|
+
<div class="lp-meta">
|
|
4228
|
+
<span class="lp-bundle">${app.bundle}</span>
|
|
4229
|
+
<div class="lp-components">${chips}</div>
|
|
4230
|
+
</div>
|
|
4231
|
+
${action}
|
|
4232
|
+
</article>
|
|
4233
|
+
`;
|
|
4234
|
+
}).join("");
|
|
4235
|
+
return `
|
|
4236
|
+
<div class="lp-backdrop" @click.self="$close()">
|
|
4237
|
+
<div class="lp">
|
|
4238
|
+
<header class="lp-head">
|
|
4239
|
+
<h1>Launchpad</h1>
|
|
4240
|
+
<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>
|
|
4241
|
+
</header>
|
|
4242
|
+
<div class="lp-grid">${tiles}</div>
|
|
4243
|
+
</div>
|
|
4244
|
+
</div>
|
|
4245
|
+
`;
|
|
4246
|
+
}
|
|
4247
|
+
styles(node) {
|
|
4248
|
+
node.setStyles(`
|
|
4249
|
+
.lp-backdrop {
|
|
4250
|
+
position: absolute;
|
|
4251
|
+
inset: 0;
|
|
4252
|
+
display: flex;
|
|
4253
|
+
align-items: center;
|
|
4254
|
+
justify-content: center;
|
|
4255
|
+
background: rgba(8, 6, 18, 0.55);
|
|
4256
|
+
backdrop-filter: blur(26px) saturate(160%);
|
|
4257
|
+
-webkit-backdrop-filter: blur(26px) saturate(160%);
|
|
4258
|
+
animation: lp-fade 0.2s ease;
|
|
4259
|
+
}
|
|
4260
|
+
@keyframes lp-fade { from { opacity: 0; } to { opacity: 1; } }
|
|
4261
|
+
.lp { width: min(900px, 88vw); max-height: 80vh; overflow: auto; }
|
|
4262
|
+
.lp-head { text-align: center; margin-bottom: 30px; }
|
|
4263
|
+
.lp-head h1 { font-size: 30px; font-weight: 800; letter-spacing: -0.02em; }
|
|
4264
|
+
.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; }
|
|
4265
|
+
.lp-grid {
|
|
4266
|
+
display: grid;
|
|
4267
|
+
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
|
4268
|
+
gap: 18px;
|
|
4269
|
+
}
|
|
4270
|
+
.lp-tile {
|
|
4271
|
+
display: flex;
|
|
4272
|
+
flex-direction: column;
|
|
4273
|
+
align-items: flex-start;
|
|
4274
|
+
gap: 8px;
|
|
4275
|
+
padding: 22px;
|
|
4276
|
+
border-radius: 18px;
|
|
4277
|
+
background: rgba(30, 24, 48, 0.5);
|
|
4278
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
4279
|
+
}
|
|
4280
|
+
.lp-icon {
|
|
4281
|
+
width: 56px; height: 56px;
|
|
4282
|
+
display: flex; align-items: center; justify-content: center;
|
|
4283
|
+
font-size: 30px;
|
|
4284
|
+
border-radius: 15px;
|
|
4285
|
+
background: linear-gradient(160deg, color-mix(in srgb, var(--accent) 85%, white 0%), color-mix(in srgb, var(--accent) 65%, black 20%));
|
|
4286
|
+
box-shadow: inset 0 1px 0 rgba(255,255,255,0.35);
|
|
4287
|
+
margin-bottom: 4px;
|
|
4288
|
+
}
|
|
4289
|
+
.lp-name { font-size: 17px; font-weight: 700; }
|
|
4290
|
+
.lp-tagline { font-size: 13px; color: rgba(245,245,247,0.7); line-height: 1.5; }
|
|
4291
|
+
.lp-meta { width: 100%; margin: 8px 0 6px; }
|
|
4292
|
+
.lp-bundle { font-size: 11px; font-family: ui-monospace, monospace; color: #8b7fb0; }
|
|
4293
|
+
.lp-components { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px; }
|
|
4294
|
+
.lp-components code { font-size: 10px; padding: 2px 6px; border-radius: 6px; background: rgba(255,255,255,0.08); color: #c9b8ff; }
|
|
4295
|
+
.lp-btn {
|
|
4296
|
+
margin-top: auto;
|
|
4297
|
+
align-self: stretch;
|
|
4298
|
+
padding: 9px 14px;
|
|
4299
|
+
border: none;
|
|
4300
|
+
border-radius: 10px;
|
|
4301
|
+
font-size: 13px;
|
|
4302
|
+
font-weight: 600;
|
|
4303
|
+
cursor: pointer;
|
|
4304
|
+
transition: filter 0.15s;
|
|
4305
|
+
}
|
|
4306
|
+
.lp-btn:hover { filter: brightness(1.12); }
|
|
4307
|
+
.lp-install { background: var(--accent); color: white; }
|
|
4308
|
+
.lp-open { background: rgba(255,255,255,0.14); color: #f5f5f7; }
|
|
4309
|
+
`);
|
|
4310
|
+
}
|
|
4311
|
+
};
|
|
4312
|
+
__name(_OsLaunchpad, "OsLaunchpad");
|
|
4313
|
+
__decorateClass([
|
|
4314
|
+
Are.Template,
|
|
4315
|
+
__decorateParam(0, Yt(te)),
|
|
4316
|
+
__decorateParam(1, Yt(AppRegistry))
|
|
4317
|
+
], _OsLaunchpad.prototype, "template", 1);
|
|
4318
|
+
__decorateClass([
|
|
4319
|
+
Are.EventHandler,
|
|
4320
|
+
__decorateParam(0, Yt(AreEvent)),
|
|
4321
|
+
__decorateParam(1, Yt(A_SignalBus)),
|
|
4322
|
+
__decorateParam(2, Yt(AppRegistry)),
|
|
4323
|
+
__decorateParam(3, Yt(AppComponentResolver)),
|
|
4324
|
+
__decorateParam(4, Yt(A_Logger))
|
|
4325
|
+
], _OsLaunchpad.prototype, "install", 1);
|
|
4326
|
+
__decorateClass([
|
|
4327
|
+
Are.EventHandler,
|
|
4328
|
+
__decorateParam(0, Yt(AreEvent)),
|
|
4329
|
+
__decorateParam(1, Yt(A_SignalBus))
|
|
4330
|
+
], _OsLaunchpad.prototype, "open", 1);
|
|
4331
|
+
__decorateClass([
|
|
4332
|
+
Are.EventHandler,
|
|
4333
|
+
__decorateParam(0, Yt(AreEvent)),
|
|
4334
|
+
__decorateParam(1, Yt(A_SignalBus))
|
|
4335
|
+
], _OsLaunchpad.prototype, "close", 1);
|
|
4336
|
+
__decorateClass([
|
|
4337
|
+
Are.Styles,
|
|
4338
|
+
__decorateParam(0, Yt(te))
|
|
4339
|
+
], _OsLaunchpad.prototype, "styles", 1);
|
|
4340
|
+
var OsLaunchpad = _OsLaunchpad;
|
|
4341
|
+
|
|
4342
|
+
// examples/os-desktop/src/concept.ts
|
|
4343
|
+
(async () => {
|
|
4344
|
+
try {
|
|
4345
|
+
const available = await fetch("/api/apps").then((res) => res.json()).catch(() => []);
|
|
4346
|
+
const registry = new AppRegistry({ available });
|
|
4347
|
+
const componentResolver = new AppComponentResolver({ registry });
|
|
4348
|
+
const signalsContext = new AreSignalsContext({});
|
|
4349
|
+
const container = new AreContainer({
|
|
4350
|
+
name: "ARE OS Desktop",
|
|
4351
|
+
components: [
|
|
4352
|
+
// ── OS shell ─────────────────────────────────────────────
|
|
4353
|
+
OsDesktop,
|
|
4354
|
+
MenuBar,
|
|
4355
|
+
OsHud,
|
|
4356
|
+
OsDock,
|
|
4357
|
+
AppStage,
|
|
4358
|
+
AppWindow,
|
|
4359
|
+
OsLaunchpad,
|
|
4360
|
+
// ── Directives ───────────────────────────────────────────
|
|
4361
|
+
AreDirectiveIf,
|
|
4362
|
+
AreDirectiveFor,
|
|
4363
|
+
AreDirectiveShow,
|
|
4364
|
+
// ── Engine ───────────────────────────────────────────────
|
|
4365
|
+
A_SignalBus,
|
|
4366
|
+
AreRoot,
|
|
4367
|
+
AreRouteWatcher,
|
|
4368
|
+
ConfigReader,
|
|
4369
|
+
AreHTMLEngine,
|
|
4370
|
+
A_Logger
|
|
4371
|
+
],
|
|
4372
|
+
entities: [
|
|
4373
|
+
AreInit,
|
|
4374
|
+
OSRoute,
|
|
4375
|
+
MouseState,
|
|
4376
|
+
SelectionState
|
|
4377
|
+
],
|
|
4378
|
+
fragments: [
|
|
4379
|
+
// EVERY dispatched signal type must appear here too, or
|
|
4380
|
+
// state.has() returns false and the bus drops the signal.
|
|
4381
|
+
new A_SignalState([AreInit, OSRoute, MouseState, SelectionState]),
|
|
4382
|
+
signalsContext,
|
|
4383
|
+
new AreHTMLEngineContext({ container: document }),
|
|
4384
|
+
registry,
|
|
4385
|
+
componentResolver,
|
|
4386
|
+
new A_Config({
|
|
4387
|
+
defaults: {
|
|
4388
|
+
[A_LOGGER_ENV_KEYS.LOG_LEVEL]: "info"
|
|
4389
|
+
}
|
|
4390
|
+
})
|
|
4391
|
+
]
|
|
4392
|
+
});
|
|
4393
|
+
const concept = new ct({
|
|
4394
|
+
name: "adaas-are-example-os-desktop",
|
|
4395
|
+
fragments: [
|
|
4396
|
+
new A_Config({
|
|
4397
|
+
variables: ["CONFIG_VERBOSE", "DEV_MODE"],
|
|
4398
|
+
defaults: { CONFIG_VERBOSE: true, DEV_MODE: true }
|
|
4399
|
+
})
|
|
4400
|
+
],
|
|
4401
|
+
components: [A_Logger, ConfigReader, A_Polyfill],
|
|
4402
|
+
containers: [container]
|
|
4403
|
+
});
|
|
4404
|
+
await concept.load();
|
|
4405
|
+
await concept.start();
|
|
4406
|
+
} catch (error) {
|
|
4407
|
+
const logger = _.root.resolve(A_Logger);
|
|
4408
|
+
logger.error(error);
|
|
4409
|
+
}
|
|
4410
|
+
})();
|