@adaas/are-html 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +4 -4
  2. package/dist/browser/index.d.mts +88 -5
  3. package/dist/browser/index.mjs +542 -176
  4. package/dist/browser/index.mjs.map +1 -1
  5. package/dist/node/attributes/AreBinding.attribute.js +17 -4
  6. package/dist/node/attributes/AreBinding.attribute.js.map +1 -1
  7. package/dist/node/attributes/AreBinding.attribute.mjs +10 -3
  8. package/dist/node/attributes/AreBinding.attribute.mjs.map +1 -1
  9. package/dist/node/attributes/AreDirective.attribute.js +17 -4
  10. package/dist/node/attributes/AreDirective.attribute.js.map +1 -1
  11. package/dist/node/attributes/AreDirective.attribute.mjs +10 -3
  12. package/dist/node/attributes/AreDirective.attribute.mjs.map +1 -1
  13. package/dist/node/attributes/AreEvent.attribute.js +17 -4
  14. package/dist/node/attributes/AreEvent.attribute.js.map +1 -1
  15. package/dist/node/attributes/AreEvent.attribute.mjs +10 -3
  16. package/dist/node/attributes/AreEvent.attribute.mjs.map +1 -1
  17. package/dist/node/attributes/AreStatic.attribute.js +17 -4
  18. package/dist/node/attributes/AreStatic.attribute.js.map +1 -1
  19. package/dist/node/attributes/AreStatic.attribute.mjs +10 -3
  20. package/dist/node/attributes/AreStatic.attribute.mjs.map +1 -1
  21. package/dist/node/directives/AreDirectiveFor.directive.d.mts +8 -0
  22. package/dist/node/directives/AreDirectiveFor.directive.d.ts +8 -0
  23. package/dist/node/directives/AreDirectiveFor.directive.js +78 -33
  24. package/dist/node/directives/AreDirectiveFor.directive.js.map +1 -1
  25. package/dist/node/directives/AreDirectiveFor.directive.mjs +78 -33
  26. package/dist/node/directives/AreDirectiveFor.directive.mjs.map +1 -1
  27. package/dist/node/directives/AreDirectiveIf.directive.d.mts +18 -0
  28. package/dist/node/directives/AreDirectiveIf.directive.d.ts +18 -0
  29. package/dist/node/directives/AreDirectiveIf.directive.js +10 -3
  30. package/dist/node/directives/AreDirectiveIf.directive.js.map +1 -1
  31. package/dist/node/directives/AreDirectiveIf.directive.mjs +10 -3
  32. package/dist/node/directives/AreDirectiveIf.directive.mjs.map +1 -1
  33. package/dist/node/engine/AreHTML.compiler.d.mts +2 -2
  34. package/dist/node/engine/AreHTML.compiler.d.ts +2 -2
  35. package/dist/node/engine/AreHTML.compiler.js +57 -29
  36. package/dist/node/engine/AreHTML.compiler.js.map +1 -1
  37. package/dist/node/engine/AreHTML.compiler.mjs +58 -30
  38. package/dist/node/engine/AreHTML.compiler.mjs.map +1 -1
  39. package/dist/node/engine/AreHTML.constants.d.mts +53 -1
  40. package/dist/node/engine/AreHTML.constants.d.ts +53 -1
  41. package/dist/node/engine/AreHTML.constants.js +100 -0
  42. package/dist/node/engine/AreHTML.constants.js.map +1 -1
  43. package/dist/node/engine/AreHTML.constants.mjs +93 -0
  44. package/dist/node/engine/AreHTML.constants.mjs.map +1 -1
  45. package/dist/node/engine/AreHTML.context.d.mts +6 -2
  46. package/dist/node/engine/AreHTML.context.d.ts +6 -2
  47. package/dist/node/engine/AreHTML.context.js +42 -7
  48. package/dist/node/engine/AreHTML.context.js.map +1 -1
  49. package/dist/node/engine/AreHTML.context.mjs +35 -6
  50. package/dist/node/engine/AreHTML.context.mjs.map +1 -1
  51. package/dist/node/engine/AreHTML.engine.js +10 -7
  52. package/dist/node/engine/AreHTML.engine.js.map +1 -1
  53. package/dist/node/engine/AreHTML.engine.mjs +10 -7
  54. package/dist/node/engine/AreHTML.engine.mjs.map +1 -1
  55. package/dist/node/engine/AreHTML.interpreter.js +155 -43
  56. package/dist/node/engine/AreHTML.interpreter.js.map +1 -1
  57. package/dist/node/engine/AreHTML.interpreter.mjs +155 -43
  58. package/dist/node/engine/AreHTML.interpreter.mjs.map +1 -1
  59. package/dist/node/engine/AreHTML.lifecycle.js +17 -12
  60. package/dist/node/engine/AreHTML.lifecycle.js.map +1 -1
  61. package/dist/node/engine/AreHTML.lifecycle.mjs +9 -2
  62. package/dist/node/engine/AreHTML.lifecycle.mjs.map +1 -1
  63. package/dist/node/engine/AreHTML.tokenizer.js +14 -9
  64. package/dist/node/engine/AreHTML.tokenizer.js.map +1 -1
  65. package/dist/node/engine/AreHTML.tokenizer.mjs +10 -3
  66. package/dist/node/engine/AreHTML.tokenizer.mjs.map +1 -1
  67. package/dist/node/engine/AreHTML.transformer.js +13 -8
  68. package/dist/node/engine/AreHTML.transformer.js.map +1 -1
  69. package/dist/node/engine/AreHTML.transformer.mjs +9 -2
  70. package/dist/node/engine/AreHTML.transformer.mjs.map +1 -1
  71. package/dist/node/index.d.mts +2 -1
  72. package/dist/node/index.d.ts +2 -1
  73. package/dist/node/index.js +3 -3
  74. package/dist/node/index.mjs +1 -1
  75. package/dist/node/instructions/AddAttribute.instruction.js +3 -4
  76. package/dist/node/instructions/AddAttribute.instruction.js.map +1 -1
  77. package/dist/node/instructions/AddAttribute.instruction.mjs +3 -4
  78. package/dist/node/instructions/AddAttribute.instruction.mjs.map +1 -1
  79. package/dist/node/instructions/AddComment.instruction.js +3 -4
  80. package/dist/node/instructions/AddComment.instruction.js.map +1 -1
  81. package/dist/node/instructions/AddComment.instruction.mjs +3 -4
  82. package/dist/node/instructions/AddComment.instruction.mjs.map +1 -1
  83. package/dist/node/instructions/AddElement.instruction.js +3 -4
  84. package/dist/node/instructions/AddElement.instruction.js.map +1 -1
  85. package/dist/node/instructions/AddElement.instruction.mjs +3 -4
  86. package/dist/node/instructions/AddElement.instruction.mjs.map +1 -1
  87. package/dist/node/instructions/AddInterpolation.instruction.js +3 -4
  88. package/dist/node/instructions/AddInterpolation.instruction.js.map +1 -1
  89. package/dist/node/instructions/AddInterpolation.instruction.mjs +3 -4
  90. package/dist/node/instructions/AddInterpolation.instruction.mjs.map +1 -1
  91. package/dist/node/instructions/AddListener.instruction.js +3 -4
  92. package/dist/node/instructions/AddListener.instruction.js.map +1 -1
  93. package/dist/node/instructions/AddListener.instruction.mjs +3 -4
  94. package/dist/node/instructions/AddListener.instruction.mjs.map +1 -1
  95. package/dist/node/instructions/AddStyle.instruction.js +3 -4
  96. package/dist/node/instructions/AddStyle.instruction.js.map +1 -1
  97. package/dist/node/instructions/AddStyle.instruction.mjs +3 -4
  98. package/dist/node/instructions/AddStyle.instruction.mjs.map +1 -1
  99. package/dist/node/instructions/AddText.instruction.js +3 -4
  100. package/dist/node/instructions/AddText.instruction.js.map +1 -1
  101. package/dist/node/instructions/AddText.instruction.mjs +3 -4
  102. package/dist/node/instructions/AddText.instruction.mjs.map +1 -1
  103. package/dist/node/lib/AreDirective/AreDirective.component.js +5 -0
  104. package/dist/node/lib/AreDirective/AreDirective.component.js.map +1 -1
  105. package/dist/node/lib/AreDirective/AreDirective.component.mjs +5 -0
  106. package/dist/node/lib/AreDirective/AreDirective.component.mjs.map +1 -1
  107. package/dist/node/lib/AreHTMLAttribute/AreHTML.attribute.js +17 -4
  108. package/dist/node/lib/AreHTMLAttribute/AreHTML.attribute.js.map +1 -1
  109. package/dist/node/lib/AreHTMLAttribute/AreHTML.attribute.mjs +10 -3
  110. package/dist/node/lib/AreHTMLAttribute/AreHTML.attribute.mjs.map +1 -1
  111. package/dist/node/lib/AreHTMLNode/AreHTMLNode.js +3 -4
  112. package/dist/node/lib/AreHTMLNode/AreHTMLNode.js.map +1 -1
  113. package/dist/node/lib/AreHTMLNode/AreHTMLNode.mjs +3 -4
  114. package/dist/node/lib/AreHTMLNode/AreHTMLNode.mjs.map +1 -1
  115. package/dist/node/lib/AreRoot/AreRoot.component.js +3 -4
  116. package/dist/node/lib/AreRoot/AreRoot.component.js.map +1 -1
  117. package/dist/node/lib/AreRoot/AreRoot.component.mjs +3 -4
  118. package/dist/node/lib/AreRoot/AreRoot.component.mjs.map +1 -1
  119. package/dist/node/lib/{AreWatcher/AreWatcher.component.d.mts → AreRouteWatcher/AreRouteWatcher.component.d.mts} +2 -2
  120. package/dist/node/lib/{AreWatcher/AreWatcher.component.d.ts → AreRouteWatcher/AreRouteWatcher.component.d.ts} +2 -2
  121. package/dist/node/lib/{AreWatcher/AreWatcher.component.js → AreRouteWatcher/AreRouteWatcher.component.js} +9 -10
  122. package/dist/node/lib/AreRouteWatcher/AreRouteWatcher.component.js.map +1 -0
  123. package/dist/node/lib/{AreWatcher/AreWatcher.component.mjs → AreRouteWatcher/AreRouteWatcher.component.mjs} +10 -11
  124. package/dist/node/lib/AreRouteWatcher/AreRouteWatcher.component.mjs.map +1 -0
  125. package/dist/node/lib/AreStyle/AreStyle.context.js +17 -4
  126. package/dist/node/lib/AreStyle/AreStyle.context.js.map +1 -1
  127. package/dist/node/lib/AreStyle/AreStyle.context.mjs +10 -3
  128. package/dist/node/lib/AreStyle/AreStyle.context.mjs.map +1 -1
  129. package/dist/node/nodes/AreComment.js +17 -4
  130. package/dist/node/nodes/AreComment.js.map +1 -1
  131. package/dist/node/nodes/AreComment.mjs +10 -3
  132. package/dist/node/nodes/AreComment.mjs.map +1 -1
  133. package/dist/node/nodes/AreComponent.js +3 -4
  134. package/dist/node/nodes/AreComponent.js.map +1 -1
  135. package/dist/node/nodes/AreComponent.mjs +3 -4
  136. package/dist/node/nodes/AreComponent.mjs.map +1 -1
  137. package/dist/node/nodes/AreInterpolation.js +17 -4
  138. package/dist/node/nodes/AreInterpolation.js.map +1 -1
  139. package/dist/node/nodes/AreInterpolation.mjs +10 -3
  140. package/dist/node/nodes/AreInterpolation.mjs.map +1 -1
  141. package/dist/node/nodes/AreRoot.js +3 -4
  142. package/dist/node/nodes/AreRoot.js.map +1 -1
  143. package/dist/node/nodes/AreRoot.mjs +3 -4
  144. package/dist/node/nodes/AreRoot.mjs.map +1 -1
  145. package/dist/node/nodes/AreText.js +17 -4
  146. package/dist/node/nodes/AreText.js.map +1 -1
  147. package/dist/node/nodes/AreText.mjs +10 -3
  148. package/dist/node/nodes/AreText.mjs.map +1 -1
  149. package/dist/node/signals/AreRoute.signal.js +18 -5
  150. package/dist/node/signals/AreRoute.signal.js.map +1 -1
  151. package/dist/node/signals/AreRoute.signal.mjs +10 -3
  152. package/dist/node/signals/AreRoute.signal.mjs.map +1 -1
  153. package/docs/SYNTAX.md +714 -0
  154. package/docs/arehtml.monaco.json +235 -0
  155. package/docs/arehtml.monaco.ts +119 -0
  156. package/examples/dashboard/dist/index.html +1 -1
  157. package/examples/dashboard/dist/mpioi5ab-8c3oa9.js +13674 -0
  158. package/examples/jumpstart/dist/index.html +1 -1
  159. package/examples/{dashboard/dist/mnzfypsd-6zjt7w.js → jumpstart/dist/mor90p6y-0plg7g.js} +1869 -1926
  160. package/examples/jumpstart/dist/{mnpl1g4i-nobz9g.js → mor90p7p-1898bz.js} +2797 -2282
  161. package/examples/jumpstart/src/components/List.component.ts +14 -13
  162. package/examples/jumpstart/src/concept.ts +5 -4
  163. package/jest.config.ts +1 -1
  164. package/package.json +10 -6
  165. package/src/attributes/AreBinding.attribute.ts +5 -0
  166. package/src/attributes/AreDirective.attribute.ts +5 -0
  167. package/src/attributes/AreEvent.attribute.ts +5 -0
  168. package/src/attributes/AreStatic.attribute.ts +5 -0
  169. package/src/directives/AreDirectiveFor.directive.ts +97 -60
  170. package/src/directives/AreDirectiveIf.directive.ts +37 -15
  171. package/src/engine/AreHTML.compiler.ts +64 -36
  172. package/src/engine/AreHTML.constants.ts +144 -0
  173. package/src/engine/AreHTML.context.ts +33 -4
  174. package/src/engine/AreHTML.engine.ts +12 -7
  175. package/src/engine/AreHTML.interpreter.ts +195 -68
  176. package/src/engine/AreHTML.lifecycle.ts +5 -0
  177. package/src/engine/AreHTML.tokenizer.ts +6 -1
  178. package/src/engine/AreHTML.transformer.ts +5 -0
  179. package/src/index.ts +2 -2
  180. package/src/instructions/AddAttribute.instruction.ts +3 -4
  181. package/src/instructions/AddComment.instruction.ts +3 -4
  182. package/src/instructions/AddElement.instruction.ts +3 -4
  183. package/src/instructions/AddInterpolation.instruction.ts +3 -4
  184. package/src/instructions/AddListener.instruction.ts +3 -4
  185. package/src/instructions/AddStyle.instruction.ts +3 -4
  186. package/src/instructions/AddText.instruction.ts +3 -4
  187. package/src/lib/AreDirective/AreDirective.component.ts +5 -0
  188. package/src/lib/AreHTMLAttribute/AreHTML.attribute.ts +5 -0
  189. package/src/lib/AreHTMLNode/AreHTMLNode.ts +3 -4
  190. package/src/lib/AreRoot/AreRoot.component.ts +3 -4
  191. package/src/lib/{AreWatcher/AreWatcher.component.ts → AreRouteWatcher/AreRouteWatcher.component.ts} +5 -6
  192. package/src/lib/AreStyle/AreStyle.context.ts +5 -0
  193. package/src/nodes/AreComment.ts +5 -0
  194. package/src/nodes/AreComponent.ts +3 -4
  195. package/src/nodes/AreInterpolation.ts +5 -0
  196. package/src/nodes/AreRoot.ts +3 -4
  197. package/src/nodes/AreText.ts +5 -0
  198. package/src/signals/AreRoute.signal.ts +5 -0
  199. package/dist/node/lib/AreWatcher/AreWatcher.component.js.map +0 -1
  200. package/dist/node/lib/AreWatcher/AreWatcher.component.mjs.map +0 -1
