@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,13 +1,15 @@
1
- import { InitMode, InitModes } from "../contracts/common";
2
- import { ComponentInterface } from "../contracts/ComponentInterface";
3
- import { HtmlInterface, OneChildrenFactory } from "../contracts/ElementInterface";
4
- import { ViewControllerInterface } from "../contracts/ViewControllerInterface";
5
- import { ViewInterface } from "../contracts/ViewInterface";
6
- import { generateUUID } from "../hellpers/utils";
7
- import { OneObjectType } from "../types/utils";
1
+ import { InitMode, InitModes } from "../contracts/common.js";
2
+ import { ComponentInterface } from "../contracts/ComponentInterface.js";
3
+ import { HtmlInterface, SaoChildrenFactory } from "../contracts/ElementInterface.js";
4
+ import { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
5
+ import { ViewInterface } from "../contracts/ViewInterface.js";
6
+ import { generateUUID } from "../helpers/utils.js";
7
+ import { activateView, claimHydratedView, commitView, mountChildrenBeforeAnchor } from "../helpers/view.js";
8
+ import markerRegistry from "../services/MarkerRegistry.js";
9
+ import { SaoObjectType } from "../types/utils.js";
8
10
 
9
11
  export class Component implements ComponentInterface {
10
- oneType: OneObjectType = 'Component';
12
+ saoType: SaoObjectType = 'Component';
11
13
  ctx: ViewControllerInterface;
12
14
  parent: HtmlInterface | null;
13
15
  domChildren: Node[] = []; // For compatibility with HtmlInterface; Component itself doesn't have a single root element
@@ -23,6 +25,18 @@ export class Component implements ComponentInterface {
23
25
  initMode: InitMode = InitModes.CREATE; // Default initialization mode
24
26
  subscribeFn: () => void = () => {};
25
27
  unsubscribeFn: () => void = () => {};
28
+ /**
29
+ * Listener khai báo tại thẻ cha: `<mycomp @edit(openEditor(event))>`.
30
+ *
31
+ * Con gọi `emit('edit', payload)` → ViewController.emit tra Ở ĐÂY, không đi
32
+ * qua event bus: không rò sang instance khác, không phải gỡ đăng ký.
33
+ *
34
+ * Đọc lúc phát chứ không copy vào data con: mỗi lần cha render lại,
35
+ * `ViewController.include()` thay bảng này bằng closure mới, nên handler
36
+ * luôn nhìn thấy biến vòng lặp / prop của LƯỢT RENDER hiện tại — kể cả khi
37
+ * component không có prop reactive nào để kích hoạt updateData.
38
+ */
39
+ listeners: Record<string, (...args: any[]) => any> = {};
26
40
  dataFactory: ((parentElement: HtmlInterface | null) => Record<string, any>) | null = null;
27
41
 
28
42
  constructor({
@@ -36,6 +50,7 @@ export class Component implements ComponentInterface {
36
50
  type = 'default',
37
51
  condition = null,
38
52
  initMode = InitModes.CREATE,
53
+ listeners = {},
39
54
  }: {
40
55
  ctx: ViewControllerInterface;
41
56
  parent?: HtmlInterface | null;
@@ -47,19 +62,46 @@ export class Component implements ComponentInterface {
47
62
  type?: 'default' | 'if' | 'when';
48
63
  condition?: {stateKeys: string[], checker: () => any} | null;
49
64
  initMode?: InitMode;
65
+ listeners?: Record<string, (...args: any[]) => any>;
50
66
  }) {
51
67
  this.ctx = ctx;
52
68
  this.parent = parent;
53
69
  this.stateKeys = stateKeys;
54
70
  this.data = data || {};
55
- this.openTag = document.createComment(`component-start`);
56
- this.closeTag = document.createComment(`component-end`);
57
- this.id = `${ctx.viewId}-${id ?? generateUUID(10)}`; // Unique ID for debugging
71
+ // id khớp server: Blade emit @startMarker('component', '{hash}') →
72
+ // <!--s:c:{parentViewId}-{hash}-s--> (MarkerRegistryDirectiveService)
73
+ this.id = `${ctx.viewId}-${id ?? generateUUID(10)}`;
58
74
  this.dataFactory = dataFactory;
59
75
  this.path = path;
60
76
  this.type = type;
61
77
  this.condition = condition;
78
+ this.listeners = listeners || {};
62
79
  this.initMode = initMode ?? InitModes.CREATE;
80
+
81
+ if (this.initMode === InitModes.HYDRATE) {
82
+ // ── Hydrate: claim cặp marker component server đã render ─────
83
+ const claimed = this.claimSSRMarkers();
84
+ if (claimed) {
85
+ this.openTag = claimed.open;
86
+ this.closeTag = claimed.close;
87
+ } else {
88
+ // Partial hydration fallback (server không render vùng này)
89
+ this.openTag = markerRegistry.createMarkerStart('component', this.id);
90
+ this.closeTag = markerRegistry.createMarkerEnd('component', this.id);
91
+ }
92
+ } else {
93
+ // Format chuẩn s:c:{id}-s/-e — PHẢI khớp server để SSR/CSR cùng kết quả
94
+ this.openTag = markerRegistry.createMarkerStart('component', this.id);
95
+ this.closeTag = markerRegistry.createMarkerEnd('component', this.id);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Tìm cặp marker component từ server-rendered HTML (format chuẩn §5.1):
101
+ * open: s:c:{id}-s close: s:c:{id}-e
102
+ */
103
+ private claimSSRMarkers(): { open: Comment; close: Comment } | null {
104
+ return markerRegistry.claim('component', this.id, this.parent?.element ?? null);
63
105
  }
64
106
 
65
107
  mergeData(newData: Record<string, any>): void {
@@ -75,6 +117,9 @@ export class Component implements ComponentInterface {
75
117
  setStateKeys(stateKeys: string[]) {
76
118
  this.stateKeys = stateKeys;
77
119
  }
120
+ setListeners(listeners: Record<string, (...args: any[]) => any>) {
121
+ this.listeners = listeners || {};
122
+ }
78
123
  setParentElement(parent: HtmlInterface | null): void {
79
124
  this.parent = parent;
80
125
  }
@@ -86,23 +131,312 @@ export class Component implements ComponentInterface {
86
131
  this.parent = parent;
87
132
  }
88
133
 
134
+ /** Registry guard */
135
+ public __destroyed__: boolean = false;
136
+ private _isStarted = false;
137
+ private unsubscribeData: (() => void) | null = null;
138
+ private unsubscribeCondition: (() => void) | null = null;
139
+ /** 'when' type: trạng thái mounted hiện tại của child */
140
+ private _childMounted = false;
141
+
142
+ /**
143
+ * Render — @include (RUNTIME_CONTRACT.md §2):
144
+ * 1. Đặt component markers (idempotent — caller có thể đã đặt đúng vị trí)
145
+ * 2. Resolve child view từ registry (App.View)
146
+ * 3. Render child wrapper GIỮA markers, liên kết parent ↔ child
147
+ * 4. commitData cho child (start sẽ do lifecycle cascade gọi)
148
+ *
149
+ * Hydrate mode (markers claim được từ SSR): child view được tạo với đúng
150
+ * viewId server đã dùng (discover từ marker view bên trong) → toàn bộ cây
151
+ * con CLAIM DOM server thay vì tạo mới — SSR/CSR cho cùng kết quả.
152
+ */
153
+ render(): void {
154
+ if (this.__destroyed__) return;
155
+
156
+ // ── Hydrate path: markers đã có trong SSR DOM ──────────────────
157
+ if (this.initMode === InitModes.HYDRATE) {
158
+ if (this.openTag.parentNode) {
159
+ // Điều kiện đánh giá với state ĐÃ commit (hydrateView commit trước
160
+ // render) → kết quả khớp server-rendered output.
161
+ if (this.type === 'when' && this.condition && !this.condition.checker()) {
162
+ this.initMode = InitModes.CREATE;
163
+ return; // server cũng không render child (điều kiện falsy)
164
+ }
165
+ if (this.type === 'if') {
166
+ const viewManager = (this.ctx as any).App?.View;
167
+ if (!viewManager?.exists?.(this.path ?? '')) {
168
+ this.initMode = InitModes.CREATE;
169
+ return;
170
+ }
171
+ }
172
+ this.guardChildMount(() => this.hydrateChild());
173
+ this.initMode = InitModes.CREATE; // re-render sau này dùng CSR flow
174
+ return;
175
+ }
176
+ // Partial hydration fallback: server không render component này → CSR
177
+ this.initMode = InitModes.CREATE;
178
+ }
179
+
180
+ // 1. Markers
181
+ if (!this.openTag.parentNode) {
182
+ const parentEl = this.parent?.element;
183
+ if (!parentEl) return;
184
+ parentEl.appendChild(this.openTag);
185
+ parentEl.appendChild(this.closeTag);
186
+ }
187
+
188
+ // 2. Điều kiện theo type
189
+ if (this.type === 'when' && this.condition) {
190
+ const shouldMount = !!this.condition.checker();
191
+ if (!shouldMount) {
192
+ this.unmountChild();
193
+ return;
194
+ }
195
+ }
196
+ if (this.type === 'if') {
197
+ const viewManager = (this.ctx as any).App?.View;
198
+ if (!viewManager?.exists?.(this.path ?? '')) {
199
+ return; // @includeIf: view không tồn tại → bỏ qua, không lỗi
200
+ }
201
+ }
202
+
203
+ this.guardChildMount(() => this.mountChild());
204
+ }
205
+
206
+ /**
207
+ * Error boundary cho subtree con (@include). Boundary được tìm từ ctx —
208
+ * controller CHỨA @include này, không phải view con — nên onError của một
209
+ * view không bắt lỗi render của chính nó (giống React ErrorBoundary).
210
+ * Không boundary nào xử lý → rethrow, giữ nguyên hành vi cũ (bubble lên
211
+ * try/catch của renderPageView).
212
+ */
213
+ private guardChildMount(mount: () => void): void {
214
+ try {
215
+ mount();
216
+ } catch (err) {
217
+ const result = this.ctx.handleError(err, { phase: 'render', path: this.path ?? '' });
218
+ if (!result.handled) throw err;
219
+ this.mountFallback(result.fallback);
220
+ }
221
+ }
222
+
223
+ /** Dọn DOM dở dang của lần render lỗi rồi chèn fallback giữa cặp marker. */
224
+ private mountFallback(fallback: any): void {
225
+ let current: Node | null = this.openTag.nextSibling;
226
+ while (current && current !== this.closeTag) {
227
+ const next: Node | null = current.nextSibling;
228
+ current.parentNode?.removeChild(current);
229
+ current = next;
230
+ }
231
+ if (fallback === null || fallback === undefined) return;
232
+ mountChildrenBeforeAnchor(
233
+ this.closeTag,
234
+ Array.isArray(fallback) ? fallback : [fallback],
235
+ this.parent,
236
+ );
237
+ }
238
+
239
+ /**
240
+ * Discover viewId server đã dùng cho child view: quét comment giữa cặp
241
+ * marker component, tìm marker view mở đầu tiên <!--s:v:{id}-s-->.
242
+ */
243
+ private discoverChildViewId(): string | null {
244
+ let current: Node | null = this.openTag.nextSibling;
245
+ while (current && current !== this.closeTag) {
246
+ if (current.nodeType === Node.COMMENT_NODE) {
247
+ const parsed = markerRegistry.parseComment(current.nodeValue ?? '');
248
+ if (parsed && parsed.tag === 'view' && !parsed.isClose) return parsed.id;
249
+ } else if (current.nodeType === Node.ELEMENT_NODE) {
250
+ const walker = document.createTreeWalker(current, NodeFilter.SHOW_COMMENT);
251
+ let node: Comment | null;
252
+ while ((node = walker.nextNode() as Comment | null)) {
253
+ const parsed = markerRegistry.parseComment(node.nodeValue ?? '');
254
+ if (parsed && parsed.tag === 'view' && !parsed.isClose) return parsed.id;
255
+ }
256
+ }
257
+ current = current.nextSibling;
258
+ }
259
+ return null;
260
+ }
261
+
262
+ /**
263
+ * Hydrate child view — thứ tự chuẩn hydration (như ViewManager.hydrateView):
264
+ * discover viewId → tạo instance → commit state → flush discard →
265
+ * render claim DOM → mount() (hook + asset). KHÔNG chèn node mới.
266
+ */
267
+ private hydrateChild(): void {
268
+ if (this._childMounted && this.viewRef) return;
269
+
270
+ // Không có marker view của server giữa cặp marker component → server
271
+ // KHÔNG render view con ở đây. Hydrate tiếp thì child giữ viewId do
272
+ // client sinh ('c…'), rồi đi tìm class "{c…}-{id}" và marker
273
+ // "s:v:c…" không tồn tại → dựng cây mới CẠNH cây server, tức nhân đôi
274
+ // DOM. Dọn phần server bỏ lại rồi đi đường CSR — giống nhánh
275
+ // "partial hydration fallback" ở render() khi marker component vắng.
276
+ const ssrViewId = this.discoverChildViewId();
277
+ if (!ssrViewId) {
278
+ this.unmountChild();
279
+ this.mountChild();
280
+ return;
281
+ }
282
+
283
+ const childView = this.resolveChildView();
284
+ if (!childView) return;
285
+ const childCtrl = childView.__ctrl__;
286
+
287
+ // Ghi đè viewId = viewId server (trước render — Wrapper/Html/Output của
288
+ // child claim theo marker/class prefix bằng viewId này)
289
+ childCtrl.viewId = ssrViewId;
290
+
291
+ // Commit state TRƯỚC render (factory @if/@foreach sinh đúng cây khớp SSR);
292
+ // flush ngay khi chưa subscribe → discard pending, không phá DOM claim.
293
+ childCtrl.initMode = InitModes.HYDRATE;
294
+ commitView(childCtrl, true);
295
+
296
+ const wrapper = childCtrl.render();
297
+ if (wrapper && typeof wrapper === 'object' && 'openTag' in wrapper && this.parent) {
298
+ claimHydratedView(childCtrl, this.parent, wrapper as any);
299
+ }
300
+
301
+ childCtrl.initMode = InitModes.CREATE;
302
+ this.finishChildMount(childCtrl);
303
+ }
304
+
305
+ /** Tạo + mount child view giữa markers (nếu chưa có) */
306
+ private mountChild(): void {
307
+ if (this._childMounted && this.viewRef) {
308
+ if (this.dataFactory) {
309
+ const ctrl = this.viewRef.__ctrl__;
310
+ ctrl.updateData(this.dataFactory(this.parent));
311
+ ctrl.states.__.flushNow();
312
+ }
313
+ return;
314
+ }
315
+ const childView = this.resolveChildView();
316
+ if (!childView) return;
317
+ const childCtrl = childView.__ctrl__;
318
+
319
+ // Render child tree giữa markers
320
+ const wrapper = childCtrl.render();
321
+ if (wrapper && typeof wrapper === 'object' && 'openTag' in wrapper) {
322
+ const insertBeforeClose = (node: Node) => {
323
+ this.closeTag.parentNode?.insertBefore(node, this.closeTag);
324
+ };
325
+ insertBeforeClose(wrapper.openTag);
326
+ insertBeforeClose(wrapper.closeTag);
327
+ // Wrapper render idempotent qua mountChildrenBeforeAnchor
328
+ mountChildrenBeforeAnchor(wrapper.closeTag, wrapper.render(), this.parent);
329
+ }
330
+
331
+ // mount(): nội dung child đã nằm giữa markers → fire mounting/mounted + acquire asset
332
+ childCtrl.mount();
333
+ commitView(childCtrl);
334
+ this.markChildMounted(childCtrl);
335
+ }
336
+
337
+ /** Resolve one child instance and establish ownership before either DOM strategy. */
338
+ private resolveChildView(): ViewInterface | null {
339
+ const viewManager = (this.ctx as any).App?.View;
340
+ if (!viewManager || !this.path) {
341
+ console.error(`[Component] Không resolve được view "${this.path}" — App.View chưa sẵn sàng.`);
342
+ return null;
343
+ }
344
+
345
+ const data = this.dataFactory ? this.dataFactory(this.parent) : this.data;
346
+ // Sync: render tree không await được (xem ViewManager.resolveViewSync).
347
+ const childView = viewManager.resolveViewSync(this.path, data ?? {}, false);
348
+ if (!childView) {
349
+ console.error(`[Component] View "${this.path}" không tồn tại trong registry.`);
350
+ return null;
351
+ }
352
+
353
+ this.viewRef = childView;
354
+ const childCtrl = childView.__ctrl__;
355
+ childCtrl.ownerComponent = this;
356
+ childCtrl.setParent(this.ctx);
357
+ this.ctx.addChild(childCtrl);
358
+ childCtrl.setParentElement(this.parent);
359
+ return childView;
360
+ }
361
+
362
+ /** Hydration mounts after commit/claim; CSR commits immediately after mount. */
363
+ private finishChildMount(childCtrl: ViewControllerInterface): void {
364
+ childCtrl.mount();
365
+ this.markChildMounted(childCtrl);
366
+ }
367
+
368
+ private markChildMounted(childCtrl: ViewControllerInterface): void {
369
+ this._childMounted = true;
370
+ if (this._isStarted) activateView(childCtrl);
371
+ }
372
+
373
+ /** Gỡ child (when=false hoặc destroy) */
374
+ private unmountChild(): void {
375
+ if (this.viewRef) {
376
+ this.viewRef.__ctrl__.destroy();
377
+ this.viewRef = null;
378
+ }
379
+ // Clear mọi node giữa markers
380
+ let current: Node | null = this.openTag.nextSibling;
381
+ while (current && current !== this.closeTag) {
382
+ const next: Node | null = current.nextSibling;
383
+ (current as ChildNode).remove();
384
+ current = next;
385
+ }
386
+ this._childMounted = false;
387
+ }
388
+
389
+ /**
390
+ * Start — kích hoạt child + subscribe:
391
+ * - stateKeys: props reactive — đổi → dataFactory mới → child.updateData()
392
+ * - condition (type 'when'): đổi → mount/unmount child
393
+ */
89
394
  start(): void {
395
+ if (this._isStarted || this.__destroyed__) return;
396
+ this._isStarted = true;
397
+
398
+ if (this.viewRef) {
399
+ activateView(this.viewRef.__ctrl__);
400
+ }
401
+
402
+ if (this.stateKeys.length > 0) {
403
+ this.unsubscribeData = this.ctx.states.__.subscribe(this.stateKeys, () => {
404
+ if (this.viewRef && this.dataFactory) {
405
+ this.viewRef.__ctrl__.updateData(this.dataFactory(this.parent));
406
+ }
407
+ });
408
+ }
409
+
410
+ if (this.type === 'when' && this.condition && this.condition.stateKeys.length > 0) {
411
+ this.unsubscribeCondition = this.ctx.states.__.subscribe(this.condition.stateKeys, () => {
412
+ this.render(); // re-evaluate checker → mount/unmount
413
+ });
414
+ }
90
415
  }
91
416
 
92
417
  stop(): void {
93
- }
418
+ if (!this._isStarted) return;
419
+ this._isStarted = false;
94
420
 
95
- render(): void {
96
- // To be implemented by specific components
421
+ if (this.unsubscribeData) { this.unsubscribeData(); this.unsubscribeData = null; }
422
+ if (this.unsubscribeCondition) { this.unsubscribeCondition(); this.unsubscribeCondition = null; }
423
+ this.viewRef?.__ctrl__.stop();
97
424
  }
98
425
 
99
426
  destroy(): void {
100
- // To be implemented by specific components
427
+ if (this.__destroyed__) return;
428
+ this.stop();
429
+ this.__destroyed__ = true;
430
+ this.ctx.releaseElement?.(this);
431
+ this.unmountChild();
432
+ this.openTag.remove();
433
+ this.closeTag.remove();
434
+ this.parent = null;
101
435
  }
102
- get isOneElement(): boolean {
436
+ get isSaoElement(): boolean {
103
437
  return true;
104
438
  }
105
- set isOneElement(value: boolean) {
439
+ set isSaoElement(value: boolean) {
106
440
  // No-op, just for type compatibility
107
441
  }
108
442
  get isOneComponent(): boolean {
@@ -111,4 +445,4 @@ export class Component implements ComponentInterface {
111
445
  set isOneComponent(value: boolean) {
112
446
  // No-op, just for type compatibility
113
447
  }
114
- }
448
+ }
@@ -1,12 +1,12 @@
1
- import type { BlockRenderFactory } from "../contracts/BlockInterface";
2
- import type { HtmlInterface, OneElementConfig, OneChildrenFactory } from "../contracts/ElementInterface";
3
- import type { ReactiveRenderFn } from "../contracts/ReactiveInterface";
4
- import type { ViewControllerInterface } from "../contracts/ViewControllerInterface";
5
- import { Html } from "./Html";
6
- import { TextElement } from "./TextElement";
7
- import { Reactive } from "./Reactive";
8
- import { Fragment } from "./Fragment";
9
- import { Block } from "./Block";
1
+ import type { BlockRenderFactory } from "../contracts/BlockInterface.js";
2
+ import type { HtmlInterface, SaoElementConfig, SaoChildrenFactory } from "../contracts/ElementInterface.js";
3
+ import type { ReactiveRenderFn } from "../contracts/ReactiveInterface.js";
4
+ import type { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
5
+ import { Html } from "./Html.js";
6
+ import { TextElement } from "./TextElement.js";
7
+ import { Reactive } from "./Reactive.js";
8
+ import { Fragment } from "./Fragment.js";
9
+ import { Block } from "./Block.js";
10
10
 
11
11
  /**
12
12
  * OneElementManager — factory/registry for creating One elements.
@@ -0,0 +1,170 @@
1
+ /**
2
+ * ForeachSlotCache — keyed slot manager cho @foreach reconciliation.
3
+ *
4
+ * # Cơ chế
5
+ * Mỗi item được identify bằng một cache key:
6
+ * - Có @key(expr) → key = giá trị expr (field keying, vd `item.id`)
7
+ * - Không có @key → key = object reference của item (identity keying)
8
+ * Duplicate keys (primitive lists `['a','b','a']`, refs lặp) được phân biệt
9
+ * bằng occurrence index trong pass — key thứ n trùng nhau map vào slot thứ n.
10
+ *
11
+ * # Reuse contract
12
+ * Slot chỉ được REUSE khi cache key khớp VÀ item ref không đổi
13
+ * (`slot.item === item`). Ref đổi → recreate, vì compiled output đóng gói
14
+ * `item` trực tiếp vào closure (`() => item.name`) — reuse với ref cũ sẽ
15
+ * hiển thị data cũ. Field key vì vậy chủ yếu mang lại: bookkeeping đúng cho
16
+ * duplicate/primitive items + ý định tường minh của developer.
17
+ *
18
+ * # Pass protocol (một chu kỳ render)
19
+ * 1. `beginPass()` — reset occurrence counters + tập slot touched
20
+ * 2. `claim(key, item)` mỗi item theo thứ tự list:
21
+ * hit (slot.item === item) → dùng lại, slot được mark touched
22
+ * miss → caller tạo elements rồi `store(key, occ, item, elements)`
23
+ * 3. `prunePass(onRemove)` — slot KHÔNG được touch trong pass = item đã rời
24
+ * list → callback destroy + gỡ khỏi cache. (Trước đây bước này là dead
25
+ * code — cache không bao giờ gỡ entry → element của item bị xoá leak.)
26
+ * Slot bị `store()` GHI ĐÈ (cùng key, ref mới) cũng đi qua đây: nó rơi khỏi
27
+ * `_map` ngay lúc ghi đè nên prunePass không còn thấy → phải giữ tạm trong
28
+ * `_evicted`. Thiếu bước này thì refresh list từ server (`@key` giữ nguyên,
29
+ * ref đổi) KHÔNG destroy elements cũ → view con của @include bị tái dùng
30
+ * trong DOM đã detach và biến mất khỏi trang.
31
+ *
32
+ * # Quan hệ với Reactive
33
+ * Reactive type 'foreach' giữ một instance; cửa sổ `_currentForeachCache`
34
+ * chỉ mở quanh CHÍNH childrenFactory call (không mở trong child.render() —
35
+ * tránh nested @foreach ghi nhầm vào cache của loop ngoài).
36
+ *
37
+ * @see Reactive.renderForeach()
38
+ * @see ViewController.__foreach()
39
+ */
40
+
41
+ /** Một slot tương ứng với một item trong foreach list. */
42
+ export interface ForeachSlot {
43
+ /** Reference tới item gốc — điều kiện reuse là ref không đổi */
44
+ item: any;
45
+ /** Saola elements sinh ra bởi item factory — Html, Output, Reactive, ... */
46
+ elements: any[];
47
+ /** Compiler opt-in: closures are refreshed within this occurrence's scope. */
48
+ registry?: Map<string, any>;
49
+ identity?: number;
50
+ }
51
+
52
+ /** Kết quả claim: slot khi reuse được, occ để store khi phải tạo mới. */
53
+ export interface ForeachClaim {
54
+ slot: ForeachSlot | null;
55
+ occ: number;
56
+ }
57
+
58
+ export class ForeachSlotCache {
59
+ readonly refreshedElements = new Set<any>();
60
+ private nextIdentity = 0;
61
+
62
+ /** Initial SSR indexes, then monotonically allocated identities for new rows. */
63
+ allocateIdentity(index: number): number {
64
+ const identity = Math.max(index, this.nextIdentity);
65
+ this.nextIdentity = identity + 1;
66
+ return identity;
67
+ }
68
+ /** Map key → danh sách slot theo occurrence (duplicate keys) */
69
+ private _map: Map<any, ForeachSlot[]> = new Map();
70
+ /** Occurrence counter của pass hiện tại */
71
+ private _passOcc: Map<any, number> = new Map();
72
+ /** Slots được claim-hit hoặc store trong pass hiện tại */
73
+ private _touched: Set<ForeachSlot> = new Set();
74
+ /** Slots bị store() ghi đè trong pass hiện tại — prunePass destroy rồi clear */
75
+ private _evicted: ForeachSlot[] = [];
76
+
77
+ /** Tổng số slot đang cache (mọi occurrence). */
78
+ get size(): number {
79
+ let n = 0;
80
+ for (const slots of this._map.values()) n += slots.length;
81
+ return n;
82
+ }
83
+
84
+ /** Bắt đầu một chu kỳ render — reset counters + touched set. */
85
+ beginPass(): void {
86
+ this.refreshedElements.clear();
87
+ this._passOcc = new Map();
88
+ this._touched = new Set();
89
+ this._evicted = [];
90
+ }
91
+
92
+ /**
93
+ * Claim slot cho (key, item) theo thứ tự list.
94
+ * Trả slot khi reuse được (key khớp + ref không đổi); ngược lại slot=null
95
+ * và caller phải store(key, occ, ...) sau khi tạo elements.
96
+ */
97
+ claim(key: any, item: any, reconcile = false): ForeachClaim {
98
+ const occ = this._passOcc.get(key) ?? 0;
99
+ this._passOcc.set(key, occ + 1);
100
+
101
+ const slot = this._map.get(key)?.[occ] ?? null;
102
+ if (slot && (slot.item === item || (reconcile && slot.registry))) {
103
+ this._touched.add(slot);
104
+ return { slot, occ };
105
+ }
106
+ return { slot: null, occ };
107
+ }
108
+
109
+ /**
110
+ * Lưu slot mới tại (key, occ) — ghi đè slot cũ nếu ref đã đổi.
111
+ * Slot bị ghi đè KHÔNG còn nằm trong `_map` nên prunePass duyệt `_map` sẽ
112
+ * không thấy nó nữa → phải chuyển sang `_evicted` để vẫn được destroy.
113
+ */
114
+ store(key: any, occ: number, item: any, elements: any[], registry?: Map<string, any>): ForeachSlot {
115
+ const slot: ForeachSlot = { item, elements, registry };
116
+ let slots = this._map.get(key);
117
+ if (!slots) {
118
+ slots = [];
119
+ this._map.set(key, slots);
120
+ }
121
+ const evicted = slots[occ];
122
+ if (evicted && !this._touched.has(evicted)) {
123
+ this._evicted.push(evicted);
124
+ }
125
+ slots[occ] = slot;
126
+ this._touched.add(slot);
127
+ return slot;
128
+ }
129
+
130
+ /**
131
+ * Kết thúc pass: mọi slot không được touch = item đã rời list (hoặc bị
132
+ * thay bằng ref mới) → gọi onRemove(slot) để destroy elements, gỡ khỏi cache.
133
+ */
134
+ prunePass(onRemove: (slot: ForeachSlot) => void): void {
135
+ // Slot bị ghi đè: đã rời `_map` lúc store() nên chỉ cần destroy.
136
+ for (const slot of this._evicted) onRemove(slot);
137
+ this._evicted = [];
138
+
139
+ for (const [key, slots] of this._map) {
140
+ let hasRemoval = false;
141
+ for (const slot of slots) {
142
+ if (slot && !this._touched.has(slot)) {
143
+ onRemove(slot);
144
+ hasRemoval = true;
145
+ }
146
+ }
147
+ if (hasRemoval) {
148
+ const kept = slots.filter(s => s && this._touched.has(s));
149
+ if (kept.length) {
150
+ this._map.set(key, kept);
151
+ } else {
152
+ this._map.delete(key);
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Xoá toàn bộ cache — dùng khi Reactive bị destroy.
160
+ * KHÔNG tự gọi destroy() trên elements — caller phải làm trước.
161
+ */
162
+ clear(): void {
163
+ this.nextIdentity = 0;
164
+ this.refreshedElements.clear();
165
+ this._map.clear();
166
+ this._passOcc = new Map();
167
+ this._touched = new Set();
168
+ this._evicted = [];
169
+ }
170
+ }