@saolabs/client 1.0.0 → 1.1.0

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 (383) hide show
  1. package/README.md +52 -195
  2. package/dist/index.d.ts +20 -5
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +14 -3
  5. package/dist/index.js.map +1 -1
  6. package/dist/plugins.d.ts +2 -2
  7. package/dist/plugins.js +2 -2
  8. package/dist/src/core/app/Application.d.ts +25 -15
  9. package/dist/src/core/app/Application.d.ts.map +1 -1
  10. package/dist/src/core/app/Application.js +55 -18
  11. package/dist/src/core/app/Application.js.map +1 -1
  12. package/dist/src/core/bootstrap/app.d.ts +1 -1
  13. package/dist/src/core/bootstrap/app.d.ts.map +1 -1
  14. package/dist/src/core/bootstrap/app.js +77 -4
  15. package/dist/src/core/bootstrap/app.js.map +1 -1
  16. package/dist/src/core/bootstrap/bundle.d.ts +41 -0
  17. package/dist/src/core/bootstrap/bundle.d.ts.map +1 -0
  18. package/dist/src/core/bootstrap/bundle.js +98 -0
  19. package/dist/src/core/bootstrap/bundle.js.map +1 -0
  20. package/dist/src/core/bootstrap/providers/ApiServiceProvider.d.ts +1 -1
  21. package/dist/src/core/bootstrap/providers/ApiServiceProvider.d.ts.map +1 -1
  22. package/dist/src/core/bootstrap/providers/ApiServiceProvider.js +3 -3
  23. package/dist/src/core/bootstrap/providers/ApiServiceProvider.js.map +1 -1
  24. package/dist/src/core/bootstrap/providers/CoreServiceProvider.d.ts +1 -1
  25. package/dist/src/core/bootstrap/providers/CoreServiceProvider.d.ts.map +1 -1
  26. package/dist/src/core/bootstrap/providers/CoreServiceProvider.js +11 -8
  27. package/dist/src/core/bootstrap/providers/CoreServiceProvider.js.map +1 -1
  28. package/dist/src/core/bootstrap/providers/HelperServiceProvider.d.ts +3 -1
  29. package/dist/src/core/bootstrap/providers/HelperServiceProvider.d.ts.map +1 -1
  30. package/dist/src/core/bootstrap/providers/HelperServiceProvider.js +8 -3
  31. package/dist/src/core/bootstrap/providers/HelperServiceProvider.js.map +1 -1
  32. package/dist/src/core/bootstrap/providers/RouteServiceProvider.d.ts +2 -1
  33. package/dist/src/core/bootstrap/providers/RouteServiceProvider.d.ts.map +1 -1
  34. package/dist/src/core/bootstrap/providers/RouteServiceProvider.js +14 -4
  35. package/dist/src/core/bootstrap/providers/RouteServiceProvider.js.map +1 -1
  36. package/dist/src/core/bootstrap/providers/ServiceProvider.d.ts +4 -4
  37. package/dist/src/core/bootstrap/providers/ServiceProvider.d.ts.map +1 -1
  38. package/dist/src/core/bootstrap/providers/ServiceProvider.js +5 -5
  39. package/dist/src/core/bootstrap/providers/ServiceProvider.js.map +1 -1
  40. package/dist/src/core/bootstrap/providers/ViewServiceProvider.d.ts +1 -1
  41. package/dist/src/core/bootstrap/providers/ViewServiceProvider.d.ts.map +1 -1
  42. package/dist/src/core/bootstrap/providers/ViewServiceProvider.js +3 -3
  43. package/dist/src/core/bootstrap/providers/ViewServiceProvider.js.map +1 -1
  44. package/dist/src/core/bootstrap/providers/default-providers.d.ts +1 -1
  45. package/dist/src/core/bootstrap/providers/default-providers.d.ts.map +1 -1
  46. package/dist/src/core/bootstrap/providers/default-providers.js +44 -11
  47. package/dist/src/core/bootstrap/providers/default-providers.js.map +1 -1
  48. package/dist/src/core/bootstrap/providers/index.d.ts +9 -9
  49. package/dist/src/core/bootstrap/providers/index.d.ts.map +1 -1
  50. package/dist/src/core/bootstrap/providers/index.js +8 -8
  51. package/dist/src/core/bootstrap/providers/index.js.map +1 -1
  52. package/dist/src/core/bootstrap/providers/provider-order.d.ts +1 -1
  53. package/dist/src/core/bootstrap/providers/provider-order.d.ts.map +1 -1
  54. package/dist/src/core/bootstrap/ssr.d.ts +47 -0
  55. package/dist/src/core/bootstrap/ssr.d.ts.map +1 -0
  56. package/dist/src/core/bootstrap/ssr.js +129 -0
  57. package/dist/src/core/bootstrap/ssr.js.map +1 -0
  58. package/dist/src/core/cache/ViewCache.d.ts +1 -1
  59. package/dist/src/core/cache/ViewCache.d.ts.map +1 -1
  60. package/dist/src/core/cache/index.d.ts +1 -1
  61. package/dist/src/core/cache/index.d.ts.map +1 -1
  62. package/dist/src/core/cache/index.js +1 -1
  63. package/dist/src/core/cache/index.js.map +1 -1
  64. package/dist/src/core/contracts/ApplicationInterface.d.ts +15 -11
  65. package/dist/src/core/contracts/ApplicationInterface.d.ts.map +1 -1
  66. package/dist/src/core/contracts/BlockInterface.d.ts +10 -10
  67. package/dist/src/core/contracts/BlockInterface.d.ts.map +1 -1
  68. package/dist/src/core/contracts/BundleInterface.d.ts +38 -0
  69. package/dist/src/core/contracts/BundleInterface.d.ts.map +1 -0
  70. package/dist/src/core/contracts/BundleInterface.js +2 -0
  71. package/dist/src/core/contracts/BundleInterface.js.map +1 -0
  72. package/dist/src/core/contracts/ComponentInterface.d.ts +7 -6
  73. package/dist/src/core/contracts/ComponentInterface.d.ts.map +1 -1
  74. package/dist/src/core/contracts/ElementInterface.d.ts +91 -27
  75. package/dist/src/core/contracts/ElementInterface.d.ts.map +1 -1
  76. package/dist/src/core/contracts/HeadServiceInterface.d.ts +37 -0
  77. package/dist/src/core/contracts/HeadServiceInterface.d.ts.map +1 -0
  78. package/dist/src/core/contracts/HeadServiceInterface.js +3 -0
  79. package/dist/src/core/contracts/HeadServiceInterface.js.map +1 -0
  80. package/dist/src/core/contracts/HelperInterface.d.ts +9 -1
  81. package/dist/src/core/contracts/HelperInterface.d.ts.map +1 -1
  82. package/dist/src/core/contracts/HttpServiceInterface.d.ts +3 -0
  83. package/dist/src/core/contracts/HttpServiceInterface.d.ts.map +1 -1
  84. package/dist/src/core/contracts/MarkerInterface.d.ts +29 -44
  85. package/dist/src/core/contracts/MarkerInterface.d.ts.map +1 -1
  86. package/dist/src/core/contracts/ReactiveInterface.d.ts +9 -9
  87. package/dist/src/core/contracts/ReactiveInterface.d.ts.map +1 -1
  88. package/dist/src/core/contracts/RouterInterface.d.ts +5 -2
  89. package/dist/src/core/contracts/RouterInterface.d.ts.map +1 -1
  90. package/dist/src/core/contracts/SectionInterface.d.ts +48 -0
  91. package/dist/src/core/contracts/SectionInterface.d.ts.map +1 -0
  92. package/dist/src/core/contracts/SectionInterface.js +2 -0
  93. package/dist/src/core/contracts/SectionInterface.js.map +1 -0
  94. package/dist/src/core/contracts/ServiceProviderInterface.d.ts +3 -3
  95. package/dist/src/core/contracts/ServiceProviderInterface.d.ts.map +1 -1
  96. package/dist/src/core/contracts/ViewControllerInterface.d.ts +116 -15
  97. package/dist/src/core/contracts/ViewControllerInterface.d.ts.map +1 -1
  98. package/dist/src/core/contracts/ViewInterface.d.ts +31 -5
  99. package/dist/src/core/contracts/ViewInterface.d.ts.map +1 -1
  100. package/dist/src/core/contracts/ViewManagerInterface.d.ts +47 -5
  101. package/dist/src/core/contracts/ViewManagerInterface.d.ts.map +1 -1
  102. package/dist/src/core/contracts/ViewStateInterface.d.ts +39 -2
  103. package/dist/src/core/contracts/ViewStateInterface.d.ts.map +1 -1
  104. package/dist/src/core/contracts/common.d.ts +3 -3
  105. package/dist/src/core/contracts/common.js +3 -3
  106. package/dist/src/core/contracts/utils.d.ts +19 -19
  107. package/dist/src/core/contracts/utils.d.ts.map +1 -1
  108. package/dist/src/core/contracts/views.d.ts +1 -22
  109. package/dist/src/core/contracts/views.d.ts.map +1 -1
  110. package/dist/src/core/devtools/hook.d.ts +66 -0
  111. package/dist/src/core/devtools/hook.d.ts.map +1 -0
  112. package/dist/src/core/devtools/hook.js +114 -0
  113. package/dist/src/core/devtools/hook.js.map +1 -0
  114. package/dist/src/core/devtools/index.d.ts +34 -0
  115. package/dist/src/core/devtools/index.d.ts.map +1 -0
  116. package/dist/src/core/devtools/index.js +33 -0
  117. package/dist/src/core/devtools/index.js.map +1 -0
  118. package/dist/src/core/devtools/inspector.d.ts +31 -0
  119. package/dist/src/core/devtools/inspector.d.ts.map +1 -0
  120. package/dist/src/core/devtools/inspector.js +249 -0
  121. package/dist/src/core/devtools/inspector.js.map +1 -0
  122. package/dist/src/core/elements/Block.d.ts +9 -7
  123. package/dist/src/core/elements/Block.d.ts.map +1 -1
  124. package/dist/src/core/elements/Block.js +33 -9
  125. package/dist/src/core/elements/Block.js.map +1 -1
  126. package/dist/src/core/elements/BlockOutlet.d.ts +19 -8
  127. package/dist/src/core/elements/BlockOutlet.d.ts.map +1 -1
  128. package/dist/src/core/elements/BlockOutlet.js +57 -13
  129. package/dist/src/core/elements/BlockOutlet.js.map +1 -1
  130. package/dist/src/core/elements/Component.d.ts +83 -11
  131. package/dist/src/core/elements/Component.d.ts.map +1 -1
  132. package/dist/src/core/elements/Component.js +334 -13
  133. package/dist/src/core/elements/Component.js.map +1 -1
  134. package/dist/src/core/elements/ForeachSlotCache.d.ts +95 -0
  135. package/dist/src/core/elements/ForeachSlotCache.d.ts.map +1 -0
  136. package/dist/src/core/elements/ForeachSlotCache.js +149 -0
  137. package/dist/src/core/elements/ForeachSlotCache.js.map +1 -0
  138. package/dist/src/core/elements/Fragment.d.ts +19 -13
  139. package/dist/src/core/elements/Fragment.d.ts.map +1 -1
  140. package/dist/src/core/elements/Fragment.js +43 -20
  141. package/dist/src/core/elements/Fragment.js.map +1 -1
  142. package/dist/src/core/elements/Html.d.ts +89 -12
  143. package/dist/src/core/elements/Html.d.ts.map +1 -1
  144. package/dist/src/core/elements/Html.js +707 -106
  145. package/dist/src/core/elements/Html.js.map +1 -1
  146. package/dist/src/core/elements/Output.d.ts +34 -10
  147. package/dist/src/core/elements/Output.d.ts.map +1 -1
  148. package/dist/src/core/elements/Output.js +141 -26
  149. package/dist/src/core/elements/Output.js.map +1 -1
  150. package/dist/src/core/elements/Reactive.d.ts +85 -14
  151. package/dist/src/core/elements/Reactive.d.ts.map +1 -1
  152. package/dist/src/core/elements/Reactive.js +315 -41
  153. package/dist/src/core/elements/Reactive.js.map +1 -1
  154. package/dist/src/core/elements/TextElement.d.ts +11 -7
  155. package/dist/src/core/elements/TextElement.d.ts.map +1 -1
  156. package/dist/src/core/elements/TextElement.js +18 -11
  157. package/dist/src/core/elements/TextElement.js.map +1 -1
  158. package/dist/src/core/elements/Wrapper.d.ts +24 -13
  159. package/dist/src/core/elements/Wrapper.d.ts.map +1 -1
  160. package/dist/src/core/elements/Wrapper.js +65 -8
  161. package/dist/src/core/elements/Wrapper.js.map +1 -1
  162. package/dist/src/core/elements/Yield.d.ts +10 -9
  163. package/dist/src/core/elements/Yield.d.ts.map +1 -1
  164. package/dist/src/core/elements/Yield.js +31 -19
  165. package/dist/src/core/elements/Yield.js.map +1 -1
  166. package/dist/src/core/elements/helpers.d.ts +7 -7
  167. package/dist/src/core/elements/helpers.d.ts.map +1 -1
  168. package/dist/src/core/elements/helpers.js +4 -4
  169. package/dist/src/core/elements/index.d.ts +12 -11
  170. package/dist/src/core/elements/index.d.ts.map +1 -1
  171. package/dist/src/core/elements/index.js +12 -11
  172. package/dist/src/core/elements/index.js.map +1 -1
  173. package/dist/src/core/elements/reconcileChildren.d.ts +7 -0
  174. package/dist/src/core/elements/reconcileChildren.d.ts.map +1 -0
  175. package/dist/src/core/elements/reconcileChildren.js +74 -0
  176. package/dist/src/core/elements/reconcileChildren.js.map +1 -0
  177. package/dist/src/core/helpers/ApiClient.d.ts.map +1 -0
  178. package/dist/src/core/{hellpers → helpers}/ApiClient.js +10 -1
  179. package/dist/src/core/helpers/ApiClient.js.map +1 -0
  180. package/dist/src/core/helpers/app.d.ts +3 -0
  181. package/dist/src/core/helpers/app.d.ts.map +1 -0
  182. package/dist/src/core/{hellpers → helpers}/app.js +11 -1
  183. package/dist/src/core/helpers/app.js.map +1 -0
  184. package/dist/src/core/helpers/transition.d.ts +34 -0
  185. package/dist/src/core/helpers/transition.d.ts.map +1 -0
  186. package/dist/src/core/helpers/transition.js +118 -0
  187. package/dist/src/core/helpers/transition.js.map +1 -0
  188. package/dist/src/core/{hellpers → helpers}/utils.d.ts +2 -2
  189. package/dist/src/core/helpers/utils.d.ts.map +1 -0
  190. package/dist/src/core/{hellpers → helpers}/utils.js +4 -4
  191. package/dist/src/core/helpers/utils.js.map +1 -0
  192. package/dist/src/core/helpers/view.d.ts +39 -0
  193. package/dist/src/core/helpers/view.d.ts.map +1 -0
  194. package/dist/src/core/helpers/view.js +286 -0
  195. package/dist/src/core/helpers/view.js.map +1 -0
  196. package/dist/src/core/routers/Router.d.ts +108 -7
  197. package/dist/src/core/routers/Router.d.ts.map +1 -1
  198. package/dist/src/core/routers/Router.js +444 -68
  199. package/dist/src/core/routers/Router.js.map +1 -1
  200. package/dist/src/core/routers/index.d.ts +2 -2
  201. package/dist/src/core/routers/index.d.ts.map +1 -1
  202. package/dist/src/core/routers/index.js +1 -1
  203. package/dist/src/core/routers/index.js.map +1 -1
  204. package/dist/src/core/services/AssetManager.d.ts +112 -0
  205. package/dist/src/core/services/AssetManager.d.ts.map +1 -0
  206. package/dist/src/core/services/AssetManager.js +361 -0
  207. package/dist/src/core/services/AssetManager.js.map +1 -0
  208. package/dist/src/core/services/BlockManager.d.ts +73 -2
  209. package/dist/src/core/services/BlockManager.d.ts.map +1 -1
  210. package/dist/src/core/services/BlockManager.js +270 -48
  211. package/dist/src/core/services/BlockManager.js.map +1 -1
  212. package/dist/src/core/services/DomService.js +3 -3
  213. package/dist/src/core/services/DomService.js.map +1 -1
  214. package/dist/src/core/services/HeadService.d.ts +24 -0
  215. package/dist/src/core/services/HeadService.d.ts.map +1 -0
  216. package/dist/src/core/services/HeadService.js +120 -0
  217. package/dist/src/core/services/HeadService.js.map +1 -0
  218. package/dist/src/core/services/HelperService.d.ts +14 -3
  219. package/dist/src/core/services/HelperService.d.ts.map +1 -1
  220. package/dist/src/core/services/HelperService.js +32 -2
  221. package/dist/src/core/services/HelperService.js.map +1 -1
  222. package/dist/src/core/services/HttpService.d.ts +8 -0
  223. package/dist/src/core/services/HttpService.d.ts.map +1 -1
  224. package/dist/src/core/services/HttpService.js +121 -47
  225. package/dist/src/core/services/HttpService.js.map +1 -1
  226. package/dist/src/core/services/MarkerRegistry.d.ts +51 -8
  227. package/dist/src/core/services/MarkerRegistry.d.ts.map +1 -1
  228. package/dist/src/core/services/MarkerRegistry.js +135 -14
  229. package/dist/src/core/services/MarkerRegistry.js.map +1 -1
  230. package/dist/src/core/services/PageCache.d.ts +101 -0
  231. package/dist/src/core/services/PageCache.d.ts.map +1 -0
  232. package/dist/src/core/services/PageCache.js +184 -0
  233. package/dist/src/core/services/PageCache.js.map +1 -0
  234. package/dist/src/core/services/SectionManager.d.ts +100 -0
  235. package/dist/src/core/services/SectionManager.d.ts.map +1 -0
  236. package/dist/src/core/services/SectionManager.js +408 -0
  237. package/dist/src/core/services/SectionManager.js.map +1 -0
  238. package/dist/src/core/services/StorageService.js +1 -1
  239. package/dist/src/core/services/TranslationService.d.ts +21 -0
  240. package/dist/src/core/services/TranslationService.d.ts.map +1 -0
  241. package/dist/src/core/services/TranslationService.js +95 -0
  242. package/dist/src/core/services/TranslationService.js.map +1 -0
  243. package/dist/src/core/services/index.d.ts +11 -8
  244. package/dist/src/core/services/index.d.ts.map +1 -1
  245. package/dist/src/core/services/index.js +11 -8
  246. package/dist/src/core/services/index.js.map +1 -1
  247. package/dist/src/core/types/utils.d.ts +8 -8
  248. package/dist/src/core/types/utils.d.ts.map +1 -1
  249. package/dist/src/core/view/LoopContext.d.ts +21 -1
  250. package/dist/src/core/view/LoopContext.d.ts.map +1 -1
  251. package/dist/src/core/view/LoopContext.js +36 -0
  252. package/dist/src/core/view/LoopContext.js.map +1 -1
  253. package/dist/src/core/view/ResourceScope.d.ts +38 -0
  254. package/dist/src/core/view/ResourceScope.d.ts.map +1 -0
  255. package/dist/src/core/view/ResourceScope.js +167 -0
  256. package/dist/src/core/view/ResourceScope.js.map +1 -0
  257. package/dist/src/core/view/Section.d.ts +17 -32
  258. package/dist/src/core/view/Section.d.ts.map +1 -1
  259. package/dist/src/core/view/Section.js +14 -55
  260. package/dist/src/core/view/Section.js.map +1 -1
  261. package/dist/src/core/view/View.d.ts +43 -7
  262. package/dist/src/core/view/View.d.ts.map +1 -1
  263. package/dist/src/core/view/View.js +22 -4
  264. package/dist/src/core/view/View.js.map +1 -1
  265. package/dist/src/core/view/ViewController.d.ts +313 -40
  266. package/dist/src/core/view/ViewController.d.ts.map +1 -1
  267. package/dist/src/core/view/ViewController.js +1002 -147
  268. package/dist/src/core/view/ViewController.js.map +1 -1
  269. package/dist/src/core/view/ViewManager.d.ts +318 -27
  270. package/dist/src/core/view/ViewManager.d.ts.map +1 -1
  271. package/dist/src/core/view/ViewManager.js +1199 -258
  272. package/dist/src/core/view/ViewManager.js.map +1 -1
  273. package/dist/src/core/view/ViewState.d.ts +185 -6
  274. package/dist/src/core/view/ViewState.d.ts.map +1 -1
  275. package/dist/src/core/view/ViewState.js +851 -123
  276. package/dist/src/core/view/ViewState.js.map +1 -1
  277. package/dist/src/core/view/index.d.ts +5 -5
  278. package/dist/src/core/view/index.d.ts.map +1 -1
  279. package/dist/src/core/view/index.js +5 -5
  280. package/dist/src/core/view/index.js.map +1 -1
  281. package/dist/src/testing/index.d.ts +69 -0
  282. package/dist/src/testing/index.d.ts.map +1 -0
  283. package/dist/src/testing/index.js +140 -0
  284. package/dist/src/testing/index.js.map +1 -0
  285. package/index.ts +29 -5
  286. package/package.json +30 -21
  287. package/plugins.ts +2 -2
  288. package/src/core/app/Application.ts +72 -31
  289. package/src/core/bootstrap/app.ts +84 -6
  290. package/src/core/bootstrap/bundle.ts +101 -0
  291. package/src/core/bootstrap/providers/ApiServiceProvider.ts +4 -4
  292. package/src/core/bootstrap/providers/CoreServiceProvider.ts +14 -8
  293. package/src/core/bootstrap/providers/HelperServiceProvider.ts +8 -3
  294. package/src/core/bootstrap/providers/RouteServiceProvider.ts +15 -4
  295. package/src/core/bootstrap/providers/ServiceProvider.ts +8 -8
  296. package/src/core/bootstrap/providers/ViewServiceProvider.ts +3 -3
  297. package/src/core/bootstrap/providers/default-providers.ts +51 -16
  298. package/src/core/bootstrap/providers/index.ts +9 -9
  299. package/src/core/bootstrap/providers/provider-order.test.ts +16 -27
  300. package/src/core/bootstrap/providers/provider-order.ts +1 -1
  301. package/src/core/bootstrap/ssr.ts +141 -0
  302. package/src/core/cache/ViewCache.ts +1 -1
  303. package/src/core/cache/index.ts +1 -1
  304. package/src/core/contracts/ApplicationInterface.ts +15 -11
  305. package/src/core/contracts/BlockInterface.ts +18 -18
  306. package/src/core/contracts/BundleInterface.ts +40 -0
  307. package/src/core/contracts/ComponentInterface.ts +7 -6
  308. package/src/core/contracts/ElementInterface.ts +95 -27
  309. package/src/core/contracts/HeadServiceInterface.ts +41 -0
  310. package/src/core/contracts/HelperInterface.ts +9 -1
  311. package/src/core/contracts/HttpServiceInterface.ts +3 -0
  312. package/src/core/contracts/MarkerInterface.ts +22 -49
  313. package/src/core/contracts/ReactiveInterface.ts +9 -9
  314. package/src/core/contracts/RouterInterface.ts +5 -2
  315. package/src/core/contracts/SectionInterface.ts +49 -0
  316. package/src/core/contracts/ServiceProviderInterface.ts +3 -3
  317. package/src/core/contracts/ViewControllerInterface.ts +124 -16
  318. package/src/core/contracts/ViewInterface.ts +34 -5
  319. package/src/core/contracts/ViewManagerInterface.ts +40 -5
  320. package/src/core/contracts/ViewStateInterface.ts +39 -2
  321. package/src/core/contracts/common.ts +3 -3
  322. package/src/core/contracts/utils.ts +19 -19
  323. package/src/core/contracts/views.ts +2 -22
  324. package/src/core/devtools/hook.ts +142 -0
  325. package/src/core/devtools/index.ts +38 -0
  326. package/src/core/devtools/inspector.ts +270 -0
  327. package/src/core/elements/Block.ts +43 -21
  328. package/src/core/elements/BlockOutlet.ts +61 -21
  329. package/src/core/elements/Component.ts +352 -18
  330. package/src/core/elements/ElementManager.ts +9 -9
  331. package/src/core/elements/ForeachSlotCache.ts +170 -0
  332. package/src/core/elements/Fragment.ts +50 -32
  333. package/src/core/elements/Html.ts +696 -117
  334. package/src/core/elements/Output.ts +147 -30
  335. package/src/core/elements/Reactive.ts +329 -52
  336. package/src/core/elements/TextElement.ts +25 -16
  337. package/src/core/elements/Wrapper.ts +84 -18
  338. package/src/core/elements/Yield.ts +35 -24
  339. package/src/core/elements/helpers.ts +12 -12
  340. package/src/core/elements/index.ts +25 -24
  341. package/src/core/elements/reconcileChildren.ts +70 -0
  342. package/src/core/{hellpers → helpers}/ApiClient.ts +8 -1
  343. package/src/core/{hellpers → helpers}/app.ts +13 -2
  344. package/src/core/helpers/transition.ts +127 -0
  345. package/src/core/{hellpers → helpers}/utils.ts +4 -4
  346. package/src/core/helpers/view.ts +283 -0
  347. package/src/core/routers/Router.ts +458 -64
  348. package/src/core/routers/index.ts +2 -2
  349. package/src/core/services/AssetManager.ts +387 -0
  350. package/src/core/services/BlockManager.ts +272 -50
  351. package/src/core/services/DomService.ts +3 -3
  352. package/src/core/services/HeadService.ts +124 -0
  353. package/src/core/services/HelperService.ts +34 -4
  354. package/src/core/services/HttpService.ts +116 -55
  355. package/src/core/services/MarkerRegistry.ts +142 -16
  356. package/src/core/services/PageCache.ts +239 -0
  357. package/src/core/services/SectionManager.ts +387 -0
  358. package/src/core/services/StorageService.ts +1 -1
  359. package/src/core/services/TranslationService.ts +91 -0
  360. package/src/core/services/index.ts +11 -8
  361. package/src/core/types/utils.ts +8 -8
  362. package/src/core/view/LoopContext.ts +38 -1
  363. package/src/core/view/ResourceScope.ts +174 -0
  364. package/src/core/view/Section.ts +21 -68
  365. package/src/core/view/View.ts +57 -8
  366. package/src/core/view/ViewController.ts +1070 -186
  367. package/src/core/view/ViewManager.ts +1346 -272
  368. package/src/core/view/ViewState.ts +834 -116
  369. package/src/core/view/index.ts +5 -5
  370. package/src/testing/index.ts +184 -0
  371. package/dist/src/core/hellpers/ApiClient.d.ts.map +0 -1
  372. package/dist/src/core/hellpers/ApiClient.js.map +0 -1
  373. package/dist/src/core/hellpers/app.d.ts +0 -3
  374. package/dist/src/core/hellpers/app.d.ts.map +0 -1
  375. package/dist/src/core/hellpers/app.js.map +0 -1
  376. package/dist/src/core/hellpers/utils.d.ts.map +0 -1
  377. package/dist/src/core/hellpers/utils.js.map +0 -1
  378. package/dist/src/core/services/MarkerService.d.ts +0 -299
  379. package/dist/src/core/services/MarkerService.d.ts.map +0 -1
  380. package/dist/src/core/services/MarkerService.js +0 -723
  381. package/dist/src/core/services/MarkerService.js.map +0 -1
  382. package/src/core/services/MarkerService.ts +0 -923
  383. /package/dist/src/core/{hellpers → helpers}/ApiClient.d.ts +0 -0