@@ -1,2 +1,146 @@
1
+ /**
2
+ * Boolean HTML attributes whose presence (regardless of value) implies "true",
3
+ * and whose absence implies "false". Setting these via `setAttribute(name, value)`
4
+ * always renders the attribute, which is wrong for reactive bindings.
5
+ *
6
+ * Reference: https://html.spec.whatwg.org/multipage/indices.html#attributes-3
7
+ */
8
+ export const BOOLEAN_ATTRIBUTES = new Set<string>([
9
+ 'allowfullscreen',
10
+ 'async',
11
+ 'autofocus',
12
+ 'autoplay',
13
+ 'checked',
14
+ 'controls',
15
+ 'default',
16
+ 'defer',
17
+ 'disabled',
18
+ 'formnovalidate',
19
+ 'hidden',
20
+ 'inert',
21
+ 'ismap',
22
+ 'itemscope',
23
+ 'loop',
24
+ 'multiple',
25
+ 'muted',
26
+ 'nomodule',
27
+ 'novalidate',
28
+ 'open',
29
+ 'playsinline',
30
+ 'readonly',
31
+ 'required',
32
+ 'reversed',
33
+ 'selected',
34
+ ]);
35
+
36
+ export function isBooleanAttribute(name: string): boolean {
37
+ return BOOLEAN_ATTRIBUTES.has(name.toLowerCase());
38
+ }
39
+
40
+ /**
41
+ * Form-control IDL properties that must be set as a JS property
42
+ * (not just an attribute) so live user input is reflected.
43
+ *
44
+ * `<input value="foo">` only sets the *default* value;
45
+ * `input.value = "foo"` updates the live state.
46
+ */
47
+ export const IDL_FORM_PROPERTIES: Record<string, Set<string>> = {
48
+ INPUT: new Set(['value', 'checked', 'indeterminate']),
49
+ TEXTAREA: new Set(['value']),
50
+ SELECT: new Set(['value']),
51
+ OPTION: new Set(['selected']),
52
+ };
53
+
54
+ export function isIDLFormProperty(tagName: string, attrName: string): boolean {
55
+ const set = IDL_FORM_PROPERTIES[tagName.toUpperCase()];
56
+ return !!set && set.has(attrName);
57
+ }
58
+
59
+ /**
60
+ * Normalize a `:class` binding value into a single space-separated string.
61
+ * Supports the common shapes:
62
+ * - string → "a b"
63
+ * - array<string | object | falsy> → ["a", { b: true, c: cond }, null]
64
+ * - object<string, boolean> → { a: true, b: false }
65
+ */
66
+ export function normalizeClassValue(value: any): string {
67
+ if (value === null || value === undefined || value === false) return '';
68
+ if (typeof value === 'string') return value;
69
+ if (typeof value === 'number') return String(value);
70
+
71
+ if (Array.isArray(value)) {
72
+ return value.map(normalizeClassValue).filter(Boolean).join(' ');
73
+ }
74
+ if (typeof value === 'object') {
75
+ const parts: string[] = [];
76
+ for (const key of Object.keys(value)) {
77
+ if (value[key]) parts.push(key);
78
+ }
79
+ return parts.join(' ');
80
+ }
81
+ return '';
82
+ }
83
+
84
+ /**
85
+ * Normalize a `:style` binding value into an inline-style string.
86
+ * Supports:
87
+ * - string → "color: red; font-size: 12px"
88
+ * - object<string, string|number> → { color: 'red', fontSize: '12px' }
89
+ * - array<string | object> → ['color: red', { fontSize: '12px' }]
90
+ */
91
+ export function normalizeStyleValue(value: any): string {
92
+ if (value === null || value === undefined || value === false) return '';
93
+ if (typeof value === 'string') return value;
94
+ if (typeof value === 'number') return String(value);
95
+
96
+ if (Array.isArray(value)) {
97
+ return value.map(normalizeStyleValue).filter(Boolean).join('; ');
98
+ }
99
+ if (typeof value === 'object') {
100
+ const parts: string[] = [];
101
+ for (const key of Object.keys(value)) {
102
+ const v = value[key];
103
+ if (v === null || v === undefined || v === false) continue;
104
+ const kebab = key.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
105
+ parts.push(`${kebab}: ${v}`);
106
+ }
107
+ return parts.join('; ');
108
+ }
109
+ return '';
110
+ }
111
+
112
+ /**
113
+ * Parse a DOM event name with modifiers, e.g. "click.stop.prevent" or "keydown.enter".
114
+ * Returns the bare event name plus the modifier set.
115
+ */
116
+ export interface ParsedEventName {
117
+ event: string;
118
+ modifiers: Set<string>;
119
+ }
120
+
121
+ export function parseEventName(raw: string): ParsedEventName {
122
+ const [event, ...modifiers] = raw.split('.');
123
+ return { event, modifiers: new Set(modifiers) };
124
+ }
125
+
126
+ /**
127
+ * Known event-listener modifiers that map directly to addEventListener options.
128
+ */
129
+ export const LISTENER_OPTION_MODIFIERS = new Set(['capture', 'once', 'passive']);
130
+
131
+ /**
132
+ * Coerce a value into a string for DOM consumption.
133
+ * Avoids "undefined"/"null"/"[object Object]" leaks into the DOM.
134
+ */
135
+ export function toDOMString(value: any): string {
136
+ if (value === null || value === undefined) return '';
137
+ if (typeof value === 'string') return value;
138
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value);
139
+ try {
140
+ return JSON.stringify(value);
141
+ } catch {
142
+ return '';
143
+ }
144
+ }
1
145
 
