@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
@@ -23,20 +23,21 @@ export class ListComponent extends Are {
23
23
  <li @click="$handleClick('Products')" :class="active==='Products' ? 'menu-item-active' : ''" class="menu-item"><span class="menu-icon">⊠</span> <span class="menu-text">{{item3}}</span></li>
24
24
  <li @click="$handleClick('Orders')" :class="active==='Orders' ? 'menu-item-active' : ''" class="menu-item"><span class="menu-icon">⊟</span> <span class="menu-text">{{item4}}</span></li>
25
25
  </ul>
26
- <div class="menu-section">System</div>
26
+ <div class="menu-section">System</div>
27
27
  <button @click="$add">Add +</button>
28
- <ul class="menu">
29
- <li
30
- $for="item in items" @click="$handleClick(item.name)"
31
- $if="active=='Dashboard'"
32
- :class="active===item.name ? 'menu-item-active' : ''"
33
- class="menu-item">
34
- <span class="menu-icon">⊙</span>
35
- <span class="menu-text">{{item.name}}</span>
36
- <span $if="item.badge > 0" class="menu-badge">{{item.badge}}</span>
37
- <button @click="$remove(item)">+</button>
38
- </li>
39
- </ul>
28
+ <div $if="active=='Dashboard'">
29
+ <ul class="menu">
30
+ <li
31
+ $for="item in items track item.name" @click="$handleClick(item.name)"
32
+ :class="active===item.name ? 'menu-item-active' : ''"
33
+ class="menu-item">
34
+ <span class="menu-icon">⊙</span>
35
+ <span class="menu-text">{{item.name}}</span>
36
+ <span $if="item.badge > 0" class="menu-badge">{{item.badge}}</span>
37
+ <button @click.stop="$remove(item)">+</button>
38
+ </li>
39
+ </ul>
40
+ </div>
40
41
  `);
41
42
  }
42
43
 
@@ -7,10 +7,9 @@ import { A_Config, ConfigReader } from "@adaas/a-utils/a-config";
7
7
  import { A_Logger, A_LOGGER_ENV_KEYS } from "@adaas/a-utils/a-logger";
8
8
  import { A_SignalBus, A_SignalState } from "@adaas/a-utils/a-signal";
9
9
  import { A_Polyfill } from "@adaas/a-utils/a-polyfill";
10
- import { AreApp } from "src/containers/AreWebApp.container";
11
10
  import { AreRoot } from "src/lib/AreRoot/AreRoot.component";
12
11
  import { AreHTMLEngine } from "src/engine/AreHTML.engine";
13
- import { AreInit } from "@adaas/are";
12
+ import { AreContainer, AreInit } from "@adaas/are";
14
13
  import { AreRoute } from "@adaas/are";
15
14
  import { AreContext } from "@adaas/are";
16
15
  import { AreHTMLEngineContext } from "src/engine/AreHTML.context";
@@ -26,7 +25,7 @@ import { AreDirectiveFor } from "src/directives/AreDirectiveFor.directive";
26
25
  (async () => {
27
26
  try {
28
27
 
29
- const container = new AreApp({
28
+ const container = new AreContainer({
30
29
  name: 'ARE Jumpstart',
31
30
  components: [
32
31
  // ----------------------------------
@@ -66,7 +65,9 @@ import { AreDirectiveFor } from "src/directives/AreDirectiveFor.directive";
66
65
  ],
67
66
  fragments: [
68
67
  new A_SignalState([AreRoute]),
69
- new AreHTMLEngineContext(document.body.innerHTML),
68
+ new AreHTMLEngineContext({
69
+ container:document
70
+ }),
70
71
 
71
72
  new A_Config({
72
73
  defaults: {
package/jest.config.ts CHANGED
@@ -29,7 +29,7 @@ const config: Config.InitialOptions = {
29
29
  "^@adaas/are/syntax/(.*)": "<rootDir>/node_modules/@adaas/are/dist/node/lib/AreSyntax/$1.js",
30
30
  "^@adaas/are/tokenizer/(.*)": "<rootDir>/node_modules/@adaas/are/dist/node/lib/AreTokenizer/$1.js",
31
31
  "^@adaas/are/transformer/(.*)": "<rootDir>/node_modules/@adaas/are/dist/node/lib/AreTransformer/$1.js",
32
- "^@adaas/are/watcher/(.*)": "<rootDir>/node_modules/@adaas/are/dist/node/lib/AreWatcher/$1.js",
32
+ "^@adaas/are/watcher/(.*)": "<rootDir>/node_modules/@adaas/are/dist/node/lib/AreRouteWatcher/$1.js",
33
33
 
34
34
  // ── @adaas/are-html internal path aliases (mirrors tsconfig.json paths) ──
35
35
  // Base ARE-HTML entities
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaas/are-html",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "A-Concept Rendering Engine (ARE) is a powerful rendering engine designed to work seamlessly with the A-Concept framework. This library provides an HTML engine implementation of ARE, enabling developers to create dynamic and interactive user interfaces for web applications using standard HTML syntax.",
5
5
  "keywords": [
6
6
  "a-concept",
@@ -79,13 +79,17 @@
79
79
  "echo-version": "echo $npm_package_version",
80
80
  "build": "tsup --config tsup.config.ts"
81
81
  },
82
- "dependencies": {
83
- "@adaas/a-concept": "^0.3.13",
84
- "@adaas/a-frame": "^0.0.22",
85
- "@adaas/a-utils": "^0.3.16",
86
- "@adaas/are": "^0.0.9"
82
+ "peerDependencies": {
83
+ "@adaas/a-concept": "^0.3.16",
84
+ "@adaas/a-frame": "^0.1.2",
85
+ "@adaas/a-utils": "^0.3.20",
86
+ "@adaas/are": "^0.0.10"
87
87
  },
88
88
  "devDependencies": {
89
+ "@adaas/a-concept": "^0.3.16",
90
+ "@adaas/a-frame": "^0.1.2",
91
+ "@adaas/a-utils": "^0.3.20",
92
+ "@adaas/are": "^0.0.10",
89
93
  "@types/chai": "^4.3.14",
90
94
  "@types/jest": "^29.5.12",
91
95
  "@types/mocha": "^10.0.6",
@@ -1,10 +1,15 @@
1
1
  import { AreHTMLAttribute } from "@adaas/are-html/attribute";
2
+ import { A_Frame } from "@adaas/a-frame/core";
2
3
 
3
4
 
4
5
 
5
6
 
6
7
 
7
8
 
9
+ @A_Frame.Define({
10
+ namespace: 'a-are-html',
11
+ 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.'
12
+ })
8
13
  export class AreBindingAttribute extends AreHTMLAttribute {
9
14
 
10
15
  // get value(): string {
@@ -3,9 +3,14 @@ import type { AreDirective } from "@adaas/are-html/directive/AreDirective.compon
3
3
  import { AreHTMLAttribute } from "@adaas/are-html/attribute";
4
4
  import { AreStoreWatchingEntity } from "@adaas/are";
5
5
  import { AreHTMLNode } from "@adaas/are-html/node";
6
+ import { A_Frame } from "@adaas/a-frame/core";
6
7
 
7
8
 
8
9
 
10
+ @A_Frame.Define({
11
+ namespace: 'a-are-html',
12
+ 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.'
13
+ })
9
14
  export class AreDirectiveAttribute extends AreHTMLAttribute implements AreStoreWatchingEntity {
10
15
 
11
16
  cache?: any
@@ -1,5 +1,10 @@
1
1
  import { AreHTMLAttribute } from "@adaas/are-html/attribute";
2
+ import { A_Frame } from "@adaas/a-frame/core";
2
3
 
3
4
 
5
+ @A_Frame.Define({
6
+ namespace: 'a-are-html',
7
+ description: 'Attribute type for DOM event listeners (@ prefix). Marks the attribute as an event binding — the compiler emits an AddListener instruction that attaches a handler expression resolved from the store to the specified event name on the host element.'
8
+ })
4
9
  export class AreEventAttribute extends AreHTMLAttribute {
5
10
  }
@@ -1,6 +1,11 @@
1
1
  import { AreHTMLAttribute } from "@adaas/are-html/attribute";
2
+ import { A_Frame } from "@adaas/a-frame/core";
2
3
 
3
4
 
5
+ @A_Frame.Define({
6
+ namespace: 'a-are-html',
7
+ 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.'
8
+ })
4
9
  export class AreStaticAttribute extends AreHTMLAttribute {
5
10
 
6
11
  }
@@ -6,15 +6,22 @@ import { AreDirective } from "@adaas/are-html/directive/AreDirective.component";
6
6
  import { AddCommentInstruction } from "@adaas/are-html/instructions/AddComment.instruction";
7
7
  import { AreHTMLNode } from "@adaas/are-html/node";
8
8
  import { AreDirectiveContext } from "@adaas/are-html/directive/AreDirective.context";
9
+ import { A_Frame } from "@adaas/a-frame/core";
9
10
 
10
11
 
11
12
  type AreForExpression = {
12
13
  key: string;
13
14
  index: string | undefined;
14
15
  arrayExpr: string;
16
+ /** Optional `track <expr>` clause, e.g. `track row.id` */
17
+ trackExpr: string | undefined;
15
18
  };
16
19
 
17
20
 
21
+ @A_Frame.Define({
22
+ namespace: 'a-are-html',
23
+ 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.'
24
+ })
18
25
  @AreDirective.Priority(1)
19
26
  export class AreDirectiveFor extends AreDirective {
20
27
 
@@ -73,8 +80,6 @@ export class AreDirectiveFor extends AreDirective {
73
80
 
74
81
  attribute.value = array;
75
82
 
76
- console.log('Initial array for "for" directive:', scene);
77
-
78
83
  /**
79
84
  * For each item in the array, spawn a clone of the template with the
80
85
  * item's store values pre-set and its scene activated.
@@ -86,8 +91,6 @@ export class AreDirectiveFor extends AreDirective {
86
91
  for (let i = 0; i < array.length; i++) {
87
92
  this.spawnItemNode(attribute.template!, attribute.owner, key, index, array[i], i);
88
93
  }
89
-
90
- console.log('Template for "for" directive:', forTemplate);
91
94
  }
92
95
 
93
96
 
@@ -123,7 +126,7 @@ export class AreDirectiveFor extends AreDirective {
123
126
  /**
124
127
  * Re-evaluate the source array.
125
128
  */
126
- const { key, index, arrayExpr } = this.parseExpression(attribute.content);
129
+ const { key, index, arrayExpr, trackExpr } = this.parseExpression(attribute.content);
127
130
  const newArray = this.resolveArray(store, arrayExpr, attribute.content);
128
131
 
129
132
  const owner = attribute.owner;
@@ -131,70 +134,61 @@ export class AreDirectiveFor extends AreDirective {
131
134
 
132
135
  attribute.value = newArray;
133
136
 
134
- const newLen = newArray.length;
137
+ const computeKey = this.makeKeyFn(key, index, trackExpr);
135
138
 
136
- /**
137
- * Build a set of new items for O(1) identity lookup.
138
- * Uses reference equality — items that exist in newArray are "kept".
139
- */
140
- const newItemSet = new Set(newArray);
139
+ // ── 1. Index existing children by stable key ────────────────────────
140
+ const childByKey = new Map<any, AreHTMLNode>();
141
+ const remaining = new Set<AreHTMLNode>();
141
142
 
142
- /**
143
- * Partition existing children by identity: nodes whose stored item is
144
- * still present in the new array are kept; the rest are removed.
145
- * This correctly handles deletions from any position, not just the tail.
146
- */
147
- const keptChildren: AreHTMLNode[] = [];
148
- const removedChildren: AreHTMLNode[] = [];
149
-
150
- for (const child of currentChildren) {
143
+ for (let i = 0; i < currentChildren.length; i++) {
144
+ const child = currentChildren[i];
151
145
  const ctx = child.scope.resolveFlat(AreDirectiveContext);
146
+ const k = ctx ? computeKey(ctx.scope[key], ctx.scope[index || 'index']) : Symbol('orphan');
147
+ childByKey.set(k, child);
148
+ remaining.add(child);
149
+ }
152
150
 
153
- if (ctx && newItemSet.has(ctx.scope[key])) {
154
- keptChildren.push(child);
151
+ // ── 2. Walk desired list; reuse existing or spawn new ───────────────
152
+ const desired: AreHTMLNode[] = [];
153
+ const newOnes: AreHTMLNode[] = [];
154
+
155
+ for (let i = 0; i < newArray.length; i++) {
156
+ const item = newArray[i];
157
+ const k = computeKey(item, i);
158
+ const existing = childByKey.get(k);
159
+
160
+ if (existing) {
161
+ remaining.delete(existing);
162
+
163
+ let directiveContext = existing.scope.resolveFlat(AreDirectiveContext);
164
+ if (!directiveContext) {
165
+ directiveContext = new AreDirectiveContext(existing.aseid);
166
+ existing.scope.register(directiveContext);
167
+ }
168
+ directiveContext.scope = {
169
+ ...directiveContext.scope,
170
+ [key]: item,
171
+ [index || 'index']: i,
172
+ };
173
+ desired.push(existing);
155
174
  } else {
156
- removedChildren.push(child);
175
+ const itemNode = this.spawnItemNode(attribute.template!, owner, key, index, item, i);
176
+ desired.push(itemNode);
177
+ newOnes.push(itemNode);
157
178
  }
158
179
  }
159
180
 
160
- /**
161
- * Unmount and detach nodes whose items are no longer in the array.
162
- */
163
- for (const child of removedChildren) {
181
+ // ── 3. Unmount + detach removed children ─────────────────────────────
182
+ for (const child of remaining) {
164
183
  child.unmount();
165
184
  owner.removeChild(child);
166
185
  }
167
186
 
168
- /**
169
- * Update store values on kept nodes so their reactive bindings pick up
170
- * the latest data and corrected indices without a full re-render.
171
- */
172
- for (let i = 0; i < keptChildren.length; i++) {
173
- let directiveContext = keptChildren[i].scope.resolveFlat(AreDirectiveContext);
174
-
175
- if (!directiveContext) {
176
- directiveContext = new AreDirectiveContext(keptChildren[i].aseid);
177
- keptChildren[i].scope.register(directiveContext);
178
- }
179
-
180
- directiveContext.scope = {
181
- ...directiveContext.scope,
182
- [key]: newArray[i],
183
- [index || 'index']: i,
184
- };
185
- }
186
-
187
- /**
188
- * Append nodes for items added beyond the current kept count.
189
- * These are created outside the main compile cycle so they need an
190
- * explicit compile + mount to appear in the DOM.
191
- */
192
- for (let i = keptChildren.length; i < newLen; i++) {
193
- const itemNode = this.spawnItemNode(attribute.template!, owner, key, index, newArray[i], i);
194
-
195
- itemNode.transform();
196
- itemNode.compile();
197
- itemNode.mount();
187
+ // ── 4. Mount only the new ones (kept children stay where they are). ─
188
+ for (const child of newOnes) {
189
+ child.transform();
190
+ child.compile();
191
+ child.mount();
198
192
  }
199
193
  }
200
194
 
@@ -203,6 +197,34 @@ export class AreDirectiveFor extends AreDirective {
203
197
  // ── Helpers ──────────────────────────────────────────────────────────────────
204
198
  // ─────────────────────────────────────────────────────────────────────────────
205
199
 
200
+ /**
201
+ * Build a key-function that derives a stable identity from each item.
202
+ * If the user provided a `track <expr>` clause, evaluate it as a path on
203
+ * the item; otherwise fall back to the item identity (reference equality).
204
+ */
205
+ private makeKeyFn(key: string, index: string | undefined, trackExpr: string | undefined): (item: any, i: number) => any {
206
+ if (!trackExpr) {
207
+ return (item, i) => item ?? i;
208
+ }
209
+
210
+ // Strip any leading `key.` so users can write `track row.id`.
211
+ const path = trackExpr.startsWith(key + '.') ? trackExpr.slice(key.length + 1) : trackExpr;
212
+
213
+ return (item, i) => {
214
+ if (item == null) return i;
215
+ if (path === key || path === '$index') return path === '$index' ? i : item;
216
+
217
+ // dotted path lookup
218
+ const parts = path.split('.');
219
+ let v: any = item;
220
+ for (const p of parts) {
221
+ if (v == null) return i;
222
+ v = v[p];
223
+ }
224
+ return v ?? i;
225
+ };
226
+ }
227
+
206
228
  /**
207
229
  * Parses the $for expression string into its constituent parts.
208
230
  *
@@ -212,17 +234,32 @@ export class AreDirectiveFor extends AreDirective {
212
234
  * (item, index) in items
213
235
  * item in filter(items)
214
236
  * item, index in filter(items, 'active')
237
+ * item in items track item.id
238
+ * (item, i) in items track item.id
215
239
  */
216
240
  private parseExpression(content: string): AreForExpression {
217
- const inIndex = content.lastIndexOf(' in ');
218
- const keyAndIndex = content.slice(0, inIndex).trim().replace(/^\(|\)$/g, '');
219
- const arrayExpr = content.slice(inIndex + 4).trim();
241
+ // Strip optional `track <expr>` suffix first.
242
+ let trackExpr: string | undefined;
243
+ const trackIdx = content.search(/\s+track\s+/);
244
+ let body = content;
245
+ if (trackIdx !== -1) {
246
+ const m = content.slice(trackIdx).match(/\s+track\s+(.+)$/);
247
+ if (m) {
248
+ trackExpr = m[1].trim();
249
+ body = content.slice(0, trackIdx).trim();
250
+ }
251
+ }
252
+
253
+ const inIndex = body.lastIndexOf(' in ');
254
+ const keyAndIndex = body.slice(0, inIndex).trim().replace(/^\(|\)$/g, '');
255
+ const arrayExpr = body.slice(inIndex + 4).trim();
220
256
  const keyParts = keyAndIndex.split(',').map(p => p.trim());
221
257
 
222
258
  return {
223
259
  key: keyParts[0],
224
260
  index: keyParts[1] || undefined,
225
261
  arrayExpr,
262
+ trackExpr,
226
263
  };
227
264
  }
228
265
 
@@ -5,9 +5,32 @@ import { AreScene, AreStore, AreSyntax } from "@adaas/are";
5
5
  import { AreDirective } from "@adaas/are-html/directive/AreDirective.component";
6
6
  import { AddCommentInstruction } from "@adaas/are-html/instructions/AddComment.instruction";
7
7
  import { AreDirectiveContext } from "@adaas/are-html/directive/AreDirective.context";
8
-
9
-
10
-
8
+ import { A_Frame } from "@adaas/a-frame/core";
9
+
10
+
11
+
12
+ /**
13
+ * `$if` directive — conditionally renders a node based on an expression.
14
+ *
15
+ * ⚠️ Known limitation: do NOT use `$if` and `$for` on the SAME element.
16
+ * Doing so produces duplicated DOM on toggle because the two directives
17
+ * share an owner node and clone its scope independently. Wrap one in a
18
+ * parent element instead, e.g.:
19
+ *
20
+ * <div $if="visible">
21
+ * <li $for="item in items">{{item.name}}</li>
22
+ * </div>
23
+ *
24
+ * or
25
+ *
26
+ * <ul $for="item in items">
27
+ * <li $if="item.visible">{{item.name}}</li>
28
+ * </ul>
29
+ */
30
+ @A_Frame.Define({
31
+ namespace: 'a-are-html',
32
+ 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.'
33
+ })
11
34
  @AreDirective.Priority(2)
12
35
  export class AreDirectiveIf extends AreDirective {
13
36
 
@@ -69,11 +92,8 @@ export class AreDirectiveIf extends AreDirective {
69
92
  @A_Inject(AreDirectiveContext) directiveContext?: AreDirectiveContext,
70
93
  ...args: any[]
71
94
  ): void {
72
-
73
- console.log('Compiling directive "if" with attribute content:', attribute);
74
-
75
95
  /**
76
- * 1. Extract the value from the store based on the attribute content
96
+ * 1. Extract the value from the store based on the attribute content
77
97
  * (which is the path to the value in the store)
78
98
  */
79
99
  attribute.value = syntax.evaluate(attribute.content, store, {
@@ -110,19 +130,21 @@ export class AreDirectiveIf extends AreDirective {
110
130
  ...args: any[]
111
131
  ): void {
112
132
  /**
113
- * 1. Extract the value from the store based on the attribute content
133
+ * 1. Extract the value from the store based on the attribute content
114
134
  * (which is the path to the value in the store)
115
135
  */
116
- attribute.value = syntax.evaluate(attribute.content, store);
136
+ const previous = !!attribute.value;
137
+ const next = !!syntax.evaluate(attribute.content, store);
138
+ attribute.value = next;
117
139
 
118
- if (attribute.value) {
119
- attribute.template!.scene.activate();
140
+ // Skip when truthiness has not changed — avoids redundant mount/unmount.
141
+ if (previous === next) return;
120
142
 
121
- attribute.template!.mount()
122
- }
123
- else {
143
+ if (next) {
144
+ attribute.template!.scene.activate();
145
+ attribute.template!.mount();
146
+ } else {
124
147
  attribute.template!.unmount();
125
-
126
148
  attribute.template!.scene.deactivate();
127
149
  }
128
150
  }
@@ -1,7 +1,7 @@
1
1
  import { A_Caller, A_Dependency, A_Feature, A_FormatterHelper, A_Inject } from "@adaas/a-concept";
2
2
  import { A_Logger } from "@adaas/a-utils/a-logger";
3
- import { A_Frame } from "@adaas/a-frame";
4
- import { AreCompiler, AreScene, AreCompilerError, AreStore } from "@adaas/are";
3
+ import { A_Frame } from "@adaas/a-frame/core";
4
+ import { AreCompiler, AreScene, AreCompilerError, AreStore, AreSyntax } from "@adaas/are";
5
5
  import { AreDirectiveAttribute } from "@adaas/are-html/attributes/AreDirective.attribute";
6
6
  import { AreStaticAttribute } from "@adaas/are-html/attributes/AreStatic.attribute";
7
7
  import { AreDirectiveFeatures } from "@adaas/are-html/directive/AreDirective.constants";
@@ -16,9 +16,8 @@ import { AddListenerInstruction} from "@adaas/are-html/instructions/AddListener.
16
16
 
17
17
 
18
18
 
19
- @A_Frame.Component({
20
- namespace: 'A-ARE',
21
- name: 'AreHTMLCompiler',
19
+ @A_Frame.Define({
20
+ namespace: 'a-are-html',
22
21
  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.'
23
22
  })
24
23
  export class AreHTMLCompiler extends AreCompiler {
@@ -171,6 +170,7 @@ export class AreHTMLCompiler extends AreCompiler {
171
170
  @A_Dependency.Parent()
172
171
  @A_Inject(AreStore) parentStore: AreStore,
173
172
  @A_Inject(AreStore) store: AreStore,
173
+ @A_Inject(AreSyntax) syntax: AreSyntax,
174
174
  ...args: any[]
175
175
  ) {
176
176
  if (!scene.host)
@@ -181,45 +181,73 @@ export class AreHTMLCompiler extends AreCompiler {
181
181
 
182
182
 
183
183
  const node = attribute.owner;
184
+ const props = node.component?.props;
185
+
186
+ // Component prop names are typically declared in camelCase, while template
187
+ // markup uses kebab-case. Try both forms when matching.
188
+ let propName: string | undefined;
189
+ if (props) {
190
+ if (props[attribute.name]) {
191
+ propName = attribute.name;
192
+ } else {
193
+ const camel = A_FormatterHelper.toCamelCase(attribute.name);
194
+ if (props[camel]) propName = camel;
195
+ }
196
+ }
184
197
 
185
198
  /**
186
- * 1. If the binding is related to a component prop, then we should set the value to the component props, so it can be used in the component logic and rendering.
187
- * This is a special case for component props, since they are not regular attributes and have a special meaning and usage in the component. By setting the value directly to the component props, we can ensure that it is properly reactive and can influence the component's behavior and rendering based on its value.
199
+ * 1. Component prop binding evaluate against the parent store and
200
+ * keep the child store reactive to upstream changes.
188
201
  */
189
- if (node.component && node.component.props[attribute.name]) {
190
- const propDefinition = node.component.props[attribute.name];
191
- let value = parentStore.get(attribute.content);
192
-
193
- if (propDefinition.type) {
194
- switch (propDefinition.type) {
195
- case 'string':
196
- value = String(value);
197
- break;
198
- case 'number':
199
- value = Number(value);
200
- break;
201
- case 'boolean':
202
- value = Boolean(value);
203
- break;
204
- default:
205
- break;
202
+ if (propName && props) {
203
+ const propDefinition = props[propName];
204
+
205
+ const coerce = (raw: any): any => {
206
+ let value = raw;
207
+ if (propDefinition.type) {
208
+ switch (propDefinition.type) {
209
+ case 'string': value = value === undefined || value === null ? '' : String(value); break;
210
+ case 'number': value = Number(value); break;
211
+ case 'boolean': value = Boolean(value); break;
212
+ }
206
213
  }
207
- }
214
+ return value;
215
+ };
216
+
217
+ // The watcher entity below is registered against parentStore so that
218
+ // updates to the bound expression in the parent flow into the child store.
219
+ const watcher = {
220
+ update: () => {
221
+ try {
222
+ parentStore.watch(watcher);
223
+ const next = coerce(syntax.evaluate(attribute.content, parentStore));
224
+ parentStore.unwatch(watcher);
225
+ store.set(propName!, next);
226
+ } catch (e) {
227
+ parentStore.unwatch(watcher);
228
+ }
229
+ }
230
+ };
231
+
232
+ // Initial read with watch active so dependencies are recorded.
233
+ parentStore.watch(watcher);
234
+ const initial = coerce(syntax.evaluate(attribute.content, parentStore));
235
+ parentStore.unwatch(watcher);
208
236
 
209
- store.set(attribute.name, value);
237
+ store.set(propName, initial);
238
+ return;
210
239
  }
240
+
211
241
  /**
212
- * 2. In other cases, we just want to add it as a regular attribute to the node, since it can be used in the template and rendering as a dynamic value that can change based on the store value. By adding it as a regular attribute, we can ensure that it is properly rendered and updated in the DOM based on its value in the store.
242
+ * 2. Default attribute binding evaluated reactively against the local store.
213
243
  */
214
- else {
215
- const instruction = new AddAttributeInstruction(scene.host, {
216
- name: attribute.name,
217
- content: attribute.content,
218
- evaluate: true
219
- })
220
-
221
- scene.plan(instruction);
222
- }
244
+ const instruction = new AddAttributeInstruction(scene.host, {
245
+ name: attribute.name,
246
+ content: attribute.content,
247
+ evaluate: true
248
+ })
249
+
250
+ scene.plan(instruction);
223
251
  }
224
252
 
225
253