@@ -1,10 +1,14 @@
1
- import { InitMode, InitModes } from "../contracts/common";
2
- import type { HtmlInterface, OneChildrenFactoryOutput, OneElementChildren } from "../contracts/ElementInterface";
3
- import type { ReactiveInterface, ReactiveChildrenFactory, ReactiveRenderFn } from "../contracts/ReactiveInterface";
4
- import type { ViewControllerInterface } from "../contracts/ViewControllerInterface";
5
- import { generateUUID } from "../hellpers/utils";
6
- import markerRegistry from "../services/MarkerRegistry";
7
- import type { OneObjectType } from "../types/utils";
1
+ import { reconcileChildren } from './reconcileChildren.js';
2
+ import { InitMode, InitModes } from "../contracts/common.js";
3
+ import type { HtmlInterface, SaoChildrenFactoryOutput, SaoElementChildren } from "../contracts/ElementInterface.js";
4
+ import type { ReactiveInterface, ReactiveChildrenFactory, ReactiveRenderFn } from "../contracts/ReactiveInterface.js";
5
+ import type { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
6
+ import { generateUUID } from "../helpers/utils.js";
7
+ import { mountChildrenBeforeAnchor } from "../helpers/view.js";
8
+ import markerRegistry from "../services/MarkerRegistry.js";
9
+ import type { SaoObjectType } from "../types/utils.js";
10
+ import { ForeachSlotCache } from "./ForeachSlotCache.js";
11
+ import { isLeaving } from "../helpers/transition.js";
8
12
 
9
13
  /**
10
14
  * Reactive — a region in the DOM bounded by comment markers that
@@ -22,27 +26,38 @@ import type { OneObjectType } from "../types/utils";
22
26
  */
23
27
  export class Reactive implements ReactiveInterface {
24
28
  static class = 'Reactive';
25
- oneType: OneObjectType = 'Reactive';
29
+ saoType: SaoObjectType = 'Reactive';
26
30
  public id: string; // Unique ID for debugging and marker registry
27
31
  public type: string; // Custom type for different reactive behaviors (e.g. 'if', 'list')
28
32
  public openTag: Comment;
29
33
  public closeTag: Comment;
30
34
  public parentElement: HtmlInterface | null;
31
35
  public parentReactive: ReactiveInterface | null;
32
- public parent: HtmlInterface | null = null; // Alias for parentReactive to satisfy OneNodeInterface
36
+ public parent: HtmlInterface | null = null; // Alias for parentReactive to satisfy SaoNodeInterface
33
37
 
34
38
  public ctx: ViewControllerInterface;
35
39
  public childrenFactory: ReactiveChildrenFactory;
36
- public children: OneElementChildren = [];
40
+ public children: SaoElementChildren = [];
37
41
  private mounted: boolean = false;
38
42
  public stateKeys: string[];
39
- public unsubscribe: () => void = () => {};
43
+ public unsubscribe: () => void = () => { };
40
44
  private _isStarted = false;
45
+ /** Marker model (hydration) — gán bởi BlockManager/SSR khi cần; mặc định null. */
46
+ /** Key trả về bởi markerRegistry.register — destroy() dùng để gỡ lại */
47
+ private markerKey: string | null = null;
41
48
 
42
49
 
43
50
 
44
51
  domChildren: Node[] = []; // For compatibility with HtmlInterface; Reactive itself doesn't have a single root element
45
52
  initMode: InitMode = InitModes.CREATE;
53
+
54
+ /**
55
+ * Slot cache cho @foreach reconciliation (Phase 5).
56
+ * Được tạo khi type === 'foreach', null cho tất cả các type khác.
57
+ * Cache maps item ref → Saola elements — cho phép reuse elements
58
+ * khi item object reference không thay đổi.
59
+ */
60
+ private _foreachCache: ForeachSlotCache | null = null;
46
61
  constructor({
47
62
  type = 'reactive',
48
63
  id = null,
@@ -65,19 +80,49 @@ export class Reactive implements ReactiveInterface {
65
80
  this.ctx = ctx;
66
81
  this.parentElement = parentElement;
67
82
  this.parentReactive = parentReactive;
68
- this.id = `${ctx.viewId}-${id || generateUUID(5)}`;
83
+ this.id = id ? id : `${ctx.viewId}-${generateUUID(5)}`;
69
84
  this.type = type;
70
85
  this.childrenFactory = childrenFactory;
71
86
  this.stateKeys = stateKeys;
72
87
  this.initMode = initMode;
73
- markerRegistry.register('reactive', this.id, {
74
- type: this.type,
75
- stateKeys: this.stateKeys,
76
- viewID: this.ctx.viewId,
77
- });
78
88
 
79
- this.openTag = markerRegistry.createMarkerStart('reactive', this.id);
80
- this.closeTag = markerRegistry.createMarkerEnd('reactive', this.id);
89
+ // foreach type: khởi tạo slot cache để hỗ trợ identity-keyed reconciliation
90
+ if (type === 'foreach') {
91
+ this._foreachCache = new ForeachSlotCache();
92
+ }
93
+
94
+ if (this.initMode === InitModes.CREATE) {
95
+ this.markerKey = markerRegistry.register('reactive', this.id, {
96
+ type: this.type,
97
+ stateKeys: this.stateKeys,
98
+ viewID: this.ctx.viewId,
99
+ });
100
+
101
+ this.openTag = markerRegistry.createMarkerStart('reactive', this.id);
102
+ this.closeTag = markerRegistry.createMarkerEnd('reactive', this.id);
103
+ }
104
+ else {
105
+ // ── Hydrate: tìm markers SSR trong DOM ──────────────────────
106
+ // Scan parent element cho cặp comment <!--r:id--> ... <!--/r:id-->
107
+ // (MarkerRegistry format). Fallback tạo mới nếu không tìm thấy.
108
+ const claimed = this.claimSSRMarkers();
109
+ if (claimed) {
110
+ this.openTag = claimed.open;
111
+ this.closeTag = claimed.close;
112
+ } else {
113
+ this.openTag = markerRegistry.createMarkerStart('reactive', this.id);
114
+ this.closeTag = markerRegistry.createMarkerEnd('reactive', this.id);
115
+ }
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Tìm cặp comment markers từ server-rendered HTML.
121
+ * Format MarkerRegistry: open = `r:id`, close = `/r:id`
122
+ * (r = shortcut cho 'reactive').
123
+ */
124
+ private claimSSRMarkers(): { open: Comment; close: Comment } | null {
125
+ return markerRegistry.claim('reactive', this.id, this.parentElement?.element ?? null);
81
126
  }
82
127
 
83
128
  setParentElement(parent: HtmlInterface | null): void {
@@ -97,15 +142,62 @@ export class Reactive implements ReactiveInterface {
97
142
  }
98
143
  }
99
144
 
100
- /** Mount markers into parent and render content */
145
+ /** Registry guard — element đã destroy không được reuse */
146
+ public __destroyed__: boolean = false;
147
+
148
+ /**
149
+ * Render — idempotent + position-aware (RUNTIME_CONTRACT.md §2):
150
+ * - Markers chưa trong DOM → đặt markers (trước closeTag của parentReactive
151
+ * nếu có, ngược lại append vào parentElement — đường mountElementList
152
+ * duyệt tuần tự nên vị trí đúng).
153
+ * - Markers đã trong DOM → chỉ thay nội dung GIỮA markers.
154
+ * Children loại marker-based (Output, nested Reactive, Fragment) được CALLER
155
+ * đặt markers vào đúng vị trí trước, rồi mới gọi child.render() — FIX(baseline#6).
156
+ * Children sinh ra khi đang active được start() ngay — FIX(baseline#7).
157
+ */
101
158
  render(): void {
102
- const target = this.getInsertionTarget();
103
- if (!target) return;
159
+ try {
160
+ this._render();
161
+ } catch (err) {
162
+ // Lỗi ở vùng reactive thường xảy ra SAU khi trang đã sống (state đổi do
163
+ // user tương tác) — ngoài mọi try/catch của renderPageView. 'render' cho
164
+ // lần đầu, 'update' cho re-render.
165
+ const phase = this.mounted ? 'update' : 'render';
166
+ const result = this.ctx.handleError(err, { phase, path: this.ctx.path });
167
+ if (!result.handled) throw err;
168
+ this.clearContent();
169
+ if (result.fallback !== null && result.fallback !== undefined) {
170
+ mountChildrenBeforeAnchor(
171
+ this.closeTag,
172
+ Array.isArray(result.fallback) ? result.fallback : [result.fallback],
173
+ this.parentElement,
174
+ );
175
+ }
176
+ }
177
+ }
178
+
179
+ private _render(): void {
180
+ if (this.__destroyed__) return;
181
+
182
+ // ── Hydrate mode: markers đã trong DOM từ SSR ────────────────
183
+ // Tiền đề: ViewManager.hydrateView() đã gọi commitData() TRƯỚC bước này,
184
+ // nên closure state (vd `show`) đã có giá trị = trạng thái server.
185
+ // → childrenFactory sinh đúng element tree khớp với SSR DOM.
186
+ // _hydrateChildren tạo JS objects (Html claim DOM, Output claim markers)
187
+ // mà KHÔNG insert node mới — giữ nguyên DOM server-rendered.
188
+ if (this.initMode === InitModes.HYDRATE && this.openTag.parentNode) {
189
+ this._hydrateChildren();
190
+ this.mounted = true;
191
+ // Chuyển sang CREATE: re-render sau này (do state đổi) dùng CSR flow.
192
+ this.initMode = InitModes.CREATE;
193
+ return;
194
+ }
104
195
 
105
- if (!this.mounted) {
106
- // First render: place markers
107
- if (this.parentReactive) {
108
- // Insert before the parent's close marker
196
+ if (!this.openTag.parentNode) {
197
+ // ── Lần đầu mount: đặt markers vào DOM ───────────────────────────
198
+ const target = this.getInsertionTarget();
199
+ if (!target) return;
200
+ if (this.parentReactive && this.parentReactive.closeTag.parentNode === target) {
109
201
  target.insertBefore(this.openTag, this.parentReactive.closeTag);
110
202
  target.insertBefore(this.closeTag, this.parentReactive.closeTag);
111
203
  } else {
@@ -113,28 +205,114 @@ export class Reactive implements ReactiveInterface {
113
205
  target.appendChild(this.closeTag);
114
206
  }
115
207
  this.mounted = true;
208
+
209
+ // Initial render — _renderChildren tự mở cửa sổ cache quanh factory call
210
+ this._renderChildren();
211
+ } else if (this._foreachCache) {
212
+ // ── Re-render @foreach: dùng reconciler ──────────────────────────
213
+ this.renderForeach();
116
214
  } else {
117
- // Re-render: clear existing content between markers
118
- this.clearContent();
215
+ // ── Re-render các type khác: clear + factory ──────────────────────
216
+ const output = this._runFactoryWithCache();
217
+ const next = output.filter(child => child != null).map(child =>
218
+ typeof child === 'string' || typeof child === 'number' ? document.createTextNode(String(child)) : child);
219
+ const parent = this.closeTag.parentNode;
220
+ if (parent) reconcileChildren(parent, this.children, next, this.closeTag);
221
+ this.children = next as any;
222
+ this._cleanOrphanNodes(next);
223
+ if (this._isStarted) for (const child of next) (child as any).start?.();
119
224
  }
225
+ this.mounted = true;
226
+ }
120
227
 
121
- // Produce children
122
- const output: OneChildrenFactoryOutput = this.childrenFactory(this, this.parentElement);
228
+ /**
229
+ * Phần render nội bộ: chạy childrenFactory, insert children vào DOM.
230
+ * Dùng cho initial render (mọi type) và re-render non-foreach.
231
+ * Precondition: markers đã trong DOM.
232
+ */
233
+ private _renderChildren(): void {
234
+ // Cửa sổ cache chỉ mở quanh factory call — KHÔNG mở trong child.render()
235
+ // phía dưới, tránh nested @foreach (chạy lúc render con) ghi nhầm entry
236
+ // vào cache của loop ngoài rồi bị prunePass destroy oan ở pass sau.
237
+ const output = this._runFactoryWithCache();
238
+ const newChildren: any[] = [];
123
239
 
124
- // Insert children between markers
125
240
  for (const child of output) {
241
+ if (child === null || child === undefined) continue;
126
242
  if (typeof child === 'string' || typeof child === 'number') {
127
243
  const textNode = document.createTextNode(String(child));
128
244
  this.insertBeforeClose(textNode);
129
245
  this.children.push(textNode);
130
- } else if (child && typeof child === 'object') {
131
- if ('element' in child) {
132
- // HtmlInterface, TextInterface, FragmentInterface
133
- this.insertBeforeClose(child.element);
246
+ } else if (child instanceof Node) {
247
+ this.insertBeforeClose(child);
248
+ this.children.push(child as any);
249
+ } else if (typeof child === 'object') {
250
+ if ('element' in child && (child as any).element) {
251
+ // Html, TextElement — single root element
252
+ this.insertBeforeClose((child as any).element);
253
+ this.children.push(child);
254
+ newChildren.push(child);
255
+ child.render();
256
+ } else if ('openTag' in child) {
257
+ // Marker-based: Output, nested Reactive, Fragment...
258
+ this.insertBeforeClose((child as any).openTag);
259
+ this.insertBeforeClose((child as any).closeTag);
260
+ this.children.push(child);
261
+ newChildren.push(child);
262
+ child.render();
263
+ }
264
+ }
265
+ }
266
+
267
+ // Vùng active: children mới cần start() ngay (có subscription/event)
268
+ if (this._isStarted) {
269
+ for (const child of newChildren) {
270
+ if (typeof (child as any).start === 'function') {
271
+ (child as any).start();
272
+ }
273
+ }
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Chạy childrenFactory với cửa sổ cache foreach (nếu có) mở đúng quanh
279
+ * factory call. beginPass reset occurrence counters + touched set.
280
+ */
281
+ private _runFactoryWithCache(): SaoChildrenFactoryOutput {
282
+ if (!this._foreachCache) {
283
+ return this.childrenFactory(this, this.parentElement);
284
+ }
285
+ this._foreachCache.beginPass();
286
+ this.ctx._currentForeachCache = this._foreachCache;
287
+ try {
288
+ return this.childrenFactory(this, this.parentElement);
289
+ } finally {
290
+ this.ctx._currentForeachCache = null;
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Hydrate children — tạo JS element objects từ factory nhưng KHÔNG insert DOM.
296
+ * Html children đã claim server DOM nodes trong constructor.
297
+ * Output/Reactive children gọi render() để claim markers.
298
+ * Sau hydrate, re-render tiếp theo dùng flow CSR bình thường.
299
+ */
300
+ private _hydrateChildren(): void {
301
+ const output = this._runFactoryWithCache();
302
+
303
+ for (const child of output) {
304
+ if (child === null || child === undefined) continue;
305
+ if (typeof child === 'string' || typeof child === 'number') continue;
306
+ if (child instanceof Node) continue;
307
+
308
+ if (typeof child === 'object') {
309
+ if ('element' in child && (child as any).element) {
134
310
  this.children.push(child);
135
311
  child.render();
136
312
  } else if ('openTag' in child) {
137
- // Nested Reactive
313
+ if (typeof (child as any).setParentElement === 'function') {
314
+ (child as any).setParentElement(this.parentElement);
315
+ }
138
316
  this.children.push(child);
139
317
  child.render();
140
318
  }
@@ -142,6 +320,100 @@ export class Reactive implements ReactiveInterface {
142
320
  }
143
321
  }
144
322
 
323
+ /**
324
+ * renderForeach — identity-keyed reconciliation cho @foreach (Phase 5).
325
+ *
326
+ * # Thuật toán
327
+ * 1. Snapshot cache hiện tại (prevSlots) — biết item nào đang hiển thị
328
+ * 2. Chạy childrenFactory với cache active → __foreach trả về:
329
+ * - cached elements nếu item ref giống (no DOM create)
330
+ * - elements mới nếu item mới (và lưu vào cache)
331
+ * 3. Diff: items nào KHÔNG xuất hiện trong kết quả mới → destroy + xoá cache
332
+ * 4. Reorder DOM: insertBefore(closeTag) cho tất cả elements mới (move hoặc append)
333
+ * 5. Cleanup orphan DOM nodes (nodes còn sót từ destroyed items)
334
+ * 6. Start elements mới nếu vùng đang active
335
+ *
336
+ * # Complexity
337
+ * - O(n) forEach pass trong __foreach
338
+ * - O(n) destroy pass cho removed items
339
+ * - O(n) DOM move pass (insertBefore là O(1) mỗi node)
340
+ * → Tổng O(n) so với O(2n) của clear+recreate, nhưng tiết kiệm DOM create/destroy
341
+ * cho unchanged items.
342
+ */
343
+ private renderForeach(): void {
344
+ // ── Step 1: Ghi nhớ children đang hiển thị (phân biệt reuse/new) ─────
345
+ const cache = this._foreachCache!;
346
+ const prevChildren = new Set(this.children); // old Saola elements
347
+
348
+ // ── Step 2: Chạy factory với cache active (beginPass + claim/store) ──
349
+ const output = this._runFactoryWithCache();
350
+
351
+ // Flatten output thành danh sách children mới
352
+ const newChildren: any[] = [];
353
+ for (const child of output) {
354
+ if (child === null || child === undefined) continue;
355
+ if (typeof child === 'object') {
356
+ newChildren.push(child);
357
+ }
358
+ }
359
+
360
+ // ── Step 3: Destroy slot không được claim/store trong pass ───────────
361
+ // = item đã rời list, hoặc bị thay bằng ref mới (key trùng nhưng ref đổi).
362
+ cache.prunePass((slot) => {
363
+ for (const el of slot.elements) {
364
+ if (el && typeof el.destroy === 'function') {
365
+ el.destroy();
366
+ }
367
+ }
368
+ });
369
+
370
+ const trulyNew = newChildren.filter(child => !prevChildren.has(child));
371
+ const parent = this.closeTag.parentNode;
372
+ if (parent) reconcileChildren(parent, this.children, newChildren, this.closeTag,
373
+ child => cache.refreshedElements.has(child));
374
+ this._cleanOrphanNodes(newChildren);
375
+ this.children = newChildren;
376
+ if (this._isStarted) {
377
+ for (const child of trulyNew) child.start?.();
378
+ }
379
+ }
380
+
381
+ /**
382
+ * Xoá các DOM nodes "mồ côi" giữa openTag và closeTag của Reactive.
383
+ * Orphan = nodes không thuộc bất kỳ child nào trong newChildren.
384
+ *
385
+ * Build tập hợp "expected nodes" từ newChildren, sau đó scan DOM
386
+ * và remove nodes không nằm trong tập đó.
387
+ */
388
+ private _cleanOrphanNodes(newChildren: any[]): void {
389
+ // Thu thập tất cả DOM nodes hợp lệ (thuộc new children)
390
+ const validNodes = new Set<Node>();
391
+ for (const child of newChildren) {
392
+ if ('element' in child && (child as any).element) {
393
+ validNodes.add((child as any).element);
394
+ } else if ('openTag' in child) {
395
+ // Marker-based: collect tất cả nodes trong block
396
+ let node: Node | null = (child as any).openTag;
397
+ while (node && node !== (child as any).closeTag) {
398
+ validNodes.add(node);
399
+ node = node.nextSibling;
400
+ }
401
+ if (node) validNodes.add(node); // closeTag
402
+ }
403
+ }
404
+
405
+ // Scan từ openTag → closeTag, remove nodes không hợp lệ.
406
+ // Node đang chạy leave PHẢI nằm lại tới khi animation xong — nó tự gỡ.
407
+ let current = this.openTag.nextSibling;
408
+ while (current && current !== this.closeTag) {
409
+ const next = current.nextSibling;
410
+ if (!validNodes.has(current) && !isLeaving(current)) {
411
+ current.remove();
412
+ }
413
+ current = next;
414
+ }
415
+ }
416
+
145
417
  /** Schedule a re-render through the ViewController */
146
418
  update(): void {
147
419
  this.ctx.scheduleUpdate(this);
@@ -157,11 +429,12 @@ export class Reactive implements ReactiveInterface {
157
429
  }
158
430
  this.children = [];
159
431
 
160
- // Remove DOM nodes between markers
432
+ // Remove DOM nodes between markers — trừ node đang leave (child.destroy()
433
+ // phía trên vừa khởi động animation cho chúng; tự gỡ khi xong).
161
434
  let current = this.openTag.nextSibling;
162
435
  while (current && current !== this.closeTag) {
163
436
  const next = current.nextSibling;
164
- current.remove();
437
+ if (!isLeaving(current)) current.remove();
165
438
  current = next;
166
439
  }
167
440
  }
@@ -208,7 +481,7 @@ export class Reactive implements ReactiveInterface {
208
481
 
209
482
  if (this.unsubscribe) {
210
483
  this.unsubscribe();
211
- this.unsubscribe = () => {};
484
+ this.unsubscribe = () => { };
212
485
  }
213
486
 
214
487
  // Recursively stop children
@@ -219,18 +492,22 @@ export class Reactive implements ReactiveInterface {
219
492
  }
220
493
  }
221
494
 
222
- /** Remove content but keep markers (for hide/show scenarios) */
223
- hide(): void {
224
- this.clearContent();
225
- }
226
-
227
- /** Re-render content (for show after hide) */
228
- show(): void {
229
- this.render();
230
- }
231
-
232
495
  destroy(): void {
496
+ this.__destroyed__ = true;
497
+ this.ctx.releaseElement?.(this);
498
+ // MarkerRegistry là singleton TOÀN CỤC (sống qua navigate). Reactive trong
499
+ // loop có id kèm item (`{viewId}-{hash}-${item.id}`) → không gỡ thì mỗi
500
+ // trang/mỗi lần refresh để lại một record vĩnh viễn.
501
+ if (this.markerKey) {
502
+ markerRegistry.remove(this.markerKey);
503
+ this.markerKey = null;
504
+ }
505
+ this.stop();
233
506
  this.clearContent();
507
+ // Giải phóng cache — clearContent() đã destroy các elements
508
+ if (this._foreachCache) {
509
+ this._foreachCache.clear();
510
+ }
234
511
  this.openTag.remove();
235
512
  this.closeTag.remove();
236
513
  this.mounted = false;
@@ -244,10 +521,10 @@ export class Reactive implements ReactiveInterface {
244
521
  set isOneReactive(value: boolean) {
245
522
  // No-op setter to satisfy the Interface; this property is always true for Reactive elements
246
523
  }
247
- set isOneElement(value: boolean) {
524
+ set isSaoElement(value: boolean) {
248
525
  // No-op setter to satisfy the Interface; Reactive is a type of OneElement
249
526
  }
250
- get isOneElement(): boolean {
527
+ get isSaoElement(): boolean {
251
528
  return true;
252
529
  }
253
- }
530
+ }
@@ -1,7 +1,6 @@
1
- import type { HtmlInterface, TextInterface } from "../contracts/ElementInterface";
2
- import type { ViewControllerInterface } from "../contracts/ViewControllerInterface";
3
- import { escapeHTML } from "../hellpers/utils";
4
- import { OneObjectType } from "../types/utils";
1
+ import type { HtmlInterface, TextInterface } from "../contracts/ElementInterface.js";
2
+ import type { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
3
+ import { SaoObjectType } from "../types/utils.js";
5
4
 
6
5
  /**
7
6
  * TextElement — wraps a DOM Text node.
@@ -11,7 +10,7 @@ import { OneObjectType } from "../types/utils";
11
10
  * no DOM removal/insertion needed.
12
11
  */
13
12
  export class TextElement implements TextInterface {
14
- oneType: OneObjectType = 'TextElement';
13
+ saoType: SaoObjectType = 'TextElement';
15
14
  public element: Text;
16
15
  public parent: HtmlInterface | null;
17
16
  private ctx: ViewControllerInterface;
@@ -22,6 +21,7 @@ export class TextElement implements TextInterface {
22
21
  public shouldEscapeHTML: boolean = true; // Whether to escape HTML in text content
23
22
 
24
23
  public isStarted: boolean = false;
24
+ readonly isStatic: boolean;
25
25
 
26
26
 
27
27
  domChildren: Node[] = []; // For compatibility with HtmlInterface; Text itself doesn't have a single root element
@@ -30,11 +30,11 @@ export class TextElement implements TextInterface {
30
30
  this.generateText = generateText;
31
31
  this.parent = parent;
32
32
  this.statekeys = stateKeys;
33
+ this.isStatic = stateKeys.length === 0;
33
34
  this.shouldEscapeHTML = isEscapeHTML;
35
+ // FIX(baseline#3): KHÔNG escapeHTML khi ghi vào Text node — text node tự an toàn,
36
+ // escape thủ công gây double-escape. shouldEscapeHTML chỉ dùng cho SSR string path.
34
37
  this._text = this.generateText();
35
- if (this.shouldEscapeHTML) {
36
- this._text = escapeHTML(this._text);
37
- }
38
38
  this.element = document.createTextNode(this._text);
39
39
  }
40
40
 
@@ -57,28 +57,37 @@ export class TextElement implements TextInterface {
57
57
  }
58
58
  }
59
59
  setParentElement(parent: HtmlInterface | null): void {
60
-
60
+ this.parent = parent;
61
61
  }
62
62
 
63
63
  /** Update text content in-place */
64
64
  update(newText: string): void {
65
65
  if (this._text !== newText) {
66
66
  this._text = newText;
67
- this.element.textContent = this.shouldEscapeHTML ? escapeHTML(newText) : newText;
67
+ this.element.textContent = newText;
68
68
  }
69
69
  }
70
70
 
71
- render(): void {
72
- if (this.parent && this.parent.element) {
73
- this.parent.element.appendChild(this.element);
74
- }
71
+ render(): HTMLElement | Text | Comment {
72
+ const text = this.generateText();
73
+ this.update(text);
74
+ return this.element;
75
+ }
76
+
77
+ replaceStaticText(text: string): void {
78
+ this.generateText = () => text;
79
+ this.update(text);
75
80
  }
76
81
 
77
82
  remove(): void {
78
83
  this.element.remove();
79
84
  }
80
85
 
86
+ /** Registry guard — element đã destroy không được reuse */
87
+ public __destroyed__: boolean = false;
88
+
81
89
  destroy(): void {
90
+ this.__destroyed__ = true;
82
91
  if (this.unsubscribe) {
83
92
  this.unsubscribe();
84
93
  this.unsubscribe = null;
@@ -95,10 +104,10 @@ export class TextElement implements TextInterface {
95
104
  this.update(newText);
96
105
  }
97
106
 
98
- get isOneElement(): boolean {
107
+ get isSaoElement(): boolean {
99
108
  return true;
100
109
  }
101
- set isOneElement(value: boolean) {
110
+ set isSaoElement(value: boolean) {
102
111
  // No-op setter to satisfy the Interface; this property is always true for Text elements
103
112
  }
104
113
  get isOneText(): boolean {