2
146
 
@@ -1,7 +1,12 @@
1
1
  import { AreContext, AreInstruction, AreNode } from "@adaas/are";
2
2
  import { AreHTMLContextConstructor } from "./AreHTML.types";
3
+ import { A_Frame } from "@adaas/a-frame/core";
3
4
 
4
5
 
6
+ @A_Frame.Define({
7
+ namespace: 'a-are-html',
8
+ 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.'
9
+ })
5
10
  export class AreHTMLEngineContext extends AreContext {
6
11
 
7
12
  /**
@@ -41,7 +46,7 @@ export class AreHTMLEngineContext extends AreContext {
41
46
  /**
42
47
  * 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).
43
48
  */
44
- elementListeners: new WeakMap<Node, Map<string, EventListenerOrEventListenerObject>>()
49
+ elementListeners: new WeakMap<Node, Map<string, Set<EventListenerOrEventListenerObject>>>()
45
50
  }
46
51
 
47
52
  /**
@@ -172,7 +177,11 @@ export class AreHTMLEngineContext extends AreContext {
172
177
  if (!this.index.elementListeners.has(element)) {
173
178
  this.index.elementListeners.set(element, new Map());
174
179
  }
175
- this.index.elementListeners.get(element)!.set(eventName, listener);
180
+ const byEvent = this.index.elementListeners.get(element)!;
181
+ if (!byEvent.has(eventName)) {
182
+ byEvent.set(eventName, new Set());
183
+ }
184
+ byEvent.get(eventName)!.add(listener);
176
185
  }
177
186
  /**
178
187
  * 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.
@@ -182,6 +191,16 @@ export class AreHTMLEngineContext extends AreContext {
182
191
  * @returns
183
192
  */
184
193
  getListener(element: Node, eventName: string): EventListenerOrEventListenerObject | undefined {
194
+ const set = this.index.elementListeners.get(element)?.get(eventName);
195
+ if (!set || set.size === 0) return undefined;
196
+ // Return the first listener for backwards compatibility.
197
+ return set.values().next().value;
198
+ }
199
+
200
+ /**
201
+ * Returns all listeners registered for a given element + event name.
202
+ */
203
+ getListeners(element: Node, eventName: string): Set<EventListenerOrEventListenerObject> | undefined {
185
204
  return this.index.elementListeners.get(element)?.get(eventName);
186
205
  }
187
206
  /**
@@ -190,7 +209,17 @@ export class AreHTMLEngineContext extends AreContext {
190
209
  * @param element
191
210
  * @param eventName
192
211
  */
193
- removeListener(element: Node, eventName: string): void {
194
- this.index.elementListeners.get(element)?.delete(eventName);
212
+ removeListener(element: Node, eventName: string, listener?: EventListenerOrEventListenerObject): void {
213
+ const byEvent = this.index.elementListeners.get(element);
214
+ if (!byEvent) return;
215
+ if (listener) {
216
+ const set = byEvent.get(eventName);
217
+ if (set) {
218
+ set.delete(listener);
219
+ if (set.size === 0) byEvent.delete(eventName);
220
+ }
221
+ } else {
222
+ byEvent.delete(eventName);
223
+ }
195
224
  }
196
225
  }
@@ -1,5 +1,5 @@
1
1
  import { A_Feature, A_Inject, A_Scope } from "@adaas/a-concept";
2
- import { A_Frame } from "@adaas/a-frame";
2
+ import { A_Frame } from "@adaas/a-frame/core"
3
3
  import { A_ServiceFeatures } from "@adaas/a-utils/a-service";
4
4
  import { AreEngine, AreSyntaxTokenMatch, AreSyntax } from "@adaas/are";
5
5
  import { AreHTMLInterpreter } from "@adaas/are-html/interpreter";
@@ -16,10 +16,9 @@ import { AreHTMLCompiler } from "./AreHTML.compiler";
16
16
 
17
17
 
18
18
 
19
- @A_Frame.Component({
20
- namespace: 'A-ARE',
21
- name: 'AreHTMLEngine',
22
- description: 'HTML Rendering Engine for A-Concept Rendering Engine (ARE), responsible for processing and rendering HTML templates within the ARE framework.'
19
+ @A_Frame.Define({
20
+ namespace: 'a-are-html',
21
+ 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.'
23
22
  })
24
23
  export class AreHTMLEngine extends AreEngine {
25
24
 
@@ -167,8 +166,14 @@ export class AreHTMLEngine extends AreEngine {
167
166
  if (nextOpen !== -1 && nextOpen < nextClose) {
168
167
  const charAfter = source[nextOpen + tagName.length + 1]
169
168
  if (charAfter === ' ' || charAfter === '>' || charAfter === '/') {
170
- level++
171
- searchIndex = nextOpen + tagName.length + 1
169
+ // Skip self-closing nested occurrences, e.g. <div/> inside <div>...</div>.
170
+ const innerEnd = AreHTMLEngine.findTagClose(source, nextOpen)
171
+ const isSelfClose = innerEnd !== -1 && source[innerEnd - 1] === '/'
172
+
173
+ if (!isSelfClose) {
174
+ level++
175
+ }
176
+ searchIndex = (innerEnd === -1 ? nextOpen + tagName.length + 1 : innerEnd + 1)
172
177
  continue
173
178
  }
174
179
  }
@@ -1,5 +1,5 @@
1
1
  import { A_Caller, A_Inject } from "@adaas/a-concept";
2
- import { A_Frame } from "@adaas/a-frame";
2
+ import { A_Frame } from "@adaas/a-frame/core"
3
3
  import { A_Logger } from "@adaas/a-utils/a-logger";
4
4
  import {
5
5
  AreSyntax, AreStore,
@@ -17,18 +17,25 @@ import { AddTextInstruction } from "@adaas/are-html/instructions/AddText.instruc
17
17
  import { AreDirectiveContext } from "@adaas/are-html/directive/AreDirective.context";
18
18
  import { AreHTMLNode } from "../lib/AreHTMLNode/AreHTMLNode";
19
19
  import { AreHTMLEngineContext } from "./AreHTML.context";
20
-
21
-
22
- @A_Frame.Component({
23
- namespace: 'A-ARE',
24
- name: 'AreHTMLInterpreter',
25
- description: 'AreHTMLInterpreter is a component that serves as a host for rendering AreNodes into HTML. It provides the necessary context and environment for AreNodes to be rendered and interact with the DOM.'
20
+ import {
21
+ isBooleanAttribute,
22
+ isIDLFormProperty,
23
+ normalizeClassValue,
24
+ normalizeStyleValue,
25
+ parseEventName,
26
+ toDOMString,
27
+ } from "./AreHTML.constants";
28
+
29
+
30
+ @A_Frame.Define({
31
+ namespace: 'a-are-html',
32
+ description: 'DOM interpreter for the HTML rendering pipeline. Extends AreInterpreter to apply and revert each ARE instruction type directly against the browser DOM — 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.'
26
33
  })
27
34
  export class AreHTMLInterpreter extends AreInterpreter {
28
35
  // ─────────────────────────────────────────────────────────────────────────────
29
36
  // ── CreateElement — Apply / Revert ───────────────────────────────────────────
30
37
  // ─────────────────────────────────────────────────────────────────────────────
31
- @A_Frame.Method({
38
+ @A_Frame.Define({
32
39
  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.'
33
40
  })
34
41
  @AreInterpreter.Apply(AreInstructionDefaultNames.Default)
@@ -68,6 +75,7 @@ export class AreHTMLInterpreter extends AreInterpreter {
68
75
  }
69
76
 
70
77
  const element = context.container.createElement(tag);
78
+ element.setAttribute('data-aseid', node.aseid.toString());
71
79
 
72
80
  if (mountPoint.nodeType === Node.ELEMENT_NODE) {
73
81
  // parent is a real element — just append
@@ -109,7 +117,7 @@ export class AreHTMLInterpreter extends AreInterpreter {
109
117
  }
110
118
 
111
119
 
112
- @A_Frame.Method({
120
+ @A_Frame.Define({
113
121
  description: 'Remove an HTML element that was created by a CreateElement declaration. Cleans up the DOM and the context index.'
114
122
  })
115
123
  @AreInterpreter.Revert(AreInstructionDefaultNames.Default)
@@ -131,7 +139,7 @@ export class AreHTMLInterpreter extends AreInterpreter {
131
139
  // ─────────────────────────────────────────────────────────────────────────────
132
140
  // ── AddAttribute — Apply / Revert ────────────────────────────────────────────
133
141
  // ─────────────────────────────────────────────────────────────────────────────
134
- @A_Frame.Method({
142
+ @A_Frame.Define({
135
143
  description: 'Add an attribute to an HTML element based on the provided mutation instruction.'
136
144
  })
137
145
  @AreInterpreter.Apply(AreHTMLInstructions.AddAttribute)
@@ -156,43 +164,93 @@ export class AreHTMLInterpreter extends AreInterpreter {
156
164
  }
157
165
  const { name, content, evaluate } = mutation.payload;
158
166
 
159
- const value = evaluate ? syntax.evaluate(content, store, {
167
+ const rawValue = evaluate ? syntax.evaluate(content, store, {
160
168
  ...(directiveContext?.scope || {})
161
169
  }) : content;
162
170
 
171
+ const el = element as HTMLElement;
172
+ const lowerName = name.toLowerCase();
163
173
 
164
- /**
165
- * First time
166
- */
167
- if (mutation.cache === undefined) {
168
-
169
- const existingValue = (element as HTMLElement).getAttribute(name);
170
-
171
- const result = (existingValue ? `${existingValue} ${value}` : value);
172
-
173
- (element as HTMLElement).setAttribute(name, result);
174
-
175
- mutation.cache = value;
174
+ // ── 1. Boolean attributes ────────────────────────────────────────────
175
+ if (isBooleanAttribute(lowerName)) {
176
+ if (rawValue) {
177
+ el.setAttribute(lowerName, '');
178
+ // also reflect IDL property where supported (disabled, hidden, …)
179
+ try { (el as any)[lowerName] = true; } catch { /* ignore */ }
180
+ } else {
181
+ el.removeAttribute(lowerName);
182
+ try { (el as any)[lowerName] = false; } catch { /* ignore */ }
183
+ }
184
+ mutation.cache = rawValue ? 'true' : '';
185
+ return;
186
+ }
176
187
 
177
- } else {
188
+ // ── 2. Form-control IDL properties (value/checked/selected) ─────────
189
+ if (isIDLFormProperty(el.tagName, name)) {
190
+ const propName = name === 'value' ? 'value'
191
+ : name === 'checked' ? 'checked'
192
+ : name === 'selected' ? 'selected'
193
+ : name === 'indeterminate' ? 'indeterminate'
194
+ : name;
195
+ try {
196
+ if (propName === 'checked' || propName === 'selected' || propName === 'indeterminate') {
197
+ (el as any)[propName] = !!rawValue;
198
+ } else {
199
+ (el as any)[propName] = toDOMString(rawValue);
200
+ }
201
+ } catch { /* ignore */ }
202
+ // also keep the attribute in sync for SSR/CSS selectors
203
+ if (propName !== 'value') {
204
+ if (rawValue) el.setAttribute(name, ''); else el.removeAttribute(name);
205
+ } else {
206
+ el.setAttribute(name, toDOMString(rawValue));
207
+ }
208
+ mutation.cache = toDOMString(rawValue);
209
+ return;
210
+ }
178
211
 
179
- const existingValue = (element as HTMLElement).getAttribute(name);
212
+ // ── 3. Class binding — supports object/array/string and merges ──────
213
+ if (lowerName === 'class') {
214
+ const newValue = normalizeClassValue(rawValue);
180
215
 
181
- const existingParts = existingValue ? existingValue.split(/\s+/).filter(Boolean) : [];
182
- const oldParts = new Set((mutation.cache as string).split(/\s+/).filter(Boolean));
183
- const newParts = value ? value.split(/\s+/).filter(Boolean) : [];
216
+ if (mutation.cache === undefined) {
217
+ const existingValue = el.getAttribute('class');
218
+ const merged = existingValue ? `${existingValue} ${newValue}`.trim() : newValue;
219
+ if (merged) el.setAttribute('class', merged); else el.removeAttribute('class');
220
+ } else {
221
+ const existingValue = el.getAttribute('class');
222
+ const existingParts = existingValue ? existingValue.split(/\s+/).filter(Boolean) : [];
223
+ const oldParts = new Set((mutation.cache as string).split(/\s+/).filter(Boolean));
224
+ const newParts = newValue ? newValue.split(/\s+/).filter(Boolean) : [];
184
225
 
185
- const result = [...existingParts.filter(part => !oldParts.has(part)), ...newParts].join(' ');
226
+ const merged = [...existingParts.filter(p => !oldParts.has(p)), ...newParts].join(' ');
227
+ if (merged) el.setAttribute('class', merged); else el.removeAttribute('class');
228
+ }
229
+ mutation.cache = newValue;
230
+ return;
231
+ }
186
232
 
187
- (element as HTMLElement).setAttribute(name, result);
233
+ // ── 4. Style binding — supports object/array/string ─────────────────
234
+ if (lowerName === 'style') {
235
+ const newValue = normalizeStyleValue(rawValue);
236
+ if (newValue) el.setAttribute('style', newValue); else el.removeAttribute('style');
237
+ mutation.cache = newValue;
238
+ return;
239
+ }
188
240
 
189
- mutation.cache = value;
241
+ // ── 5. Default: replace attribute (no whitespace merge) ─────────────
242
+ const stringValue = toDOMString(rawValue);
243
+ if (stringValue === '' && evaluate && (rawValue === false || rawValue === null || rawValue === undefined)) {
244
+ el.removeAttribute(name);
245
+ } else {
246
+ el.setAttribute(name, stringValue);
190
247
  }
248
+ mutation.cache = stringValue;
191
249
 
192
250
 
193
251
  }
194
252
 
195
- @A_Frame.Method({
253
+ @A_Frame.Define({
196
254
  description: 'Remove an attribute from an HTML element based on the provided mutation instruction.'
197
255
  })
198
256
  @AreInterpreter.Revert(AreHTMLInstructions.AddAttribute)
@@ -221,7 +279,7 @@ export class AreHTMLInterpreter extends AreInterpreter {
221
279
  // ── addEventListener — Apply / Revert ────────────────────────────────────────
222
280
  // ─────────────────────────────────────────────────────────────────────────────
223
281
 
224
- @A_Frame.Method({
282
+ @A_Frame.Define({
225
283
  description: 'Add an event listener to an HTML element based on the provided mutation instruction.'
226
284
  })
227
285
  @AreInterpreter.Apply(AreHTMLInstructions.AddListener)
@@ -244,54 +302,117 @@ export class AreHTMLInterpreter extends AreInterpreter {
244
302
  }
245
303
 
246
304
  /**
247
- * e.g. @click="handleClick"
248
- *
305
+ * e.g. @click="handleClick"
249
306
  * e.g. @click="handleClick($event, element)"
250
- *
251
- * e.g. @click="(e)=> user.name ? handleClick(e) : null" (with conditional logic)
252
- *
253
- * e.g. @click="(e)=> isValid(user.name) ? handleClick(e) : null" (with conditional logic)
254
- *
255
- * e.g. @click="(e)=> isValid(user.name) ? handleClick(e, format(user.name)) : null" (with conditional logic)
307
+ * e.g. @click.stop.prevent="handleClick"
308
+ * e.g. @keydown.enter="submit"
309
+ * e.g. @click="(e)=> user.name ? handleClick(e) : null"
256
310
  */
257
311
 
312
+ const { event: eventName, modifiers } = parseEventName(mutation.payload.name);
313
+
314
+ const listenerOptions: AddEventListenerOptions = {};
315
+ if (modifiers.has('capture')) listenerOptions.capture = true;
316
+ if (modifiers.has('once')) listenerOptions.once = true;
317
+ if (modifiers.has('passive')) listenerOptions.passive = true;
258
318
 
259
319
  const handlers = syntax.extractEmitHandlers(mutation.payload.handler);
260
- const handlerScope = {}
320
+
321
+ // Holds the live DOM event so handler invocations (with or without
322
+ // template arguments) always have access to it.
323
+ let liveEvent: Event | null = null;
324
+
325
+ const handlerScope: Record<string, any> = {};
261
326
 
262
327
  for (const handler of handlers) {
263
328
  const handlerFn = (...args: any[]) => {
264
- const event = new AreEvent(handler)
265
-
266
- event.set('args', args);
329
+ const event = new AreEvent(handler);
330
+ // If user passed only template args (e.g. $h('x')), append the DOM
331
+ // event as the last arg. If they passed nothing, args[0] is the DOM event.
332
+ const effectiveArgs = args.length === 0 && liveEvent
333
+ ? [liveEvent]
334
+ : liveEvent
335
+ ? [...args, liveEvent]
336
+ : args;
337
+ event.set('args', effectiveArgs);
267
338
  event.set('element', element);
268
339
  event.set('instruction', mutation);
269
-
270
340
  mutation.owner.emit(event);
271
- }
272
-
341
+ };
273
342
  handlerScope[`$${handler}`] = handlerFn;
274
343
  }
275
344
 
276
345
  const callback = (e: Event) => {
277
- context.startPerformance('Click')
346
+ try {
347
+ liveEvent = e;
348
+
349
+ if (modifiers.has('self') && e.target !== element) return;
350
+ if (modifiers.has('stop')) e.stopPropagation();
351
+ if (modifiers.has('prevent')) e.preventDefault();
352
+
353
+ // key-name modifiers for keyboard events: @keydown.enter / .esc / .tab / .space / .up / .down / .left / .right / .delete
354
+ if (e instanceof KeyboardEvent && modifiers.size > 0) {
355
+ const key = (e.key || '').toLowerCase();
356
+ const KEY_ALIASES: Record<string, string[]> = {
357
+ enter: ['enter'],
358
+ esc: ['escape'],
359
+ escape: ['escape'],
360
+ tab: ['tab'],
361
+ space: [' ', 'spacebar'],
362
+ up: ['arrowup'],
363
+ down: ['arrowdown'],
364
+ left: ['arrowleft'],
365
+ right: ['arrowright'],
366
+ delete: ['delete', 'backspace'],
367
+ };
368
+ const keyMods = [...modifiers].filter(m =>
369
+ m in KEY_ALIASES ||
370
+ m === 'ctrl' || m === 'alt' || m === 'shift' || m === 'meta');
371
+
372
+ if (keyMods.length > 0) {
373
+ const keyMatch = keyMods.some(m => {
374
+ if (m === 'ctrl') return e.ctrlKey;
375
+ if (m === 'alt') return e.altKey;
376
+ if (m === 'shift') return e.shiftKey;
377
+ if (m === 'meta') return e.metaKey;
378
+ const aliases = KEY_ALIASES[m];
379
+ return aliases && aliases.includes(key);
380
+ });
381
+ if (!keyMatch) return;
382
+ }
383
+ }
278
384
 
279
- const result = syntax.evaluate(mutation.payload.handler, store, {
280
- ...handlerScope,
281
- ...(directiveContext?.scope || {})
282
- });
283
- if (typeof result === 'function') result(e);
284
- };
385
+ context.startPerformance('event:' + eventName);
386
+
387
+ const result = syntax.evaluate(mutation.payload.handler, store, {
388
+ ...handlerScope,
389
+ $event: e,
390
+ ...(directiveContext?.scope || {})
391
+ });
392
+ if (typeof result === 'function') result(e);
285
393
 
394
+ context.endPerformance('event:' + eventName);
395
+ } catch (err) {
396
+ logger?.error(err);
397
+ } finally {
398
+ liveEvent = null;
399
+ }
400
+ };
286
401
 
287
- if (callback) {
288
- element.addEventListener(mutation.payload.name, callback);
289
- context.addListener(element, mutation.payload.name, callback);
402
+ const useOptions = listenerOptions.capture || listenerOptions.once || listenerOptions.passive;
403
+ if (useOptions) {
404
+ element.addEventListener(eventName, callback, listenerOptions);
405
+ } else {
406
+ element.addEventListener(eventName, callback);
290
407
  }
408
+ // Track on both the context (for diagnostics) and the mutation itself
409
+ // so the revert path can detach the exact same callback.
410
+ (mutation.payload as any)._callback = callback;
411
+ context.addListener(element, mutation.payload.name, callback);
291
412
  }
292
413
 
293
414
 
294
- @A_Frame.Method({
415
+ @A_Frame.Define({
295
416
  description: 'Remove an event listener from an HTML element based on the provided mutation instruction.'
296
417
  })
297
418
  @AreInterpreter.Revert(AreHTMLInstructions.AddListener)
@@ -304,12 +425,14 @@ export class AreHTMLInterpreter extends AreInterpreter {
304
425
  if (!element) return;
305
426
 
306
427
  const { name } = mutation.payload;
428
+ const { event: eventName } = parseEventName(name);
307
429
 
308
- const listener = context.getListener(element, name);
430
+ const listener = (mutation.payload as any)._callback as EventListenerOrEventListenerObject | undefined;
309
431
 
310
432
  if (listener) {
311
- element.removeEventListener(name, listener);
312
- context.removeListener(element, name);
433
+ element.removeEventListener(eventName, listener);
434
+ context.removeListener(element, name, listener);
435
+ (mutation.payload as any)._callback = undefined;
313
436
  }
314
437
  }
315
438
 
@@ -318,7 +441,7 @@ export class AreHTMLInterpreter extends AreInterpreter {
318
441
  // ── AddText — Apply / Revert ─────────────────────────────────────────────────
319
442
  // ─────────────────────────────────────────────────────────────────────────────
320
443
 
321
- @A_Frame.Method({
444
+ @A_Frame.Define({
322
445
  description: 'Add text content to an HTML element based on the provided declaration instruction.'
323
446
  })
324
447
  @AreInterpreter.Apply(AreHTMLInstructions.AddText)
@@ -335,10 +458,12 @@ export class AreHTMLInterpreter extends AreInterpreter {
335
458
  const node = declaration.owner.parent;
336
459
  const { content, evaluate } = declaration.payload;
337
460
 
338
- const value = evaluate ? syntax.evaluate(content, store, {
461
+ const rawValue = evaluate ? syntax.evaluate(content, store, {
339
462
  ...(directiveContext?.scope || {})
340
463
  }) : content;
341
464
 
465
+ const value = toDOMString(rawValue);
466
+
342
467
 
343
468
  if (!node) {
344
469
  const textNode = context.container.createTextNode(value);
@@ -375,7 +500,7 @@ export class AreHTMLInterpreter extends AreInterpreter {
375
500
  }
376
501
 
377
502
 
378
- @A_Frame.Method({
503
+ @A_Frame.Define({
379
504
  description: 'Remove text content from an HTML element based on the provided declaration instruction.'
380
505
  })
381
506
  @AreInterpreter.Revert(AreHTMLInstructions.AddText)
@@ -393,7 +518,7 @@ export class AreHTMLInterpreter extends AreInterpreter {
393
518
 
394
519
 
395
520
 
396
- @A_Frame.Method({
521
+ @A_Frame.Define({
397
522
  description: 'Add a comment node to the DOM based on the provided declaration instruction.'
398
523
  })
399
524
  @AreInterpreter.Apply(AreHTMLInstructions.AddComment)
@@ -410,10 +535,12 @@ export class AreHTMLInterpreter extends AreInterpreter {
410
535
  const node = declaration.owner.parent;
411
536
  const { content, evaluate } = declaration.payload;
412
537
 
413
- const value = evaluate ? syntax.evaluate(content, store, {
538
+ const rawValue = evaluate ? syntax.evaluate(content, store, {
414
539
  ...(directiveContext?.scope || {})
415
540
  }) : content;
416
541
 
542
+ const value = toDOMString(rawValue);
543
+
417
544
 
418
545
  if (!node) {
419
546
  const commentNode = context.container.createComment(value);
@@ -448,7 +575,7 @@ export class AreHTMLInterpreter extends AreInterpreter {
448
575
  }
449
576
 
450
577
 
451
- @A_Frame.Method({
578
+ @A_Frame.Define({
452
579
  description: 'Remove a comment node from the DOM based on the provided declaration instruction.'
453
580
  })
454
581
  @AreInterpreter.Revert(AreHTMLInstructions.AddComment)